@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.6

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 (96) hide show
  1. package/CHANGELOG.md +82 -9
  2. package/dist/application/task-lifecycle/advance.js +17 -4
  3. package/dist/application/task-lifecycle/observe.js +43 -29
  4. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  5. package/dist/build-stamp.json +3 -3
  6. package/dist/executors/dag-pi-executor.js +233 -202
  7. package/dist/executors/pi-executor.js +2 -0
  8. package/dist/executors/pi-sdk-executor.js +77 -39
  9. package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
  10. package/dist/task/source-references.js +48 -23
  11. package/dist/worker/console/chat/pi-runtime.js +113 -95
  12. package/dist/worker/console/chat/routes.js +164 -54
  13. package/dist/worker/console/chat/scm-routes.js +193 -0
  14. package/dist/worker/console/chat/scm-service.js +253 -0
  15. package/dist/worker/console/chat/scm-tools.js +111 -0
  16. package/dist/worker/console/chat/session-catalog.js +1 -0
  17. package/dist/worker/console/chat/session-mode-view.js +48 -0
  18. package/dist/worker/console/chat/session-mode.js +218 -0
  19. package/dist/worker/console/chat/session-store.js +22 -6
  20. package/dist/worker/console/chat/shortcuts.js +6 -0
  21. package/dist/worker/console/chat/tools.js +1 -0
  22. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CCj36aaC.js → abnfDiagram-N423BO3Z-BuyrlONK.js} +1 -1
  23. package/dist/worker/console/static/assets/{arc-Cl3MIVFh.js → arc-BvCl_wpi.js} +1 -1
  24. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C_n4kMJS.js → architectureDiagram-T3A2C74G-BkBpBaRq.js} +1 -1
  25. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DlWYJ7cK.js → blockDiagram-VBNYF7ZC-yve4Mcb_.js} +1 -1
  26. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ud5pS7vS.js → c4Diagram-5PPSVZJV-9yLGboBx.js} +1 -1
  27. package/dist/worker/console/static/assets/channel-BRahIhjw.js +1 -0
  28. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CQPwOv_j.js → chunk-2GRJ4B5K-BKP69b8r.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CMzjxrWI.js → chunk-2Q5K7J3B-NWFRnXju.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-pcnXnD6t.js → chunk-5RXB4S5H-BiweqLEc.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CEB3AvU2.js → chunk-5VM5RSS4-CP9GHmRY.js} +1 -1
  32. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DSKnbXbp.js → chunk-6Q2QTUOP-BiGDkBtj.js} +1 -1
  33. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CS6-PjCZ.js → chunk-GF5L2VYU-DkJ4ZjQS.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-f6q8Tnzz.js → chunk-JWPE2WC7-B5jjn7zq.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DkB0QX3q.js → chunk-KBJHAD2P-C6KoK9O0.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BdB4jAv6.js → chunk-RYQCIY6F-BvNIfyn-.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVm1bnX6.js → chunk-XXDRQBXY-BZ33iX4W.js} +1 -1
  38. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GOC80mhq.js +1 -0
  39. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GOC80mhq.js +1 -0
  40. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-FV_P-IEp.js → cose-bilkent-JH36ORCC-CP8eIuZd.js} +1 -1
  41. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BM7RtkA2.js → cynefin-VYW2F7L2-DZECjpOe.js} +1 -1
  42. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C1BZwTG5.js → cynefinDiagram-MW4NZA55-CvVJoFqf.js} +1 -1
  43. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C6McNKmY.js → dagre-VZM6K2ZE-D_JTqdAn.js} +1 -1
  44. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-C0aWRUaE.js → diagram-7IWD3JNH-Db7AQ0z0.js} +1 -1
  45. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bfk_SKE8.js → diagram-B4RE2ZJO-AZPVo5CZ.js} +1 -1
  46. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-prx1T4qf.js → diagram-LBJQPF4R-BgXqI3E6.js} +1 -1
  47. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DBvty0eU.js → diagram-Q27KOJAE-BYjFn3Ba.js} +1 -1
  48. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CF0em8e9.js → diagram-UB23O5K3-B4rJgGGO.js} +1 -1
  49. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CF90b4Ws.js → ebnfDiagram-BXEA7PRR-C6XWZhpw.js} +1 -1
  50. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-hfunxzof.js → erDiagram-JOGREHBK-1YdQRIQW.js} +1 -1
  51. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D-QSy4Nl.js → flowDiagram-UKHOOZJN-l3a0AwN0.js} +1 -1
  52. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DaBGNcvT.js → ganttDiagram-PKOTCBZU-CQL96F2Z.js} +1 -1
  53. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BChcubGL.js → gitGraphDiagram-DS77QQ5N-BxCvafRb.js} +1 -1
  54. package/dist/worker/console/static/assets/index-4wCGsXuU.js +465 -0
  55. package/dist/worker/console/static/assets/index-CiF9WBoR.css +1 -0
  56. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BkC67fIa.js → infoDiagram-6WML65LV-BUipIeDO.js} +1 -1
  57. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-ibhLczCD.js → ishikawaDiagram-WSZJBQD7-CRfomVMX.js} +1 -1
  58. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DCRMitay.js → journeyDiagram-NVQOT4AX-_UFvyviX.js} +1 -1
  59. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ_hKaYI.js → kanban-definition-27J2QSJJ-CKIRoqKv.js} +1 -1
  60. package/dist/worker/console/static/assets/{linear-CIlLsYsA.js → linear-CjttJDCM.js} +1 -1
  61. package/dist/worker/console/static/assets/{mermaid.core-C-5IQMP_.js → mermaid.core-DPvHX155.js} +5 -5
  62. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-TCzV5TQT.js → mindmap-definition-FAOFIHXS-Qmj-qwBe.js} +1 -1
  63. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bfhd2BQH.js → pegDiagram-VL7TDLO6-Auw6DRgq.js} +1 -1
  64. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxFRAm5N.js → pieDiagram-7S7Q4E2Y-CqMrfD4c.js} +1 -1
  65. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C-m12lxv.js → quadrantDiagram-CIZ2JOQS-BAddeggH.js} +1 -1
  66. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CwmLiGgx.js → railroadDiagram-AXF67PYL-DG1JuOu2.js} +1 -1
  67. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-oc0Q1jiD.js → requirementDiagram-LRYGKXZP-DLYGMKlC.js} +1 -1
  68. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-9Ght6Yur.js → sankeyDiagram-W5VNT64P-g68SbLM9.js} +1 -1
  69. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-ndm3lyz9.js → sequenceDiagram-SI44F4Z6-CmszLKv8.js} +1 -1
  70. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7sPlKVcw.js → sizeCapture-X5ZJPWSS-D2tEKUCj.js} +1 -1
  71. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Izl21-oS.js → stateDiagram-OKZ733FA-Syc5E_Jf.js} +1 -1
  72. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CwWwuPI-.js +1 -0
  73. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CtO3D65o.js → swimlanes-SLNWSIFB-Ibn0IRCa.js} +2 -2
  74. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-A7fqemr6.js +8 -0
  75. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cyg5cY7z.js → timeline-definition-Z64GVDOM-2ZW9W3iS.js} +1 -1
  76. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CwtE_TFK.js → vennDiagram-T6HMQDX7-v50HsKUh.js} +1 -1
  77. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dtt9v8OO.js → wardleyDiagram-T6FBY63Y-DqCXEoDH.js} +1 -1
  78. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B2Y3ryb6.js → xychartDiagram-ELKLHX3M-vIP8Vo9d.js} +1 -1
  79. package/dist/worker/console/static/index.html +2 -2
  80. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +20 -4
  81. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
  82. package/dist/worker/console/workspace-context.js +6 -0
  83. package/dist/workflows/dag/frontend-capacity.js +9 -0
  84. package/dist/workflows/dag/frontend-design-policy.js +2 -1
  85. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  86. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  87. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  88. package/package.json +1 -1
  89. package/skills/frontend-plan/SKILL.md +7 -0
  90. package/dist/worker/console/static/assets/channel-CEMRpNm-.js +0 -1
  91. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +0 -1
  92. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +0 -1
  93. package/dist/worker/console/static/assets/index-CJkCY1XR.js +0 -464
  94. package/dist/worker/console/static/assets/index-DUBCbiqv.css +0 -1
  95. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +0 -1
  96. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +0 -8
@@ -32,7 +32,8 @@ import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMess
32
32
  import { parseProviderError, } from "./provider-error.js";
33
33
  import { projectCompactSnapshot, } from "./chat-event-store.js";
34
34
  import { extractUsageSample } from "./usage.js";
35
- import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
35
+ import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
36
+ import { authorizeChatSessionTool, DEFAULT_CONSOLE_CHAT_SESSION_MODE, PI_TODO_WRITE_DESCRIPTION, chatActiveToolBaseline, composeChatSystemPrompt, composeChatSystemPromptParts, computeChatActiveToolNames, shouldInjectGoalContinuation, shouldLoadWorkflowBusinessTools, } from "./session-mode.js";
36
37
  import { buildAgentCustomTool, ExploreOrchestrator, } from "./subagents/index.js";
37
38
  import { createOperatorChatResourceLoader, } from "./resource-loader.js";
38
39
  import { applySkillAutoInvocationPreferences, isAutoInvocationEnabled, loadResourcePreferences, skillResourceId, } from "./resource-preferences-store.js";
@@ -44,6 +45,7 @@ import { filterActiveInterviewTools } from "../interview/tools.js";
44
45
  import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
45
46
  import { buildTodoContinuationContext, } from "./todos.js";
46
47
  import { buildPersistentTerminalTools } from "./terminal-tools.js";
48
+ import { buildScmTools } from "./scm-tools.js";
47
49
  import { buildGoalContinuationContext, } from "./goals.js";
48
50
  import { injectWizardLocalHeaders, } from "./sdd-data-alignment.js";
49
51
  /**
@@ -76,15 +78,6 @@ export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
76
78
  "Continue until success, user stop, or no safe recovery remains; then stop and report failure/evidence. Never create a new task for a transient failure or replace repair-pi with direct edits.",
77
79
  "Narrate long-DAG progress briefly at start, meaningful phase changes/findings, verification failure/recovery, prolonged quiet and terminal state: say what finished and what is next. Never narrate unchanged operationWait polls or heartbeats, or claim beyond structured facts.",
78
80
  ].join("\n");
79
- /** Compose the inspectable system prompt actually injected into Operator Chat. */
80
- export function composeOperatorChatSystemPrompt(input) {
81
- return [
82
- OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
83
- ...(input.systemPromptSuffix?.trim()
84
- ? [input.systemPromptSuffix.trim()]
85
- : []),
86
- ].join("\n\n");
87
- }
88
81
  /**
89
82
  * Built-in SDK tool names registered for every Chat session (ADR 0011).
90
83
  * Full Pi builtins: read/write/edit/bash/grep/find/ls. safe-* custom tools
@@ -113,27 +106,8 @@ export const OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS = Object.freeze([
113
106
  "git-diff",
114
107
  ]);
115
108
  export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
116
- return [
117
- ...OPERATOR_CHAT_ALLOWED_TOOLS,
118
- ...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
119
- ...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
120
- ...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
121
- ];
109
+ return chatActiveToolBaseline("workflow");
122
110
  }
123
- /**
124
- * Non-Pi write / coding channels that must never be activated in Operator
125
- * Chat even when a user/project extension registers them (ADR 0011 / 0012).
126
- * Mirrors the NON_PI_WRITE_CHANNELS guard in tools.ts for the Gate 2 pin.
127
- */
128
- const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
129
- "apply_patch",
130
- "apply-patch",
131
- "full-tools",
132
- "full_tools",
133
- "coding-chat",
134
- "coding_chat",
135
- "shell",
136
- ].map((t) => t.toLowerCase()));
137
111
  /**
138
112
  * Gate 2 active-tool set for a live session (ADR 0012): the operator-chat
139
113
  * baseline (operator actions + Pi builtins + safe explore) PLUS extension
@@ -144,24 +118,8 @@ const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
144
118
  * coding-chat). Unknown/denied names are never activated; Gate 3 still
145
119
  * re-authorizes every tool call per invocation.
146
120
  */
147
- export function computeOperatorChatActiveToolNames(session) {
148
- const baseline = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
149
- if (!session?.getAllTools)
150
- return baseline;
151
- const baselineLower = new Set(baseline.map((t) => t.toLowerCase()));
152
- const extras = [];
153
- for (const tool of session.getAllTools()) {
154
- const name = tool?.name;
155
- if (!name || typeof name !== "string")
156
- continue;
157
- const lower = name.toLowerCase();
158
- if (baselineLower.has(lower))
159
- continue;
160
- if (OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
161
- continue;
162
- extras.push(name);
163
- }
164
- return extras.length > 0 ? [...baseline, ...extras] : baseline;
121
+ export function computeOperatorChatActiveToolNames(session, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
122
+ return computeChatActiveToolNames(session, sessionMode);
165
123
  }
166
124
  const OPERATOR_CHAT_SESSION_EXCLUDE_TOOLS = Object.freeze([
167
125
  "apply_patch",
@@ -217,8 +175,8 @@ async function bindSessionExtensions(session) {
217
175
  return;
218
176
  await session.bindExtensions(createConsoleExtensionBindings());
219
177
  }
220
- function pinOperatorChatTools(session) {
221
- session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
178
+ function pinOperatorChatTools(session, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
179
+ session.setActiveToolsByName(computeChatActiveToolNames(session, sessionMode));
222
180
  }
223
181
  /**
224
182
  * Pi has no hook result that merely appends instructions to its built-in
@@ -370,7 +328,7 @@ async function buildCustomOperatorTools(options) {
370
328
  promptSnippet: schema.description,
371
329
  parameters: parameters,
372
330
  async execute(toolCallId, params) {
373
- const decision = authorizeOperatorChatTool(schema.toolId);
331
+ const decision = authorizeChatSessionTool(schema.toolId, options.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
374
332
  if (!decision.ok) {
375
333
  return {
376
334
  content: [
@@ -387,7 +345,7 @@ async function buildCustomOperatorTools(options) {
387
345
  }
388
346
  // Defer require to avoid circular import at module load.
389
347
  const { dispatchChatToolCall } = await import("./session-store.js");
390
- const result = await dispatchChatToolCall(ctx, decision.toolId, params, toolCallId);
348
+ const result = await dispatchChatToolCall(ctx, decision.toolId, params, toolCallId, options.sessionMode);
391
349
  const payload = result.ok
392
350
  ? result.result
393
351
  : { error: result.message, code: result.errorCode };
@@ -445,11 +403,14 @@ async function buildTodoWriteTool(options) {
445
403
  import("typebox"),
446
404
  import("@earendil-works/pi-coding-agent"),
447
405
  ]);
406
+ const description = options.sessionMode === "pi"
407
+ ? PI_TODO_WRITE_DESCRIPTION
408
+ : TODO_WRITE_DESCRIPTION;
448
409
  return defineTool({
449
410
  name: "todo_write",
450
411
  label: "Update todo list",
451
- description: TODO_WRITE_DESCRIPTION,
452
- promptSnippet: TODO_WRITE_DESCRIPTION,
412
+ description,
413
+ promptSnippet: description,
453
414
  parameters: Type.Object({
454
415
  todos: Type.Array(Type.Object({
455
416
  content: Type.String({ description: "Short imperative task description." }),
@@ -1022,12 +983,14 @@ export class ConsolePiRuntime {
1022
983
  sessionInit = new Map();
1023
984
  /** Resolved model binding after materialization (create may return before this is known). */
1024
985
  sessionModels = new Map();
986
+ /** Durable session mode; record is the source of truth, this is the live handle cache. */
987
+ sessionModes = new Map();
1025
988
  /** SessionIds disposed while materialization was still in flight. */
1026
989
  disposedSessions = new Set();
1027
990
  compactionControllers = new Map();
1028
991
  siftStates = new Map();
1029
992
  /** Session-less tool definition caches (no AgentSession state). */
1030
- operatorCustomToolsPromise;
993
+ operatorCustomToolsByMode = new Map();
1031
994
  exploreCustomToolsPromise;
1032
995
  exploreOrchestrator;
1033
996
  bindings;
@@ -1086,10 +1049,20 @@ export class ConsolePiRuntime {
1086
1049
  * after a PATCH sees the latest resource-preferences.json (not create-time
1087
1050
  * closure state).
1088
1051
  */
1052
+ sessionModeOf(sessionId) {
1053
+ if (!sessionId)
1054
+ return DEFAULT_CONSOLE_CHAT_SESSION_MODE;
1055
+ return this.sessionModes.get(sessionId) ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE;
1056
+ }
1057
+ rememberSessionMode(sessionId, sessionMode = DEFAULT_CONSOLE_CHAT_SESSION_MODE) {
1058
+ this.sessionModes.set(sessionId, sessionMode);
1059
+ return sessionMode;
1060
+ }
1089
1061
  async buildResourceLoaderOptions(base = {}, sessionId) {
1090
1062
  const inheritedFactories = Array.isArray(base.extensionFactories)
1091
1063
  ? base.extensionFactories
1092
1064
  : [];
1065
+ const sessionMode = this.sessionModeOf(sessionId);
1093
1066
  const todoContextExtension = sessionId && this.options.todoRegistry
1094
1067
  ? createTodoCompactionContextExtension({
1095
1068
  sessionId,
@@ -1099,7 +1072,9 @@ export class ConsolePiRuntime {
1099
1072
  const pressureExtension = sessionId
1100
1073
  ? this.createContextPressureInlineExtension(sessionId)
1101
1074
  : undefined;
1102
- const goalContextExtension = sessionId && this.options.goalRegistry
1075
+ const goalContextExtension = sessionId &&
1076
+ this.options.goalRegistry &&
1077
+ shouldInjectGoalContinuation(sessionMode)
1103
1078
  ? createGoalCompactionContextExtension({
1104
1079
  sessionId,
1105
1080
  registry: this.options.goalRegistry,
@@ -1348,6 +1323,7 @@ export class ConsolePiRuntime {
1348
1323
  await this.syncAimaxAuthToken();
1349
1324
  const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
1350
1325
  this.disposedSessions.delete(sessionId);
1326
+ const sessionMode = this.rememberSessionMode(sessionId, init?.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
1351
1327
  const sessionManager = await this.bindings.createSessionManager({
1352
1328
  cwd: this.options.cwd,
1353
1329
  sessionDir: this.options.sessionDir,
@@ -1373,6 +1349,7 @@ export class ConsolePiRuntime {
1373
1349
  sessionManager,
1374
1350
  model: init?.model,
1375
1351
  systemPromptSuffix: init?.systemPromptSuffix,
1352
+ sessionMode,
1376
1353
  // Create path must resolve default model from the SAME services object
1377
1354
  // used for formal materialization (no throwaway createServices).
1378
1355
  resolveDefaultModel: !init?.model,
@@ -1382,7 +1359,8 @@ export class ConsolePiRuntime {
1382
1359
  sessionFile,
1383
1360
  createdAt: new Date().toISOString(),
1384
1361
  model: init?.model,
1385
- activeTools: OPERATOR_CHAT_ACTIVE_TOOL_NAMES(),
1362
+ activeTools: chatActiveToolBaseline(sessionMode),
1363
+ sessionMode,
1386
1364
  initializing: true,
1387
1365
  };
1388
1366
  }
@@ -1444,14 +1422,16 @@ export class ConsolePiRuntime {
1444
1422
  if (this.disposedSessions.has(sessionId))
1445
1423
  return;
1446
1424
  await this.syncAimaxAuthToken();
1425
+ const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? this.sessionModeOf(sessionId));
1447
1426
  const agentDir = await safeGetAgentDir(this.bindings);
1448
1427
  // Capture before services parse models.json. A concurrent edit can therefore
1449
1428
  // cause at most one harmless extra refresh, never a missed next-turn refresh.
1450
1429
  const modelConfigFile = await captureModelConfigFile(agentDir);
1451
- const appendSystemPrompt = [
1452
- OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
1453
- ...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
1454
- ];
1430
+ const appendSystemPrompt = composeChatSystemPromptParts({
1431
+ sessionMode,
1432
+ workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
1433
+ systemPromptSuffix: init.systemPromptSuffix,
1434
+ });
1455
1435
  // Single services construction for formal materialization (+ optional
1456
1436
  // default-model resolve from the same modelRuntime). Repo-scoped skill
1457
1437
  // auto-invocation prefs inject skillsOverride without touching SKILL.md.
@@ -1502,7 +1482,7 @@ export class ConsolePiRuntime {
1502
1482
  return;
1503
1483
  }
1504
1484
  await bindSessionExtensions(session);
1505
- pinOperatorChatTools(session);
1485
+ pinOperatorChatTools(session, sessionMode);
1506
1486
  // Register then re-check dispose race: a concurrent dispose must win.
1507
1487
  this.sessions.set(sessionId, session);
1508
1488
  this.sessionManagers.set(sessionId, init.sessionManager);
@@ -1530,6 +1510,7 @@ export class ConsolePiRuntime {
1530
1510
  this.modelRuntimes.delete(sessionId);
1531
1511
  this.modelConfigFiles.delete(sessionId);
1532
1512
  this.sessionModels.delete(sessionId);
1513
+ this.sessionModes.delete(sessionId);
1533
1514
  this.serviceScopes.delete(sessionId);
1534
1515
  this.revisions.delete(sessionId);
1535
1516
  this.thinkingSelections.delete(sessionId);
@@ -1537,8 +1518,12 @@ export class ConsolePiRuntime {
1537
1518
  }
1538
1519
  /** Cached session-less custom tool definitions (operator + explore). */
1539
1520
  async loadCustomTools(sessionId) {
1521
+ const sessionMode = this.sessionModeOf(sessionId);
1522
+ const loadWorkflowTools = shouldLoadWorkflowBusinessTools(sessionMode);
1540
1523
  const [operatorCustomTools, exploreCustomTools] = await Promise.all([
1541
- this.loadOperatorCustomTools(),
1524
+ loadWorkflowTools
1525
+ ? this.loadOperatorCustomTools(sessionMode)
1526
+ : Promise.resolve([]),
1542
1527
  this.loadExploreCustomTools(),
1543
1528
  ]);
1544
1529
  const questionTool = this.options.questionRegistry
@@ -1548,9 +1533,13 @@ export class ConsolePiRuntime {
1548
1533
  })
1549
1534
  : undefined;
1550
1535
  const todoTool = this.options.todoRegistry
1551
- ? await buildTodoWriteTool({ sessionId, registry: this.options.todoRegistry })
1536
+ ? await buildTodoWriteTool({
1537
+ sessionId,
1538
+ registry: this.options.todoRegistry,
1539
+ sessionMode,
1540
+ })
1552
1541
  : undefined;
1553
- const goalTools = this.options.goalRegistry
1542
+ const goalTools = loadWorkflowTools && this.options.goalRegistry
1554
1543
  ? await buildGoalTools({ sessionId, registry: this.options.goalRegistry })
1555
1544
  : [];
1556
1545
  const terminalTools = this.options.terminalRegistry
@@ -1563,6 +1552,12 @@ export class ConsolePiRuntime {
1563
1552
  return [];
1564
1553
  })
1565
1554
  : [];
1555
+ const scmTools = this.options.scmService
1556
+ ? await buildScmTools({ scm: this.options.scmService }).catch((error) => {
1557
+ process.stderr.write(`[console] chat scm-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
1558
+ return [];
1559
+ })
1560
+ : [];
1566
1561
  const agentTool = await buildAgentCustomTool({
1567
1562
  parentSessionId: sessionId,
1568
1563
  orchestrator: this.exploreOrchestrator,
@@ -1588,23 +1583,27 @@ export class ConsolePiRuntime {
1588
1583
  ...(todoTool ? [todoTool] : []),
1589
1584
  ...goalTools,
1590
1585
  ...terminalTools,
1586
+ ...scmTools,
1591
1587
  ...(agentTool ? [agentTool] : []),
1592
1588
  ];
1593
1589
  }
1594
- loadOperatorCustomTools() {
1590
+ loadOperatorCustomTools(sessionMode) {
1595
1591
  if (!this.options.actionContext)
1596
1592
  return Promise.resolve([]);
1597
- if (!this.operatorCustomToolsPromise) {
1598
- this.operatorCustomToolsPromise = buildCustomOperatorTools({
1599
- actionContext: this.options.actionContext,
1600
- }).catch((error) => {
1601
- process.stderr.write(`[console] chat custom-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
1602
- // Allow a later create/reopen to retry after a transient failure.
1603
- this.operatorCustomToolsPromise = undefined;
1604
- return [];
1605
- });
1606
- }
1607
- return this.operatorCustomToolsPromise;
1593
+ const cached = this.operatorCustomToolsByMode.get(sessionMode);
1594
+ if (cached)
1595
+ return cached;
1596
+ const promise = buildCustomOperatorTools({
1597
+ actionContext: this.options.actionContext,
1598
+ sessionMode,
1599
+ }).catch((error) => {
1600
+ process.stderr.write(`[console] chat custom-tools build failed: ${error instanceof Error ? error.message : String(error)}\n`);
1601
+ // Allow a later create/reopen to retry after a transient failure.
1602
+ this.operatorCustomToolsByMode.delete(sessionMode);
1603
+ return [];
1604
+ });
1605
+ this.operatorCustomToolsByMode.set(sessionMode, promise);
1606
+ return promise;
1608
1607
  }
1609
1608
  loadExploreCustomTools() {
1610
1609
  if (!this.exploreCustomToolsPromise) {
@@ -1622,6 +1621,7 @@ export class ConsolePiRuntime {
1622
1621
  if (!this.bindings.forkSessionManager)
1623
1622
  throw new Error("SESSION_FORK_UNSUPPORTED");
1624
1623
  const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
1624
+ const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
1625
1625
  const sessionManager = await this.bindings.forkSessionManager({
1626
1626
  sourcePath: init.sourceSessionFile,
1627
1627
  targetCwd: this.options.cwd,
@@ -1632,8 +1632,8 @@ export class ConsolePiRuntime {
1632
1632
  sessionId,
1633
1633
  sessionManager,
1634
1634
  model: init.model,
1635
+ sessionMode,
1635
1636
  });
1636
- built.session.setActiveToolsByName(computeOperatorChatActiveToolNames(built.session));
1637
1637
  this.sessions.set(sessionId, built.session);
1638
1638
  this.sessionManagers.set(sessionId, sessionManager);
1639
1639
  this.modelRuntimes.set(sessionId, built.modelRuntime);
@@ -1649,6 +1649,7 @@ export class ConsolePiRuntime {
1649
1649
  createdAt: new Date().toISOString(),
1650
1650
  model: init.model,
1651
1651
  activeTools: built.session.getActiveToolNames(),
1652
+ sessionMode,
1652
1653
  };
1653
1654
  }
1654
1655
  async branchSession(input) {
@@ -1827,15 +1828,22 @@ export class ConsolePiRuntime {
1827
1828
  async getRuntimeSystemPrompt(sessionId) {
1828
1829
  await this.ensureSessionReady(sessionId).catch(() => undefined);
1829
1830
  const session = this.sessions.get(sessionId);
1830
- if (!session)
1831
- return composeOperatorChatSystemPrompt({});
1831
+ if (!session) {
1832
+ return composeChatSystemPrompt({
1833
+ sessionMode: this.sessionModeOf(sessionId),
1834
+ workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
1835
+ });
1836
+ }
1832
1837
  const fromState = session.state?.systemPrompt;
1833
1838
  const fromGetter = session.systemPrompt;
1834
1839
  const loader = this.serviceScopes.get(sessionId)?.resourceLoader;
1835
1840
  return (fromState ||
1836
1841
  fromGetter ||
1837
1842
  loader?.getSystemPrompt?.() ||
1838
- composeOperatorChatSystemPrompt({}));
1843
+ composeChatSystemPrompt({
1844
+ sessionMode: this.sessionModeOf(sessionId),
1845
+ workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
1846
+ }));
1839
1847
  }
1840
1848
  /**
1841
1849
  * Read the RAW system prompt for the snapshot, converting any projection
@@ -1928,6 +1936,7 @@ export class ConsolePiRuntime {
1928
1936
  if (services)
1929
1937
  this.applySessionCompactionOverride(sessionId, services, session);
1930
1938
  await this.refreshModelConfiguration(sessionId, "force");
1939
+ pinOperatorChatTools(session, this.sessionModeOf(sessionId));
1931
1940
  }
1932
1941
  catch (error) {
1933
1942
  return {
@@ -2366,6 +2375,7 @@ export class ConsolePiRuntime {
2366
2375
  async reopenSession(init) {
2367
2376
  await this.syncAimaxAuthToken();
2368
2377
  const sessionId = init.sessionId;
2378
+ const sessionMode = this.rememberSessionMode(sessionId, init.sessionMode ?? this.sessionModeOf(sessionId));
2369
2379
  // Wait for in-flight create materialization rather than racing reopen.
2370
2380
  const initState = this.sessionInit.get(sessionId);
2371
2381
  if (initState?.status === "initializing") {
@@ -2442,6 +2452,7 @@ export class ConsolePiRuntime {
2442
2452
  sessionManager,
2443
2453
  model: init.model,
2444
2454
  systemPromptSuffix: init.systemPromptSuffix,
2455
+ sessionMode,
2445
2456
  });
2446
2457
  const session = built.session;
2447
2458
  // Prefer the SessionManager id (from the JSONL header). Some SDK
@@ -2464,7 +2475,6 @@ export class ConsolePiRuntime {
2464
2475
  message: `reopened Pi sessionId mismatch: expected ${sessionId}, got ${reportedId}`,
2465
2476
  };
2466
2477
  }
2467
- session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
2468
2478
  this.sessions.set(sessionId, session);
2469
2479
  this.sessionManagers.set(sessionId, sessionManager);
2470
2480
  this.modelRuntimes.set(sessionId, built.modelRuntime);
@@ -2482,6 +2492,7 @@ export class ConsolePiRuntime {
2482
2492
  createdAt: new Date().toISOString(),
2483
2493
  model: init.model,
2484
2494
  activeTools: session.getActiveToolNames(),
2495
+ sessionMode,
2485
2496
  },
2486
2497
  };
2487
2498
  }
@@ -2513,6 +2524,7 @@ export class ConsolePiRuntime {
2513
2524
  createdAt: new Date().toISOString(),
2514
2525
  model,
2515
2526
  activeTools: session.getActiveToolNames(),
2527
+ sessionMode: this.sessionModeOf(sessionId),
2516
2528
  };
2517
2529
  }
2518
2530
  /**
@@ -2521,12 +2533,14 @@ export class ConsolePiRuntime {
2521
2533
  * before AgentSession is ready. Tool definitions reuse runtime-level caches.
2522
2534
  */
2523
2535
  async buildSessionWithServices(init) {
2536
+ const sessionMode = this.rememberSessionMode(init.sessionId, init.sessionMode ?? this.sessionModeOf(init.sessionId));
2524
2537
  const agentDir = await safeGetAgentDir(this.bindings);
2525
2538
  const modelConfigFile = await captureModelConfigFile(agentDir);
2526
- const appendSystemPrompt = [
2527
- OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
2528
- ...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
2529
- ];
2539
+ const appendSystemPrompt = composeChatSystemPromptParts({
2540
+ sessionMode,
2541
+ workflowPrompt: OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
2542
+ systemPromptSuffix: init.systemPromptSuffix,
2543
+ });
2530
2544
  await this.refreshSkillPrefsCache().catch(() => undefined);
2531
2545
  const { services } = await this.bindings.createServices({
2532
2546
  cwd: this.options.cwd,
@@ -2551,7 +2565,7 @@ export class ConsolePiRuntime {
2551
2565
  ...(resolvedModel ? { model: resolvedModel } : {}),
2552
2566
  });
2553
2567
  await bindSessionExtensions(session);
2554
- pinOperatorChatTools(session);
2568
+ pinOperatorChatTools(session, sessionMode);
2555
2569
  this.applySessionCompactionOverride(init.sessionId, services, session);
2556
2570
  return {
2557
2571
  session,
@@ -2620,7 +2634,7 @@ export class ConsolePiRuntime {
2620
2634
  }
2621
2635
  /**
2622
2636
  * Send a prompt and stream events. Gate 3: each tool invocation is
2623
- * re-authorized by authorizeOperatorChatTool before the dispatcher runs it
2637
+ * re-authorized by authorizeChatSessionTool(mode) before the dispatcher runs it
2624
2638
  * (the dispatcher is wired by the HTTP layer, see chat-session.ts).
2625
2639
  *
2626
2640
  * Prompt ownership is acquired synchronously before the first await so two
@@ -2662,7 +2676,8 @@ export class ConsolePiRuntime {
2662
2676
  if (options?.turnId && this.options.todoRegistry) {
2663
2677
  this.options.todoRegistry.startTurn(sessionId, options.turnId);
2664
2678
  }
2665
- if (this.options.goalRegistry) {
2679
+ if (this.options.goalRegistry &&
2680
+ shouldInjectGoalContinuation(this.sessionModeOf(sessionId))) {
2666
2681
  this.options.goalRegistry.startTurn(sessionId, text);
2667
2682
  }
2668
2683
  // AC-FIX-002: if Stop already fired before readiness, never start model work.
@@ -2683,10 +2698,11 @@ export class ConsolePiRuntime {
2683
2698
  // existing closed interview allow/deny registry. The next ordinary turn
2684
2699
  // explicitly restores the normal Operator Chat set (baseline + extension
2685
2700
  // tools, ADR 0012).
2686
- const requestedTools = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
2687
- const activeTools = options?.mode === "requirement-interview"
2701
+ const sessionMode = this.sessionModeOf(sessionId);
2702
+ const requestedTools = chatActiveToolBaseline(sessionMode);
2703
+ const activeTools = options?.mode === "requirement-interview" && sessionMode === "workflow"
2688
2704
  ? filterActiveInterviewTools(requestedTools).allowed
2689
- : computeOperatorChatActiveToolNames(session);
2705
+ : computeChatActiveToolNames(session, sessionMode);
2690
2706
  session.setActiveToolsByName(activeTools);
2691
2707
  // Final pre-prompt fence: abort after tool pin must still skip session.prompt.
2692
2708
  if (options?.signal?.aborted) {
@@ -3024,6 +3040,7 @@ export class ConsolePiRuntime {
3024
3040
  this.modelRuntimes.delete(sessionId);
3025
3041
  this.modelConfigFiles.delete(sessionId);
3026
3042
  this.sessionModels.delete(sessionId);
3043
+ this.sessionModes.delete(sessionId);
3027
3044
  this.serviceScopes.delete(sessionId);
3028
3045
  this.revisions.delete(sessionId);
3029
3046
  this.thinkingSelections.delete(sessionId);
@@ -3339,7 +3356,8 @@ export function createDefaultPiSdkBindings() {
3339
3356
  };
3340
3357
  }
3341
3358
  /**
3342
- * Re-export the tool gate for the dispatcher layer so the per-tool-call gate
3343
- * (Gate 3) lives in exactly one place.
3359
+ * Re-export the tool gates for the dispatcher layer so the per-tool-call gate
3360
+ * (Gate 3) lives in exactly one place. authorizeChatSessionTool is the
3361
+ * mode-aware gate; the legacy name stays for workflow-only callers.
3344
3362
  */
3345
- export { authorizeOperatorChatTool, assertNoWriteToolInList, OPERATOR_CHAT_ALLOWED_TOOLS, };
3363
+ export { authorizeChatSessionTool, authorizeOperatorChatTool, assertNoWriteToolInList, OPERATOR_CHAT_ALLOWED_TOOLS, };