Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ export async function startCommand(options: StartOptions) {

// Resolve resume target, if requested.
let resumeSessionId: string | undefined;
let resumeTranscript: Array<{ role: 'user' | 'assistant'; text: string }> | undefined;
if (options.resume || options.continue) {
const { pickSession } = await import('../ui/session-picker.js');
const { loadSessionMeta, loadSessionHistory } = await import('../session/storage.js');
Expand All @@ -372,10 +373,11 @@ export async function startCommand(options: StartOptions) {

if (resumeSessionId) {
const meta = loadSessionMeta(resumeSessionId);
const msgs = loadSessionHistory(resumeSessionId).length;
const history = loadSessionHistory(resumeSessionId);
const when = meta ? new Date(meta.updatedAt).toLocaleString() : 'unknown';
console.log(chalk.green(` Resuming session ${resumeSessionId.slice(0, 24)}…`));
console.log(chalk.dim(` ${msgs} messages · last active ${when}\n`));
console.log(chalk.dim(` ${history.length} messages · last active ${when}\n`));
resumeTranscript = buildResumeTranscript(history);
}
}

Expand Down Expand Up @@ -413,7 +415,7 @@ export async function startCommand(options: StartOptions) {
if (process.stdin.isTTY) {
await runWithInkUI(agentConfig, model, workDir, version, walletInfo, (cb) => {
onBalanceFetched = cb;
}, fetchBalance, importedKickoffPrompt);
}, fetchBalance, importedKickoffPrompt, resumeTranscript);
} else {
await runWithBasicUI(agentConfig, model, workDir, importedKickoffPrompt);
}
Expand Down Expand Up @@ -446,6 +448,39 @@ async function runOneShot(agentConfig: AgentConfig, prompt: string): Promise<num
return exitCode;
}

function buildResumeTranscript(history: Dialogue[]): Array<{ role: 'user' | 'assistant'; text: string }> {
const entries = history
.map((msg) => {
const text = extractVisibleText(msg).replace(/\s+/g, ' ').trim();
if (!text) return null;
return { role: msg.role, text: text.length > 180 ? `${text.slice(0, 177)}...` : text };
})
.filter((entry): entry is { role: 'user' | 'assistant'; text: string } => entry !== null);

if (entries.length === 0) return [];

const started = entries.slice(0, 4);
const recentStart = entries.length > 10 ? -6 : 4;
const recent = entries.slice(recentStart);

return entries.length > 10
? [...started, { role: 'assistant', text: '...' }, ...recent]
: [...started, ...recent];
}

function extractVisibleText(msg: Dialogue): string {
if (typeof msg.content === 'string') return msg.content;
if (!Array.isArray(msg.content)) return '';

return msg.content
.map((part) => {
if ('type' in part && part.type === 'text') return part.text;
return '';
})
.filter(Boolean)
.join('\n');
}

// ─── Ink UI (interactive terminal) ─────────────────────────────────────────

async function runWithInkUI(
Expand All @@ -457,6 +492,7 @@ async function runWithInkUI(
onBalanceReady?: (cb: (bal: string) => void) => void,
fetchBalance?: () => Promise<string>,
initialInput?: string,
initialTranscript?: Array<{ role: 'user' | 'assistant'; text: string }>,
) {
const startSnapshot = snapshotStats();
const ui = launchInkUI({
Expand All @@ -465,6 +501,7 @@ async function runWithInkUI(
version,
walletAddress: walletInfo?.address,
walletBalance: walletInfo?.balance,
initialTranscript,
chain: walletInfo?.chain,
onModelChange: (newModel: string, reason?: 'user' | 'system') => {
agentConfig.model = newModel;
Expand Down
16 changes: 14 additions & 2 deletions src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ interface AppProps {
workDir: string;
walletAddress: string;
walletBalance: string;
initialTranscript?: Array<{ role: 'user' | 'assistant'; text: string }>;
startWithPicker?: boolean;
chain: string;
onSubmit: (input: string) => void;
Expand All @@ -265,7 +266,7 @@ interface AppProps {

function RunCodeApp({
initialModel, workDir, walletAddress, walletBalance, chain,
startWithPicker, onSubmit, onModelChange, onAbort, onExit,
initialTranscript, startWithPicker, onSubmit, onModelChange, onAbort, onExit,
}: AppProps) {
const { exit } = useApp();
// Track terminal rows so we can cap the dynamic-region height. Ink wipes the
Expand All @@ -283,7 +284,16 @@ function RunCodeApp({
// Last completed tool — shown in dynamic area so it can be expanded/collapsed with Tab
const [expandableTool, setExpandableTool] = useState<(ToolStatus & { key: string }) | null>(null);
// Full responses committed to Static immediately — goes into terminal scrollback
const [committedResponses, setCommittedResponses] = useState<Array<{ key: string; text: string; tokens: { input: number; output: number; calls: number }; cost: number; model?: string; tier?: string; savings?: number; thinkMs?: number; thinkChars?: number; ctxPct?: number }>>([]);
const [committedResponses, setCommittedResponses] = useState<Array<{ key: string; text: string; tokens: { input: number; output: number; calls: number }; cost: number; model?: string; tier?: string; savings?: number; thinkMs?: number; thinkChars?: number; ctxPct?: number }>>(() =>
(initialTranscript ?? []).map((entry, idx) => ({
key: `${entry.role === 'user' ? 'user' : 'resume'}-${idx}`,
text: entry.role === 'user'
? chalk.hex('#FFD700').bold('❯ ') + chalk.hex('#FFD700').bold(entry.text)
: entry.text,
tokens: { input: 0, output: 0, calls: 0 },
cost: 0,
}))
);
// Short preview of latest response shown in dynamic area (last ~5 lines, cleared on next turn)
const [responsePreview, setResponsePreview] = useState('');
const [currentModel, setCurrentModel] = useState(initialModel || PICKER_MODELS_FLAT[0].id);
Expand Down Expand Up @@ -1494,6 +1504,7 @@ export function launchInkUI(opts: {
version: string;
walletAddress?: string;
walletBalance?: string;
initialTranscript?: Array<{ role: 'user' | 'assistant'; text: string }>;
chain?: string;
showPicker?: boolean;
onModelChange?: (model: string, reason?: 'user' | 'system') => void;
Expand All @@ -1510,6 +1521,7 @@ export function launchInkUI(opts: {
workDir={opts.workDir}
walletAddress={opts.walletAddress || 'not set — run: franklin setup'}
walletBalance={opts.walletBalance || 'unknown'}
initialTranscript={opts.initialTranscript}
chain={opts.chain || 'base'}
startWithPicker={opts.showPicker}
onSubmit={(value) => {
Expand Down
Loading