blob: 59eccee4c2341955b163a6d66d2ac94bbbcffb3d [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type {
ShellRunSnapshotResult,
ShellRunToolResult,
ShellRunUpdate,
StoredMessage,
} from '@maka/core';
import { materializeTurns } from '../materialize.js';
import { createTranscriptProjection } from '../transcript-projection.js';
import type { LiveTurnProjection } from '../live-turn-projection.js';
const REF = 'maka://runtime/background-tasks/pty-1';
describe('ShellRun UI projection', () => {
test('reconciles WriteStdin into its Bash parent while retaining safe operation metadata', () => {
const messages: StoredMessage[] = [
toolCall('bash-1', 'turn-1', 'Bash', { command: 'read value', pty: true }, 1),
toolResult('bash-1', 'turn-1', shellRun(1), 2),
toolCall('write-1', 'turn-2', 'WriteStdin', {
ref: REF,
input: 'private-value\n',
size: { cols: 100, rows: 30 },
}, 3),
toolResult('write-1', 'turn-2', shellRun(2, {
operation: {
kind: 'pty_control',
failed: false,
input: { bytes: 14, queued: true },
resize: { cols: 100, rows: 30, applied: true, changed: true },
},
}), 4),
];
const turns = materializeTurns(messages);
const bash = turns[0]?.tools[0];
const write = turns[1]?.tools[0];
assert.equal(bash?.toolName, 'Bash');
assert.equal(bash?.result?.kind, 'shell_run');
if (bash?.result?.kind !== 'shell_run') assert.fail('expected ShellRun parent');
assert.equal(bash.result.revision, 2);
assert.equal(bash.result.operation, undefined);
assert.equal(write?.toolName, 'WriteStdin');
assert.deepEqual(write?.args, {
ref: REF,
inputPreview: { text: 'private-value\\n', bytes: 14, truncated: false },
size: { cols: 100, rows: 30 },
});
assert.deepEqual(
write?.result?.kind === 'shell_run' ? write.result.operation : undefined,
{
kind: 'pty_control',
failed: false,
input: { bytes: 14, queued: true },
resize: { cols: 100, rows: 30, applied: true, changed: true },
},
);
});
test('keeps a durable background update ahead of a stale live turn result', () => {
const messages: StoredMessage[] = [
toolCall('bash-1', 'turn-1', 'Bash', { command: 'job', pty: true }, 1),
toolResult('bash-1', 'turn-1', shellRun(1), 2),
{ type: 'user', id: 'user-2', turnId: 'turn-2', ts: 3, text: 'next' },
];
const projection = createTranscriptProjection();
const unrelatedTurn = projection.project({ messages })[1];
const update: ShellRunUpdate = {
sessionId: 'session-1',
ownership: { kind: 'local' },
sourceTurnId: 'turn-1',
sourceToolCallId: 'bash-1',
result: shellRunSnapshot(3, { status: 'completed', completedAt: 5, exitCode: 0 }),
};
const durable = projection.project({ messages, shellRunUpdates: [update] });
assert.equal(durable[1], unrelatedTurn);
assert.equal(durable[0]?.tools[0]?.status, 'completed');
const live: LiveTurnProjection = {
turnId: 'turn-1',
phase: 'streamed',
steps: [{
stepId: 'tool:bash-1',
contentOrder: ['tools'],
tools: [{
toolUseId: 'bash-1',
toolName: 'Bash',
status: 'running',
args: { command: 'job', pty: true },
result: shellRun(2),
}],
}],
};
const overlaid = projection.project({ messages, liveTurn: live, shellRunUpdates: [update] });
assert.equal(overlaid[1], unrelatedTurn, 'the live overlay must not disturb an unrelated turn');
const result = overlaid[0]?.tools[0]?.result;
assert.equal(result?.kind === 'shell_run' ? result.revision : undefined, 3);
assert.equal(result?.kind === 'shell_run' ? result.status : undefined, 'completed');
});
test('keeps a newer live PTY screen ahead of the persisted snapshot', () => {
const messages: StoredMessage[] = [
toolCall('bash-1', 'turn-1', 'Bash', { command: 'job', pty: true }, 1),
toolResult('bash-1', 'turn-1', shellRun(1), 2),
];
const liveResult = shellRun(2);
if (liveResult.output?.mode !== 'pty') assert.fail('expected PTY output');
liveResult.output.screen = 'still running';
const live: LiveTurnProjection = {
turnId: 'turn-1',
phase: 'streamed',
steps: [{
stepId: 'tool:bash-1',
contentOrder: ['tools'],
tools: [{
toolUseId: 'bash-1',
toolName: 'Bash',
status: 'running',
args: { command: 'job', pty: true },
result: liveResult,
outputChunks: [{
seq: 1,
stream: 'stdout',
text: 'still running',
redacted: false,
createdAt: 3,
}],
}],
}],
};
const tool = createTranscriptProjection()
.project({ messages, liveTurn: live })[0]?.tools[0];
assert.equal(tool?.result?.kind === 'shell_run' ? tool.result.revision : undefined, 2);
assert.equal(
tool?.result?.kind === 'shell_run' && tool.result.output?.mode === 'pty'
? tool.result.output.screen
: undefined,
'still running',
);
assert.equal(tool?.outputChunks?.[0]?.text, 'still running');
});
test('applies a durable update that arrives before the live Bash result', () => {
const live: LiveTurnProjection = {
turnId: 'turn-1',
phase: 'streamed',
steps: [{
stepId: 'tool:bash-1',
contentOrder: ['tools'],
tools: [{
toolUseId: 'bash-1',
toolName: 'Bash',
status: 'running',
args: { command: 'job', pty: true },
}],
}],
};
const update: ShellRunUpdate = {
sessionId: 'session-1',
ownership: { kind: 'local' },
sourceTurnId: 'turn-1',
sourceToolCallId: 'bash-1',
result: shellRunSnapshot(1),
};
const turns = createTranscriptProjection()
.project({ messages: [], liveTurn: live, shellRunUpdates: [update] });
const result = turns[0]?.tools[0]?.result;
assert.equal(result?.kind, 'shell_run');
assert.equal(result?.kind === 'shell_run' ? result.output?.mode : undefined, 'pty');
assert.equal(
result?.kind === 'shell_run' && result.output?.mode === 'pty'
? result.output.screen
: undefined,
'ready',
);
});
test('marks a running ShellRun inherited from a source session as detached', () => {
const messages: StoredMessage[] = [
toolCall('bash-1', 'turn-1', 'Bash', { command: 'job', pty: true }, 1),
toolResult('bash-1', 'turn-1', shellRun(1), 2),
];
const turns = createTranscriptProjection().project({ messages, shellRunUpdates: [{
sessionId: 'branch-session',
ownership: {
kind: 'source_owned',
sourceSessionId: 'source-session',
ownerSessionId: 'source-session',
},
sourceTurnId: 'turn-1',
sourceToolCallId: 'bash-1',
result: shellRunSnapshot(2),
}] });
assert.equal(turns[0]?.tools[0]?.shellRunSource, 'owned');
assert.equal(turns[0]?.tools[0]?.result?.kind === 'shell_run'
? turns[0]?.tools[0]?.result?.status
: undefined, 'running');
const unavailable = createTranscriptProjection().project({ messages, shellRunUpdates: [{
sessionId: 'branch-session',
ownership: { kind: 'source_unavailable', sourceSessionId: 'source-session' },
sourceTurnId: 'turn-1',
sourceToolCallId: 'bash-1',
result: shellRunSnapshot(2),
}] });
assert.equal(unavailable[0]?.tools[0]?.shellRunSource, 'unavailable');
});
});
function toolCall(
id: string,
turnId: string,
toolName: string,
args: unknown,
ts: number,
): Extract<StoredMessage, { type: 'tool_call' }> {
return { type: 'tool_call', id, turnId, ts, toolName, args };
}
function toolResult(
toolUseId: string,
turnId: string,
content: ShellRunToolResult,
ts: number,
): Extract<StoredMessage, { type: 'tool_result' }> {
return { type: 'tool_result', id: `result-${toolUseId}`, turnId, ts, toolUseId, isError: false, content };
}
function shellRun(
revision: number,
patch: Partial<Extract<ShellRunToolResult, { mode: 'pty' }>> = {},
): Extract<ShellRunToolResult, { mode: 'pty' }> {
return {
kind: 'shell_run',
ref: REF,
mode: 'pty',
status: 'running',
cwd: '/repo',
cmd: 'job',
startedAt: 1,
updatedAt: revision,
revision,
output: {
mode: 'pty',
screen: 'ready',
scrollback: '',
cols: 80,
rows: 24,
cursor: { x: 5, y: 0, visible: true },
alternateScreen: false,
truncated: false,
redacted: false,
},
...patch,
};
}
function shellRunSnapshot(
revision: number,
patch: Partial<Extract<ShellRunSnapshotResult, { mode: 'pty' }>> = {},
): Extract<ShellRunSnapshotResult, { mode: 'pty' }> {
return {
kind: 'shell_run',
ref: REF,
mode: 'pty',
status: 'running',
cwd: '/repo',
cmd: 'job',
startedAt: 1,
updatedAt: revision,
revision,
output: {
mode: 'pty',
screen: 'ready',
scrollback: '',
cols: 80,
rows: 24,
cursor: { x: 5, y: 0, visible: true },
alternateScreen: false,
truncated: false,
redacted: false,
},
...patch,
};
}