@xingular/harness 1.2.0 → 1.3.1-unstable.1
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 +63 -0
- package/README.md +26 -17
- package/builtin/skills/plugin-authoring/SKILL.md +34 -0
- package/dist/cli.js +7362 -6239
- package/dist/sdk/agents/builtin.js +3 -3
- package/dist/sdk/agents/planResearch.d.ts +23 -1
- package/dist/sdk/agents/planResearch.js +71 -8
- package/dist/sdk/agents/profiles.d.ts +16 -4
- package/dist/sdk/agents/profiles.js +75 -12
- package/dist/sdk/annotations/bridge.d.ts +2 -0
- package/dist/sdk/annotations/bridge.js +4 -0
- package/dist/sdk/annotations/capture.d.ts +44 -2
- package/dist/sdk/annotations/capture.js +104 -3
- package/dist/sdk/annotations/delivery.d.ts +24 -17
- package/dist/sdk/annotations/delivery.js +82 -49
- package/dist/sdk/annotations/inspect.js +7 -6
- package/dist/sdk/annotations/pageScript.js +4 -4
- package/dist/sdk/annotations/part.d.ts +0 -2
- package/dist/sdk/annotations/part.js +4 -33
- package/dist/sdk/annotations/queue.d.ts +2 -2
- package/dist/sdk/annotations/queue.js +12 -7
- package/dist/sdk/annotations/serverMethods.js +11 -7
- package/dist/sdk/annotations/store.d.ts +13 -21
- package/dist/sdk/annotations/store.js +47 -37
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/commands/list.js +2 -0
- package/dist/sdk/commands/registry.d.ts +11 -3
- package/dist/sdk/commands/registry.js +3 -2
- package/dist/sdk/core/links.d.ts +51 -0
- package/dist/sdk/core/links.js +155 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
- package/dist/sdk/core/workspaceBashTool.js +16 -2
- package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
- package/dist/sdk/core/workspaceSandbox.js +52 -12
- package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
- package/dist/sdk/core/workspaceWriteOperations.js +26 -8
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/harness.d.ts +5 -0
- package/dist/sdk/harness.js +11 -6
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +2 -1
- package/dist/sdk/plugins/builtin.js +14 -9
- package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
- package/dist/sdk/plugins/builtinRegistry.js +9 -3
- package/dist/sdk/plugins/discover.d.ts +7 -1
- package/dist/sdk/plugins/discover.js +41 -2
- package/dist/sdk/plugins/identity.js +1 -1
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +3 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +21 -4
- package/dist/sdk/plugins/plugins.d.ts +2 -0
- package/dist/sdk/plugins/plugins.js +1 -0
- package/dist/sdk/plugins/proposals.d.ts +17 -0
- package/dist/sdk/plugins/proposals.js +147 -0
- package/dist/sdk/plugins/types.d.ts +1 -1
- package/dist/sdk/plugins/types.js +1 -1
- package/dist/sdk/prompt/compile.d.ts +13 -4
- package/dist/sdk/prompt/compile.js +46 -20
- package/dist/sdk/prompt/standard.d.ts +1 -1
- package/dist/sdk/prompt/standard.js +1 -1
- package/dist/sdk/server/methods.js +14 -2
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
- package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
- package/dist/sdk/sessions/continuation.d.ts +4 -2
- package/dist/sdk/sessions/continuation.js +7 -6
- package/dist/sdk/sessions/contracts.d.ts +22 -4
- package/dist/sdk/sessions/contracts.js +12 -7
- package/dist/sdk/sessions/planMode.js +2 -2
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessionState.d.ts +13 -0
- package/dist/sdk/sessions/sessionState.js +31 -0
- package/dist/sdk/sessions/sessions.d.ts +7 -0
- package/dist/sdk/sessions/sessions.js +73 -15
- package/dist/sdk/sessions/subagentService.d.ts +15 -0
- package/dist/sdk/sessions/subagentService.js +195 -55
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +8 -5
- package/dist/sdk/sessions/workSlice.js +77 -45
- package/dist/sdk/terminal/controller.d.ts +20 -0
- package/dist/sdk/terminal/controller.js +43 -18
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +2 -0
- package/dist/sdk/terminal/opentui/theme.js +2 -0
- package/dist/sdk/terminal/piExtension.js +98 -15
- package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
- package/dist/sdk/terminal/subagentMonitor.js +29 -8
- package/dist/sdk/terminal/terminals.d.ts +16 -0
- package/dist/sdk/terminal/terminals.js +130 -26
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +5 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
- package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
- package/dist/sdk/terminal/tui/annotateSend.js +40 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
- package/dist/sdk/terminal/tui/commandPages.js +16 -11
- package/dist/sdk/terminal/tui/commands.d.ts +4 -0
- package/dist/sdk/terminal/tui/commands.js +12 -2
- package/dist/sdk/terminal/tui/dialogs.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
- package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
- package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
- package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
- package/dist/sdk/tools/defaultSkills.d.ts +4 -0
- package/dist/sdk/tools/defaultSkills.js +20 -0
- package/dist/sdk/tools/definitions.d.ts +3 -1
- package/dist/sdk/tools/definitions.js +17 -1
- package/dist/sdk/tools/dispatch.js +12 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/sessionName.d.ts +9 -0
- package/dist/sdk/tools/sessionName.js +58 -11
- package/dist/sdk/tools/spawnSubagent.js +9 -7
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/world/world.d.ts +0 -2
- package/dist/terminal-opentui.js +3331 -1584
- package/dist/terminal-pi.js +421 -133
- package/docs/README.md +3 -0
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +73 -36
- package/docs/architecture/03-variants.md +1 -3
- package/docs/architecture/04-prompt.md +24 -13
- package/docs/architecture/06-plugins-and-capabilities.md +46 -10
- package/docs/architecture/07-tools-and-gate.md +49 -8
- package/docs/architecture/09-accounts-and-connectors.md +8 -2
- package/docs/architecture/10-server-protocol.md +68 -16
- package/docs/architecture/11-terminal.md +94 -45
- package/docs/architecture/12-storage.md +1 -0
- package/docs/architecture/13-world-tools.md +8 -6
- package/docs/architecture/14-surfaces-and-parity.md +4 -1
- package/docs/architecture/README.md +20 -18
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/PROP-0048-simple-annotate.md +85 -0
- package/docs/architecture/proposals/README.md +11 -6
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
- package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
- package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
- package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
- package/docs/doctor.md +14 -1
- package/docs/parity.md +16 -13
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/1.3.1-unstable.1.md +8 -0
- package/docs/releases/README.md +9 -6
- package/docs/terminal-tui.md +127 -65
- package/package.json +1 -1
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
- package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -55
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { type AgentSession } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { TerminalCommandHelpGroup } from "../commandHelp.js";
|
|
3
|
+
export type ToolStatus = 'running' | 'ok' | 'error' | 'pending';
|
|
4
|
+
export interface UserItem {
|
|
5
|
+
kind: 'user';
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
images: number;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
export interface AssistantItem {
|
|
12
|
+
kind: 'assistant';
|
|
13
|
+
id: string;
|
|
14
|
+
text: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
streaming: boolean;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ThinkingItem {
|
|
20
|
+
kind: 'thinking';
|
|
21
|
+
id: string;
|
|
22
|
+
text: string;
|
|
23
|
+
streaming: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface ToolItem {
|
|
26
|
+
kind: 'tool';
|
|
27
|
+
id: string;
|
|
28
|
+
toolCallId: string;
|
|
29
|
+
name: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
status: ToolStatus;
|
|
32
|
+
output: string;
|
|
33
|
+
durationMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ImageResultItem {
|
|
36
|
+
kind: 'image-result';
|
|
37
|
+
id: string;
|
|
38
|
+
text: string;
|
|
39
|
+
imageCount: number;
|
|
40
|
+
images: {
|
|
41
|
+
data: string;
|
|
42
|
+
mimeType: string;
|
|
43
|
+
}[];
|
|
44
|
+
showImages: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface NoticeItem {
|
|
47
|
+
kind: 'notice';
|
|
48
|
+
id: string;
|
|
49
|
+
text: string;
|
|
50
|
+
level: string;
|
|
51
|
+
}
|
|
52
|
+
export interface CommandHelpItem {
|
|
53
|
+
kind: 'command-help';
|
|
54
|
+
id: string;
|
|
55
|
+
groups: readonly TerminalCommandHelpGroup[];
|
|
56
|
+
}
|
|
57
|
+
export interface BashItem {
|
|
58
|
+
kind: 'bash';
|
|
59
|
+
id: string;
|
|
60
|
+
command: string;
|
|
61
|
+
timestamp: number;
|
|
62
|
+
output: string;
|
|
63
|
+
running: boolean;
|
|
64
|
+
exitCode?: number;
|
|
65
|
+
cancelled: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface CustomItem {
|
|
68
|
+
kind: 'custom';
|
|
69
|
+
id: string;
|
|
70
|
+
label: string;
|
|
71
|
+
text: string;
|
|
72
|
+
}
|
|
73
|
+
export interface SummaryItem {
|
|
74
|
+
kind: 'summary';
|
|
75
|
+
id: string;
|
|
76
|
+
label: string;
|
|
77
|
+
text: string;
|
|
78
|
+
}
|
|
79
|
+
export interface CompactionItem {
|
|
80
|
+
kind: 'compaction';
|
|
81
|
+
id: string;
|
|
82
|
+
summary: string;
|
|
83
|
+
tokensBefore?: number;
|
|
84
|
+
}
|
|
85
|
+
/** A `todo_write` call, shown as the list it wrote rather than as a tool row. */
|
|
86
|
+
export interface TodoTranscriptItem {
|
|
87
|
+
kind: 'todo';
|
|
88
|
+
id: string;
|
|
89
|
+
/** Set for a tool call; absent for a `/todo` snapshot. */
|
|
90
|
+
toolCallId?: string;
|
|
91
|
+
/** Set for a tool call; a command snapshot has no status mark. */
|
|
92
|
+
status?: ToolStatus;
|
|
93
|
+
origin: 'tool' | 'command';
|
|
94
|
+
items: readonly TodoEntry[];
|
|
95
|
+
}
|
|
96
|
+
export interface TodoEntry {
|
|
97
|
+
text: string;
|
|
98
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
99
|
+
}
|
|
100
|
+
/** A proposed plan block from a plan-mode reply, shown as a card. */
|
|
101
|
+
export interface PlanItem {
|
|
102
|
+
kind: 'plan';
|
|
103
|
+
id: string;
|
|
104
|
+
title: string;
|
|
105
|
+
body: string;
|
|
106
|
+
streaming: boolean;
|
|
107
|
+
}
|
|
108
|
+
export type TranscriptItem = UserItem | AssistantItem | ThinkingItem | ToolItem | ImageResultItem | NoticeItem | CommandHelpItem | BashItem | CustomItem | SummaryItem | CompactionItem | PlanItem | TodoTranscriptItem;
|
|
109
|
+
export interface TranscriptView {
|
|
110
|
+
items: readonly TranscriptItem[];
|
|
111
|
+
hasUserMessages: boolean;
|
|
112
|
+
hasMultilineNotice: boolean;
|
|
113
|
+
}
|
|
114
|
+
/** The items of a `todo_write` (or pre-rename `checklist_write`) call, or null when they are not a todo list. */
|
|
115
|
+
export declare function todoEntries(args: unknown): TodoEntry[] | null;
|
|
116
|
+
export declare class TranscriptModel {
|
|
117
|
+
private session?;
|
|
118
|
+
/** Items derived from a finished message, reused while its position and tool results stay the same. */
|
|
119
|
+
private messageItems;
|
|
120
|
+
private hideThinking;
|
|
121
|
+
private showImages;
|
|
122
|
+
private expanded;
|
|
123
|
+
private notices;
|
|
124
|
+
private nextNotice;
|
|
125
|
+
private liveBash?;
|
|
126
|
+
private cachedView?;
|
|
127
|
+
private readonly cachedItems;
|
|
128
|
+
/** What a compaction took out of Pi's message list, rebuilt from the session history on bind. */
|
|
129
|
+
private history?;
|
|
130
|
+
bind(session: AgentSession): void;
|
|
131
|
+
clear(): void;
|
|
132
|
+
invalidate(): void;
|
|
133
|
+
beginBash(command: string): void;
|
|
134
|
+
appendBashOutput(id: string, delta: string): boolean;
|
|
135
|
+
finishBash(): void;
|
|
136
|
+
clearNotices(): void;
|
|
137
|
+
setThinkingHidden(hidden: boolean): void;
|
|
138
|
+
setImagesShown(shown: boolean): void;
|
|
139
|
+
setExpanded(expanded: boolean): void;
|
|
140
|
+
get isExpanded(): boolean;
|
|
141
|
+
notice(text: string, level?: string): void;
|
|
142
|
+
commandHelp(groups: readonly TerminalCommandHelpGroup[]): void;
|
|
143
|
+
/** A `/todo` snapshot: the list at the moment the command ran, with no tool row. */
|
|
144
|
+
todo(items: readonly TodoEntry[]): void;
|
|
145
|
+
private addNotice;
|
|
146
|
+
/**
|
|
147
|
+
* Notices in the order they appear, each with the message index it goes before. Compaction
|
|
148
|
+
* replaces the message list with a shorter one, so a notice follows the message it was posted
|
|
149
|
+
* after; a notice whose message was summarized goes right after the compaction card.
|
|
150
|
+
*/
|
|
151
|
+
private placedNotices;
|
|
152
|
+
view(): TranscriptView;
|
|
153
|
+
/**
|
|
154
|
+
* Pi's list after a compaction starts with the summary and the kept messages. The transcript
|
|
155
|
+
* shows the whole conversation instead: everything before the compaction from the session
|
|
156
|
+
* history, the summary, then what came after.
|
|
157
|
+
*/
|
|
158
|
+
private displayMessages;
|
|
159
|
+
/** What a message's items depend on besides the message itself. */
|
|
160
|
+
private messageKey;
|
|
161
|
+
private messageItemsFor;
|
|
162
|
+
private stabilize;
|
|
163
|
+
}
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { sessionEntryToContextMessages } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { parseProposedPlan } from "../tui/planMode.js";
|
|
3
|
+
import { ansiLabel, ansiPlain } from "./ansi.js";
|
|
4
|
+
const MAX_TOOL_OUTPUT = 12_000;
|
|
5
|
+
const OUTPUT_CLIP_MARKER = '\n… output clipped …\n';
|
|
6
|
+
function appendCapped(current, delta) {
|
|
7
|
+
const combined = current + delta;
|
|
8
|
+
if (combined.length <= MAX_TOOL_OUTPUT)
|
|
9
|
+
return combined;
|
|
10
|
+
const available = MAX_TOOL_OUTPUT - OUTPUT_CLIP_MARKER.length;
|
|
11
|
+
const head = Math.ceil(available / 2);
|
|
12
|
+
return `${combined.slice(0, head)}${OUTPUT_CLIP_MARKER}${combined.slice(-(available - head))}`;
|
|
13
|
+
}
|
|
14
|
+
function blocks(content) {
|
|
15
|
+
if (typeof content === 'string')
|
|
16
|
+
return [{ type: 'text', text: content }];
|
|
17
|
+
return Array.isArray(content) ? content.filter((item) => typeof item === 'object' && item !== null) : [];
|
|
18
|
+
}
|
|
19
|
+
function contentText(content) {
|
|
20
|
+
return blocks(content)
|
|
21
|
+
.filter(block => block.type === 'text' && typeof block.text === 'string')
|
|
22
|
+
.map(block => String(block.text))
|
|
23
|
+
.join('\n');
|
|
24
|
+
}
|
|
25
|
+
function withoutBoldMarkers(text) {
|
|
26
|
+
return text.replace(/\*\*(.*?)\*\*/gs, '$1');
|
|
27
|
+
}
|
|
28
|
+
function imageCount(content) {
|
|
29
|
+
return blocks(content).filter(block => block.type === 'image').length;
|
|
30
|
+
}
|
|
31
|
+
function imageContents(content) {
|
|
32
|
+
return blocks(content).flatMap(block => block.type === 'image' && typeof block.data === 'string' && typeof block.mimeType === 'string'
|
|
33
|
+
? [{ data: block.data, mimeType: block.mimeType }]
|
|
34
|
+
: []);
|
|
35
|
+
}
|
|
36
|
+
function stringField(message, name) {
|
|
37
|
+
const value = message[name];
|
|
38
|
+
return typeof value === 'string' ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
function numberField(message, name) {
|
|
41
|
+
const value = message[name];
|
|
42
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
43
|
+
}
|
|
44
|
+
function clip(text, limit = MAX_TOOL_OUTPUT) {
|
|
45
|
+
return text.length > limit ? `${text.slice(0, limit)}\n… ${text.length - limit} more characters` : text;
|
|
46
|
+
}
|
|
47
|
+
function argumentSummary(name, args) {
|
|
48
|
+
const value = (args ?? {});
|
|
49
|
+
const pick = (...names) => {
|
|
50
|
+
for (const key of names) {
|
|
51
|
+
const item = value[key];
|
|
52
|
+
if (typeof item === 'string' && item.trim())
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
const path = pick('path', 'file_path', 'filePath', 'target');
|
|
58
|
+
const summary = name === 'bash' || name === 'shell'
|
|
59
|
+
? pick('command', 'cmd')
|
|
60
|
+
: name === 'grep'
|
|
61
|
+
? [pick('pattern'), pick('path')].filter(Boolean).join(' ')
|
|
62
|
+
: name === 'find'
|
|
63
|
+
? [pick('pattern'), pick('path')].filter(Boolean).join(' ')
|
|
64
|
+
: name === 'websearch' || name === 'web_search'
|
|
65
|
+
? pick('query')
|
|
66
|
+
: name === 'webfetch' || name === 'web_fetch'
|
|
67
|
+
? pick('url')
|
|
68
|
+
: name === 'spawn_subagent' || name === 'spawnSubagent'
|
|
69
|
+
? [pick('agent'), pick('title', 'brief', 'description', 'prompt')].filter(Boolean).join(' · ')
|
|
70
|
+
: path;
|
|
71
|
+
if (summary)
|
|
72
|
+
return ansiLabel(summary, 100);
|
|
73
|
+
if (!args || typeof args !== 'object')
|
|
74
|
+
return '';
|
|
75
|
+
try {
|
|
76
|
+
return ansiLabel(JSON.stringify(args), 100);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function toolOutput(message) {
|
|
83
|
+
const text = ansiPlain(contentText(message.content));
|
|
84
|
+
const details = message.details;
|
|
85
|
+
if (details && typeof details === 'object') {
|
|
86
|
+
const diff = details.diff;
|
|
87
|
+
if (typeof diff === 'string' && diff.trim())
|
|
88
|
+
return clip(`${text}${text ? '\n' : ''}${diff}`);
|
|
89
|
+
}
|
|
90
|
+
return clip(text);
|
|
91
|
+
}
|
|
92
|
+
const TODO_STATUSES = new Set(['pending', 'in_progress', 'completed']);
|
|
93
|
+
/** The items of a `todo_write` (or pre-rename `checklist_write`) call, or null when they are not a todo list. */
|
|
94
|
+
export function todoEntries(args) {
|
|
95
|
+
const items = args?.items;
|
|
96
|
+
if (!Array.isArray(items))
|
|
97
|
+
return null;
|
|
98
|
+
const entries = [];
|
|
99
|
+
for (const item of items) {
|
|
100
|
+
const entry = item;
|
|
101
|
+
if (!entry || typeof entry.text !== 'string' || !entry.text.trim() || typeof entry.status !== 'string' || !TODO_STATUSES.has(entry.status))
|
|
102
|
+
return null;
|
|
103
|
+
entries.push({ text: ansiLabel(entry.text.trim(), 500), status: entry.status });
|
|
104
|
+
}
|
|
105
|
+
return entries;
|
|
106
|
+
}
|
|
107
|
+
function sameTranscriptItem(left, right) {
|
|
108
|
+
if (left.kind !== right.kind)
|
|
109
|
+
return false;
|
|
110
|
+
if (left.kind === 'todo' && right.kind === 'todo') {
|
|
111
|
+
return left.id === right.id && left.origin === right.origin && left.toolCallId === right.toolCallId && left.status === right.status
|
|
112
|
+
&& JSON.stringify(left.items) === JSON.stringify(right.items);
|
|
113
|
+
}
|
|
114
|
+
const leftFields = Object.entries(left);
|
|
115
|
+
const rightValues = right;
|
|
116
|
+
return leftFields.length === Object.keys(right).length
|
|
117
|
+
&& leftFields.every(([key, value]) => rightValues[key] === value);
|
|
118
|
+
}
|
|
119
|
+
/** Gives each object a stable number, so a cache key can say "this exact tool result". */
|
|
120
|
+
const objectIds = new WeakMap();
|
|
121
|
+
let nextObjectId = 0;
|
|
122
|
+
function objectId(value) {
|
|
123
|
+
let id = objectIds.get(value);
|
|
124
|
+
if (id === undefined)
|
|
125
|
+
objectIds.set(value, id = nextObjectId++);
|
|
126
|
+
return id;
|
|
127
|
+
}
|
|
128
|
+
function compactedHistory(session) {
|
|
129
|
+
const manager = session.sessionManager;
|
|
130
|
+
if (typeof manager?.getBranch !== 'function' || typeof manager.buildSessionProjection !== 'function')
|
|
131
|
+
return null;
|
|
132
|
+
const branch = manager.getBranch();
|
|
133
|
+
const at = branch.findLastIndex(entry => entry.type === 'compaction');
|
|
134
|
+
if (at < 0)
|
|
135
|
+
return null;
|
|
136
|
+
const compactionId = branch[at].id;
|
|
137
|
+
const earlier = new Set(branch.slice(0, at).map(entry => entry.id));
|
|
138
|
+
const projected = manager.buildSessionProjection().entries;
|
|
139
|
+
const first = projected[0];
|
|
140
|
+
if (!first || first.sourceEntry.id !== compactionId || first.messages.length === 0)
|
|
141
|
+
return null;
|
|
142
|
+
let head = 0;
|
|
143
|
+
for (const entry of projected) {
|
|
144
|
+
if (entry.sourceEntry.id !== compactionId && !earlier.has(entry.sourceEntry.id))
|
|
145
|
+
break;
|
|
146
|
+
head += entry.messages.length;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
archive: branch.slice(0, at).flatMap(entry => sessionEntryToContextMessages(entry)),
|
|
150
|
+
summaryIndex: first.messages.length - 1,
|
|
151
|
+
head
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export class TranscriptModel {
|
|
155
|
+
session;
|
|
156
|
+
/** Items derived from a finished message, reused while its position and tool results stay the same. */
|
|
157
|
+
messageItems = new WeakMap();
|
|
158
|
+
hideThinking = false;
|
|
159
|
+
showImages = true;
|
|
160
|
+
expanded = false;
|
|
161
|
+
notices = [];
|
|
162
|
+
nextNotice = 0;
|
|
163
|
+
liveBash;
|
|
164
|
+
cachedView;
|
|
165
|
+
cachedItems = new Map();
|
|
166
|
+
/** What a compaction took out of Pi's message list, rebuilt from the session history on bind. */
|
|
167
|
+
history;
|
|
168
|
+
bind(session) {
|
|
169
|
+
this.session = session;
|
|
170
|
+
this.history = undefined;
|
|
171
|
+
this.invalidate();
|
|
172
|
+
}
|
|
173
|
+
clear() {
|
|
174
|
+
this.session = undefined;
|
|
175
|
+
this.history = undefined;
|
|
176
|
+
this.notices = [];
|
|
177
|
+
this.liveBash = undefined;
|
|
178
|
+
this.invalidate();
|
|
179
|
+
}
|
|
180
|
+
// Pi mutates the session messages outside this model, so callers invalidate after runtime events.
|
|
181
|
+
invalidate() {
|
|
182
|
+
this.cachedView = undefined;
|
|
183
|
+
}
|
|
184
|
+
beginBash(command) {
|
|
185
|
+
const messages = (this.session?.messages ?? []);
|
|
186
|
+
this.liveBash = {
|
|
187
|
+
command,
|
|
188
|
+
output: '',
|
|
189
|
+
timestamp: Date.now(),
|
|
190
|
+
previousCount: messages.filter(message => message.role === 'bashExecution').length
|
|
191
|
+
};
|
|
192
|
+
this.invalidate();
|
|
193
|
+
}
|
|
194
|
+
appendBashOutput(id, delta) {
|
|
195
|
+
const live = this.liveBash;
|
|
196
|
+
if (!live || !delta || (live.id !== undefined && live.id !== id))
|
|
197
|
+
return false;
|
|
198
|
+
live.id ??= id;
|
|
199
|
+
live.output = appendCapped(live.output, delta);
|
|
200
|
+
this.invalidate();
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
finishBash() {
|
|
204
|
+
this.liveBash = undefined;
|
|
205
|
+
this.invalidate();
|
|
206
|
+
}
|
|
207
|
+
clearNotices() {
|
|
208
|
+
this.notices = [];
|
|
209
|
+
this.invalidate();
|
|
210
|
+
}
|
|
211
|
+
setThinkingHidden(hidden) {
|
|
212
|
+
if (this.hideThinking === hidden)
|
|
213
|
+
return;
|
|
214
|
+
this.hideThinking = hidden;
|
|
215
|
+
this.messageItems = new WeakMap();
|
|
216
|
+
this.invalidate();
|
|
217
|
+
}
|
|
218
|
+
setImagesShown(shown) {
|
|
219
|
+
if (this.showImages === shown)
|
|
220
|
+
return;
|
|
221
|
+
this.showImages = shown;
|
|
222
|
+
this.messageItems = new WeakMap();
|
|
223
|
+
this.invalidate();
|
|
224
|
+
}
|
|
225
|
+
setExpanded(expanded) {
|
|
226
|
+
this.expanded = expanded;
|
|
227
|
+
}
|
|
228
|
+
get isExpanded() {
|
|
229
|
+
return this.expanded;
|
|
230
|
+
}
|
|
231
|
+
notice(text, level = 'info') {
|
|
232
|
+
const lines = text.split(/\r\n?|\n/u).filter(line => line.trim());
|
|
233
|
+
const multiline = lines.length > 1;
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
const sequence = this.nextNotice++;
|
|
236
|
+
this.addNotice(sequence, multiline, { kind: 'notice', id: `notice-${sequence}`, text: line, level });
|
|
237
|
+
}
|
|
238
|
+
this.invalidate();
|
|
239
|
+
}
|
|
240
|
+
commandHelp(groups) {
|
|
241
|
+
const sequence = this.nextNotice++;
|
|
242
|
+
this.addNotice(sequence, true, { kind: 'command-help', id: `command-help-${sequence}`, groups });
|
|
243
|
+
this.invalidate();
|
|
244
|
+
}
|
|
245
|
+
/** A `/todo` snapshot: the list at the moment the command ran, with no tool row. */
|
|
246
|
+
todo(items) {
|
|
247
|
+
const sequence = this.nextNotice++;
|
|
248
|
+
this.addNotice(sequence, true, { kind: 'todo', id: `todo-${sequence}`, origin: 'command', items });
|
|
249
|
+
this.invalidate();
|
|
250
|
+
}
|
|
251
|
+
addNotice(sequence, multiline, item) {
|
|
252
|
+
const messages = this.session?.messages ?? [];
|
|
253
|
+
this.notices.push({ afterMessageCount: messages.length, anchor: messages.at(-1), sequence, multiline, item });
|
|
254
|
+
if (this.notices.length > 60)
|
|
255
|
+
this.notices = this.notices.slice(-60);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Notices in the order they appear, each with the message index it goes before. Compaction
|
|
259
|
+
* replaces the message list with a shorter one, so a notice follows the message it was posted
|
|
260
|
+
* after; a notice whose message was summarized goes right after the compaction card.
|
|
261
|
+
*/
|
|
262
|
+
placedNotices(messages) {
|
|
263
|
+
const indexes = new Map();
|
|
264
|
+
messages.forEach((message, index) => indexes.set(message, index));
|
|
265
|
+
const summary = messages.findIndex(message => message.role === 'compactionSummary');
|
|
266
|
+
return this.notices.map(notice => {
|
|
267
|
+
if (!notice.anchor)
|
|
268
|
+
return { place: 0, notice };
|
|
269
|
+
const index = indexes.get(notice.anchor);
|
|
270
|
+
if (index !== undefined)
|
|
271
|
+
return { place: index + 1, notice };
|
|
272
|
+
return { place: summary >= 0 ? summary + 1 : Math.min(notice.afterMessageCount, messages.length), notice };
|
|
273
|
+
}).sort((left, right) => left.place - right.place || left.notice.sequence - right.notice.sequence);
|
|
274
|
+
}
|
|
275
|
+
view() {
|
|
276
|
+
if (this.cachedView)
|
|
277
|
+
return this.cachedView;
|
|
278
|
+
const session = this.session;
|
|
279
|
+
if (!session)
|
|
280
|
+
return this.cachedView = {
|
|
281
|
+
items: this.stabilize(this.notices.map(notice => notice.item)),
|
|
282
|
+
hasUserMessages: false,
|
|
283
|
+
hasMultilineNotice: this.notices.some(notice => notice.multiline)
|
|
284
|
+
};
|
|
285
|
+
const live = session.messages;
|
|
286
|
+
const messages = this.displayMessages(live);
|
|
287
|
+
const streaming = session.state.streamingMessage;
|
|
288
|
+
const all = streaming && !messages.includes(streaming) ? [...messages, streaming] : messages;
|
|
289
|
+
const pending = session.state.pendingToolCalls ?? new Set();
|
|
290
|
+
const results = new Map();
|
|
291
|
+
for (const message of messages) {
|
|
292
|
+
if (message.role === 'toolResult' && typeof message.toolCallId === 'string')
|
|
293
|
+
results.set(message.toolCallId, message);
|
|
294
|
+
}
|
|
295
|
+
const items = [];
|
|
296
|
+
const bashExecutionCount = live.filter(message => message.role === 'bashExecution').length;
|
|
297
|
+
const notices = this.placedNotices(messages);
|
|
298
|
+
let noticeIndex = 0;
|
|
299
|
+
for (let index = 0; index < all.length; index += 1) {
|
|
300
|
+
while (noticeIndex < notices.length && notices[noticeIndex].place <= index) {
|
|
301
|
+
items.push(notices[noticeIndex++].notice.item);
|
|
302
|
+
}
|
|
303
|
+
const message = all[index];
|
|
304
|
+
if (message === streaming) {
|
|
305
|
+
items.push(...this.messageItemsFor(message, index, true, results, pending));
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const key = this.messageKey(message, index, results, pending);
|
|
309
|
+
const cached = this.messageItems.get(message);
|
|
310
|
+
if (cached && cached.key === key) {
|
|
311
|
+
items.push(...cached.items);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const derived = this.messageItemsFor(message, index, false, results, pending);
|
|
315
|
+
this.messageItems.set(message, { key, items: derived });
|
|
316
|
+
items.push(...derived);
|
|
317
|
+
}
|
|
318
|
+
if (this.liveBash && bashExecutionCount === this.liveBash.previousCount) {
|
|
319
|
+
items.push({
|
|
320
|
+
kind: 'bash', id: `live-${this.liveBash.id ?? 'pending'}`, command: this.liveBash.command,
|
|
321
|
+
timestamp: this.liveBash.timestamp, output: ansiPlain(this.liveBash.output), running: true, cancelled: false
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
for (; noticeIndex < notices.length; noticeIndex += 1)
|
|
325
|
+
items.push(notices[noticeIndex].notice.item);
|
|
326
|
+
return this.cachedView = {
|
|
327
|
+
items: this.stabilize(items),
|
|
328
|
+
hasUserMessages: messages.some(message => message.role === 'user'),
|
|
329
|
+
hasMultilineNotice: this.notices.some(notice => notice.multiline)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Pi's list after a compaction starts with the summary and the kept messages. The transcript
|
|
334
|
+
* shows the whole conversation instead: everything before the compaction from the session
|
|
335
|
+
* history, the summary, then what came after.
|
|
336
|
+
*/
|
|
337
|
+
displayMessages(live) {
|
|
338
|
+
if (this.history === undefined) {
|
|
339
|
+
try {
|
|
340
|
+
this.history = this.session ? compactedHistory(this.session) : null;
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
this.history = null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const history = this.history;
|
|
347
|
+
if (!history || live.length < history.head || live[history.summaryIndex]?.role !== 'compactionSummary')
|
|
348
|
+
return live;
|
|
349
|
+
return [...history.archive, live[history.summaryIndex], ...live.slice(history.head)];
|
|
350
|
+
}
|
|
351
|
+
/** What a message's items depend on besides the message itself. */
|
|
352
|
+
messageKey(message, index, results, pending) {
|
|
353
|
+
let key = `${index}`;
|
|
354
|
+
if (message.role !== 'assistant')
|
|
355
|
+
return key;
|
|
356
|
+
for (const block of blocks(message.content)) {
|
|
357
|
+
if (block.type !== 'toolCall' || typeof block.id !== 'string')
|
|
358
|
+
continue;
|
|
359
|
+
const result = results.get(block.id);
|
|
360
|
+
key += result ? `|${objectId(result)}` : pending.has(block.id) ? '|r' : '|p';
|
|
361
|
+
}
|
|
362
|
+
return key;
|
|
363
|
+
}
|
|
364
|
+
messageItemsFor(message, index, isStreaming, results, pending) {
|
|
365
|
+
const items = [];
|
|
366
|
+
const role = message.role;
|
|
367
|
+
if (role === 'user') {
|
|
368
|
+
const text = contentText(message.content);
|
|
369
|
+
const images = imageCount(message.content);
|
|
370
|
+
if (!text && !images)
|
|
371
|
+
return items;
|
|
372
|
+
items.push({ kind: 'user', id: `m${index}`, text, images, timestamp: numberField(message, 'timestamp') ?? 0 });
|
|
373
|
+
return items;
|
|
374
|
+
}
|
|
375
|
+
if (role === 'assistant') {
|
|
376
|
+
const model = stringField(message, 'model');
|
|
377
|
+
const content = blocks(message.content);
|
|
378
|
+
let textIndex = 0;
|
|
379
|
+
for (const block of content) {
|
|
380
|
+
if (block.type === 'thinking' && typeof block.thinking === 'string' && !this.hideThinking) {
|
|
381
|
+
const text = block.redacted === true ? '[redacted thinking]' : withoutBoldMarkers(String(block.thinking));
|
|
382
|
+
if (text.trim())
|
|
383
|
+
items.push({ kind: 'thinking', id: `m${index}.t${textIndex++}`, text, streaming: isStreaming });
|
|
384
|
+
}
|
|
385
|
+
else if (block.type === 'text' && typeof block.text === 'string') {
|
|
386
|
+
const id = `m${index}.x${textIndex++}`;
|
|
387
|
+
const timestamp = numberField(message, 'timestamp') ?? 0;
|
|
388
|
+
const plan = parseProposedPlan(block.text);
|
|
389
|
+
if (!plan) {
|
|
390
|
+
if (block.text.trim())
|
|
391
|
+
items.push({ kind: 'assistant', id, text: block.text, model, streaming: isStreaming, timestamp });
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (plan.before)
|
|
395
|
+
items.push({ kind: 'assistant', id: `${id}.b`, text: plan.before, model, streaming: isStreaming, timestamp });
|
|
396
|
+
items.push({ kind: 'plan', id: `${id}.p`, title: plan.title, body: plan.body, streaming: isStreaming && !plan.complete });
|
|
397
|
+
if (plan.after)
|
|
398
|
+
items.push({ kind: 'assistant', id: `${id}.a`, text: plan.after, model, streaming: isStreaming, timestamp });
|
|
399
|
+
}
|
|
400
|
+
else if (block.type === 'toolCall' && typeof block.id === 'string' && typeof block.name === 'string') {
|
|
401
|
+
const result = results.get(block.id);
|
|
402
|
+
const status = result ? (result.isError === true ? 'error' : 'ok') : pending.has(block.id) ? 'running' : 'pending';
|
|
403
|
+
const resultImageCount = result ? imageCount(result.content) : 0;
|
|
404
|
+
const id = `m${index}.c${textIndex++}`;
|
|
405
|
+
const todo = (block.name === 'todo_write' || block.name === 'checklist_write') && result?.isError !== true ? todoEntries(block.arguments) : null;
|
|
406
|
+
if (todo) {
|
|
407
|
+
items.push({ kind: 'todo', id, origin: 'tool', toolCallId: block.id, status, items: todo });
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
items.push({
|
|
411
|
+
kind: 'tool', id, toolCallId: block.id, name: block.name,
|
|
412
|
+
summary: argumentSummary(block.name, block.arguments), status,
|
|
413
|
+
output: result && resultImageCount === 0 ? toolOutput(result) : '',
|
|
414
|
+
durationMs: result ? numberField(result, 'durationMs') : undefined
|
|
415
|
+
});
|
|
416
|
+
if (result && resultImageCount > 0) {
|
|
417
|
+
items.push({
|
|
418
|
+
kind: 'image-result', id: `${id}.image-result`, text: toolOutput(result),
|
|
419
|
+
imageCount: resultImageCount, images: this.showImages ? imageContents(result.content) : [],
|
|
420
|
+
showImages: this.showImages
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const stop = stringField(message, 'stopReason');
|
|
426
|
+
const error = stringField(message, 'errorMessage');
|
|
427
|
+
if (stop === 'error' && error)
|
|
428
|
+
items.push({ kind: 'notice', id: `m${index}.error`, text: error, level: 'error' });
|
|
429
|
+
if (stop === 'aborted')
|
|
430
|
+
items.push({ kind: 'notice', id: `m${index}.aborted`, text: 'Stopped by the person.', level: 'warning' });
|
|
431
|
+
return items;
|
|
432
|
+
}
|
|
433
|
+
if (role === 'bashExecution') {
|
|
434
|
+
const command = stringField(message, 'command') ?? '';
|
|
435
|
+
if (!command)
|
|
436
|
+
return items;
|
|
437
|
+
items.push({
|
|
438
|
+
kind: 'bash', id: `m${index}`, command, timestamp: numberField(message, 'timestamp') ?? 0,
|
|
439
|
+
output: clip(ansiPlain(stringField(message, 'output') ?? '')),
|
|
440
|
+
running: false, exitCode: numberField(message, 'exitCode'), cancelled: message.cancelled === true
|
|
441
|
+
});
|
|
442
|
+
return items;
|
|
443
|
+
}
|
|
444
|
+
if (role === 'custom') {
|
|
445
|
+
if (message.display === false)
|
|
446
|
+
return items;
|
|
447
|
+
const text = contentText(message.content);
|
|
448
|
+
if (!text.trim())
|
|
449
|
+
return items;
|
|
450
|
+
items.push({ kind: 'custom', id: `m${index}`, label: stringField(message, 'customType') ?? 'Extension', text });
|
|
451
|
+
return items;
|
|
452
|
+
}
|
|
453
|
+
if (role === 'compactionSummary') {
|
|
454
|
+
const summary = stringField(message, 'summary') ?? '';
|
|
455
|
+
if (!summary.trim())
|
|
456
|
+
return items;
|
|
457
|
+
items.push({ kind: 'compaction', id: `m${index}`, summary, tokensBefore: numberField(message, 'tokensBefore') });
|
|
458
|
+
return items;
|
|
459
|
+
}
|
|
460
|
+
if (role === 'branchSummary') {
|
|
461
|
+
const text = stringField(message, 'summary') ?? '';
|
|
462
|
+
if (text.trim())
|
|
463
|
+
items.push({ kind: 'summary', id: `m${index}`, label: 'Branch summary', text });
|
|
464
|
+
}
|
|
465
|
+
return items;
|
|
466
|
+
}
|
|
467
|
+
stabilize(items) {
|
|
468
|
+
const stable = items.map(item => {
|
|
469
|
+
const cached = this.cachedItems.get(item.id);
|
|
470
|
+
if (cached && sameTranscriptItem(cached, item))
|
|
471
|
+
return cached;
|
|
472
|
+
this.cachedItems.set(item.id, item);
|
|
473
|
+
return item;
|
|
474
|
+
});
|
|
475
|
+
const currentIds = new Set(stable.map(item => item.id));
|
|
476
|
+
for (const id of this.cachedItems.keys()) {
|
|
477
|
+
if (!currentIds.has(id))
|
|
478
|
+
this.cachedItems.delete(id);
|
|
479
|
+
}
|
|
480
|
+
return stable;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type TerminalSubagentActivity } from "../subagentMonitor.js";
|
|
2
2
|
export type SubagentTone = 'success' | 'warning' | 'error' | 'muted' | 'accent';
|
|
3
|
-
/** One roster row: status mark, profile, session name,
|
|
3
|
+
/** One roster row: status mark, profile, session name, todo progress, in that order. */
|
|
4
4
|
export interface SubagentRowView {
|
|
5
5
|
id: string;
|
|
6
6
|
mark: string;
|
|
@@ -10,7 +10,7 @@ export interface SubagentRowView {
|
|
|
10
10
|
/** The profile's colour name (PROP-0029); null for a copy of the parent. */
|
|
11
11
|
color: string | null;
|
|
12
12
|
title: string;
|
|
13
|
-
/** `n of m tasks`, or null when the child wrote no
|
|
13
|
+
/** `n of m tasks`, or null when the child wrote no todo. */
|
|
14
14
|
tasks: string | null;
|
|
15
15
|
}
|
|
16
16
|
export interface SubagentRosterView {
|
|
@@ -18,7 +18,7 @@ export interface SubagentRosterView {
|
|
|
18
18
|
/** `+N more · /subagents` when rows were left out. */
|
|
19
19
|
overflow: string | null;
|
|
20
20
|
}
|
|
21
|
-
export declare function subagentTasks(
|
|
21
|
+
export declare function subagentTasks(todo: TerminalSubagentActivity['todo']): string | null;
|
|
22
22
|
/**
|
|
23
23
|
* The compact live roster shown under the status line: active children first, at most three
|
|
24
24
|
* rows. The session name is cut first when the terminal is narrow.
|