@xingular/harness 1.2.0 → 1.3.1-unstable.0

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +10 -5
  3. package/dist/cli.js +5560 -5056
  4. package/dist/sdk/agents/builtin.js +3 -3
  5. package/dist/sdk/agents/planResearch.d.ts +23 -1
  6. package/dist/sdk/agents/planResearch.js +71 -8
  7. package/dist/sdk/agents/profiles.d.ts +4 -1
  8. package/dist/sdk/agents/profiles.js +17 -6
  9. package/dist/sdk/capabilities/runSnapshot.js +10 -4
  10. package/dist/sdk/commands/registry.d.ts +11 -3
  11. package/dist/sdk/commands/registry.js +3 -2
  12. package/dist/sdk/core/shell.d.ts +20 -0
  13. package/dist/sdk/core/shell.js +73 -0
  14. package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
  15. package/dist/sdk/core/workspaceBashTool.js +16 -2
  16. package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
  17. package/dist/sdk/core/workspaceSandbox.js +52 -12
  18. package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
  19. package/dist/sdk/core/workspaceWriteOperations.js +26 -8
  20. package/dist/sdk/doctor/checks.js +71 -12
  21. package/dist/sdk/hub/hub.js +5 -3
  22. package/dist/sdk/index.d.ts +2 -2
  23. package/dist/sdk/index.js +1 -1
  24. package/dist/sdk/plugins/builtin.js +3 -3
  25. package/dist/sdk/plugins/install.d.ts +6 -2
  26. package/dist/sdk/plugins/install.js +29 -5
  27. package/dist/sdk/plugins/inventory.js +2 -2
  28. package/dist/sdk/prompt/compile.d.ts +2 -2
  29. package/dist/sdk/prompt/compile.js +14 -16
  30. package/dist/sdk/server/methods.js +4 -2
  31. package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
  32. package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
  33. package/dist/sdk/sessions/continuation.d.ts +4 -2
  34. package/dist/sdk/sessions/continuation.js +7 -6
  35. package/dist/sdk/sessions/contracts.d.ts +22 -4
  36. package/dist/sdk/sessions/contracts.js +12 -7
  37. package/dist/sdk/sessions/planMode.js +2 -2
  38. package/dist/sdk/sessions/runStore.js +3 -1
  39. package/dist/sdk/sessions/runner.js +2 -2
  40. package/dist/sdk/sessions/sessions.js +29 -11
  41. package/dist/sdk/sessions/subagentService.d.ts +15 -0
  42. package/dist/sdk/sessions/subagentService.js +194 -54
  43. package/dist/sdk/sessions/subagents.d.ts +10 -1
  44. package/dist/sdk/sessions/subagents.js +4 -0
  45. package/dist/sdk/sessions/workSlice.d.ts +8 -5
  46. package/dist/sdk/sessions/workSlice.js +77 -45
  47. package/dist/sdk/terminal/controller.d.ts +20 -0
  48. package/dist/sdk/terminal/controller.js +43 -18
  49. package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
  50. package/dist/sdk/terminal/opentui/ansi.js +192 -0
  51. package/dist/sdk/terminal/opentui/model.d.ts +163 -0
  52. package/dist/sdk/terminal/opentui/model.js +482 -0
  53. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
  54. package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
  55. package/dist/sdk/terminal/opentui/theme.js +1 -0
  56. package/dist/sdk/terminal/piExtension.js +28 -14
  57. package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
  58. package/dist/sdk/terminal/subagentMonitor.js +29 -8
  59. package/dist/sdk/terminal/terminals.d.ts +7 -0
  60. package/dist/sdk/terminal/terminals.js +86 -14
  61. package/dist/sdk/terminal/trajectory.d.ts +6 -1
  62. package/dist/sdk/terminal/trajectory.js +8 -0
  63. package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
  64. package/dist/sdk/terminal/tui/chrome.js +13 -0
  65. package/dist/sdk/terminal/tui/commandPages.js +14 -4
  66. package/dist/sdk/terminal/tui/commands.d.ts +4 -0
  67. package/dist/sdk/terminal/tui/commands.js +8 -0
  68. package/dist/sdk/terminal/tui/pageOverlay.js +13 -6
  69. package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
  70. package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
  71. package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
  72. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -1
  73. package/dist/sdk/terminal/tui/pages/pluginsPage.js +197 -25
  74. package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
  75. package/dist/sdk/tools/sessionName.d.ts +2 -0
  76. package/dist/sdk/tools/sessionName.js +5 -3
  77. package/dist/sdk/tools/spawnSubagent.js +9 -7
  78. package/dist/sdk/tools/todo.d.ts +15 -0
  79. package/dist/sdk/tools/todo.js +64 -0
  80. package/dist/terminal-opentui.js +1416 -348
  81. package/dist/terminal-pi.js +315 -126
  82. package/docs/architecture/00-overview.md +1 -1
  83. package/docs/architecture/01-extension-points.md +1 -1
  84. package/docs/architecture/02-sessions-and-runs.md +65 -34
  85. package/docs/architecture/04-prompt.md +8 -6
  86. package/docs/architecture/06-plugins-and-capabilities.md +10 -6
  87. package/docs/architecture/07-tools-and-gate.md +31 -5
  88. package/docs/architecture/09-accounts-and-connectors.md +8 -2
  89. package/docs/architecture/10-server-protocol.md +16 -9
  90. package/docs/architecture/11-terminal.md +51 -26
  91. package/docs/architecture/14-surfaces-and-parity.md +4 -1
  92. package/docs/architecture/README.md +18 -17
  93. package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
  94. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
  95. package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
  96. package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
  97. package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
  98. package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
  99. package/docs/architecture/proposals/README.md +10 -6
  100. package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
  101. package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
  102. package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
  103. package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
  104. package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
  105. package/docs/doctor.md +14 -1
  106. package/docs/parity.md +9 -9
  107. package/docs/releases/1.3.0.md +21 -0
  108. package/docs/releases/README.md +2 -2
  109. package/docs/terminal-tui.md +85 -46
  110. package/package.json +1 -1
  111. package/dist/sdk/tools/checklist.d.ts +0 -9
  112. package/dist/sdk/tools/checklist.js +0 -55
@@ -1,14 +1,14 @@
1
1
  import { renderCompactionCheckpoint } from "../prompt/compile.js";
2
- import { CHECKLIST_WRITE } from "../tools/checklist.js";
3
- import { checklistItemSchema, MAX_CHECKLIST_ITEMS } from "./contracts.js";
2
+ import { TODO_WRITE } from "../tools/todo.js";
3
+ import { todoItemSchema, MAX_TODO_ITEMS } from "./contracts.js";
4
4
  export const COMPACTION_CHECKPOINT_EXTENSION = 'compaction-checkpoint';
5
- function validatedChecklist(value) {
6
- if (!Array.isArray(value) || value.length > MAX_CHECKLIST_ITEMS)
5
+ function validatedTodo(value) {
6
+ if (!Array.isArray(value) || value.length > MAX_TODO_ITEMS)
7
7
  return null;
8
8
  const items = [];
9
9
  for (const valueItem of value) {
10
10
  const item = valueItem;
11
- const parsed = checklistItemSchema.safeParse({
11
+ const parsed = todoItemSchema.safeParse({
12
12
  text: typeof item?.text === 'string' ? item.text.trim() : item?.text,
13
13
  status: item?.status
14
14
  });
@@ -22,12 +22,12 @@ function validatedChecklist(value) {
22
22
  export function compactionCheckpoint(options = {}) {
23
23
  return (pi) => {
24
24
  let goal = '';
25
- let checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
25
+ let todo = validatedTodo(options.initialTodo ?? []) ?? [];
26
26
  let lastCompactionKey = null;
27
27
  let compacted = false;
28
28
  const reset = () => {
29
29
  goal = '';
30
- checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
30
+ todo = validatedTodo(options.initialTodo ?? []) ?? [];
31
31
  lastCompactionKey = null;
32
32
  compacted = false;
33
33
  };
@@ -35,16 +35,16 @@ export function compactionCheckpoint(options = {}) {
35
35
  if (event.messageType !== 'user')
36
36
  return;
37
37
  goal = event.prompt;
38
- checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
38
+ todo = validatedTodo(options.initialTodo ?? []) ?? [];
39
39
  lastCompactionKey = null;
40
40
  compacted = false;
41
41
  });
42
42
  pi.on('tool_result', (event) => {
43
- if (event.toolName !== CHECKLIST_WRITE || event.isError)
43
+ if (event.toolName !== TODO_WRITE || event.isError)
44
44
  return;
45
- const items = validatedChecklist(event.input.items);
45
+ const items = validatedTodo(event.input.items);
46
46
  if (items)
47
- checklist = items;
47
+ todo = items;
48
48
  });
49
49
  pi.on('context', (event) => {
50
50
  const summary = event.messages.findLast((message) => message.role === 'compactionSummary');
@@ -60,7 +60,7 @@ export function compactionCheckpoint(options = {}) {
60
60
  }
61
61
  if (!compacted)
62
62
  return;
63
- const content = renderCompactionCheckpoint({ goal, checklist, planInstructions });
63
+ const content = renderCompactionCheckpoint({ goal, todo, planInstructions });
64
64
  if (!content)
65
65
  return;
66
66
  return {
@@ -1,7 +1,7 @@
1
1
  import type { CapabilitySet } from "../capabilities/types.js";
2
2
  import type { HostSection } from "../prompt/compile.js";
3
3
  import type { Checkpoint } from "./contracts.js";
4
- import { type ChecklistItem } from "./contracts.js";
4
+ import { type TodoItem } from "./contracts.js";
5
5
  import { type PendingDecision } from "./runStore.js";
6
6
  export declare const CONTINUATION_SECTION_ID = "continuation";
7
7
  /** Right after the plan section, before the variant's own sections. */
@@ -28,7 +28,9 @@ export interface Continuation {
28
28
  capabilities: CapabilitySet | null;
29
29
  stoppedAt: string | null;
30
30
  terminalReason: string | null;
31
- checklist: ChecklistItem[];
31
+ todo: TodoItem[];
32
+ /** @deprecated Use `todo`; kept for one release so hosts can migrate. */
33
+ checklist: TodoItem[];
32
34
  /** The last work-slice handoff, when the run wrote one (PROP-0041). */
33
35
  checkpoint: Checkpoint | null;
34
36
  pending: PendingDecision[];
@@ -2,7 +2,7 @@
2
2
  // own (INV-RUN-4); it says whether a run can be continued and, when the application starts a new
3
3
  // run with `continueFrom`, tells the model where the earlier one stopped.
4
4
  import { loadRun as loadCapabilities } from "../capabilities/runSnapshot.js";
5
- import { checklistOf, describeChecklist } from "../tools/checklist.js";
5
+ import { todoOf, describeTodo } from "../tools/todo.js";
6
6
  import { checkpointOf, describeCheckpoint } from "../tools/checkpoint.js";
7
7
  import { isActiveStatus } from "./contracts.js";
8
8
  import { findHistory } from "./history.js";
@@ -18,7 +18,7 @@ const CONTINUATION_BUDGET = 2000;
18
18
  export function continuationOf(root, runId, sessionDir) {
19
19
  const record = findRun(root, runId);
20
20
  if (!record) {
21
- return { runId, sessionId: '', status: 'missing', resumable: false, reason: 'not_found', resume: null, accountId: null, variant: null, capabilities: null, stoppedAt: null, terminalReason: null, checklist: [], checkpoint: null, pending: [], lastTool: null, unfinishedSubagents: [] };
21
+ return { runId, sessionId: '', status: 'missing', resumable: false, reason: 'not_found', resume: null, accountId: null, variant: null, capabilities: null, stoppedAt: null, terminalReason: null, todo: [], checklist: [], checkpoint: null, pending: [], lastTool: null, unfinishedSubagents: [] };
22
22
  }
23
23
  const events = readEvents(root, runId);
24
24
  const started = events.filter((event) => event.type === 'tool.started').at(-1);
@@ -46,7 +46,8 @@ export function continuationOf(root, runId, sessionDir) {
46
46
  capabilities,
47
47
  stoppedAt: record.endedAt,
48
48
  terminalReason: record.terminalReason,
49
- checklist: checklistOf(events),
49
+ checklist: todoOf(events),
50
+ todo: todoOf(events),
50
51
  checkpoint: checkpointOf(events),
51
52
  pending: pendingDecisions(events),
52
53
  lastTool,
@@ -73,12 +74,12 @@ export function continuationBody(continuation) {
73
74
  lines.push(continuation.lastTool.finished ? `Its last tool call, ${continuation.lastTool.name}, finished.` : `Its last tool call, ${continuation.lastTool.name}, did not finish; its effect is unknown.`);
74
75
  if (continuation.checkpoint)
75
76
  lines.push('', 'The handoff it wrote when it stopped:', describeCheckpoint(continuation.checkpoint));
76
- if (continuation.checklist.length)
77
- lines.push('', 'Its checklist when it stopped:', describeChecklist(continuation.checklist));
77
+ if (continuation.todo.length)
78
+ lines.push('', 'Its todo list when it stopped:', describeTodo(continuation.todo));
78
79
  if (continuation.pending.length)
79
80
  lines.push('', 'Unanswered when it stopped (ask again if still needed; never assume an answer):', ...continuation.pending.map(describePending));
80
81
  if (continuation.unfinishedSubagents.length)
81
- lines.push('', `Subagents that did not finish: ${continuation.unfinishedSubagents.join(', ')}. Start them again if their work is still needed.`);
82
+ lines.push('', `Subagents that did not finish: ${continuation.unfinishedSubagents.join(', ')}. Their conversations are saved: continue one with spawn_subagent { "continue": "<run ID>" } if its work is still needed, instead of starting it again from scratch.`);
82
83
  return lines.join('\n');
83
84
  }
84
85
  /** The dynamic section for a continuation; on a later run, the empty slot withdraws it. */
@@ -30,6 +30,7 @@ export declare const runEventTypeSchema: z.ZodEnum<{
30
30
  "extension.notice": "extension.notice";
31
31
  "reaction.added": "reaction.added";
32
32
  "agent.message": "agent.message";
33
+ "todo.updated": "todo.updated";
33
34
  "checklist.updated": "checklist.updated";
34
35
  checkpoint: "checkpoint";
35
36
  heartbeat: "heartbeat";
@@ -67,8 +68,8 @@ export declare const questionOptionSchema: z.ZodObject<{
67
68
  sketch: z.ZodOptional<z.ZodString>;
68
69
  }, z.core.$strip>;
69
70
  export type QuestionOption = z.infer<typeof questionOptionSchema>;
70
- /** One line of the run's checklist. The list has no ids: every update replaces all of it. */
71
- export declare const checklistItemSchema: z.ZodObject<{
71
+ /** One line of the run's todo list. The list has no ids: every update replaces all of it. */
72
+ export declare const todoItemSchema: z.ZodObject<{
72
73
  text: z.ZodString;
73
74
  status: z.ZodEnum<{
74
75
  completed: "completed";
@@ -76,8 +77,8 @@ export declare const checklistItemSchema: z.ZodObject<{
76
77
  in_progress: "in_progress";
77
78
  }>;
78
79
  }, z.core.$strict>;
79
- export type ChecklistItem = z.infer<typeof checklistItemSchema>;
80
- export declare const MAX_CHECKLIST_ITEMS = 50;
80
+ export type TodoItem = z.infer<typeof todoItemSchema>;
81
+ export declare const MAX_TODO_ITEMS = 50;
81
82
  export declare const MAX_CHECKPOINT_SUMMARY = 1200;
82
83
  export declare const MAX_CHECKPOINT_DONE = 20;
83
84
  export declare const MAX_CHECKPOINT_EVIDENCE = 5;
@@ -432,6 +433,16 @@ export declare const runEventPayloadSchemas: {
432
433
  agentName: z.ZodString;
433
434
  text: z.ZodString;
434
435
  }, z.core.$strip>;
436
+ 'todo.updated': z.ZodObject<{
437
+ items: z.ZodArray<z.ZodObject<{
438
+ text: z.ZodString;
439
+ status: z.ZodEnum<{
440
+ completed: "completed";
441
+ pending: "pending";
442
+ in_progress: "in_progress";
443
+ }>;
444
+ }, z.core.$strict>>;
445
+ }, z.core.$strict>;
435
446
  'checklist.updated': z.ZodObject<{
436
447
  items: z.ZodArray<z.ZodObject<{
437
448
  text: z.ZodString;
@@ -624,6 +635,7 @@ export declare const runEventSchema: z.ZodObject<{
624
635
  "extension.notice": "extension.notice";
625
636
  "reaction.added": "reaction.added";
626
637
  "agent.message": "agent.message";
638
+ "todo.updated": "todo.updated";
627
639
  "checklist.updated": "checklist.updated";
628
640
  checkpoint: "checkpoint";
629
641
  heartbeat: "heartbeat";
@@ -681,6 +693,10 @@ export interface RunRecord {
681
693
  profile?: string | null;
682
694
  /** The short name a parent gave a subagent, shown in the roster (PROP-0030). */
683
695
  title?: string | null;
696
+ /** The model the run used, as `provider/id`: the launch selection, then the one the session opened. */
697
+ model?: string | null;
698
+ /** The reasoning effort the run used. */
699
+ effort?: string | null;
684
700
  }
685
701
  export declare const runRecordSchema: z.ZodObject<{
686
702
  resumedFromRunId: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
@@ -711,6 +727,8 @@ export declare const runRecordSchema: z.ZodObject<{
711
727
  variantVersion: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>>;
712
728
  profile: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>>;
713
729
  title: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>>;
730
+ model: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>>;
731
+ effort: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>>;
714
732
  }, z.core.$strip>;
715
733
  /** Records written before PROP-0009 name the variant `harnessScope` and `harnessVersion`. */
716
734
  export declare function upgradeRunRecord(raw: unknown): unknown;
@@ -21,6 +21,8 @@ export const runEventTypeSchema = z.enum([
21
21
  'extension.notice',
22
22
  'reaction.added',
23
23
  'agent.message',
24
+ 'todo.updated',
25
+ // Written before the rename; accepted so stored run logs keep their lists. Never written now.
24
26
  'checklist.updated',
25
27
  'checkpoint',
26
28
  'heartbeat',
@@ -174,11 +176,11 @@ const extensionNoticePayload = z.object({
174
176
  text: z.string(),
175
177
  extension: z.string().optional()
176
178
  });
177
- /** One line of the run's checklist. The list has no ids: every update replaces all of it. */
178
- export const checklistItemSchema = z.object({ text: z.string().min(1).max(500), status: z.enum(['pending', 'in_progress', 'completed']) }).strict();
179
- export const MAX_CHECKLIST_ITEMS = 50;
180
- /** The whole checklist as the model last wrote it; the latest event replaces every earlier one. */
181
- const checklistPayload = z.object({ items: z.array(checklistItemSchema).max(MAX_CHECKLIST_ITEMS) }).strict();
179
+ /** One line of the run's todo list. The list has no ids: every update replaces all of it. */
180
+ export const todoItemSchema = z.object({ text: z.string().min(1).max(500), status: z.enum(['pending', 'in_progress', 'completed']) }).strict();
181
+ export const MAX_TODO_ITEMS = 50;
182
+ /** The whole todo list as the model last wrote it; the latest event replaces every earlier one. */
183
+ const todoPayload = z.object({ items: z.array(todoItemSchema).max(MAX_TODO_ITEMS) }).strict();
182
184
  export const MAX_CHECKPOINT_SUMMARY = 1_200;
183
185
  export const MAX_CHECKPOINT_DONE = 20;
184
186
  export const MAX_CHECKPOINT_EVIDENCE = 5;
@@ -319,7 +321,8 @@ export const runEventPayloadSchemas = {
319
321
  'extension.notice': extensionNoticePayload,
320
322
  'reaction.added': reactionAddedPayload,
321
323
  'agent.message': agentMessagePayload,
322
- 'checklist.updated': checklistPayload,
324
+ 'todo.updated': todoPayload,
325
+ 'checklist.updated': todoPayload,
323
326
  checkpoint: checkpointPayload,
324
327
  heartbeat: heartbeatPayload,
325
328
  'artifact.added': artifactPayload,
@@ -378,7 +381,9 @@ export const runRecordSchema = z.object({
378
381
  variantScope: text.optional(),
379
382
  variantVersion: text.optional(),
380
383
  profile: text.optional(),
381
- title: text.optional()
384
+ title: text.optional(),
385
+ model: text.optional(),
386
+ effort: text.optional()
382
387
  });
383
388
  /** Records written before PROP-0009 name the variant `harnessScope` and `harnessVersion`. */
384
389
  export function upgradeRunRecord(raw) {
@@ -3,7 +3,7 @@
3
3
  // gate refuses everything else, and the instruction arrives as a context update.
4
4
  import { deliveredSnapshot } from "../prompt/compile.js";
5
5
  import { ASK_USER } from "../tools/askUser.js";
6
- import { CHECKLIST_WRITE } from "../tools/checklist.js";
6
+ import { TODO_WRITE } from "../tools/todo.js";
7
7
  import { SESSION_NAME } from "../tools/sessionName.js";
8
8
  import { GET_CAPABILITIES } from "../tools/getCapabilities.js";
9
9
  import { PLAN_RESEARCH_TOOLS } from "../agents/planResearch.js";
@@ -18,7 +18,7 @@ export function normalModeTools(tools) {
18
18
  return tools.filter((name) => !QUESTION_TOOL_NAMES.has(name));
19
19
  }
20
20
  export const PLAN_READ_ONLY_TOOLS = new Set([
21
- 'read', 'grep', 'find', 'ls', SESSION_NAME, CHECKLIST_WRITE, ASK_USER, 'ask_user_question', GET_CAPABILITIES,
21
+ 'read', 'grep', 'find', 'ls', SESSION_NAME, TODO_WRITE, ASK_USER, 'ask_user_question', GET_CAPABILITIES,
22
22
  'history_search', 'history_read', 'trajectory_search', 'trajectory_read',
23
23
  'wiki_entrypoint', 'wiki_search', 'wiki_read', 'wiki_list', 'memory_facts', 'skill_load', 'mcp_list_tools', WAIT_SUBAGENTS
24
24
  ]);
@@ -43,7 +43,9 @@ function ordered(record) {
43
43
  ...(record.variantScope ? { variantScope: record.variantScope } : {}),
44
44
  ...(record.variantVersion ? { variantVersion: record.variantVersion } : {}),
45
45
  ...(record.profile ? { profile: record.profile } : {}),
46
- ...(record.title ? { title: record.title } : {})
46
+ ...(record.title ? { title: record.title } : {}),
47
+ ...(record.model ? { model: record.model } : {}),
48
+ ...(record.effort ? { effort: record.effort } : {})
47
49
  };
48
50
  }
49
51
  function write(root, record) {
@@ -1,7 +1,7 @@
1
1
  import { classifyMessage } from "../accounts/accounts.js";
2
2
  import { EFFORTS } from "../core/effort.js";
3
3
  import { HarnessError } from "../core/errors.js";
4
- import { withWorkspaceWriteScope } from "../core/workspaceBashTool.js";
4
+ import { createWorkspaceShellTool } from "../core/workspaceBashTool.js";
5
5
  import { DEFAULT_COMMAND_TIMEOUT_SECONDS, withCommandBudget } from "../core/commandBudget.js";
6
6
  import { createWorkspaceBashOperations } from "../core/workspaceSandbox.js";
7
7
  import { withWorkspaceWriteAuthorization, workspaceEditOperations, workspaceWriteOperations } from "../core/workspaceWriteOperations.js";
@@ -632,7 +632,7 @@ export const piSessionFactory = async ({ params, allow, gate, tools, runtime: lo
632
632
  });
633
633
  const gatedExtension = (api) => {
634
634
  const writeAuthorizations = new Map();
635
- const bash = withWorkspaceWriteScope(pi.createBashTool(params.cwd, { operations: workspaceBash }));
635
+ const bash = createWorkspaceShellTool(pi, params.cwd, workspaceBash);
636
636
  const write = withWorkspaceWriteAuthorization(pi.createWriteTool(params.cwd, { operations: workspaceWriteOperations }), writeAuthorizations);
637
637
  const edit = withWorkspaceWriteAuthorization(pi.createEditTool(params.cwd, { operations: workspaceEditOperations }), writeAuthorizations);
638
638
  api.registerTool({ ...bash, defaultActive: false });
@@ -18,7 +18,7 @@ import { FIRST_PARTY_PLUGINS, firstPartyExtensions } from "../plugins/firstParty
18
18
  import { imagePruning } from "../world/images.js";
19
19
  import { materialise } from "../variant/materialise.js";
20
20
  import { ASK_USER } from "../tools/askUser.js";
21
- import { CHECKLIST_WRITE } from "../tools/checklist.js";
21
+ import { TODO_WRITE } from "../tools/todo.js";
22
22
  import { CHECKPOINT_WRITE, checkpointOf } from "../tools/checkpoint.js";
23
23
  import { SESSION_NAME, sessionNameTool } from "../tools/sessionName.js";
24
24
  import { DEFAULT_TOOL_NAMES } from "../tools/definitions.js";
@@ -400,7 +400,7 @@ export class Sessions {
400
400
  const canAskUser = pinnedAskUser || (interactivePlan && harnessToolsEnabled);
401
401
  const requestedTools = [...new Set([
402
402
  ...input.tools.allow.filter((name) => name !== SPAWN_SUBAGENT && name !== WAIT_SUBAGENTS && (name !== ASK_USER || canAskUser)),
403
- ...(harnessToolsEnabled ? [...(canAskUser ? [ASK_USER] : []), CHECKLIST_WRITE, CHECKPOINT_WRITE, SESSION_NAME, GET_CAPABILITIES, ...DEFAULT_TOOL_NAMES] : []),
403
+ ...(harnessToolsEnabled ? [...(canAskUser ? [ASK_USER] : []), TODO_WRITE, CHECKPOINT_WRITE, SESSION_NAME, GET_CAPABILITIES, ...DEFAULT_TOOL_NAMES] : []),
404
404
  ...(broker?.toolNames ?? []),
405
405
  ...(canSpawn ? [SPAWN_SUBAGENT, WAIT_SUBAGENTS] : [])
406
406
  ])];
@@ -452,7 +452,9 @@ export class Sessions {
452
452
  variantScope: variant?.scope ?? null,
453
453
  variantVersion: variant?.fingerprint ?? null,
454
454
  ...(input.run.profile ? { profile: input.run.profile } : {}),
455
- ...(input.run.title ? { title: input.run.title } : {})
455
+ ...(input.run.title ? { title: input.run.title } : {}),
456
+ ...(input.model ? { model: input.model } : {}),
457
+ ...(input.effort ? { effort: input.effort } : {})
456
458
  });
457
459
  // What the session is, kept with the run so later consolidation knows which memory it had.
458
460
  try {
@@ -476,7 +478,14 @@ export class Sessions {
476
478
  const delivered = historyFile ? deliveredSnapshot(historyFile) : null;
477
479
  const resumed = continuationSection(continuation, delivered?.some((entry) => entry.id === CONTINUATION_SECTION_ID) ?? false);
478
480
  // The profiles replace the variant's own subagent list, which they include.
479
- const profiles = capabilities.tools.includes(SPAWN_SUBAGENT) ? profileSection(root, input.cwd, variant?.dir ?? null, input.plan === true) : null;
481
+ const researchContext = input.plan === true ? {
482
+ frozenTools: capabilities.tools,
483
+ contributions: capabilities.contributions,
484
+ descriptors: BUILTIN_PLUGIN_REGISTRY.descriptors()
485
+ } : undefined;
486
+ const profiles = capabilities.tools.includes(SPAWN_SUBAGENT)
487
+ ? profileSection(root, input.cwd, variant?.dir ?? null, input.plan === true, researchContext)
488
+ : null;
480
489
  if (profiles && variant)
481
490
  variant = { ...variant, sections: variant.sections.filter((section) => section.id !== profiles.id) };
482
491
  const own = [plan, resumed, profiles].filter((section) => section !== null);
@@ -493,8 +502,10 @@ export class Sessions {
493
502
  fail(error);
494
503
  throw error;
495
504
  }
496
- // A reply is bounded only when its checkpoint tool is part of the frozen capability set.
497
- const sliceEnabled = slicePolicy.enabled && capabilities.tools.includes(CHECKPOINT_WRITE);
505
+ // Plan-research children are bounded too, but their read-only capability set omits checkpoint_write.
506
+ const checkpointAvailable = capabilities.tools.includes(CHECKPOINT_WRITE);
507
+ const planResearchChild = input.plan === true && input.run.relation === 'subagent';
508
+ const sliceEnabled = slicePolicy.enabled && (checkpointAvailable || planResearchChild);
498
509
  const sliceState = createSliceState(slicePolicy, sliceOf(root, runId));
499
510
  const run = this.options.runs.start(input.run.sessionId, runId);
500
511
  if (run.finished)
@@ -594,8 +605,8 @@ export class Sessions {
594
605
  runLineageJoin(root, runId),
595
606
  this.options.subagents.extension(runId),
596
607
  { name: 'image-pruning', factory: imagePruning() },
597
- { name: COMPACTION_CHECKPOINT_EXTENSION, factory: compactionCheckpoint({ initialChecklist: continuation?.checklist, planInstructions: checkpointPlanInstructions }) },
598
- ...(sliceEnabled ? [{ name: WORK_SLICE_EXTENSION, factory: workSliceGuard({ policy: slicePolicy, state: sliceState }) }] : [])
608
+ { name: COMPACTION_CHECKPOINT_EXTENSION, factory: compactionCheckpoint({ initialTodo: continuation?.todo, planInstructions: checkpointPlanInstructions }) },
609
+ ...(sliceEnabled ? [{ name: WORK_SLICE_EXTENSION, factory: workSliceGuard({ policy: slicePolicy, state: sliceState, checkpointAvailable }) }] : [])
599
610
  ],
600
611
  ...(plugins ? { plugins: { loading: () => plugins.loading({ cwd: input.cwd, agentDir: context.configDir, install: true, caller: { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null }, contributions: capabilities.contributions }), loaded: (extensions) => plugins.loaded(extensions) } } : {}),
601
612
  system,
@@ -625,10 +636,17 @@ export class Sessions {
625
636
  throw new HarnessError('native_resume_unavailable', 'The saved conversation could not be restored.');
626
637
  }
627
638
  const opened = pi;
628
- updateRun(root, runId, (record) => void (record.nativeSessionId = opened.sessionId));
639
+ const openedModel = pi.model?.id && pi.model.provider ? `${pi.model.provider}/${pi.model.id}` : null;
640
+ updateRun(root, runId, (record) => {
641
+ record.nativeSessionId = opened.sessionId;
642
+ if (openedModel)
643
+ record.model = openedModel;
644
+ if (opened.thinkingLevel)
645
+ record.effort = opened.thinkingLevel;
646
+ });
629
647
  this.options.subagents.register(input, account.id, capabilities, Boolean(pi.sessionManager));
630
- if (pi.model?.id && pi.model.provider)
631
- this.options.subagents.resolvedModel(runId, `${pi.model.provider}/${pi.model.id}`, pi.thinkingLevel);
648
+ if (openedModel)
649
+ this.options.subagents.resolvedModel(runId, openedModel, pi.thinkingLevel);
632
650
  params.settle = () => this.options.subagents.verify(runId, opened.sessionManager ? subagentHistory(opened.sessionManager.getBranch()) : undefined);
633
651
  void runTurn(run, pi, params).then(() => (sliceEnabled && !input.command ? this.continueSlice({ root, runId, source: input, policy: slicePolicy }) : undefined));
634
652
  return { runId, nativeSessionId: pi.sessionId };
@@ -14,6 +14,7 @@ type Started = {
14
14
  export type SpawnedSubagent = Started & {
15
15
  agent?: string;
16
16
  unavailableTools?: string[];
17
+ continued?: string;
17
18
  };
18
19
  export interface SubagentRuntimeSettings {
19
20
  model: string | null;
@@ -42,9 +43,23 @@ export declare class Subagents {
42
43
  private rememberRuntimeSettings;
43
44
  resolvedModel(runId: string, model: string, effort?: Effort): void;
44
45
  private parent;
46
+ private planSpawnCheck;
45
47
  /** The plan-mode per-call gate and spawn implementation share this eligibility check. */
46
48
  planSpawnRefusal(parentRunId: string, args: unknown): string | null;
47
49
  spawn(request: SubagentInput): Promise<SpawnedSubagent>;
50
+ /** Records ownership, starts the child, and writes a failed record when it never opened. */
51
+ private launchChild;
52
+ /** The children this parent may read or continue: its own, and those recovered from its history. */
53
+ private owned;
54
+ /** The newest run in a continued child's chain, among the runs this parent owns. */
55
+ private latestLink;
56
+ /** The first run of a chain: its folder holds the child's history for every later link. */
57
+ private firstLink;
58
+ /**
59
+ * Continues a stopped child as a new child run with `continueFrom`: the same history, profile,
60
+ * account, variant version and capability set, which must still lie within the parent's.
61
+ */
62
+ private continueChild;
48
63
  private selected;
49
64
  results(selection: SubagentSelection): SubagentResult[];
50
65
  wait(selection: SubagentSelection, signal?: AbortSignal): Promise<SubagentResult[]>;