@superjackfruitlabs/salaam 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +12 -11
- package/dist/src/acp-desk.js +140 -0
- package/dist/src/acp-outcome.js +182 -18
- package/dist/src/acp.js +296 -15
- package/dist/src/agent-profiles.js +45 -0
- package/dist/src/agentpod-authority.js +169 -11
- package/dist/src/agentpod-default-config.js +12 -0
- package/dist/src/agentpod-work-decision-protocol.js +40 -6
- package/dist/src/agentpod-work-decisions.js +108 -4
- package/dist/src/agentpod-work.js +1214 -127
- package/dist/src/approval-rules.js +287 -0
- package/dist/src/attach.js +3 -1
- package/dist/src/autonomy.js +80 -9
- package/dist/src/board-reads.js +106 -0
- package/dist/src/children.js +83 -0
- package/dist/src/cli-help.js +61 -6
- package/dist/src/cli.js +140 -4
- package/dist/src/command-danger.js +797 -0
- package/dist/src/command-network.js +200 -0
- package/dist/src/command-sandbox.js +327 -0
- package/dist/src/custom-commands.js +159 -0
- package/dist/src/danger-classifier.js +286 -0
- package/dist/src/execution-evidence.js +33 -1
- package/dist/src/git-helpers.js +160 -0
- package/dist/src/hooks.js +351 -0
- package/dist/src/host-bind.js +111 -0
- package/dist/src/host-breaker.js +88 -0
- package/dist/src/host-start.js +41 -1
- package/dist/src/host.js +284 -6
- package/dist/src/house-extension.js +292 -61
- package/dist/src/local-coding.js +726 -35
- package/dist/src/local-edit-helper.js +25 -0
- package/dist/src/local-edits.js +168 -6
- package/dist/src/local-undo.js +25 -16
- package/dist/src/managed.js +1 -1
- package/dist/src/mentions.js +277 -0
- package/dist/src/operator-mcp-cli.js +107 -0
- package/dist/src/operator-mcp.js +342 -0
- package/dist/src/persona.js +31 -0
- package/dist/src/pi-sessions.js +5 -1
- package/dist/src/pi.js +22 -1
- package/dist/src/project-instructions.js +195 -0
- package/dist/src/protocol.js +9 -3
- package/dist/src/rate-limit-retries.js +62 -0
- package/dist/src/recurring-work.js +1 -0
- package/dist/src/repository-snapshots.js +5 -2
- package/dist/src/run-grants.js +62 -0
- package/dist/src/service.js +482 -10
- package/dist/src/session-pick.js +77 -0
- package/dist/src/shell-passthrough.js +39 -0
- package/dist/src/station-checkpoints.js +139 -0
- package/dist/src/station-config.js +8 -1
- package/dist/src/station-files.js +20 -1
- package/dist/src/station-task-verification.js +6 -3
- package/dist/src/station-work.js +315 -22
- package/dist/src/store.js +70 -4
- package/dist/src/supervisor.js +408 -25
- package/dist/src/task-budget.js +39 -3
- package/dist/src/task-context.js +8 -2
- package/dist/src/task-critic.js +115 -0
- package/dist/src/task-driver.js +46 -2
- package/dist/src/task-policy.js +44 -12
- package/dist/src/task-verification.js +42 -9
- package/dist/src/task-work.js +47 -0
- package/dist/src/terminal-status.js +88 -0
- package/dist/src/tool-identity.js +69 -0
- package/dist/src/tui-activity.js +99 -0
- package/dist/src/tui-classifier.js +25 -0
- package/dist/src/tui-client.js +186 -5
- package/dist/src/tui-commands.js +23 -5
- package/dist/src/tui-composer.js +113 -0
- package/dist/src/tui-keymap.js +116 -0
- package/dist/src/tui-mentions.js +38 -0
- package/dist/src/tui-modals.js +157 -34
- package/dist/src/tui-notify.js +62 -0
- package/dist/src/tui-review.js +55 -0
- package/dist/src/tui-turn-keys.js +52 -0
- package/dist/src/tui-view.js +243 -69
- package/dist/src/tui.js +875 -118
- package/dist/src/turn-checkpoints.js +221 -0
- package/dist/src/turn-rewind.js +168 -0
- package/dist/src/usage.js +1 -1
- package/dist/src/web-fetch.js +114 -0
- package/dist/src/web-guard.js +163 -0
- package/dist/src/web-search.js +302 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/src/acp-desk.ts +155 -0
- package/src/acp-outcome.ts +174 -20
- package/src/acp.ts +386 -30
- package/src/agent-profiles.ts +48 -0
- package/src/agentpod-authority.ts +234 -15
- package/src/agentpod-default-config.ts +16 -0
- package/src/agentpod-work-decision-protocol.ts +69 -8
- package/src/agentpod-work-decisions.ts +123 -5
- package/src/agentpod-work.ts +1455 -152
- package/src/approval-rules.ts +360 -0
- package/src/attach.ts +4 -1
- package/src/autonomy.ts +111 -10
- package/src/board-reads.ts +148 -0
- package/src/children.ts +112 -0
- package/src/cli-help.ts +60 -6
- package/src/cli.ts +174 -4
- package/src/command-danger.ts +886 -0
- package/src/command-network.ts +215 -0
- package/src/command-sandbox.ts +393 -0
- package/src/custom-commands.ts +175 -0
- package/src/danger-classifier.ts +321 -0
- package/src/execution-evidence.ts +31 -1
- package/src/git-helpers.ts +177 -0
- package/src/hooks.ts +425 -0
- package/src/host-bind.ts +156 -0
- package/src/host-breaker.ts +89 -0
- package/src/host-start.ts +51 -1
- package/src/host.ts +365 -6
- package/src/house-extension.ts +313 -60
- package/src/local-coding.ts +894 -43
- package/src/local-edit-helper.ts +26 -0
- package/src/local-edits.ts +191 -8
- package/src/local-undo.ts +29 -14
- package/src/managed.ts +2 -2
- package/src/mentions.ts +304 -0
- package/src/operator-mcp-cli.ts +108 -0
- package/src/operator-mcp.ts +420 -0
- package/src/persona.ts +37 -0
- package/src/pi-sessions.ts +9 -1
- package/src/pi.ts +23 -1
- package/src/project-instructions.ts +229 -0
- package/src/protocol.ts +25 -3
- package/src/rate-limit-retries.ts +69 -0
- package/src/recurring-work.ts +1 -0
- package/src/repository-snapshots.ts +5 -2
- package/src/run-grants.ts +107 -0
- package/src/service.ts +709 -10
- package/src/session-pick.ts +102 -0
- package/src/shell-passthrough.ts +38 -0
- package/src/station-checkpoints.ts +183 -0
- package/src/station-config.ts +10 -1
- package/src/station-files.ts +20 -1
- package/src/station-task-verification.ts +6 -3
- package/src/station-work.ts +369 -20
- package/src/store.ts +70 -3
- package/src/supervisor.ts +463 -26
- package/src/task-budget.ts +48 -3
- package/src/task-context.ts +9 -2
- package/src/task-critic.ts +171 -0
- package/src/task-driver.ts +49 -2
- package/src/task-policy.ts +65 -13
- package/src/task-verification.ts +53 -9
- package/src/task-work.ts +75 -0
- package/src/terminal-status.ts +103 -0
- package/src/tool-identity.ts +110 -0
- package/src/tui-activity.ts +133 -0
- package/src/tui-classifier.ts +38 -0
- package/src/tui-client.ts +239 -8
- package/src/tui-commands.ts +49 -10
- package/src/tui-composer.ts +124 -0
- package/src/tui-keymap.ts +152 -0
- package/src/tui-mentions.ts +43 -0
- package/src/tui-modals.ts +173 -43
- package/src/tui-notify.ts +77 -0
- package/src/tui-review.ts +76 -0
- package/src/tui-turn-keys.ts +77 -0
- package/src/tui-view.ts +341 -88
- package/src/tui.ts +1047 -137
- package/src/turn-checkpoints.ts +253 -0
- package/src/turn-rewind.ts +258 -0
- package/src/usage.ts +1 -1
- package/src/web-fetch.ts +140 -0
- package/src/web-guard.ts +195 -0
- package/src/web-search.ts +402 -0
package/dist/src/tui-view.js
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
import { Markdown, wrapTextWithAnsi, visibleWidth } from '@earendil-works/pi-tui';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import { safeText, shortId
|
|
3
|
+
import { safeText, shortId } from './tui-theme.js';
|
|
4
|
+
import { typedText } from './shell-passthrough.js';
|
|
5
|
+
import { instructionsLabel, sessionFailure, usageCost, usageInput, } from './tui-client.js';
|
|
4
6
|
import { workspaceDiff } from './workspace-diff.js';
|
|
5
7
|
import { commandTerminal } from './coding.js';
|
|
8
|
+
import { interactiveCoding, runningAttempt } from './tui-turn-keys.js';
|
|
9
|
+
import { backgroundNote, changeCounts } from './tui-review.js';
|
|
10
|
+
import { activityVerb, progressLine, toolLine } from './tui-activity.js';
|
|
11
|
+
/** The session's tool calls, oldest first, each as one compact line. */
|
|
12
|
+
export function toolLines(view) {
|
|
13
|
+
const calls = new Map();
|
|
14
|
+
for (const event of view.client.activity) {
|
|
15
|
+
const p = event.payload;
|
|
16
|
+
const key = p.attempt_id + ':' + p.tool_call_id;
|
|
17
|
+
const before = calls.get(key);
|
|
18
|
+
calls.set(key, {
|
|
19
|
+
tool: p.tool,
|
|
20
|
+
input: event.kind === 'tool.started' ? p.input : before?.input,
|
|
21
|
+
finished: event.kind === 'tool.finished' ? { failed: p.failed, output: p.output } : before?.finished,
|
|
22
|
+
parent: p.parent_tool_call_id ? p.attempt_id + ':' + p.parent_tool_call_id : before?.parent,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return [...calls.entries()].map(([key, c]) => {
|
|
26
|
+
const line = toolLine(c.tool, c.input, c.finished);
|
|
27
|
+
// A command's recorded result is JSON; the transcript shows its output as text.
|
|
28
|
+
return { key, parent: c.parent, output: line.full ?? c.finished?.output, ...line };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** How the status line names a mode. */
|
|
32
|
+
export function modeLabel(mode) {
|
|
33
|
+
return ({
|
|
34
|
+
review: 'review',
|
|
35
|
+
'accept-edits': 'accept edits',
|
|
36
|
+
auto: 'auto',
|
|
37
|
+
inspect: 'inspect only',
|
|
38
|
+
'scoped-edit': 'scoped edits',
|
|
39
|
+
}[mode] ?? mode);
|
|
40
|
+
}
|
|
6
41
|
export function modelLabel(client) {
|
|
7
42
|
const route = client.snapshot?.provider_route ?? client.health?.provider_route;
|
|
8
43
|
if (!route)
|
|
@@ -15,6 +50,14 @@ export function modelLabel(client) {
|
|
|
15
50
|
: '';
|
|
16
51
|
return (route.model.replace('claude-sonnet-', 'Claude Sonnet ').replace(/(\d)-(\d)$/, '$1.$2') + warning);
|
|
17
52
|
}
|
|
53
|
+
/** 950 → "950", 12_400 → "12.4k", 2_300_000 → "2.3M". */
|
|
54
|
+
export function compactTokens(n) {
|
|
55
|
+
if (n < 1000)
|
|
56
|
+
return String(n);
|
|
57
|
+
if (n < 1_000_000)
|
|
58
|
+
return (n / 1000).toFixed(n < 10_000 ? 1 : 0).replace(/\.0$/, '') + 'k';
|
|
59
|
+
return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
60
|
+
}
|
|
18
61
|
export class Dynamic {
|
|
19
62
|
content;
|
|
20
63
|
constructor(content) {
|
|
@@ -65,33 +108,84 @@ export function status(view, width) {
|
|
|
65
108
|
const { client: c, theme: t } = view;
|
|
66
109
|
const pending = c.snapshot?.approvals.filter((a) => a.state === 'pending').length ?? 0;
|
|
67
110
|
const active = c.snapshot?.attempts.find((a) => ['accepted', 'dispatching', 'running'].includes(a.state));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
// The turn is timed from when this terminal first saw it running (attempts carry no start time).
|
|
112
|
+
if (active && view.turn?.attempt !== active.id)
|
|
113
|
+
view.turn = { attempt: active.id, since: Date.now() };
|
|
114
|
+
if (!active)
|
|
115
|
+
view.turn = undefined;
|
|
116
|
+
const turn = active ? view.turn : undefined;
|
|
117
|
+
const running = toolLines(view)
|
|
118
|
+
.filter((l) => l.state === 'running')
|
|
119
|
+
.at(-1);
|
|
120
|
+
const state = turn && !pending
|
|
121
|
+
? progressLine(activityVerb(running), turn.since, Date.now(), c.usage?.observations
|
|
122
|
+
? compactTokens(c.usage.known_input + c.usage.known_output)
|
|
123
|
+
: undefined)
|
|
124
|
+
: view.busy
|
|
125
|
+
? 'Working…'
|
|
126
|
+
: pending
|
|
127
|
+
? `${pending} approval${pending === 1 ? '' : 's'} waiting · ctrl+o to review`
|
|
128
|
+
: sessionFailure(c.snapshot) ||
|
|
129
|
+
c.notice ||
|
|
130
|
+
(!c.snapshot
|
|
131
|
+
? 'ctrl+n to start · ctrl+p to return to a session'
|
|
132
|
+
: active
|
|
133
|
+
? `Agent ${active.state} · detach anytime`
|
|
134
|
+
: c.controlling
|
|
135
|
+
? 'Ready when you are.'
|
|
136
|
+
: 'Observing · ctrl+k → Take control');
|
|
137
|
+
const tone = pending
|
|
138
|
+
? 'amber'
|
|
139
|
+
: sessionFailure(c.snapshot) && !view.busy
|
|
140
|
+
? 'red'
|
|
141
|
+
: turn
|
|
142
|
+
? 'jade'
|
|
143
|
+
: c.connection === 'reconnecting'
|
|
144
|
+
? 'amber'
|
|
145
|
+
: 'muted';
|
|
146
|
+
const guidance = c.snapshot?.project_instructions?.files.length
|
|
147
|
+
? ' Using ' + instructionsLabel(c.snapshot.project_instructions)
|
|
148
|
+
: '';
|
|
149
|
+
const spend = c.usage?.observations
|
|
150
|
+
? ' ' +
|
|
151
|
+
compactTokens(usageInput(c.usage) + c.usage.known_output) +
|
|
152
|
+
' tokens · $' +
|
|
153
|
+
usageCost(c.usage).toFixed(2)
|
|
154
|
+
: '';
|
|
155
|
+
const mode = view.mode
|
|
156
|
+
? ' ' +
|
|
157
|
+
t.ink('mode ' + modeLabel(view.mode.mode), 'muted') +
|
|
158
|
+
(view.mode.access === 'yolo'
|
|
159
|
+
? ' ' + t.ink('YOLO', 'red')
|
|
160
|
+
: view.mode.access === 'full'
|
|
161
|
+
? ' ' + t.ink('FULL ACCESS', 'amber')
|
|
162
|
+
: '') +
|
|
163
|
+
t.ink(' · shift+tab', 'muted')
|
|
164
|
+
: '';
|
|
81
165
|
const identity = c.snapshot
|
|
82
166
|
? ' session ' +
|
|
83
167
|
shortId(c.snapshot.id) +
|
|
84
168
|
(c.workspace ? ' · workspace ' + shortId(c.workspace.id) + ' r' + c.workspace.revision : '')
|
|
85
169
|
: '';
|
|
86
170
|
return [
|
|
87
|
-
t.line(t.ink(safeText(state).replace(/\bPi\b/g, 'Worker'), tone) +
|
|
88
|
-
|
|
89
|
-
t.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
171
|
+
t.line(t.ink(safeText(state).replace(/\bPi\b/g, 'Worker'), tone) +
|
|
172
|
+
mode +
|
|
173
|
+
t.ink(safeText(guidance) + spend + identity, 'muted'), width),
|
|
174
|
+
t.line((c.snapshot && interactiveCoding(c.snapshot) && runningAttempt(c.snapshot)
|
|
175
|
+
? [
|
|
176
|
+
t.key('esc', 'interrupt'),
|
|
177
|
+
t.key('enter', 'steer'),
|
|
178
|
+
t.key('tab', 'queue'),
|
|
179
|
+
t.key('alt+enter', 'newline'),
|
|
180
|
+
t.key('ctrl+c ×2', 'leave'),
|
|
181
|
+
]
|
|
182
|
+
: [
|
|
183
|
+
t.key('enter', 'send'),
|
|
184
|
+
t.key('alt+enter', 'newline'),
|
|
185
|
+
t.key('ctrl+k', 'actions'),
|
|
186
|
+
t.key('ctrl+p', 'sessions'),
|
|
187
|
+
t.key('ctrl+c ×2', 'leave'),
|
|
188
|
+
]).join(' '), width),
|
|
95
189
|
];
|
|
96
190
|
}
|
|
97
191
|
export class DocumentView {
|
|
@@ -116,6 +210,56 @@ export class DocumentView {
|
|
|
116
210
|
}
|
|
117
211
|
return cached.markdown.render(Math.max(1, width));
|
|
118
212
|
}
|
|
213
|
+
/** Per-file review of what changed in the folder since the review base. */
|
|
214
|
+
reviewLines(width) {
|
|
215
|
+
const t = this.view.theme, review = this.view.review, lines = this.title('CHANGES SINCE REVIEW', width);
|
|
216
|
+
if (!review)
|
|
217
|
+
return [...lines, ...this.text('Loading changes…', width)];
|
|
218
|
+
if (!review.data)
|
|
219
|
+
return [
|
|
220
|
+
...lines,
|
|
221
|
+
...this.text('Changes appear here after the first turn starts; each turn is checkpointed.', width),
|
|
222
|
+
];
|
|
223
|
+
const note = backgroundNote(review.data.background_running);
|
|
224
|
+
if (note)
|
|
225
|
+
lines.push(...this.text(note, width));
|
|
226
|
+
if (!review.data.files.length)
|
|
227
|
+
return [
|
|
228
|
+
...lines,
|
|
229
|
+
...this.text('Nothing to review: the folder matches what you accepted.', width),
|
|
230
|
+
];
|
|
231
|
+
review.data.files.forEach((change, i) => {
|
|
232
|
+
const mark = i === review.file ? '▸ ' : ' ';
|
|
233
|
+
lines.push(t.line(mark +
|
|
234
|
+
safeText(change.path) +
|
|
235
|
+
' ' +
|
|
236
|
+
t.ink(change.status + ' · ' + changeCounts(change), 'muted'), width));
|
|
237
|
+
});
|
|
238
|
+
lines.push(t.fill('', width));
|
|
239
|
+
const file = review.data.files[review.file];
|
|
240
|
+
const hunks = review.hunks ?? [];
|
|
241
|
+
lines.push(...this.title(safeText(file.path) +
|
|
242
|
+
(review.hunk >= 0 ? ` · hunk ${review.hunk + 1} of ${hunks.length}` : ' · whole file'), width));
|
|
243
|
+
if (!hunks.length)
|
|
244
|
+
lines.push(...this.text('No text diff to show for this file.', width));
|
|
245
|
+
hunks.forEach((hunk, i) => {
|
|
246
|
+
const selected = i === review.hunk;
|
|
247
|
+
lines.push(...t
|
|
248
|
+
.highlight(safeText(hunk.text).replace(/\n$/, ''), 'diff')
|
|
249
|
+
.flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width - 4)))
|
|
250
|
+
.map((line) => t.line((selected ? '┃ ' : review.hunk >= 0 ? ' ' : '') + line, width)));
|
|
251
|
+
});
|
|
252
|
+
lines.push(t.fill('', width), t.line(t.key('alt+←/→', 'file') +
|
|
253
|
+
' ' +
|
|
254
|
+
t.key('alt+↑/↓', 'hunk') +
|
|
255
|
+
' ' +
|
|
256
|
+
t.key('a', 'accept') +
|
|
257
|
+
' ' +
|
|
258
|
+
t.key('r', 'revert') +
|
|
259
|
+
' ' +
|
|
260
|
+
t.key('i', 'instruct'), width), ...this.text('a/r/i work while the message box is empty. Accept marks a change reviewed; revert writes the reviewed base back. /rewind returns to before any turn.', width));
|
|
261
|
+
return lines;
|
|
262
|
+
}
|
|
119
263
|
title(label, width, role = 'jade') {
|
|
120
264
|
const t = this.view.theme;
|
|
121
265
|
return [t.line(t.ink(label, role, true), width)];
|
|
@@ -145,7 +289,7 @@ export class DocumentView {
|
|
|
145
289
|
: 'A durable workspace that stays with you, in this terminal or over SSH.', width),
|
|
146
290
|
t.fill('', width),
|
|
147
291
|
t.line(t.key('ctrl+n', 'new session') + ' ' + t.key('ctrl+p', 'recent sessions'), width),
|
|
148
|
-
t.line(t.key('ctrl+k', 'all actions') + ' ' + t.key('
|
|
292
|
+
t.line(t.key('ctrl+k', 'all actions') + ' ' + t.key('alt+g', 'review changes'), width),
|
|
149
293
|
...(c.snapshot
|
|
150
294
|
? [
|
|
151
295
|
t.fill('', width),
|
|
@@ -153,13 +297,41 @@ export class DocumentView {
|
|
|
153
297
|
? 'Owned coding workspace · guarded edits'
|
|
154
298
|
: c.snapshot.capabilities.includes('local-workspace-read')
|
|
155
299
|
? c.snapshot.capabilities.includes('local-coding-reviewed')
|
|
156
|
-
? 'Local coding · reviewed edits · commands
|
|
300
|
+
? 'Local coding · reviewed edits · commands ' +
|
|
301
|
+
(this.view.mode?.sandbox
|
|
302
|
+
? '· ' + this.view.mode.sandbox
|
|
303
|
+
: 'run as your user')
|
|
157
304
|
: 'Local workspace · read, list and search · /coding for edits and commands'
|
|
158
305
|
: 'Conversation session · coding tools are off', 'muted'), width),
|
|
159
306
|
]
|
|
160
307
|
: []),
|
|
161
308
|
];
|
|
162
309
|
}
|
|
310
|
+
/** Compact lines for tool calls (with their output in the full transcript), children indented. */
|
|
311
|
+
toolBlock(calls, byKey, width) {
|
|
312
|
+
const t = this.view.theme, lines = [], shown = this.view.transcript ? calls : calls.slice(-8);
|
|
313
|
+
if (shown.length < calls.length)
|
|
314
|
+
lines.push(t.line(t.ink(` … ${calls.length - shown.length} earlier calls · ctrl+x shows all`, 'muted'), width));
|
|
315
|
+
for (const call of shown) {
|
|
316
|
+
let depth = 0, parent = call.parent;
|
|
317
|
+
const visited = new Set();
|
|
318
|
+
while (parent && !visited.has(parent) && depth < 4) {
|
|
319
|
+
visited.add(parent);
|
|
320
|
+
depth++;
|
|
321
|
+
parent = byKey.get(parent)?.parent;
|
|
322
|
+
}
|
|
323
|
+
const indent = depth ? ' '.repeat(depth) + '└─ ' : '';
|
|
324
|
+
lines.push(t.line(t.ink(call.state === 'running' ? '◌ ' : call.state === 'failed' ? '× ' : '✓ ', call.state === 'failed' ? 'red' : call.state === 'running' ? 'amber' : 'jade') + t.ink(indent + call.label), width));
|
|
325
|
+
const detail = this.view.transcript
|
|
326
|
+
? call.output
|
|
327
|
+
? safeText(call.output).split('\n').slice(0, 40)
|
|
328
|
+
: []
|
|
329
|
+
: call.tail;
|
|
330
|
+
for (const tail of detail)
|
|
331
|
+
lines.push(...this.text(' ' + ' '.repeat(indent.length) + '│ ' + tail, width));
|
|
332
|
+
}
|
|
333
|
+
return lines;
|
|
334
|
+
}
|
|
163
335
|
render(width) {
|
|
164
336
|
const { client: c, theme: t, tab } = this.view;
|
|
165
337
|
if (!c.snapshot) {
|
|
@@ -181,48 +353,43 @@ export class DocumentView {
|
|
|
181
353
|
if (c.snapshot.history_truncated)
|
|
182
354
|
lines.push(...this.text('Recent conversation · earlier history is available through the history command.', width), t.fill('', width));
|
|
183
355
|
const ids = new Set(c.snapshot.messages.map((m) => m.entry_id));
|
|
356
|
+
// Each turn's tool calls sit where they ran: before the message that followed them.
|
|
357
|
+
const calls = toolLines(this.view), byKey = new Map(calls.map((l) => [l.key, l])), groups = new Map(), rootOf = new Map(), placed = new Set();
|
|
358
|
+
for (const call of calls) {
|
|
359
|
+
let root = call.key;
|
|
360
|
+
const visited = new Set();
|
|
361
|
+
while (byKey.get(root)?.parent && !visited.has(root) && visited.size < 8) {
|
|
362
|
+
visited.add(root);
|
|
363
|
+
root = byKey.get(root).parent;
|
|
364
|
+
}
|
|
365
|
+
groups.set(root, [...(groups.get(root) ?? []), call]);
|
|
366
|
+
rootOf.set(root.slice(root.indexOf(':') + 1), root);
|
|
367
|
+
}
|
|
184
368
|
for (const message of c.snapshot.messages) {
|
|
369
|
+
const before = (message.tool_calls ?? [])
|
|
370
|
+
.map((id) => rootOf.get(id))
|
|
371
|
+
.filter((root) => !!root && !placed.has(root));
|
|
372
|
+
if (before.length) {
|
|
373
|
+
for (const root of before)
|
|
374
|
+
placed.add(root);
|
|
375
|
+
lines.push(...this.toolBlock(before.flatMap((root) => groups.get(root)), byKey, width), t.fill('', width));
|
|
376
|
+
}
|
|
185
377
|
lines.push(...this.title(message.role === 'user' ? 'YOU' : 'SALAAM', width, message.role === 'user' ? 'muted' : 'jade'));
|
|
186
|
-
lines.push(...this.prose(message.entry_id, message.text, width - 4).map((line) => t.line(line, width)), ...(message.truncated
|
|
378
|
+
lines.push(...this.prose(message.entry_id, message.role === 'user' ? typedText(message.text) : message.text, width - 4).map((line) => t.line(line, width)), ...(message.truncated
|
|
187
379
|
? this.text('Message preview truncated. Read its full history entry to inspect all content.', width)
|
|
188
380
|
: []), t.fill('', width));
|
|
189
381
|
}
|
|
190
382
|
for (const submission of [...c.snapshot.submissions]
|
|
191
383
|
.reverse()
|
|
192
384
|
.filter((s) => ['accepted', 'dispatching', 'running'].includes(s.state))) {
|
|
193
|
-
lines.push(...this.title(submission.state === 'accepted' ? 'YOU · queued' : 'YOU · in progress', width, 'muted'), ...this.text(submission.text, width), t.fill('', width));
|
|
385
|
+
lines.push(...this.title(submission.state === 'accepted' ? 'YOU · queued' : 'YOU · in progress', width, 'muted'), ...this.text(typedText(submission.text), width), t.fill('', width));
|
|
194
386
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
label: event.kind === 'tool.started'
|
|
202
|
-
? humanTool(p.tool, p.input)
|
|
203
|
-
: (before?.label ?? humanTool(p.tool)),
|
|
204
|
-
state: event.kind === 'tool.started' ? 'running' : p.failed ? 'failed' : 'finished',
|
|
205
|
-
failed: !!p.failed,
|
|
206
|
-
parent: p.parent_tool_call_id
|
|
207
|
-
? p.attempt_id + ':' + p.parent_tool_call_id
|
|
208
|
-
: before?.parent,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
if (tools.size) {
|
|
212
|
-
lines.push(...this.title('TOOL ACTIVITY', width, 'muted'));
|
|
213
|
-
for (const tool of [...tools.values()].slice(-6)) {
|
|
214
|
-
let depth = 0, parent = tool.parent;
|
|
215
|
-
const visited = new Set();
|
|
216
|
-
while (parent && !visited.has(parent) && depth < 4) {
|
|
217
|
-
visited.add(parent);
|
|
218
|
-
depth++;
|
|
219
|
-
parent = tools.get(parent)?.parent;
|
|
220
|
-
}
|
|
221
|
-
lines.push(t.line(t.ink(tool.state === 'running' ? '◌ ' : tool.failed ? '× ' : '✓ ', tool.failed ? 'red' : tool.state === 'running' ? 'amber' : 'jade') +
|
|
222
|
-
t.ink((depth ? ' '.repeat(depth) + '└─ ' : '') + tool.label) +
|
|
223
|
-
t.ink(' ' + tool.state, 'muted'), width));
|
|
224
|
-
}
|
|
225
|
-
lines.push(t.fill('', width));
|
|
387
|
+
// Calls not placed before a message: the turn still running, or ones history no longer shows.
|
|
388
|
+
const rest = [...groups.entries()].filter(([root]) => !placed.has(root));
|
|
389
|
+
if (rest.length) {
|
|
390
|
+
lines.push(...this.title(this.view.transcript
|
|
391
|
+
? 'TRANSCRIPT · every tool call'
|
|
392
|
+
: 'TOOLS · ctrl+x for the full transcript', width, 'muted'), ...this.toolBlock(rest.flatMap(([, group]) => group), byKey, width), t.fill('', width));
|
|
226
393
|
}
|
|
227
394
|
if (c.preview)
|
|
228
395
|
lines.push(...this.title('SALAAM · writing', width), ...this.prose('live-preview', c.preview.text, width - 4).map((line) => t.line(line, width)), ...(c.preview.truncated
|
|
@@ -239,15 +406,17 @@ export class DocumentView {
|
|
|
239
406
|
if (key !== 'live-preview' && !ids.has(key))
|
|
240
407
|
this.cache.delete(key);
|
|
241
408
|
}
|
|
242
|
-
else if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
409
|
+
else if (tab === 'changes' && c.snapshot.capabilities.includes('local-coding-reviewed')) {
|
|
410
|
+
lines.push(...this.reviewLines(width));
|
|
411
|
+
}
|
|
412
|
+
else if (tab === 'commands' && c.snapshot.capabilities.includes('local-coding-reviewed')) {
|
|
413
|
+
const operations = c.snapshot.operations.filter((op) => op.kind === 'workspace' && op.target.startsWith('Local command:'));
|
|
414
|
+
lines.push(...this.title('LOCAL COMMAND RESULTS', width));
|
|
247
415
|
if (!operations.length)
|
|
248
|
-
lines.push(...this.text(
|
|
249
|
-
|
|
250
|
-
|
|
416
|
+
lines.push(...this.text('Commands appear here with their recorded exit status and output. ' +
|
|
417
|
+
(this.view.mode?.sandbox
|
|
418
|
+
? 'Where commands run: ' + this.view.mode.sandbox + '.'
|
|
419
|
+
: 'Commands run as your local user.'), width));
|
|
251
420
|
for (const op of operations.slice().reverse()) {
|
|
252
421
|
lines.push(t.rule(width), ...this.title(shortId(op.id) + ' · ' + op.state, width, op.state === 'succeeded' ? 'jade' : 'amber'), ...this.text(op.value, width));
|
|
253
422
|
if (op.receipt)
|
|
@@ -258,6 +427,9 @@ export class DocumentView {
|
|
|
258
427
|
lines.push(...this.text('Ctrl+O reviews pending approvals. Cancel current work from Ctrl+K stops the active foreground command. These are saved operation records, not a live Git diff.', width));
|
|
259
428
|
}
|
|
260
429
|
else if (tab === 'changes') {
|
|
430
|
+
const failed = sessionFailure(c.snapshot);
|
|
431
|
+
if (failed && !c.workspace)
|
|
432
|
+
return this.text(failed, width);
|
|
261
433
|
if (!c.workspace)
|
|
262
434
|
return this.text('This session has no coding workspace. Create a coding session to review guarded edits.', width);
|
|
263
435
|
lines.push(...this.title('WORKSPACE CHANGES', width), ...this.text('Original checkout: ' + c.workspace.source, width), ...this.text('Workspace ' +
|
|
@@ -434,7 +606,7 @@ export class DocumentView {
|
|
|
434
606
|
: []));
|
|
435
607
|
lines.push(t.fill('', width), ...this.title('USAGE', width));
|
|
436
608
|
if (c.usage)
|
|
437
|
-
lines.push(...this.text(`${c.usage.observations} provider observations · ${c.usage.unknown_observations} unknown`, width), ...this.text(`Recorded tokens: ${c.usage
|
|
609
|
+
lines.push(...this.text(`${c.usage.observations} provider observations · ${c.usage.unknown_observations} unknown`, width), ...this.text(`Recorded tokens: ${usageInput(c.usage)} input (${c.usage.known_cache_read ?? 0} cache reads, ${c.usage.known_cache_write ?? 0} cache writes) · ${c.usage.known_output} output`, width), ...this.text(`Recorded cost $${c.usage.known_cost.toFixed(4)} · estimated $${c.usage.estimated_cost.toFixed(4)}`, width));
|
|
438
610
|
if (c.health?.execution?.backend)
|
|
439
611
|
lines.push(t.fill('', width), ...this.title('EXECUTION', width), ...this.text(JSON.stringify(c.health.execution.backend, null, 2), width));
|
|
440
612
|
}
|
|
@@ -485,11 +657,13 @@ export function sidebar(view, width) {
|
|
|
485
657
|
row(modelLabel(c));
|
|
486
658
|
if (c.usage) {
|
|
487
659
|
label('USAGE');
|
|
488
|
-
row(c.usage.
|
|
660
|
+
row(usageInput(c.usage).toLocaleString() +
|
|
489
661
|
' in · ' +
|
|
490
662
|
c.usage.known_output.toLocaleString() +
|
|
491
663
|
' out');
|
|
492
|
-
|
|
664
|
+
if (c.usage.known_cache_read)
|
|
665
|
+
row(c.usage.known_cache_read.toLocaleString() + ' from cache', 'muted');
|
|
666
|
+
row((c.usage.estimated_observations ? 'Est. $' : '$') + usageCost(c.usage).toFixed(4), 'muted');
|
|
493
667
|
if (c.usage.unknown_observations)
|
|
494
668
|
row(c.usage.unknown_observations + ' unknown entries', 'amber');
|
|
495
669
|
}
|
|
@@ -502,7 +676,7 @@ export function sidebar(view, width) {
|
|
|
502
676
|
: 'Files · local read'
|
|
503
677
|
: 'Files · off', c.workspace || c.snapshot?.capabilities.includes('local-workspace-read') ? 'jade' : 'muted');
|
|
504
678
|
row(c.snapshot?.capabilities.includes('local-coding-reviewed')
|
|
505
|
-
? 'Commands · local user'
|
|
679
|
+
? 'Commands · ' + (view.mode?.sandbox ?? 'local user')
|
|
506
680
|
: c.workspace && c.health?.execution?.commands
|
|
507
681
|
? 'Commands · isolated'
|
|
508
682
|
: 'Commands · off', c.workspace && c.health?.execution?.commands ? 'jade' : 'muted');
|