@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
|
@@ -21,24 +21,26 @@ function mark(status, frame) {
|
|
|
21
21
|
case 'running': return { mark: frame, tone: 'warning' };
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
export function subagentTasks(
|
|
25
|
-
return
|
|
24
|
+
export function subagentTasks(todo) {
|
|
25
|
+
return todo && todo.total > 0 ? `${todo.done} of ${todo.total} tasks` : null;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* The compact live roster shown under the status line: active children first, at most three
|
|
29
29
|
* rows. The session name is cut first when the terminal is narrow.
|
|
30
30
|
*/
|
|
31
31
|
export function subagentRoster(items, width, frame = getWorkingSpinnerFrames()[0] ?? '') {
|
|
32
|
-
|
|
32
|
+
// The status line already describes the main agent; its row is only on the /subagents page.
|
|
33
|
+
const children = items.filter(item => item.relation !== 'main');
|
|
34
|
+
if (!children.length || width < 1)
|
|
33
35
|
return null;
|
|
34
|
-
const ordered = [...
|
|
36
|
+
const ordered = [...children].sort((left, right) => {
|
|
35
37
|
const active = Number(ACTIVE_SUBAGENT_STATUSES.has(right.status)) - Number(ACTIVE_SUBAGENT_STATUSES.has(left.status));
|
|
36
38
|
return active || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id);
|
|
37
39
|
});
|
|
38
40
|
const rows = ordered.slice(0, MAX_VISIBLE_ROWS).map((item) => {
|
|
39
41
|
const status = mark(item.status, frame);
|
|
40
42
|
const profile = cut(safe(item.profile?.name ?? '') || 'subagent', Math.max(1, Math.min(24, width - 2)));
|
|
41
|
-
const tasks = subagentTasks(item.
|
|
43
|
+
const tasks = subagentTasks(item.todo);
|
|
42
44
|
const used = 2 + visibleWidth(profile) + GAP.length + (tasks ? GAP.length + tasks.length : 0);
|
|
43
45
|
const title = cut(safe(item.title) || 'Subagent', Math.max(1, width - used));
|
|
44
46
|
return { id: item.id, mark: status.mark, tone: status.tone, profile, color: item.profile?.color ?? null, title, tasks };
|
|
@@ -17,6 +17,7 @@ export const opentuiTheme = {
|
|
|
17
17
|
questionActionText: '#f7fff8',
|
|
18
18
|
primary: '#fab283',
|
|
19
19
|
accent: '#8ab4f8',
|
|
20
|
+
linkHover: '#c9ddff',
|
|
20
21
|
success: '#a6da95',
|
|
21
22
|
warning: '#eed49f',
|
|
22
23
|
error: '#ed8796',
|
|
@@ -37,6 +38,7 @@ export function profileColor(theme, name) {
|
|
|
37
38
|
case 'cyan': return theme.cyan;
|
|
38
39
|
case 'orange': return theme.primary;
|
|
39
40
|
case 'purple': return theme.purple;
|
|
41
|
+
case 'main': return theme.primary;
|
|
40
42
|
default: return theme.accent;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
// session does to the harness, asks before each tool call, takes instructions the harness
|
|
3
3
|
// queues for it, and gives the session the harness's own tools. It runs in the `pi` process,
|
|
4
4
|
// so everything it knows about the harness comes through the session socket.
|
|
5
|
-
import { createBashTool, createEditTool, createWriteTool } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { createBashTool, createEditTool, createPowerShellTool, createWriteTool } from '@earendil-works/pi-coding-agent';
|
|
6
6
|
import { Type } from 'typebox';
|
|
7
7
|
import { connectionFromEnv, socketCall, terminalCall } from "./client.js";
|
|
8
8
|
import { controlCall } from "./control.js";
|
|
9
9
|
import { SPAWN_SUBAGENT, SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, subagentHistory } from "../sessions/subagents.js";
|
|
10
10
|
import { PLAN_RESEARCH_TOOLS } from "../agents/planResearch.js";
|
|
11
11
|
import { askUserTool } from "../tools/askUser.js";
|
|
12
|
-
import {
|
|
13
|
-
import { checkpointTool } from "../tools/checkpoint.js";
|
|
12
|
+
import { TODO_WRITE, todoTool } from "../tools/todo.js";
|
|
13
|
+
import { CHECKPOINT_WRITE, checkpointTool } from "../tools/checkpoint.js";
|
|
14
14
|
import { createSliceState, parseWorkSlice, workSliceGuard } from "../sessions/workSlice.js";
|
|
15
15
|
import { DEFAULT_COMMAND_TIMEOUT_SECONDS, withCommandBudget } from "../core/commandBudget.js";
|
|
16
|
-
import { sessionNameTool } from "../tools/sessionName.js";
|
|
16
|
+
import { SESSION_NAME, deriveSessionName, sessionNameTool } from "../tools/sessionName.js";
|
|
17
17
|
import { CAPABILITY_DISCOVERY, describeCapabilities } from "../tools/getCapabilities.js";
|
|
18
18
|
import { PLAN_INSTRUCTIONS, planRefusal } from "../sessions/planMode.js";
|
|
19
19
|
import { compactionCheckpoint } from "../sessions/compactionCheckpoint.js";
|
|
20
|
+
import { sessionStateText } from "../prompt/compile.js";
|
|
20
21
|
import { pruneToolImages } from "../world/images.js";
|
|
21
22
|
import { parseInitialBashCommand } from "../sessions/initialBash.js";
|
|
22
23
|
import { inspectWorkspaceWrite } from "../sessions/workspaceBoundary.js";
|
|
23
|
-
import {
|
|
24
|
+
import { createWorkspaceShellTool } from "../core/workspaceBashTool.js";
|
|
24
25
|
import { createWorkspaceBashOperations, runWithWorkspaceWriteScope } from "../core/workspaceSandbox.js";
|
|
25
26
|
import { withWorkspaceWriteAuthorization, workspaceEditOperations, workspaceWriteOperations } from "../core/workspaceWriteOperations.js";
|
|
26
27
|
import { BUDGET_GUARD, OUTPUT_TRIM, budgetGuard, outputTrim } from "../plugins/firstParty.js";
|
|
@@ -48,6 +49,7 @@ export default async function (pi, planRun, getSession) {
|
|
|
48
49
|
let context = null;
|
|
49
50
|
let nativeSubagents = false;
|
|
50
51
|
let subagentParent = false;
|
|
52
|
+
let stoppedChildren = null;
|
|
51
53
|
let timer = null;
|
|
52
54
|
let polling = false;
|
|
53
55
|
let failure;
|
|
@@ -60,6 +62,10 @@ export default async function (pi, planRun, getSession) {
|
|
|
60
62
|
const fileTools = new Map();
|
|
61
63
|
const writeAuthorizations = new Map();
|
|
62
64
|
let workspaceRoot = null;
|
|
65
|
+
let sessionState = null;
|
|
66
|
+
let stateDelivered = false;
|
|
67
|
+
let autoNameTried = false;
|
|
68
|
+
let lastTodo = [];
|
|
63
69
|
// The harness's own tools for this session (connected apps and what the application offers).
|
|
64
70
|
let stage = 'Tool discovery';
|
|
65
71
|
try {
|
|
@@ -135,9 +141,24 @@ export default async function (pi, planRun, getSession) {
|
|
|
135
141
|
},
|
|
136
142
|
workspaceWriteScope: () => planRun?.active ? 'read-only' : undefined
|
|
137
143
|
});
|
|
138
|
-
pi.registerTool(
|
|
144
|
+
pi.registerTool(createWorkspaceShellTool({ createBashTool, createPowerShellTool }, process.cwd(), workspaceBash));
|
|
139
145
|
pi.registerTool(withWorkspaceWriteAuthorization(createWriteTool(process.cwd(), { operations: workspaceWriteOperations }), writeAuthorizations));
|
|
140
146
|
pi.registerTool(withWorkspaceWriteAuthorization(createEditTool(process.cwd(), { operations: workspaceEditOperations }), writeAuthorizations));
|
|
147
|
+
// The current name and this run's todo ride with each request; they never change the stable prefix.
|
|
148
|
+
pi.on('context', (event) => {
|
|
149
|
+
if (!sessionState || stateDelivered)
|
|
150
|
+
return;
|
|
151
|
+
stateDelivered = true;
|
|
152
|
+
return {
|
|
153
|
+
messages: [...event.messages, {
|
|
154
|
+
role: 'custom',
|
|
155
|
+
customType: 'xingular-session-state',
|
|
156
|
+
content: sessionState,
|
|
157
|
+
display: false,
|
|
158
|
+
timestamp: Date.now()
|
|
159
|
+
}]
|
|
160
|
+
};
|
|
161
|
+
});
|
|
141
162
|
compactionCheckpoint({ planInstructions: () => planRun?.active ? PLAN_INSTRUCTIONS : null })(pi);
|
|
142
163
|
// A terminal reply is bounded like any other (PROP-0041): a checkpoint at the boundary, then the
|
|
143
164
|
// person decides whether to continue.
|
|
@@ -145,7 +166,7 @@ export default async function (pi, planRun, getSession) {
|
|
|
145
166
|
const sliceState = createSliceState(slicePolicy);
|
|
146
167
|
// `XINGULAR_WORK_SLICE=off` at launch leaves terminal replies unbounded, as before PROP-0041.
|
|
147
168
|
if (slicePolicy.enabled)
|
|
148
|
-
workSliceGuard({ policy: slicePolicy, state: sliceState })(pi);
|
|
169
|
+
workSliceGuard({ policy: slicePolicy, state: sliceState, checkpointAvailable: () => !planRun?.active || planRun.allowedTools.has(CHECKPOINT_WRITE) })(pi);
|
|
149
170
|
// Only the latest screenshots stay in what the model sees (world/images.ts).
|
|
150
171
|
pi.on('context', (event) => {
|
|
151
172
|
const messages = pruneToolImages(event.messages);
|
|
@@ -153,9 +174,14 @@ export default async function (pi, planRun, getSession) {
|
|
|
153
174
|
});
|
|
154
175
|
pi.on('session_start', async (_event, ctx) => {
|
|
155
176
|
context = ctx;
|
|
177
|
+
sessionState = null;
|
|
178
|
+
stateDelivered = false;
|
|
179
|
+
autoNameTried = false;
|
|
180
|
+
lastTodo = [];
|
|
156
181
|
if (timer)
|
|
157
182
|
clearInterval(timer);
|
|
158
|
-
|
|
183
|
+
// The branch's parent runs let the controller show the latest one's rows again on reopen.
|
|
184
|
+
await terminalCall('ready', { nativeSessionId: ctx.sessionManager.getSessionFile(), owners: subagentHistory(ctx.sessionManager.getBranch?.() ?? []).owners });
|
|
159
185
|
timer = setInterval(() => {
|
|
160
186
|
if (polling || !context)
|
|
161
187
|
return;
|
|
@@ -188,15 +214,20 @@ export default async function (pi, planRun, getSession) {
|
|
|
188
214
|
reasoningTokens = undefined;
|
|
189
215
|
costReported = false;
|
|
190
216
|
// A run a plugin starts with a custom message has no user prompt to record; it still begins a turn.
|
|
191
|
-
const begun = await terminalCall('begin', { text: event.messageType === 'user' ? event.prompt : '' });
|
|
217
|
+
const begun = await terminalCall('begin', { text: event.messageType === 'user' ? event.prompt : '', model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel?.() ?? null });
|
|
192
218
|
workspaceRoot = typeof begun.workspaceRoot === 'string' ? begun.workspaceRoot : null;
|
|
193
219
|
if (!workspaceRoot)
|
|
194
220
|
throw new Error('The active project workspace could not be resolved; the turn was not started.');
|
|
221
|
+
// Every turn marks its run on the branch, so a reopened conversation finds its latest one.
|
|
222
|
+
pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
|
|
223
|
+
stoppedChildren = null;
|
|
195
224
|
if (nativeSubagents) {
|
|
196
|
-
pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
|
|
197
225
|
const snapshot = await controlCall('terminal.subagents.register', { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), plan: planRun?.active === true, history: subagentHistory(ctx.sessionManager.getBranch()) });
|
|
198
226
|
if (planRun && Array.isArray(snapshot.connectors))
|
|
199
227
|
planRun.connectors = snapshot.connectors;
|
|
228
|
+
if (planRun && snapshot.subagentProfiles)
|
|
229
|
+
planRun.subagentProfiles = snapshot.subagentProfiles;
|
|
230
|
+
stoppedChildren = snapshot.notice ?? null;
|
|
200
231
|
subagentParent = true;
|
|
201
232
|
}
|
|
202
233
|
if (planRun && event.messageType === 'user') {
|
|
@@ -260,6 +291,51 @@ export default async function (pi, planRun, getSession) {
|
|
|
260
291
|
: 'The user’s initial Bash command has already run. Read its native Bash result and explain what it shows. Do not run the same command again unless the user explicitly asks.';
|
|
261
292
|
return { message: { customType: 'xingular-initial-bash', content: instruction, display: false, details: {} } };
|
|
262
293
|
});
|
|
294
|
+
// A second handler, so the notice joins any initial-Bash message rather than replacing it.
|
|
295
|
+
pi.on('before_agent_start', async () => {
|
|
296
|
+
const notice = stoppedChildren;
|
|
297
|
+
stoppedChildren = null;
|
|
298
|
+
if (notice)
|
|
299
|
+
return { message: { customType: SUBAGENT_RESULTS, content: notice.text, display: false, details: { [SUBAGENT_RECEIPT]: notice.receipt } } };
|
|
300
|
+
});
|
|
301
|
+
// A new user turn starts with an empty todo. The previous list is context, not this run's list.
|
|
302
|
+
// A substantive first turn also gets a provisional name when the terminal policy allows it.
|
|
303
|
+
pi.on('before_agent_start', async (event, ctx) => {
|
|
304
|
+
if (event.messageType !== 'user')
|
|
305
|
+
return;
|
|
306
|
+
const branch = ctx.sessionManager.getBranch?.() ?? [];
|
|
307
|
+
const hasUser = branch.some((entry) => entry.type === 'message' && entry.message?.role === 'user');
|
|
308
|
+
let name = typeof pi.getSessionName === 'function' ? pi.getSessionName() ?? null : null;
|
|
309
|
+
if (!name && !autoNameTried && pi.setSessionName) {
|
|
310
|
+
const candidate = deriveSessionName(event.prompt);
|
|
311
|
+
if (candidate) {
|
|
312
|
+
autoNameTried = true;
|
|
313
|
+
try {
|
|
314
|
+
const policy = await controlCall('terminal.policy', { tool: SESSION_NAME, args: { name: candidate } });
|
|
315
|
+
if (policy.allowed && !policy.requiresApproval) {
|
|
316
|
+
pi.setSessionName(candidate);
|
|
317
|
+
name = candidate;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Session naming is metadata; a refused or failed name never fails the turn.
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const state = {
|
|
326
|
+
name,
|
|
327
|
+
todo: [],
|
|
328
|
+
...(lastTodo.length ? { previousTodo: lastTodo } : {}),
|
|
329
|
+
firstTurn: !hasUser
|
|
330
|
+
};
|
|
331
|
+
lastTodo = [];
|
|
332
|
+
const active = typeof pi.getActiveTools === 'function' ? pi.getActiveTools() : null;
|
|
333
|
+
sessionState = sessionStateText(state, {
|
|
334
|
+
name: active ? active.includes(SESSION_NAME) : true,
|
|
335
|
+
todo: active ? active.includes(TODO_WRITE) : true
|
|
336
|
+
});
|
|
337
|
+
stateDelivered = false;
|
|
338
|
+
});
|
|
263
339
|
const deliverChildren = async (event, ctx, settle) => {
|
|
264
340
|
if (!nativeSubagents || (settle && event.outcome !== 'completed') || ctx.signal?.aborted)
|
|
265
341
|
return;
|
|
@@ -421,6 +497,10 @@ export default async function (pi, planRun, getSession) {
|
|
|
421
497
|
clearInterval(timer);
|
|
422
498
|
timer = null;
|
|
423
499
|
context = null;
|
|
500
|
+
sessionState = null;
|
|
501
|
+
stateDelivered = false;
|
|
502
|
+
autoNameTried = false;
|
|
503
|
+
lastTodo = [];
|
|
424
504
|
// This clears the controller's readiness while the next native session binds. The
|
|
425
505
|
// process-level control claim survives replacement; only the session poller stops.
|
|
426
506
|
await terminalCall('shutdown').catch(() => { });
|
|
@@ -437,16 +517,19 @@ export default async function (pi, planRun, getSession) {
|
|
|
437
517
|
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
438
518
|
}
|
|
439
519
|
});
|
|
440
|
-
// The
|
|
520
|
+
// The todo list is recorded on the harness run; the shared tool validates and the socket carries the event.
|
|
441
521
|
let recorded = Promise.resolve();
|
|
442
|
-
const
|
|
522
|
+
const todo = todoTool({ emit: (_type, payload) => { recorded = terminalCall('todo', payload); } });
|
|
443
523
|
pi.registerTool({
|
|
444
|
-
name:
|
|
445
|
-
parameters: Type.Unsafe(
|
|
524
|
+
name: todo.name, label: todo.label, description: todo.description,
|
|
525
|
+
parameters: Type.Unsafe(todo.parameters(Type)),
|
|
446
526
|
async execute(_id, input) {
|
|
447
527
|
recorded = Promise.resolve();
|
|
448
|
-
const result = await
|
|
528
|
+
const result = await todo.execute(input);
|
|
449
529
|
await recorded;
|
|
530
|
+
const items = input.items;
|
|
531
|
+
if (Array.isArray(items))
|
|
532
|
+
lastTodo = items;
|
|
450
533
|
return { content: [{ type: 'text', text: typeof result === 'string' ? result : result.text }], details: undefined };
|
|
451
534
|
}
|
|
452
535
|
});
|
|
@@ -5,10 +5,13 @@ export interface TerminalSubagentProfile {
|
|
|
5
5
|
name: string;
|
|
6
6
|
color: string;
|
|
7
7
|
}
|
|
8
|
+
/** The main agent's row: the parent run itself, shown by name rather than by a profile. */
|
|
9
|
+
export declare const MAIN_PROFILE: TerminalSubagentProfile;
|
|
8
10
|
export interface TerminalSubagentRun {
|
|
9
11
|
id: string;
|
|
10
12
|
parentRunId: string | null;
|
|
11
|
-
|
|
13
|
+
/** `main` is the parent run itself; every other row is a child or nested child. */
|
|
14
|
+
relation: 'main' | 'subagent';
|
|
12
15
|
/** The short name the parent gave the child (PROP-0030). */
|
|
13
16
|
title: string;
|
|
14
17
|
goal: string | null;
|
|
@@ -27,8 +30,8 @@ export interface TerminalSubagentActivity extends TerminalSubagentRun {
|
|
|
27
30
|
activity: string;
|
|
28
31
|
/** Cumulative run usage only; provider reasoning summaries and request details stay private. */
|
|
29
32
|
usage: TerminalSubagentUsage | null;
|
|
30
|
-
/** How far the child's own
|
|
31
|
-
|
|
33
|
+
/** How far the child's own todo is: counts only, never the items. */
|
|
34
|
+
todo: {
|
|
32
35
|
done: number;
|
|
33
36
|
total: number;
|
|
34
37
|
} | null;
|
|
@@ -41,7 +44,17 @@ export declare class SubagentMonitor {
|
|
|
41
44
|
private parentRunId;
|
|
42
45
|
private readonly activities;
|
|
43
46
|
begin(parentRunId: string): void;
|
|
44
|
-
|
|
47
|
+
/** Folds one event of the tracked parent (`main`) or one of its descendants; `null` seeds the row. */
|
|
48
|
+
observe(parentRunId: string, run: TerminalSubagentRun, event: RunEvent | null): void;
|
|
49
|
+
/**
|
|
50
|
+
* Rebuilds the rows of a parent run that is no longer live, such as the latest one of a reopened
|
|
51
|
+
* conversation, by folding each run's saved events. Rows keep their final status.
|
|
52
|
+
*/
|
|
53
|
+
restore(parentRunId: string, runs: readonly {
|
|
54
|
+
run: TerminalSubagentRun;
|
|
55
|
+
events: readonly RunEvent[];
|
|
56
|
+
}[]): void;
|
|
57
|
+
/** The main row first, then children in start order. */
|
|
45
58
|
snapshot(parentRunId: string): TerminalSubagentActivity[];
|
|
46
59
|
clear(parentRunId?: string): void;
|
|
47
60
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { usagePayloadSchema } from "../sessions/contracts.js";
|
|
2
|
+
/** The main agent's row: the parent run itself, shown by name rather than by a profile. */
|
|
3
|
+
export const MAIN_PROFILE = { name: 'main', color: 'main' };
|
|
2
4
|
export const ACTIVE_SUBAGENT_STATUSES = new Set(['queued', 'running', 'waiting_for_approval', 'waiting_for_input']);
|
|
3
5
|
export function terminalSubagentDuration(activity, now = Date.now()) {
|
|
4
6
|
const start = Date.parse(activity.startedAt);
|
|
@@ -50,8 +52,11 @@ export class SubagentMonitor {
|
|
|
50
52
|
this.parentRunId = parentRunId;
|
|
51
53
|
this.activities.clear();
|
|
52
54
|
}
|
|
55
|
+
/** Folds one event of the tracked parent (`main`) or one of its descendants; `null` seeds the row. */
|
|
53
56
|
observe(parentRunId, run, event) {
|
|
54
|
-
if (this.parentRunId !== parentRunId
|
|
57
|
+
if (this.parentRunId !== parentRunId)
|
|
58
|
+
return;
|
|
59
|
+
if ((run.relation === 'main') !== (run.id === parentRunId))
|
|
55
60
|
return;
|
|
56
61
|
const row = this.activities.get(run.id) ?? {
|
|
57
62
|
id: run.id,
|
|
@@ -63,7 +68,7 @@ export class SubagentMonitor {
|
|
|
63
68
|
startedAt: run.startedAt,
|
|
64
69
|
endedAt: run.endedAt,
|
|
65
70
|
activity: statusActivity(run.status),
|
|
66
|
-
|
|
71
|
+
todo: null,
|
|
67
72
|
profile: run.profile ?? null,
|
|
68
73
|
accountLabel: run.accountLabel ?? null,
|
|
69
74
|
model: run.model ?? null,
|
|
@@ -86,8 +91,11 @@ export class SubagentMonitor {
|
|
|
86
91
|
row.status = run.status;
|
|
87
92
|
row.startedAt = run.startedAt;
|
|
88
93
|
row.endedAt = run.endedAt;
|
|
89
|
-
const payload = payloadOf(event);
|
|
90
|
-
if (event
|
|
94
|
+
const payload = event ? payloadOf(event) : {};
|
|
95
|
+
if (!event) {
|
|
96
|
+
// A seeded row keeps what it already folded.
|
|
97
|
+
}
|
|
98
|
+
else if (event.type === 'tool.started') {
|
|
91
99
|
const toolCallId = stringOf(payload.toolCallId);
|
|
92
100
|
const name = stringOf(payload.name) ?? 'Tool';
|
|
93
101
|
if (toolCallId)
|
|
@@ -125,22 +133,35 @@ export class SubagentMonitor {
|
|
|
125
133
|
};
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
|
-
else if (event.type === 'checklist.updated') {
|
|
136
|
+
else if (event.type === 'todo.updated' || event.type === 'checklist.updated') {
|
|
129
137
|
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
130
|
-
row.
|
|
138
|
+
row.todo = items.length ? { done: items.filter(item => item?.status === 'completed').length, total: items.length } : null;
|
|
131
139
|
}
|
|
132
140
|
if (run.status !== 'running')
|
|
133
141
|
row.tools.clear();
|
|
134
142
|
row.activity = run.status === 'running'
|
|
135
|
-
? latestTool(row.tools) ?? row.lastTool?.text ?? (event
|
|
143
|
+
? latestTool(row.tools) ?? row.lastTool?.text ?? (event?.type === 'message.delta' || (!event && row.activity === 'Responding') ? 'Responding' : 'Working')
|
|
136
144
|
: statusActivity(run.status);
|
|
137
145
|
this.activities.set(run.id, row);
|
|
138
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Rebuilds the rows of a parent run that is no longer live, such as the latest one of a reopened
|
|
149
|
+
* conversation, by folding each run's saved events. Rows keep their final status.
|
|
150
|
+
*/
|
|
151
|
+
restore(parentRunId, runs) {
|
|
152
|
+
this.begin(parentRunId);
|
|
153
|
+
for (const { run, events } of runs) {
|
|
154
|
+
this.observe(parentRunId, run, null);
|
|
155
|
+
for (const event of events)
|
|
156
|
+
this.observe(parentRunId, run, event);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** The main row first, then children in start order. */
|
|
139
160
|
snapshot(parentRunId) {
|
|
140
161
|
if (this.parentRunId !== parentRunId)
|
|
141
162
|
return [];
|
|
142
163
|
return [...this.activities.values()]
|
|
143
|
-
.sort((left, right) => left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id))
|
|
164
|
+
.sort((left, right) => Number(right.relation === 'main') - Number(left.relation === 'main') || left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id))
|
|
144
165
|
.map(({ tools: _tools, lastTool: _lastTool, ...activity }) => activity);
|
|
145
166
|
}
|
|
146
167
|
clear(parentRunId) {
|
|
@@ -11,6 +11,7 @@ import type { CallerContext } from "../core/caller.js";
|
|
|
11
11
|
import { type VariantChoice } from "../variant/materialise.js";
|
|
12
12
|
import type { VariantTrust } from "../variant/trust.js";
|
|
13
13
|
import type { AttachParams, PluginScope } from "../plugins/api.js";
|
|
14
|
+
import type { Proposal } from "../plugins/proposals.js";
|
|
14
15
|
import type { Plugins } from "../plugins/plugins.js";
|
|
15
16
|
import type { Plugin } from "../plugins/types.js";
|
|
16
17
|
import type { Hub } from "../hub/hub.js";
|
|
@@ -131,6 +132,12 @@ export interface PluginControl {
|
|
|
131
132
|
remove?(params: PluginScope & {
|
|
132
133
|
pluginId: string;
|
|
133
134
|
}): Promise<Plugin[]>;
|
|
135
|
+
proposals?: {
|
|
136
|
+
list(): Proposal[] | Promise<Proposal[]>;
|
|
137
|
+
preview(id: string): Proposal | Promise<Proposal>;
|
|
138
|
+
accept(id: string, hash: string): Proposal | Promise<Proposal>;
|
|
139
|
+
reject(id: string, hash: string): Proposal | Promise<Proposal>;
|
|
140
|
+
};
|
|
134
141
|
}
|
|
135
142
|
export declare class Terminals {
|
|
136
143
|
private readonly options;
|
|
@@ -138,6 +145,8 @@ export declare class Terminals {
|
|
|
138
145
|
private readonly diagnosticOwners;
|
|
139
146
|
/** The variant each live session runs, for loading its skills. */
|
|
140
147
|
private readonly variantDirs;
|
|
148
|
+
/** The subagent profile files each live session's packages bring, chosen when it started. */
|
|
149
|
+
private readonly packageProfiles;
|
|
141
150
|
/** The world tools each session was given, so a call can only reach one of them. */
|
|
142
151
|
private readonly worldNames;
|
|
143
152
|
private readonly worldReadOnly;
|
|
@@ -150,6 +159,13 @@ export declare class Terminals {
|
|
|
150
159
|
/** Each child's profile name and colour, looked up once per child run. */
|
|
151
160
|
private readonly childProfiles;
|
|
152
161
|
private subagentProfile;
|
|
162
|
+
/** Account, model and effort for a monitor row, from the run record. */
|
|
163
|
+
private monitorRuntime;
|
|
164
|
+
/**
|
|
165
|
+
* Shows the reopened conversation's latest parent run again: the last owner on its native branch
|
|
166
|
+
* that ran in this folder, with every descendant flat, as each ended.
|
|
167
|
+
*/
|
|
168
|
+
private hydrateMonitor;
|
|
153
169
|
constructor(options: TerminalsOptions);
|
|
154
170
|
/** The commands a terminal session finds on PATH. Rewritten only when they change. */
|
|
155
171
|
private ensureCommands;
|