@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.
- package/CHANGELOG.md +39 -23
- package/dist/application/task-lifecycle/advance.js +4 -254
- package/dist/application/task-lifecycle/gates.js +0 -50
- package/dist/application/task-lifecycle/observe.js +2 -11
- package/dist/commands/init-upgrade.js +1 -32
- package/dist/commands/init.js +3 -94
- package/dist/executors/shell-executor.js +91 -4
- package/dist/executors/shell-write-guard.js +8 -26
- package/dist/shared/operator/capabilities.js +42 -72
- package/dist/task/source-prepare/index.js +0 -2
- package/dist/task/source-prepare/parse-intent.js +10 -58
- package/dist/task/source-prepare/prepare.js +16 -180
- package/dist/task/source-prepare/reference-integrity.js +2 -18
- package/dist/worker/console/app-data.js +0 -2
- package/dist/worker/console/chat/chat-event-store.js +25 -190
- package/dist/worker/console/chat/instruction-skills.js +217 -0
- package/dist/worker/console/chat/pi-console-config.js +32 -250
- package/dist/worker/console/chat/pi-runtime.js +71 -625
- package/dist/worker/console/chat/resource-loader.js +4 -5
- package/dist/worker/console/chat/routes.js +146 -324
- package/dist/worker/console/chat/runtime-context.js +12 -48
- package/dist/worker/console/chat/runtime-selection.js +0 -59
- package/dist/worker/console/chat/shortcuts.js +0 -1
- package/dist/worker/console/chat/tool-adapter.js +3 -9
- package/dist/worker/console/chat/tools.js +1 -5
- package/dist/worker/console/operator-actions.js +68 -559
- package/dist/worker/console/server.js +15 -8
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
- package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
- package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
- package/dist/worker/delivery/final-verification.js +5 -13
- package/dist/worker/delivery/package.js +19 -31
- package/dist/worker/delivery/verification-bundle.js +4 -6
- package/dist/worker/observe/static/operator-chrome.css +2 -5
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/styles.css +9 -39
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +552 -124
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
- package/dist/workflows/dag/init-hybrid.js +49 -37
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/workspace-checkpoint.js +27 -8
- package/docs/templates/backend-test-dag.json +33 -30
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -3
- package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
- package/skills/loop-agent/references/task-workflow.md +0 -4
- package/dist/shared/resilient-git.js +0 -133
- package/dist/task/source-prepare/artifact-meta.js +0 -137
- package/dist/task/source-prepare/semantic-intake.js +0 -404
- package/dist/worker/console/dag-execution-receipt.js +0 -380
- package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
- package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
- 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
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
*
|
|
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
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
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
|
-
//
|
|
699
|
-
//
|
|
700
|
-
//
|
|
701
|
-
|
|
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
|
|
794
|
-
//
|
|
795
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
:
|
|
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() {
|