@tea-agent/loop-agent 0.33.6 → 0.33.7-beta.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +39 -23
  2. package/dist/application/task-lifecycle/advance.js +4 -254
  3. package/dist/application/task-lifecycle/gates.js +0 -50
  4. package/dist/application/task-lifecycle/observe.js +2 -11
  5. package/dist/commands/init-upgrade.js +1 -32
  6. package/dist/commands/init.js +3 -94
  7. package/dist/executors/shell-executor.js +91 -4
  8. package/dist/executors/shell-write-guard.js +8 -26
  9. package/dist/shared/operator/capabilities.js +42 -72
  10. package/dist/task/source-prepare/index.js +0 -2
  11. package/dist/task/source-prepare/parse-intent.js +10 -58
  12. package/dist/task/source-prepare/prepare.js +16 -180
  13. package/dist/task/source-prepare/reference-integrity.js +2 -18
  14. package/dist/worker/console/app-data.js +0 -2
  15. package/dist/worker/console/chat/chat-event-store.js +25 -190
  16. package/dist/worker/console/chat/instruction-skills.js +217 -0
  17. package/dist/worker/console/chat/pi-console-config.js +32 -250
  18. package/dist/worker/console/chat/pi-runtime.js +71 -625
  19. package/dist/worker/console/chat/resource-loader.js +4 -5
  20. package/dist/worker/console/chat/routes.js +146 -324
  21. package/dist/worker/console/chat/runtime-context.js +12 -48
  22. package/dist/worker/console/chat/runtime-selection.js +0 -59
  23. package/dist/worker/console/chat/shortcuts.js +0 -1
  24. package/dist/worker/console/chat/tool-adapter.js +3 -9
  25. package/dist/worker/console/chat/tools.js +1 -5
  26. package/dist/worker/console/operator-actions.js +68 -559
  27. package/dist/worker/console/server.js +15 -8
  28. package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
  29. package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
  30. package/dist/worker/console/static/index.html +2 -2
  31. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
  32. package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
  33. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
  34. package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
  35. package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
  36. package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
  37. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
  38. package/dist/worker/delivery/final-verification.js +5 -13
  39. package/dist/worker/delivery/package.js +19 -31
  40. package/dist/worker/delivery/verification-bundle.js +4 -6
  41. package/dist/worker/observe/static/operator-chrome.css +2 -5
  42. package/dist/worker/observe/static/operator-chrome.js +1 -6
  43. package/dist/worker/observe/static/styles.css +9 -39
  44. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
  45. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  46. package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
  47. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  48. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  49. package/dist/workflows/dag/backend-test-scenario-param.js +552 -124
  50. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  51. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  52. package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
  53. package/dist/workflows/dag/init-hybrid.js +49 -37
  54. package/dist/workflows/dag/types.js +7 -0
  55. package/dist/workflows/dag/workspace-checkpoint.js +27 -8
  56. package/docs/templates/backend-test-dag.json +33 -30
  57. package/harness.json +1 -1
  58. package/package.json +1 -1
  59. package/skills/loop-agent/references/command-reference.md +1 -3
  60. package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
  61. package/skills/loop-agent/references/task-workflow.md +0 -4
  62. package/dist/shared/resilient-git.js +0 -133
  63. package/dist/task/source-prepare/artifact-meta.js +0 -137
  64. package/dist/task/source-prepare/semantic-intake.js +0 -404
  65. package/dist/worker/console/dag-execution-receipt.js +0 -380
  66. package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
  67. package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
  68. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
  69. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +0 -196
@@ -9,64 +9,60 @@
9
9
  * - Chat sessions are persisted to a DEDICATED sessionDir (not the user's
10
10
  * default ~/.pi/agent/sessions). Naming prefix `operator-chat-` makes them
11
11
  * auditable and cleanable (plan Q4 / Q8).
12
- * - The ResourceLoader uses the SDK's standard OPEN surface (ADR 0012):
13
- * user/project AGENTS.md context files, user (~/.pi/agent/skills/) and
14
- * project (.agents/skills/ init projection) skills, and user/project
15
- * extensions all load via the SDK DefaultResourceLoader; credential/model
16
- * plane is shared via the same agentDir auth.json/models.json.
12
+ * - The ResourceLoader is configured to NOT auto-load user extensions/skills
13
+ * (noContextFiles / closed surface); credential/model plane is shared via
14
+ * the same agentDir auth.json/models.json.
17
15
  * - Active tools are pinned to the operator-chat surface at session create
18
16
  * AND re-pinned before each prompt (three-gate, design §7.5 / ADR 0011):
19
17
  * the FULL operator action set PLUS full Pi builtins
20
- * (read/write/edit/bash/grep/find/ls) and optional safe-*, PLUS extension
21
- * tools registered by user/project extensions (ADR 0012). Non-Pi write
18
+ * (read/write/edit/bash/grep/find/ls) and optional safe-*. Non-Pi write
22
19
  * channels (apply_patch/full-tools/shell/coding-chat) stay excluded.
23
20
  *
24
21
  * The actual SDK calls are injected via `PiSdkBindings` so this module is
25
22
  * unit-testable without a live Pi install. Production bindings come from
26
23
  * `createDefaultPiSdkBindings()`.
27
24
  */
28
- import { createHash, randomBytes } from "node:crypto";
25
+ import { randomBytes } from "node:crypto";
29
26
  import { access } from "node:fs/promises";
30
27
  import path from "node:path";
31
28
  import { projectCompactSnapshot, } from "./chat-event-store.js";
32
29
  import { extractUsageSample } from "./usage.js";
33
30
  import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
34
31
  import { createOperatorChatResourceLoader, } from "./resource-loader.js";
32
+ import { loadOperatorChatInstructionSkills, composeInstructionSkillsPrompt, OPERATOR_CHAT_ALLOWED_INSTRUCTION_SKILLS, } from "./instruction-skills.js";
35
33
  import { buildModelCallableToolSchemas } from "./tool-adapter.js";
36
34
  import { resolveDefaultChatModel, } from "./model-resolver.js";
37
35
  import { filterActiveInterviewTools } from "../interview/tools.js";
38
- import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
39
36
  /**
40
37
  * Base system prompt fragment every General Operator Chat session receives.
41
38
  *
42
- * 2026-08-11 (ADR 0013): this is a SHORT runtime responsibility layer only.
43
- * Project governance (operator-only policy, allowedPaths/forbiddenPaths,
44
- * writer writeSet, taskKind routing, verification matrix, DAG recovery
45
- * commands, closeout) lives in the loaded target-project AGENTS.md. The
46
- * prompt below covers: Operator identity, the real tool surface (operator_*
47
- * first + Pi tools, obey AGENTS.md), denied non-Pi runtimes, safe-* probes,
48
- * init/init-upgrade fallback for uninitialized repos, DAG supervision with
49
- * 15/30/60s adaptive polling (operationGet/status/dagReport/dagDoctor), R2
50
- * recovery (fresh eligible dagRerunPlan → dagRerun; ineligible → same-task
51
- * rerun/advance, resume, Worker retry) and G2 bounded writeSet auto-approval.
39
+ * Establishes the operator-vs-implementer role boundary (ADR 0005 D2) and the
40
+ * current tool surface contract (2026-07-25 widening): the Chat exposes the
41
+ * FULL operator action surface (all registry actions, including high-risk
42
+ * mutations like contractApply / runDag / dagRerun) PLUS the built-in
43
+ * read/explore tools (bash / read / grep / find / ls). The ONLY thing still
44
+ * forbidden is direct file-WRITING via coding tools (edit / write /
45
+ * apply_patch / full-tools / shell / coding-chat) those are excluded from
46
+ * the SDK registry entirely and can never be activated.
52
47
  *
53
- * The old Human Gate tutorial paragraphs (prepare + browser Human Gate,
54
- * "confirmDagConfirmation is a human-only action", the long "Recovery default
55
- * order" table) are REMOVED — bounded DAGs start and recover autonomously
56
- * (AC-01: ≤ 1900 chars and ≤ 80% of the 2498-char baseline).
48
+ * Instruction skills are appended AFTER this as read-only context (plan D1).
57
49
  */
58
50
  export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
59
51
  "You are the General Operator Chat for loop-agent / agent-worker.",
60
- "Operate through operator_* tools first. Pi read/write/edit/bash/grep/find/ls are available, but obey the loaded repository AGENTS.md. apply_patch, full-tools, shell, and coding-chat are denied alternate runtimes. Prefer safe-read/safe-grep for sensitive probes.",
61
- "If a repository has no loop-agent harness and the user requests initialization, run `loop-agent init instructions --repo-root .` then `loop-agent init --repo-root . --profile full --merge`; finish the generated setup, init doctor, inspect, docs audit, and quick verification. For updates use `loop-agent init upgrade --repo-root . --json` until stable; use init check-update only for an explicitly read-only request.",
62
- "After a DAG is started or accepted, do not end on accepted/queued/running or an operationId. Supervise it to a terminal outcome. Poll operationGet, task status, dagReport, and dagDoctor after about 15 seconds on start/change, every 30 seconds during progress, and every 60 seconds after 3 minutes unchanged. Report only meaningful node/rank changes, review/verify/closeout, recovery, liveness concerns, and terminal outcomes.",
63
- "On failure read primaryFailure, primaryRecovery, and doctor evidence. If meaningful progress exists, wait. Otherwise use a fresh eligible dagRerunPlan and rerun its safe node; when ineligible follow AGENTS.md/runtime recovery for same-task rerun/advance, resume, or Worker retry. Auto-approve only a bounded writeSet inside allowedPaths, outside forbiddenPaths, without broad/destructive risk, and with structured verification.",
64
- "Continue until success, user stop, or no safe eligible recovery remains because limits, bindings, auth/quota recovery, or required external authorization are exhausted. Never create a new task for a transient failure or replace repair-pi with direct edits.",
52
+ "You are an OPERATOR first: orchestrate and inspect via operator_* tools, and you also have full Pi repository tools (read, write, edit, bash, grep, find, ls) plus optional safe-read/safe-grep/git-status/git-diff.",
53
+ "Capability honesty (ADR 0011): read/write/edit/bash ARE available. Prefer governed loop-agent / Agent DAG paths (implement-pi / repair-pi) and Human Gate for large refactors, public contracts, credentials, or production-risk changes treat direct write/edit/bash as soft-disciplined, not as a second DAG kernel.",
54
+ "safe-read / safe-grep enforce a repo-relative path boundary, a sensitive-file denylist (.env* / *.key / *.pem / .git/** / auth.json / sessions/**) and secret scrubbing. Prefer them for sensitive probes; avoid dumping secrets via raw read/bash.",
55
+ "You do NOT have apply_patch / full-tools / shell / coding-chat as alternate coding runtimes those non-Pi channels stay denied.",
56
+ "High-risk mutations still use prepare + browser Human Gate. Prepare contract via interview then apply via contractApply, prepare DAG runs via prepareDagConfirmation, prepare other mutations via prepareMutationGate, then confirm in the browser Human Gate. When a high-risk action (contractApply / runDag / dagRerun / etc.) fails on missing prepared state, diagnose with status/doctor/dagReport/inspect/contractShow/read/safe-read/safe-grep.",
57
+ "You cannot self-confirm a DAG run: confirmDagConfirmation is a human-only action (executed by the browser with a server-signed confirmation token). You may only prepare it via prepareDagConfirmation; the user must confirm in the UI.",
58
+ "Prefer read-only diagnosis (status, doctor, dagReport, inspect, contractShow, read, safe-read, safe-grep, git-status) before mutating.",
59
+ "Recovery default order (save tokens/time; do NOT open a new task-id for provider flake): (1) dagReport/doctor/status — read primaryFailure.nodeId + primaryRecovery; (2) for completed failed|partial_failed standalone runs prefer dagRerunPlan then prepareMutationGate/dagRerun from the failed or safe upstream node (plan → planHash → human gate); (3) paused → approve path / resume, never treat resume as failure retry; (4) only when plan is ineligible (writer/decision/fingerprint) or contract/source truly changed: standaloneTaskRerun or same-task task advance — never invent a fresh task-id without cause; (5) Worker-owned failures use workerTaskRetry, not dagRerun. Never use direct write/edit as a substitute for repair-pi.",
65
60
  ].join("\n");
66
61
  /** Compose the inspectable system prompt actually injected into Operator Chat. */
67
62
  export function composeOperatorChatSystemPrompt(input) {
68
63
  return [
69
64
  OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
65
+ ...(input.skillFragment?.trim() ? [input.skillFragment.trim()] : []),
70
66
  ...(input.systemPromptSuffix?.trim()
71
67
  ? [input.systemPromptSuffix.trim()]
72
68
  : []),
@@ -106,49 +102,6 @@ export function OPERATOR_CHAT_ACTIVE_TOOL_NAMES() {
106
102
  ...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
107
103
  ];
108
104
  }
109
- /**
110
- * Non-Pi write / coding channels that must never be activated in Operator
111
- * Chat even when a user/project extension registers them (ADR 0011 / 0012).
112
- * Mirrors the NON_PI_WRITE_CHANNELS guard in tools.ts for the Gate 2 pin.
113
- */
114
- const OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
115
- "apply_patch",
116
- "apply-patch",
117
- "full-tools",
118
- "full_tools",
119
- "coding-chat",
120
- "coding_chat",
121
- "shell",
122
- ].map((t) => t.toLowerCase()));
123
- /**
124
- * Gate 2 active-tool set for a live session (ADR 0012): the operator-chat
125
- * baseline (operator actions + Pi builtins + safe explore) PLUS extension
126
- * tools registered by user/project extensions. SDK setActiveToolsByName is
127
- * REPLACEMENT-style, so extension tools would be disabled by the fixed
128
- * baseline; this helper re-adds every registry tool that is not part of the
129
- * baseline and not on the deny surface (apply_patch/full-tools/shell/
130
- * coding-chat). Unknown/denied names are never activated; Gate 3 still
131
- * re-authorizes every tool call per invocation.
132
- */
133
- export function computeOperatorChatActiveToolNames(session) {
134
- const baseline = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
135
- if (!session?.getAllTools)
136
- return baseline;
137
- const baselineLower = new Set(baseline.map((t) => t.toLowerCase()));
138
- const extras = [];
139
- for (const tool of session.getAllTools()) {
140
- const name = tool?.name;
141
- if (!name || typeof name !== "string")
142
- continue;
143
- const lower = name.toLowerCase();
144
- if (baselineLower.has(lower))
145
- continue;
146
- if (OPERATOR_CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
147
- continue;
148
- extras.push(name);
149
- }
150
- return extras.length > 0 ? [...baseline, ...extras] : baseline;
151
- }
152
105
  /**
153
106
  * Build Pi custom ToolDefinition objects for every whitelisted operator action.
154
107
  * Each tool's execute() routes into the operator action dispatcher (Gate 3).
@@ -429,138 +382,6 @@ export const THINKING_LEVELS = [
429
382
  "xhigh",
430
383
  "max",
431
384
  ];
432
- /**
433
- * Structured critical-failure error for snapshot projection (RF-02). Model
434
- * enumeration or system prompt projection failures throw this so consuming
435
- * routes can return 503 PI_RUNTIME_UNAVAILABLE instead of a fake-empty 200.
436
- * Partial ResourceLoader / package-inventory failures are NOT critical — they
437
- * keep the 200 snapshot and surface as structured diagnostics instead.
438
- */
439
- export class RuntimeSnapshotUnavailableError extends Error {
440
- code = "PI_RUNTIME_UNAVAILABLE";
441
- constructor(message) {
442
- super(message);
443
- this.name = "RuntimeSnapshotUnavailableError";
444
- }
445
- }
446
- /**
447
- * Browser-safe projection of a live runtime snapshot (design §2.2 / RF-03).
448
- *
449
- * Every browser-visible string runs the shared secret-scrub chain
450
- * (scrubSecrets + ghp_/Bearer redaction via redactRuntimeText) and a fixed
451
- * per-field length limit: 200 (names/spec/source/origin), 400 (paths/errors/
452
- * diagnostic messages), 600 (skill descriptions), 80 (package versions);
453
- * truncation uses the fixed `…[truncated N chars …]` marker. The server-only
454
- * `raw` prompt and the RAW-based `sha256`/`characters` pass through untouched,
455
- * and real empty lists stay empty.
456
- *
457
- * Pure + deterministic and exported so the route harness reuses the exact
458
- * production implementation (same code path as getRuntimeSnapshot).
459
- */
460
- export function projectRuntimeSnapshot(input) {
461
- const field = (value, max) => value === undefined ? undefined : redactRuntimeText(value, max);
462
- const projectSkill = (skill) => ({
463
- name: field(skill.name, 200) ?? "",
464
- description: field(skill.description, 600) ?? "",
465
- path: field(skill.path, 400) ?? "",
466
- ...(skill.baseDir ? { baseDir: field(skill.baseDir, 400) } : {}),
467
- scope: skill.scope,
468
- source: field(skill.source, 200) ?? "",
469
- enabled: skill.enabled,
470
- diagnostics: skill.diagnostics.map((diag) => field(diag, 400) ?? ""),
471
- });
472
- const projectExtension = (ext) => ({
473
- name: field(ext.name, 200) ?? "",
474
- path: field(ext.path, 400) ?? "",
475
- ...(ext.resolvedPath
476
- ? { resolvedPath: field(ext.resolvedPath, 400) }
477
- : {}),
478
- source: field(ext.source, 200) ?? "",
479
- scope: ext.scope,
480
- ...(ext.origin ? { origin: field(ext.origin, 200) } : {}),
481
- enabled: ext.enabled,
482
- ...(ext.error ? { error: field(ext.error, 400) } : {}),
483
- });
484
- const projectPackage = (pkg) => ({
485
- spec: field(pkg.spec, 200) ?? "",
486
- scope: pkg.scope,
487
- enabled: pkg.enabled,
488
- ...(pkg.version ? { version: field(pkg.version, 80) } : {}),
489
- ...(pkg.configuredVersion
490
- ? { configuredVersion: field(pkg.configuredVersion, 80) }
491
- : {}),
492
- ...(pkg.packageName ? { packageName: field(pkg.packageName, 200) } : {}),
493
- ...(pkg.path ? { path: field(pkg.path, 400) } : {}),
494
- ...(pkg.installedPath ? { installedPath: field(pkg.installedPath, 400) } : {}),
495
- ...(pkg.cwd ? { cwd: field(pkg.cwd, 400) } : {}),
496
- resolved: pkg.resolved.map((resource) => ({
497
- type: resource.type,
498
- name: field(resource.name, 200) ?? "",
499
- path: field(resource.path, 400) ?? "",
500
- source: field(resource.source, 200) ?? "",
501
- scope: resource.scope,
502
- })),
503
- diagnostics: pkg.diagnostics.map((diag) => field(diag, 400) ?? ""),
504
- });
505
- const projectDiagnostic = (diag) => ({
506
- type: diag.type,
507
- ...(diag.code ? { code: diag.code } : {}),
508
- message: field(diag.message, 400) ?? "",
509
- ...(diag.path ? { path: field(diag.path, 400) } : {}),
510
- ...(diag.retryable !== undefined
511
- ? { retryable: diag.retryable }
512
- : {}),
513
- });
514
- return {
515
- sessionId: input.sessionId,
516
- revision: input.revision,
517
- loadedAt: input.loadedAt,
518
- selection: {
519
- ...(input.selection.model
520
- ? {
521
- model: {
522
- provider: input.selection.model.provider,
523
- id: input.selection.model.id,
524
- ...(input.selection.model.name
525
- ? { name: field(input.selection.model.name, 200) }
526
- : {}),
527
- },
528
- }
529
- : {}),
530
- thinkingSelection: input.selection.thinkingSelection,
531
- ...(input.selection.effectiveThinkingLevel
532
- ? { effectiveThinkingLevel: input.selection.effectiveThinkingLevel }
533
- : {}),
534
- },
535
- models: input.models.map((model) => ({
536
- provider: model.provider,
537
- id: model.id,
538
- ...(model.name ? { name: field(model.name, 200) } : {}),
539
- ...(model.hasCredentials !== undefined
540
- ? { hasCredentials: model.hasCredentials }
541
- : {}),
542
- })),
543
- thinkingLevels: input.thinkingLevels.map((option) => ({ ...option })),
544
- systemPrompt: {
545
- ...(input.systemPrompt.raw !== undefined
546
- ? { raw: input.systemPrompt.raw }
547
- : {}),
548
- text: redactRuntimeText(input.systemPrompt.text, RUNTIME_CONTEXT_TEXT_MAX),
549
- sha256: input.systemPrompt.sha256,
550
- characters: input.systemPrompt.characters,
551
- },
552
- activeTools: [...input.activeTools],
553
- contextFiles: input.contextFiles.map((file) => ({
554
- path: field(file.path, 400) ?? "",
555
- characters: file.characters,
556
- })),
557
- skills: input.skills.map(projectSkill),
558
- extensions: input.extensions.map(projectExtension),
559
- packages: input.packages.map(projectPackage),
560
- diagnostics: input.diagnostics.map(projectDiagnostic),
561
- hasBash: input.hasBash,
562
- };
563
- }
564
385
  /**
565
386
  * Extract the latest assistant text fragment from an SDK message object.
566
387
  * Tolerant of multiple message shapes the SDK emits.
@@ -612,16 +433,9 @@ export class ConsolePiRuntime {
612
433
  sessions = new Map();
613
434
  modelRuntimes = new Map();
614
435
  sessionManagers = new Map();
615
- /** Per-session SDK service scope (resourceLoader / settingsManager) used by the live runtime snapshot. */
616
- serviceScopes = new Map();
617
- /** Snapshot revision: bumped on create / reopen / model / thinking / reload. */
618
- revisions = new Map();
619
- /** RF-01: per-session reload in-flight lock (concurrent second call → PI_SESSION_BUSY). */
620
- reloadInFlight = new Set();
621
- /** Composer thinking selection ("auto" = no explicit override). */
622
- thinkingSelections = new Map();
623
436
  mainlineLeaves = new Map();
624
437
  bindings;
438
+ disabledInstructionSkills = new Set();
625
439
  constructor(options) {
626
440
  this.options = options;
627
441
  this.loader = createOperatorChatResourceLoader();
@@ -647,6 +461,9 @@ export class ConsolePiRuntime {
647
461
  })),
648
462
  };
649
463
  }
464
+ setDisabledInstructionSkills(names) {
465
+ this.disabledInstructionSkills = new Set(names);
466
+ }
650
467
  /** Operator action context used by custom-tool dispatchers (Gate 3). */
651
468
  get actionContext() {
652
469
  return this.options.actionContext;
@@ -680,6 +497,22 @@ export class ConsolePiRuntime {
680
497
  modelId: descriptor.modelId,
681
498
  };
682
499
  }
500
+ /**
501
+ * Load the operator-context instruction skills (plan D1) and compose the
502
+ * read-only methodology fragment for the Chat system prompt. Returns an
503
+ * empty string when no skills load (e.g. skills dir missing) so the
504
+ * session still boots — skills are context, not a hard dependency.
505
+ */
506
+ async composeSystemPromptSkills() {
507
+ try {
508
+ const result = await loadOperatorChatInstructionSkills(this.options.skillsDir, OPERATOR_CHAT_ALLOWED_INSTRUCTION_SKILLS.filter((name) => !this.disabledInstructionSkills.has(name)));
509
+ return composeInstructionSkillsPrompt(result.loaded);
510
+ }
511
+ catch (error) {
512
+ process.stderr.write(`[console] chat instruction-skills load failed: ${error instanceof Error ? error.message : String(error)}\n`);
513
+ return "";
514
+ }
515
+ }
683
516
  /** Create a new isolated Chat session pinned to the operator-chat tool set. */
684
517
  async createSession(init) {
685
518
  const sessionId = `operator-chat-${randomBytes(12).toString("hex")}`;
@@ -695,18 +528,28 @@ export class ConsolePiRuntime {
695
528
  }
696
529
  // Final fallback only when nothing resolved: let the SDK pick its default
697
530
  // (we do NOT hardcode a provider here).
698
- // Standard open resource surface (ADR 0012): the SDK DefaultResourceLoader
699
- // loads user/project AGENTS.md context files, skills (~/.pi/agent/skills/
700
- // + .agents/skills/ init projection) and user/project extensions. Only
701
- // appendSystemPrompt carries the operator-chat system prompt.
531
+ // Load operator-context instruction skills (plan D1) and compose the
532
+ // read-only methodology fragment injected into the Chat system prompt.
533
+ // Skills are NEVER registered as executable tools (D1 hard rule).
534
+ const skillFragment = await this.composeSystemPromptSkills();
702
535
  const appendSystemPrompt = [
703
536
  OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
537
+ ...(skillFragment ? [skillFragment] : []),
704
538
  ...(init?.systemPromptSuffix ? [init.systemPromptSuffix] : []),
705
539
  ];
706
540
  const { services } = await this.bindings.createServices({
707
541
  cwd: this.options.cwd,
708
542
  agentDir,
709
543
  resourceLoaderOptions: {
544
+ // Closed surface: do not load user/project extensions, skills,
545
+ // AGENTS.md, context files. The Chat tool surface is fixed by
546
+ // the whitelist, not by filesystem discovery.
547
+ noContextFiles: true,
548
+ noSkills: true,
549
+ noExtensions: true,
550
+ // Inject the audited instruction skills as READ-ONLY context
551
+ // (plan D1). appendSystemPrompt is the SDK's documented entry
552
+ // point for adding system-prompt text without enabling tools.
710
553
  appendSystemPrompt,
711
554
  },
712
555
  });
@@ -790,19 +633,12 @@ export class ConsolePiRuntime {
790
633
  ...(resolvedModel ? { model: resolvedModel } : {}),
791
634
  });
792
635
  // Gate 2: re-pin active tools (defensive — some SDK paths may seed
793
- // defaults). Active set = operator baseline + built-in explore tools +
794
- // extension tools registered by user/project extensions (ADR 0012).
795
- // Non-Pi write channels are excluded above and cannot appear here.
796
- session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
636
+ // defaults). Active set = all operator actions + built-in explore tools.
637
+ // File-writing tools are excluded above and thus cannot appear here.
638
+ session.setActiveToolsByName(OPERATOR_CHAT_ACTIVE_TOOL_NAMES());
797
639
  this.sessions.set(sessionId, session);
798
640
  this.sessionManagers.set(sessionId, sessionManager);
799
641
  this.modelRuntimes.set(sessionId, modelRuntime);
800
- this.serviceScopes.set(sessionId, {
801
- resourceLoader: services.resourceLoader,
802
- settingsManager: services.settingsManager,
803
- });
804
- this.revisions.set(sessionId, 1);
805
- this.thinkingSelections.set(sessionId, "auto");
806
642
  return {
807
643
  sessionId,
808
644
  sessionFile: session.sessionFile,
@@ -825,13 +661,10 @@ export class ConsolePiRuntime {
825
661
  sessionManager,
826
662
  model: init.model,
827
663
  });
828
- built.session.setActiveToolsByName(computeOperatorChatActiveToolNames(built.session));
664
+ built.session.setActiveToolsByName(OPERATOR_CHAT_ACTIVE_TOOL_NAMES());
829
665
  this.sessions.set(sessionId, built.session);
830
666
  this.sessionManagers.set(sessionId, sessionManager);
831
667
  this.modelRuntimes.set(sessionId, built.modelRuntime);
832
- this.serviceScopes.set(sessionId, built.services);
833
- this.revisions.set(sessionId, 1);
834
- this.thinkingSelections.set(sessionId, "auto");
835
668
  return {
836
669
  sessionId,
837
670
  sessionFile: built.session.sessionFile,
@@ -896,31 +729,18 @@ export class ConsolePiRuntime {
896
729
  if (!resolved)
897
730
  throw new Error("MODEL_NOT_AVAILABLE");
898
731
  await session.setModel(resolved);
899
- this.bumpRevision(sessionId);
900
732
  return {
901
733
  provider: session.model?.provider ?? model.provider,
902
734
  modelId: session.model?.id ?? session.model?.modelId ?? model.modelId,
903
735
  };
904
736
  }
905
- /**
906
- * Apply a thinking-level selection. `auto` records the selection WITHOUT
907
- * sending any explicit override (the SDK keeps its own default); an explicit
908
- * level calls `session.setThinkingLevel`. Returns the effective value.
909
- */
910
737
  applyThinkingLevel(sessionId, level) {
911
738
  const session = this.sessions.get(sessionId);
912
739
  if (!session)
913
740
  throw new Error(`chat session not active: ${sessionId}`);
914
- if (level === "auto") {
915
- this.thinkingSelections.set(sessionId, "auto");
916
- this.bumpRevision(sessionId);
917
- return "auto";
918
- }
919
741
  if (!THINKING_LEVELS.includes(level) || !session.setThinkingLevel)
920
742
  throw new Error("THINKING_LEVEL_UNSUPPORTED");
921
743
  session.setThinkingLevel(level);
922
- this.thinkingSelections.set(sessionId, level);
923
- this.bumpRevision(sessionId);
924
744
  return session.thinkingLevel ?? level;
925
745
  }
926
746
  getRuntimeSelection(sessionId) {
@@ -937,370 +757,6 @@ export class ConsolePiRuntime {
937
757
  activeTools: session.getActiveToolNames(),
938
758
  };
939
759
  }
940
- bumpRevision(sessionId) {
941
- this.revisions.set(sessionId, (this.revisions.get(sessionId) ?? 0) + 1);
942
- }
943
- /**
944
- * Resolve the RAW system prompt actually injected into the Pi session
945
- * (design §2.2 / AC-03). Fallback chain: agent.state.systemPrompt →
946
- * session.systemPrompt getter → ResourceLoader.getSystemPrompt() → the
947
- * composed operator-chat base prompt. Never returns empty.
948
- */
949
- async getRuntimeSystemPrompt(sessionId) {
950
- const session = this.sessions.get(sessionId);
951
- if (!session)
952
- return composeOperatorChatSystemPrompt({});
953
- const fromState = session.state?.systemPrompt;
954
- const fromGetter = session.systemPrompt;
955
- const loader = this.serviceScopes.get(sessionId)?.resourceLoader;
956
- return (fromState ||
957
- fromGetter ||
958
- loader?.getSystemPrompt?.() ||
959
- composeOperatorChatSystemPrompt({}));
960
- }
961
- /**
962
- * Read the RAW system prompt for the snapshot, converting any projection
963
- * failure into the structured critical error (RF-02): a broken system prompt
964
- * source must fail the whole snapshot with 503 PI_RUNTIME_UNAVAILABLE rather
965
- * than silently fabricate prompt text.
966
- */
967
- async readRawSystemPrompt(sessionId) {
968
- try {
969
- return await this.getRuntimeSystemPrompt(sessionId);
970
- }
971
- catch (error) {
972
- throw new RuntimeSnapshotUnavailableError(`system prompt projection failed: ${error instanceof Error ? error.message : String(error)}`);
973
- }
974
- }
975
- /**
976
- * Official Pi session reload (AC-05 / AC-06). Rejects with a structured
977
- * code while the session is busy (streaming / compacting / bash in flight /
978
- * duplicate reload) — never aborts, never silently queues.
979
- */
980
- async reloadSession(sessionId) {
981
- const session = this.sessions.get(sessionId);
982
- if (!session) {
983
- return {
984
- ok: false,
985
- code: "PI_SESSION_NOT_FOUND",
986
- message: `chat session not active: ${sessionId}`,
987
- };
988
- }
989
- // RF2-03: the per-sessionId in-flight lock is checked FIRST — before the
990
- // streaming/compacting/bash/support checks. JS runs check-then-add in one
991
- // synchronous segment, so two concurrent calls cannot both pass; the lock
992
- // is released in `finally` on BOTH the success and the error path, and
993
- // different sessions never block each other. Priority matters: the first
994
- // reload() may synchronously flip isIdle/isCompacting/isBashRunning/
995
- // hasPendingBashMessages to busy, and a second concurrent call must still
996
- // report the reload-in-flight busy (not the streaming/compacting one).
997
- if (this.reloadInFlight.has(sessionId)) {
998
- return {
999
- ok: false,
1000
- code: "PI_SESSION_BUSY",
1001
- message: "session is busy (reload in flight); reload rejected without abort",
1002
- };
1003
- }
1004
- const busyReasons = [];
1005
- if (session.isStreaming === true || session.isIdle === false)
1006
- busyReasons.push("streaming");
1007
- if (session.isCompacting === true)
1008
- busyReasons.push("compacting");
1009
- if (session.isBashRunning === true)
1010
- busyReasons.push("bash running");
1011
- if (session.hasPendingBashMessages === true)
1012
- busyReasons.push("bash messages pending");
1013
- if (busyReasons.length > 0) {
1014
- return {
1015
- ok: false,
1016
- code: "PI_SESSION_BUSY",
1017
- message: `session is busy (${busyReasons.join(", ")}); reload rejected without abort`,
1018
- };
1019
- }
1020
- if (typeof session.reload !== "function") {
1021
- return {
1022
- ok: false,
1023
- code: "PI_RELOAD_FAILED",
1024
- message: "this Pi runtime does not support session.reload()",
1025
- };
1026
- }
1027
- this.reloadInFlight.add(sessionId);
1028
- try {
1029
- await session.reload();
1030
- }
1031
- catch (error) {
1032
- return {
1033
- ok: false,
1034
- code: "PI_RELOAD_FAILED",
1035
- message: error instanceof Error ? error.message : String(error),
1036
- };
1037
- }
1038
- finally {
1039
- this.reloadInFlight.delete(sessionId);
1040
- }
1041
- this.bumpRevision(sessionId);
1042
- return { ok: true, revision: this.revisions.get(sessionId) ?? 0 };
1043
- }
1044
- /**
1045
- * Project the unified live runtime snapshot (design §2.2). The current Pi
1046
- * session is the single source of truth: real system prompt, real model /
1047
- * thinking selection (auto + effective), the session modelRuntime model
1048
- * list, and the session ResourceLoader inventories. Undefined when the
1049
- * session is not active — callers fail closed instead of fabricating data.
1050
- */
1051
- async getRuntimeSnapshot(sessionId, options) {
1052
- const session = this.sessions.get(sessionId);
1053
- if (!session)
1054
- return undefined;
1055
- const services = this.serviceScopes.get(sessionId);
1056
- const modelRuntime = this.modelRuntimes.get(sessionId);
1057
- const resourceLoader = services?.resourceLoader;
1058
- const settingsManager = services?.settingsManager;
1059
- const thinkingSelection = this.thinkingSelections.get(sessionId) ?? "auto";
1060
- const effectiveThinkingLevel = session.thinkingLevel ??
1061
- settingsManager?.getDefaultThinkingLevel?.() ??
1062
- (thinkingSelection === "auto" ? undefined : thinkingSelection);
1063
- const rawSystemPrompt = await this.readRawSystemPrompt(sessionId);
1064
- // Models come from the session modelRuntime. A model-enumeration failure
1065
- // is a CRITICAL projection failure (RF-02): it throws
1066
- // RuntimeSnapshotUnavailableError so consuming routes return 503
1067
- // PI_RUNTIME_UNAVAILABLE — never a fabricated empty model list.
1068
- let models = [];
1069
- if (modelRuntime) {
1070
- try {
1071
- models = this.bindings
1072
- .listAvailableModels({ modelRuntime })
1073
- .map((entry) => ({
1074
- provider: entry.provider,
1075
- id: entry.id,
1076
- ...(entry.name ? { name: entry.name } : {}),
1077
- hasCredentials: entry.hasCredentials,
1078
- }));
1079
- }
1080
- catch (error) {
1081
- throw new RuntimeSnapshotUnavailableError(`model enumeration failed: ${error instanceof Error ? error.message : String(error)}`);
1082
- }
1083
- }
1084
- // Thinking options: auto is always offered; explicit levels only when the
1085
- // runtime session supports setThinkingLevel (xhigh/max ship with the SDK
1086
- // and therefore surface only when this runtime provides them).
1087
- const supportedLevels = typeof session.setThinkingLevel === "function"
1088
- ? [...THINKING_LEVELS]
1089
- : [];
1090
- const thinkingLevels = [
1091
- { value: "auto" },
1092
- ...supportedLevels.map((value) => ({ value })),
1093
- ];
1094
- // Resource inventories from the session ResourceLoader (ADR 0012 open
1095
- // surface). Missing loader → real empty lists, never fabricated entries.
1096
- // Each subface is isolated (RF-02): a throwing subface keeps the other
1097
- // inventories AND surfaces a structured retryable diagnostic instead of
1098
- // silently dropping the whole resource projection.
1099
- const contextFiles = [];
1100
- const skills = [];
1101
- const extensions = [];
1102
- const diagnostics = [];
1103
- let resolvedResources = [];
1104
- const resourceFailure = (subface, error) => {
1105
- diagnostics.push({
1106
- type: "error",
1107
- code: "RESOURCE_LOAD_FAILED",
1108
- message: `${subface} inventory failed: ${error instanceof Error ? error.message : String(error)}`,
1109
- retryable: true,
1110
- });
1111
- };
1112
- if (resourceLoader) {
1113
- try {
1114
- const agentsFiles = resourceLoader.getAgentsFiles?.();
1115
- for (const file of agentsFiles?.agentsFiles ?? []) {
1116
- if (!file.path)
1117
- continue;
1118
- contextFiles.push({
1119
- path: file.path,
1120
- characters: file.content?.length ?? 0,
1121
- });
1122
- }
1123
- }
1124
- catch (error) {
1125
- resourceFailure("context files", error);
1126
- }
1127
- try {
1128
- const skillsResult = resourceLoader.getSkills?.();
1129
- for (const skill of skillsResult?.skills ?? []) {
1130
- skills.push({
1131
- name: skill.name,
1132
- description: skill.description ?? "",
1133
- path: skill.filePath,
1134
- ...(skill.baseDir ? { baseDir: skill.baseDir } : {}),
1135
- scope: skill.sourceInfo?.scope ?? "unknown",
1136
- source: skill.sourceInfo?.source ?? "",
1137
- enabled: !skill.disableModelInvocation,
1138
- diagnostics: [],
1139
- });
1140
- resolvedResources.push({
1141
- type: "skill",
1142
- name: skill.name,
1143
- path: skill.filePath,
1144
- source: skill.sourceInfo?.source ?? "",
1145
- scope: skill.sourceInfo?.scope ?? "unknown",
1146
- });
1147
- }
1148
- for (const diag of skillsResult?.diagnostics ?? []) {
1149
- if (!diag?.message)
1150
- continue;
1151
- diagnostics.push({
1152
- type: diag.type ?? "warning",
1153
- message: diag.message,
1154
- ...(diag.path ? { path: diag.path } : {}),
1155
- });
1156
- }
1157
- }
1158
- catch (error) {
1159
- resourceFailure("skills", error);
1160
- }
1161
- try {
1162
- const extResult = resourceLoader.getExtensions?.();
1163
- for (const ext of extResult?.extensions ?? []) {
1164
- const path = ext.resolvedPath ?? ext.path ?? "";
1165
- extensions.push({
1166
- name: ext.sourceInfo?.source ?? ext.path ?? "extension",
1167
- path,
1168
- ...(ext.resolvedPath ? { resolvedPath: ext.resolvedPath } : {}),
1169
- source: ext.sourceInfo?.source ?? "",
1170
- scope: ext.sourceInfo?.scope ?? "unknown",
1171
- ...(ext.sourceInfo?.origin ? { origin: ext.sourceInfo.origin } : {}),
1172
- enabled: true,
1173
- });
1174
- resolvedResources.push({
1175
- type: "extension",
1176
- name: ext.sourceInfo?.source ?? ext.path ?? "extension",
1177
- path,
1178
- source: ext.sourceInfo?.source ?? "",
1179
- scope: ext.sourceInfo?.scope ?? "unknown",
1180
- });
1181
- }
1182
- for (const err of extResult?.errors ?? []) {
1183
- if (!err?.error)
1184
- continue;
1185
- diagnostics.push({
1186
- type: "error",
1187
- message: err.error,
1188
- ...(err.path ? { path: err.path } : {}),
1189
- });
1190
- }
1191
- }
1192
- catch (error) {
1193
- resourceFailure("extensions", error);
1194
- }
1195
- try {
1196
- const promptsResult = resourceLoader.getPrompts?.();
1197
- for (const prompt of promptsResult?.prompts ?? []) {
1198
- resolvedResources.push({
1199
- type: "prompt",
1200
- name: prompt.name,
1201
- path: prompt.filePath,
1202
- source: prompt.sourceInfo?.source ?? "",
1203
- scope: prompt.sourceInfo?.scope ?? "unknown",
1204
- });
1205
- }
1206
- for (const diag of promptsResult?.diagnostics ?? []) {
1207
- if (!diag?.message)
1208
- continue;
1209
- diagnostics.push({
1210
- type: diag.type ?? "warning",
1211
- message: diag.message,
1212
- ...(diag.path ? { path: diag.path } : {}),
1213
- });
1214
- }
1215
- }
1216
- catch (error) {
1217
- resourceFailure("prompts", error);
1218
- }
1219
- try {
1220
- const themesResult = resourceLoader.getThemes?.();
1221
- for (const theme of themesResult?.themes ?? []) {
1222
- if (!theme.path)
1223
- continue;
1224
- resolvedResources.push({
1225
- type: "theme",
1226
- name: theme.name ?? theme.path,
1227
- path: theme.path,
1228
- source: "",
1229
- scope: "unknown",
1230
- });
1231
- }
1232
- for (const diag of themesResult?.diagnostics ?? []) {
1233
- if (!diag?.message)
1234
- continue;
1235
- diagnostics.push({
1236
- type: diag.type ?? "warning",
1237
- message: diag.message,
1238
- ...(diag.path ? { path: diag.path } : {}),
1239
- });
1240
- }
1241
- }
1242
- catch (error) {
1243
- resourceFailure("themes", error);
1244
- }
1245
- }
1246
- // Packages: Pi settings specs + resolved resource metadata. Fields the SDK
1247
- // does not expose stay absent (real missing, never fabricated). A
1248
- // readPackageInventory failure is a PARTIAL failure (RF-02): the snapshot
1249
- // stays 200 with a structured retryable diagnostic — never a silent
1250
- // fake-empty package list.
1251
- const { readPackageInventory } = await import("./pi-console-config.js");
1252
- let packages = [];
1253
- try {
1254
- packages = (await readPackageInventory(await safeGetAgentDir(this.bindings), this.options.cwd, resolvedResources));
1255
- }
1256
- catch (error) {
1257
- packages = [];
1258
- diagnostics.push({
1259
- type: "error",
1260
- code: "PACKAGE_INVENTORY_FAILED",
1261
- message: error instanceof Error ? error.message : String(error),
1262
- retryable: true,
1263
- });
1264
- }
1265
- return projectRuntimeSnapshot({
1266
- sessionId,
1267
- revision: this.revisions.get(sessionId) ?? 0,
1268
- loadedAt: new Date().toISOString(),
1269
- selection: {
1270
- ...(session.model?.provider &&
1271
- (session.model?.id ?? session.model?.modelId)
1272
- ? {
1273
- model: {
1274
- provider: session.model.provider,
1275
- id: session.model.id ?? session.model.modelId,
1276
- ...(session.model.name ? { name: session.model.name } : {}),
1277
- },
1278
- }
1279
- : {}),
1280
- thinkingSelection,
1281
- ...(effectiveThinkingLevel
1282
- ? { effectiveThinkingLevel: effectiveThinkingLevel }
1283
- : {}),
1284
- },
1285
- models,
1286
- thinkingLevels,
1287
- systemPrompt: {
1288
- ...(options?.includeRaw ? { raw: rawSystemPrompt } : {}),
1289
- // The RAW prompt flows into the projection: text is scrubbed +
1290
- // length-limited while sha256/characters stay RAW-based (design §2.2).
1291
- text: rawSystemPrompt,
1292
- sha256: createHash("sha256").update(rawSystemPrompt).digest("hex"),
1293
- characters: rawSystemPrompt.length,
1294
- },
1295
- activeTools: session.getActiveToolNames(),
1296
- contextFiles,
1297
- skills,
1298
- extensions,
1299
- packages,
1300
- diagnostics,
1301
- hasBash: true,
1302
- });
1303
- }
1304
760
  /**
1305
761
  * Re-open a previously persisted Chat session after Console restart or
1306
762
  * browser refresh (roadmap M1-S04 / S05).
@@ -1382,13 +838,10 @@ export class ConsolePiRuntime {
1382
838
  message: `reopened Pi sessionId mismatch: expected ${sessionId}, got ${reportedId}`,
1383
839
  };
1384
840
  }
1385
- session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
841
+ session.setActiveToolsByName(OPERATOR_CHAT_ACTIVE_TOOL_NAMES());
1386
842
  this.sessions.set(sessionId, session);
1387
843
  this.sessionManagers.set(sessionId, sessionManager);
1388
844
  this.modelRuntimes.set(sessionId, built.modelRuntime);
1389
- this.serviceScopes.set(sessionId, built.services);
1390
- this.revisions.set(sessionId, 1);
1391
- this.thinkingSelections.set(sessionId, "auto");
1392
845
  return {
1393
846
  ok: true,
1394
847
  handle: {
@@ -1427,14 +880,19 @@ export class ConsolePiRuntime {
1427
880
  */
1428
881
  async buildSessionWithServices(init) {
1429
882
  const agentDir = await safeGetAgentDir(this.bindings);
883
+ const skillFragment = await this.composeSystemPromptSkills();
1430
884
  const appendSystemPrompt = [
1431
885
  OPERATOR_CHAT_SYSTEM_PROMPT_BASE,
886
+ ...(skillFragment ? [skillFragment] : []),
1432
887
  ...(init.systemPromptSuffix ? [init.systemPromptSuffix] : []),
1433
888
  ];
1434
889
  const { services } = await this.bindings.createServices({
1435
890
  cwd: this.options.cwd,
1436
891
  agentDir,
1437
892
  resourceLoaderOptions: {
893
+ noContextFiles: true,
894
+ noSkills: true,
895
+ noExtensions: true,
1438
896
  appendSystemPrompt,
1439
897
  },
1440
898
  });
@@ -1484,14 +942,7 @@ export class ConsolePiRuntime {
1484
942
  ...(customTools && customTools.length > 0 ? { customTools } : {}),
1485
943
  ...(resolvedModel ? { model: resolvedModel } : {}),
1486
944
  });
1487
- return {
1488
- session,
1489
- modelRuntime,
1490
- services: {
1491
- resourceLoader: services.resourceLoader,
1492
- settingsManager: services.settingsManager,
1493
- },
1494
- };
945
+ return { session, modelRuntime };
1495
946
  }
1496
947
  /** Re-open a previously created session (cross-request). */
1497
948
  open(sessionId) {
@@ -1512,12 +963,11 @@ export class ConsolePiRuntime {
1512
963
  }
1513
964
  // Gate 2 (re-pin before each turn): interview turns pass through the
1514
965
  // existing closed interview allow/deny registry. The next ordinary turn
1515
- // explicitly restores the normal Operator Chat set (baseline + extension
1516
- // tools, ADR 0012).
966
+ // explicitly restores the normal Operator Chat set.
1517
967
  const requestedTools = OPERATOR_CHAT_ACTIVE_TOOL_NAMES();
1518
968
  const activeTools = options?.mode === "requirement-interview"
1519
969
  ? filterActiveInterviewTools(requestedTools).allowed
1520
- : computeOperatorChatActiveToolNames(session);
970
+ : requestedTools;
1521
971
  session.setActiveToolsByName(activeTools);
1522
972
  const unsub = session.subscribe((event) => {
1523
973
  const mapped = mapSdkEvent(sessionId, event);
@@ -1599,10 +1049,6 @@ export class ConsolePiRuntime {
1599
1049
  this.sessionManagers.delete(sessionId);
1600
1050
  this.mainlineLeaves.delete(sessionId);
1601
1051
  this.modelRuntimes.delete(sessionId);
1602
- this.serviceScopes.delete(sessionId);
1603
- this.revisions.delete(sessionId);
1604
- this.thinkingSelections.delete(sessionId);
1605
- this.reloadInFlight.delete(sessionId);
1606
1052
  }
1607
1053
  }
1608
1054
  disposeAll() {