@gajae-code/coding-agent 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/cli/notify-cli.d.ts +23 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +38 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +38 -7
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +61 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +276 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +58 -0
- package/dist/types/notifications/threaded-render.d.ts +66 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/session/agent-session.d.ts +39 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/index.d.ts +18 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +3 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/notify-cli.ts +274 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -0
- package/src/commands/daemon.ts +47 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/migrate.ts +46 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +29 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -13
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +51 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +6 -1
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +247 -124
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +7 -1
- package/src/gjc-runtime/ultragoal-guard.ts +102 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +226 -60
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
- package/src/gjc-runtime/workflow-manifest.ts +12 -3
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +9 -6
- package/src/main.ts +30 -0
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +107 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +663 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1370 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +80 -0
- package/src/notifications/threaded-render.ts +155 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +26 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +128 -24
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/agents.ts +1 -22
- package/src/task/executor.ts +5 -1
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +108 -16
- package/src/tools/computer.ts +58 -4
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +19 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -5,7 +5,7 @@ thinking-level: medium
|
|
|
5
5
|
hide: true
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Generate AGENTS.md by launching multiple
|
|
8
|
+
Generate AGENTS.md by launching multiple canonical role agents in parallel (via `task` tool, usually `planner` or `architect`) scanning different areas (core src, tests, configs/build, scripts/docs), then synthesize findings into a single file.
|
|
9
9
|
|
|
10
10
|
<structure>
|
|
11
11
|
- **Project Overview**: Brief description of project purpose
|
|
@@ -82,7 +82,7 @@ The plan MUST be scannable yet detailed enough to execute.
|
|
|
82
82
|
|
|
83
83
|
<procedure>
|
|
84
84
|
### Phase 1: Understand
|
|
85
|
-
You MUST focus on the request and associated code. You SHOULD launch parallel
|
|
85
|
+
You MUST focus on the request and associated code. You SHOULD launch parallel canonical role agents (`planner` or `architect`) when scope spans multiple areas.
|
|
86
86
|
|
|
87
87
|
### Phase 2: Design
|
|
88
88
|
You MUST draft an approach based on exploration. You MUST consider trade-offs briefly, then choose.
|
|
@@ -38,5 +38,5 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
38
38
|
<critical>
|
|
39
39
|
- Avoid repo-root scans — narrow `paths` first
|
|
40
40
|
- Parse issues are query failure, not evidence of absence: repair the pattern or tighten `paths` before concluding "no matches"
|
|
41
|
-
- For broad/open-ended
|
|
41
|
+
- For broad/open-ended inspection across subsystems, delegate a bounded fact-finding task to an appropriate canonical role agent (`planner` or `architect`) first
|
|
42
42
|
</critical>
|
|
@@ -21,5 +21,5 @@ Searches files using powerful regex matching.
|
|
|
21
21
|
- You MUST use the built-in `search` tool for any content search. NEVER shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any other CLI search via Bash — even for a single match, even "just to check quickly", even piped through other commands.
|
|
22
22
|
- Bash `grep`/`rg` loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The `search` tool is faster, structured, and already wired into the workspace — there is no scenario where Bash search is preferable.
|
|
23
23
|
- If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the lookup through the `search` tool instead.
|
|
24
|
-
- If the search is open-ended
|
|
24
|
+
- If the search is open-ended and requires multiple rounds across subsystems, delegate a bounded fact-finding task to an appropriate canonical role agent (`planner` for sequencing/context maps or `architect` for read-only architecture assessment) instead of chaining broad `search` calls yourself.
|
|
25
25
|
</critical>
|
|
@@ -28,13 +28,12 @@ Subagents have no conversation history. Every fact, file path, and direction the
|
|
|
28
28
|
{{/if}}
|
|
29
29
|
{{#if independentMode}}- `.inheritContext`: independent mode cannot inherit parent conversation. Omit it or set `"none"`; any non-`none` value is rejected before scheduling.{{/if}}
|
|
30
30
|
{{#if customSchemaEnabled}}- `schema`: JTD schema for expected structured output (do not put format rules in assignments){{/if}}
|
|
31
|
-
- `spawnPlan` (optional): required before any batch with more than 4 tasks
|
|
31
|
+
- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.
|
|
32
32
|
{{#if isolationEnabled}}- `isolated`: run in isolated env; use when tasks edit overlapping files{{/if}}
|
|
33
33
|
</parameters>
|
|
34
34
|
|
|
35
35
|
<rules>
|
|
36
36
|
- HARD runtime gate: calls with more than 4 tasks are rejected before any child launches unless `spawnPlan` is complete.
|
|
37
|
-
- Reviewer->explore gate: a `reviewer` spawning `explore` is rejected before launch unless `spawnPlan` is complete, even for a single task.
|
|
38
37
|
- NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.
|
|
39
38
|
- **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.
|
|
40
39
|
{{#if ircEnabled}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ledger";
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export type ResearchPlanConfidence = "low" | "medium" | "high";
|
|
2
|
+
|
|
3
|
+
export type ResearchEvidenceVerdict = "support" | "contradict" | "uncertain";
|
|
4
|
+
|
|
5
|
+
export interface ResearchPlanItem {
|
|
6
|
+
claim: string;
|
|
7
|
+
confidence: ResearchPlanConfidence;
|
|
8
|
+
unknowns: string[];
|
|
9
|
+
evidenceNeeded: string[];
|
|
10
|
+
counterexampleQueries: string[];
|
|
11
|
+
sourceConflictPolicy: string;
|
|
12
|
+
dropCondition: string;
|
|
13
|
+
verifierChecks: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ResearchEvidenceEntry {
|
|
17
|
+
claim: string;
|
|
18
|
+
source: string;
|
|
19
|
+
confidence: ResearchPlanConfidence;
|
|
20
|
+
verdict: ResearchEvidenceVerdict;
|
|
21
|
+
notes?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ResearchLedgerVerdict {
|
|
25
|
+
claim: string;
|
|
26
|
+
finalVerdict: "accepted" | "rejected" | "uncertain";
|
|
27
|
+
survivingSources: ResearchEvidenceEntry[];
|
|
28
|
+
rejectReason?: string;
|
|
29
|
+
unresolvedUnknowns: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ResearchPlanValidationResult {
|
|
33
|
+
valid: boolean;
|
|
34
|
+
errors: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const CONFIDENCE_VALUES = new Set<ResearchPlanConfidence>(["low", "medium", "high"]);
|
|
38
|
+
const EVIDENCE_VERDICTS = new Set<ResearchEvidenceVerdict>(["support", "contradict", "uncertain"]);
|
|
39
|
+
|
|
40
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
41
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateStringArray(value: unknown, field: string, minLength = 1): string[] {
|
|
45
|
+
if (!Array.isArray(value)) return [`${field} must be an array`];
|
|
46
|
+
if (value.length < minLength) return [`${field} must contain at least ${minLength} item(s)`];
|
|
47
|
+
return value.flatMap((item, index) =>
|
|
48
|
+
isNonEmptyString(item) ? [] : [`${field}[${index}] must be a non-empty string`],
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function validateResearchPlanItem(item: Partial<ResearchPlanItem>): ResearchPlanValidationResult {
|
|
53
|
+
const errors: string[] = [];
|
|
54
|
+
if (!isNonEmptyString(item.claim)) errors.push("claim must be a non-empty string");
|
|
55
|
+
if (!item.confidence || !CONFIDENCE_VALUES.has(item.confidence)) {
|
|
56
|
+
errors.push("confidence must be one of: low, medium, high");
|
|
57
|
+
}
|
|
58
|
+
errors.push(...validateStringArray(item.unknowns, "unknowns", 0));
|
|
59
|
+
errors.push(...validateStringArray(item.evidenceNeeded, "evidenceNeeded"));
|
|
60
|
+
errors.push(...validateStringArray(item.counterexampleQueries, "counterexampleQueries"));
|
|
61
|
+
if (!isNonEmptyString(item.sourceConflictPolicy)) errors.push("sourceConflictPolicy must be a non-empty string");
|
|
62
|
+
if (!isNonEmptyString(item.dropCondition)) errors.push("dropCondition must be a non-empty string");
|
|
63
|
+
errors.push(...validateStringArray(item.verifierChecks, "verifierChecks"));
|
|
64
|
+
return { valid: errors.length === 0, errors };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function validateResearchEvidenceEntry(entry: Partial<ResearchEvidenceEntry>): ResearchPlanValidationResult {
|
|
68
|
+
const errors: string[] = [];
|
|
69
|
+
if (!isNonEmptyString(entry.claim)) errors.push("claim must be a non-empty string");
|
|
70
|
+
if (!isNonEmptyString(entry.source)) errors.push("source must be a non-empty string");
|
|
71
|
+
if (!entry.confidence || !CONFIDENCE_VALUES.has(entry.confidence)) {
|
|
72
|
+
errors.push("confidence must be one of: low, medium, high");
|
|
73
|
+
}
|
|
74
|
+
if (!entry.verdict || !EVIDENCE_VERDICTS.has(entry.verdict)) {
|
|
75
|
+
errors.push("verdict must be one of: support, contradict, uncertain");
|
|
76
|
+
}
|
|
77
|
+
return { valid: errors.length === 0, errors };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function lower(value: string): string {
|
|
81
|
+
return value.toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function matchesDropCondition(item: ResearchPlanItem, evidence: ResearchEvidenceEntry[]): string | undefined {
|
|
85
|
+
const condition = lower(item.dropCondition);
|
|
86
|
+
const contradiction = evidence.find(entry => entry.verdict === "contradict");
|
|
87
|
+
if (contradiction && /(counterexample|contradict|conflict|falsif)/.test(condition)) {
|
|
88
|
+
return `dropCondition matched by contradictory source: ${contradiction.source}`;
|
|
89
|
+
}
|
|
90
|
+
const unresolved = evidence.find(entry => entry.verdict === "uncertain");
|
|
91
|
+
if (unresolved && /(unknown|unresolved|uncertain)/.test(condition)) {
|
|
92
|
+
return `dropCondition matched by unresolved evidence: ${unresolved.source}`;
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sourceConflictReason(item: ResearchPlanItem, evidence: ResearchEvidenceEntry[]): string | undefined {
|
|
98
|
+
const supporting = evidence.filter(entry => entry.verdict === "support");
|
|
99
|
+
const contradicting = evidence.filter(entry => entry.verdict === "contradict");
|
|
100
|
+
if (supporting.length === 0 || contradicting.length === 0) return undefined;
|
|
101
|
+
const policy = lower(item.sourceConflictPolicy);
|
|
102
|
+
if (/(reject|drop|do not accept|prefer contradiction|requires resolution)/.test(policy)) {
|
|
103
|
+
return `sourceConflictPolicy rejected mixed support/contradiction (${supporting.length} support, ${contradicting.length} contradict)`;
|
|
104
|
+
}
|
|
105
|
+
return "source conflict remains unresolved";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function evaluateResearchLedger(
|
|
109
|
+
item: ResearchPlanItem,
|
|
110
|
+
evidence: readonly ResearchEvidenceEntry[],
|
|
111
|
+
): ResearchLedgerVerdict {
|
|
112
|
+
const relevantEvidence = evidence.filter(entry => entry.claim === item.claim);
|
|
113
|
+
const invalidItem = validateResearchPlanItem(item);
|
|
114
|
+
if (!invalidItem.valid) {
|
|
115
|
+
return {
|
|
116
|
+
claim: item.claim,
|
|
117
|
+
finalVerdict: "rejected",
|
|
118
|
+
survivingSources: [],
|
|
119
|
+
rejectReason: `invalid research plan item: ${invalidItem.errors.join("; ")}`,
|
|
120
|
+
unresolvedUnknowns: item.unknowns,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const invalidEvidence = relevantEvidence.flatMap(entry => validateResearchEvidenceEntry(entry).errors);
|
|
124
|
+
if (invalidEvidence.length > 0) {
|
|
125
|
+
return {
|
|
126
|
+
claim: item.claim,
|
|
127
|
+
finalVerdict: "rejected",
|
|
128
|
+
survivingSources: [],
|
|
129
|
+
rejectReason: `invalid evidence entry: ${invalidEvidence.join("; ")}`,
|
|
130
|
+
unresolvedUnknowns: item.unknowns,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (relevantEvidence.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
claim: item.claim,
|
|
136
|
+
finalVerdict: "uncertain",
|
|
137
|
+
survivingSources: [],
|
|
138
|
+
rejectReason: "no evidence collected for claim",
|
|
139
|
+
unresolvedUnknowns: item.unknowns,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const supporting = relevantEvidence.filter(entry => entry.verdict === "support");
|
|
143
|
+
const firstContradiction = relevantEvidence.find(entry => entry.verdict === "contradict");
|
|
144
|
+
let dropReason = matchesDropCondition(item, relevantEvidence) ?? sourceConflictReason(item, relevantEvidence);
|
|
145
|
+
// A counterexample with no surviving support falsifies the claim regardless of how the
|
|
146
|
+
// dropCondition / sourceConflictPolicy prose is worded. Without this, a purely contradicted
|
|
147
|
+
// claim would slip through as "uncertain" and reopen the hallucination survival path the
|
|
148
|
+
// evidence ledger exists to close (a contested claim already rejects via sourceConflictReason).
|
|
149
|
+
if (!dropReason && firstContradiction && supporting.length === 0) {
|
|
150
|
+
dropReason = `claim contradicted by counterexample with no supporting evidence: ${firstContradiction.source}`;
|
|
151
|
+
}
|
|
152
|
+
if (dropReason) {
|
|
153
|
+
return {
|
|
154
|
+
claim: item.claim,
|
|
155
|
+
finalVerdict: "rejected",
|
|
156
|
+
survivingSources: supporting,
|
|
157
|
+
rejectReason: dropReason,
|
|
158
|
+
unresolvedUnknowns: item.unknowns,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const uncertain = relevantEvidence.some(entry => entry.verdict === "uncertain");
|
|
162
|
+
if (uncertain || supporting.length === 0) {
|
|
163
|
+
return {
|
|
164
|
+
claim: item.claim,
|
|
165
|
+
finalVerdict: "uncertain",
|
|
166
|
+
survivingSources: supporting,
|
|
167
|
+
rejectReason: uncertain ? "unresolved uncertainty remains" : "no supporting evidence survived verification",
|
|
168
|
+
unresolvedUnknowns: item.unknowns,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
claim: item.claim,
|
|
173
|
+
finalVerdict: "accepted",
|
|
174
|
+
survivingSources: supporting,
|
|
175
|
+
unresolvedUnknowns: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
package/src/rlm/artifacts.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RLM session artifact layout under <cwd>/.gjc/rlm/<
|
|
2
|
+
* RLM session artifact layout under <cwd>/.gjc/_session-{gjcSessionId}/rlm/<rlmSessionId>/.
|
|
3
|
+
*
|
|
4
|
+
* The GJC session id (process boundary) scopes the directory; the RLM session id
|
|
5
|
+
* names the individual research run within it. The two ids are kept distinct.
|
|
3
6
|
*/
|
|
4
7
|
import * as fs from "node:fs/promises";
|
|
5
8
|
import * as path from "node:path";
|
|
6
9
|
import { readNotebookDocument } from "../edit/notebook";
|
|
10
|
+
import { rlmArtifactRoot } from "../gjc-runtime/session-layout";
|
|
11
|
+
import { resolveGjcSessionForWrite } from "../gjc-runtime/session-resolution";
|
|
7
12
|
import type { RlmArtifactPaths } from "./types";
|
|
8
13
|
|
|
9
|
-
export const RLM_DIR_SEGMENT =
|
|
14
|
+
export const RLM_DIR_SEGMENT = "rlm";
|
|
10
15
|
|
|
11
16
|
const SESSION_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
12
17
|
|
|
@@ -25,7 +30,11 @@ export function resolveRlmArtifactPaths(cwd: string, sessionId: string): RlmArti
|
|
|
25
30
|
if (!isValidRlmSessionId(sessionId)) {
|
|
26
31
|
throw new Error(`Invalid RLM session id: ${JSON.stringify(sessionId)}`);
|
|
27
32
|
}
|
|
28
|
-
const dir =
|
|
33
|
+
const dir = rlmArtifactRoot(
|
|
34
|
+
cwd,
|
|
35
|
+
resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId,
|
|
36
|
+
sessionId,
|
|
37
|
+
);
|
|
29
38
|
return {
|
|
30
39
|
dir,
|
|
31
40
|
notebookPath: path.join(dir, "notebook.ipynb"),
|
package/src/rlm/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { getProjectDir } from "@gajae-code/utils";
|
|
|
11
11
|
import { type Args, parseArgs } from "../cli/args";
|
|
12
12
|
import { disposeKernelSessionsByOwner } from "../eval/py/executor";
|
|
13
13
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
14
|
+
import { resolveSessionIdFromSources, writeSessionActivityMarker } from "../gjc-runtime/session-resolution";
|
|
14
15
|
import { type RlmPreset, runRootCommand } from "../main";
|
|
15
16
|
import rlmReportCommandPrompt from "../prompts/system/rlm-report-command.md" with { type: "text" };
|
|
16
17
|
import type { CreateAgentSessionOptions } from "../sdk";
|
|
@@ -231,10 +232,35 @@ async function writeRlmMetadata(input: {
|
|
|
231
232
|
successfulRuns: input.successfulRuns,
|
|
232
233
|
};
|
|
233
234
|
await Bun.write(input.paths.metadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
|
|
235
|
+
// Best-effort: update the per-session activity marker so latest-session auto-detect
|
|
236
|
+
// accounts for RLM-only generated output (AC2). Never let marker failure break RLM.
|
|
237
|
+
const gjcSessionId = resolveSessionIdFromSources({ envSessionId: process.env.GJC_SESSION_ID })?.gjcSessionId;
|
|
238
|
+
if (gjcSessionId) {
|
|
239
|
+
await writeSessionActivityMarker(input.cwd, gjcSessionId, { writer: "rlm" }).catch(() => {});
|
|
240
|
+
}
|
|
234
241
|
}
|
|
235
242
|
|
|
243
|
+
/**
|
|
244
|
+
* RLM artifacts are scoped under a GJC session directory and resolving their
|
|
245
|
+
* paths is a *write* (it must pick a concrete session). When `gjc rlm` runs
|
|
246
|
+
* standalone — no parent agent, no `GJC_SESSION_ID` in the environment — there is
|
|
247
|
+
* no session to resolve and `resolveGjcSessionForWrite` throws
|
|
248
|
+
* `missing_for_write`. Establish a dedicated GJC session id in that case and pin
|
|
249
|
+
* it into the environment so artifact-path resolution, the per-session activity
|
|
250
|
+
* marker, and the child agent's workflow state all share one writable session.
|
|
251
|
+
*
|
|
252
|
+
* Returns the resolved (existing or freshly generated) GJC session id.
|
|
253
|
+
*/
|
|
254
|
+
export function ensureRlmGjcSessionId(): string {
|
|
255
|
+
const existing = resolveSessionIdFromSources({ envSessionId: process.env.GJC_SESSION_ID })?.gjcSessionId;
|
|
256
|
+
if (existing) return existing;
|
|
257
|
+
const generated = `rlm-${generateRlmSessionId()}`;
|
|
258
|
+
process.env.GJC_SESSION_ID = generated;
|
|
259
|
+
return generated;
|
|
260
|
+
}
|
|
236
261
|
export async function runRlmCommand(argv: string[]): Promise<void> {
|
|
237
262
|
const cwd = getProjectDir();
|
|
263
|
+
ensureRlmGjcSessionId();
|
|
238
264
|
const { dataPath, resumeSessionId, minSuccessfulRuns, rest } = extractRlmFlags(argv);
|
|
239
265
|
const dataContext = await loadRlmDataContext(cwd, dataPath);
|
|
240
266
|
|
|
@@ -149,6 +149,52 @@ export async function updateMCPServer(filePath: string, name: string, config: MC
|
|
|
149
149
|
await writeMCPConfigFile(filePath, updated);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Result of an {@link upsertMCPServer} call.
|
|
154
|
+
* - `added`: server did not exist and was written.
|
|
155
|
+
* - `updated`: server existed and was overwritten because `force` was set.
|
|
156
|
+
* - `skipped`: server existed and `force` was not set, so nothing was written.
|
|
157
|
+
*/
|
|
158
|
+
export type UpsertMCPServerResult =
|
|
159
|
+
| { status: "added" }
|
|
160
|
+
| { status: "updated" }
|
|
161
|
+
| { status: "skipped"; reason: "exists" };
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Add an MCP server, or overwrite an existing one only when `force` is set.
|
|
165
|
+
*
|
|
166
|
+
* Collision-aware wrapper over {@link addMCPServer} / {@link updateMCPServer} used by
|
|
167
|
+
* `gjc migrate`. Never connects to the server. Reuses the underlying writers so the
|
|
168
|
+
* rest of the config file (including `disabledServers`) is preserved on update.
|
|
169
|
+
*
|
|
170
|
+
* @throws Error if the server name or config is invalid (validated before any write).
|
|
171
|
+
*/
|
|
172
|
+
export async function upsertMCPServer(
|
|
173
|
+
filePath: string,
|
|
174
|
+
name: string,
|
|
175
|
+
config: MCPServerConfig,
|
|
176
|
+
options: { force?: boolean } = {},
|
|
177
|
+
): Promise<UpsertMCPServerResult> {
|
|
178
|
+
// Validate name up front so an invalid name fails regardless of collision state.
|
|
179
|
+
const nameError = validateServerName(name);
|
|
180
|
+
if (nameError) {
|
|
181
|
+
throw new Error(nameError);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const existing = await getMCPServer(filePath, name);
|
|
185
|
+
if (existing) {
|
|
186
|
+
if (!options.force) {
|
|
187
|
+
return { status: "skipped", reason: "exists" };
|
|
188
|
+
}
|
|
189
|
+
// updateMCPServer preserves the rest of MCPConfigFile, incl. disabledServers.
|
|
190
|
+
await updateMCPServer(filePath, name, config);
|
|
191
|
+
return { status: "updated" };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await addMCPServer(filePath, name, config);
|
|
195
|
+
return { status: "added" };
|
|
196
|
+
}
|
|
197
|
+
|
|
152
198
|
/**
|
|
153
199
|
* Remove an MCP server from a config file.
|
|
154
200
|
*
|
package/src/sdk.ts
CHANGED
|
@@ -79,6 +79,12 @@ import type { HindsightSessionState } from "./hindsight/state";
|
|
|
79
79
|
import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
|
|
80
80
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
81
81
|
import { resolveMemoryBackend } from "./memory-backend";
|
|
82
|
+
import { createNotificationsExtension } from "./notifications";
|
|
83
|
+
import {
|
|
84
|
+
getNotificationConfig,
|
|
85
|
+
type NotificationConfig,
|
|
86
|
+
shouldRegisterNotificationsExtension,
|
|
87
|
+
} from "./notifications/config";
|
|
82
88
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
83
89
|
import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
84
90
|
import { MCPManager } from "./runtime-mcp";
|
|
@@ -1238,6 +1244,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1238
1244
|
getPlanModeState: () => session?.getPlanModeState(),
|
|
1239
1245
|
getGoalModeState: () => session?.getGoalModeState(),
|
|
1240
1246
|
getWorkflowGateEmitter: () => session?.getWorkflowGateEmitter(),
|
|
1247
|
+
getAskAnswerSource: () => session?.getAskAnswerSource(),
|
|
1241
1248
|
getGoalRuntime: () => session?.goalRuntime,
|
|
1242
1249
|
getClientBridge: () => session?.clientBridge,
|
|
1243
1250
|
getCompactContext: () => session.formatCompactContext(),
|
|
@@ -1386,6 +1393,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1386
1393
|
if (customTools.length > 0) {
|
|
1387
1394
|
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1388
1395
|
}
|
|
1396
|
+
let notificationCfg: NotificationConfig | undefined;
|
|
1397
|
+
try {
|
|
1398
|
+
notificationCfg = getNotificationConfig(Settings.instance);
|
|
1399
|
+
} catch {
|
|
1400
|
+
notificationCfg = undefined;
|
|
1401
|
+
}
|
|
1402
|
+
if (shouldRegisterNotificationsExtension({ env: process.env, cfg: notificationCfg })) {
|
|
1403
|
+
inlineExtensions.push(createNotificationsExtension);
|
|
1404
|
+
}
|
|
1389
1405
|
|
|
1390
1406
|
// Extension/module discovery is quarantined; retain only the private
|
|
1391
1407
|
// runtime needed for bundled product extensions, explicitly supplied SDK
|
|
@@ -183,6 +183,11 @@ import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
|
183
183
|
import type { Skill, SkillWarning } from "../extensibility/skills";
|
|
184
184
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
185
185
|
import { buildGjcRuntimeSessionEnv, consumePendingGoalModeRequest } from "../gjc-runtime/goal-mode-request";
|
|
186
|
+
import {
|
|
187
|
+
assertNonEmptyGjcSessionId,
|
|
188
|
+
modeStatePath as sessionModeStatePath,
|
|
189
|
+
sessionStateDir,
|
|
190
|
+
} from "../gjc-runtime/session-layout";
|
|
186
191
|
import { persistCoordinatorRuntimeStateFromEvent } from "../gjc-runtime/session-state-sidecar";
|
|
187
192
|
import { writeArtifact } from "../gjc-runtime/state-writer";
|
|
188
193
|
import { requestGjcWorkerIntegrationAttempt } from "../gjc-runtime/team-runtime";
|
|
@@ -233,8 +238,9 @@ import {
|
|
|
233
238
|
type DiscoverableTool,
|
|
234
239
|
type DiscoverableToolSearchIndex,
|
|
235
240
|
} from "../tool-discovery/tool-index";
|
|
236
|
-
import type { ToolSession } from "../tools";
|
|
241
|
+
import type { AskAnswerSource, ToolSession } from "../tools";
|
|
237
242
|
import { AskTool } from "../tools/ask";
|
|
243
|
+
import { getAskAnswerSource as getAskAnswerSourceFromRegistry } from "../tools/ask-answer-registry";
|
|
238
244
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
239
245
|
import { releaseTabsForOwner } from "../tools/browser/tab-supervisor";
|
|
240
246
|
import type { CheckpointState } from "../tools/checkpoint";
|
|
@@ -308,17 +314,11 @@ export type AgentSessionEvent =
|
|
|
308
314
|
| { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
|
|
309
315
|
| { type: "todo_auto_clear" }
|
|
310
316
|
| { type: "irc_message"; message: CustomMessage }
|
|
317
|
+
| { type: "subagent_steer_message"; message: CustomMessage }
|
|
311
318
|
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string }
|
|
312
319
|
| { type: "thinking_level_changed"; thinkingLevel: ThinkingLevel | undefined }
|
|
313
320
|
| { type: "goal_updated"; goal: Goal | null; state?: GoalModeState };
|
|
314
321
|
|
|
315
|
-
/**
|
|
316
|
-
* Safe path component pattern used to validate session-id segments before
|
|
317
|
-
* joining them into `.gjc/state` paths. Mirrors the regex used by the
|
|
318
|
-
* `gjc state` runtime selector resolver.
|
|
319
|
-
*/
|
|
320
|
-
const SAFE_PATH_COMPONENT = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
321
|
-
|
|
322
322
|
function isUnderProjectGjc(cwd: string, targetPath: string): boolean {
|
|
323
323
|
const relative = path.relative(path.join(path.resolve(cwd), ".gjc"), path.resolve(targetPath));
|
|
324
324
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
@@ -1370,21 +1370,17 @@ export class AgentSession {
|
|
|
1370
1370
|
getActiveSkillPhase(): string | undefined {
|
|
1371
1371
|
const active = this.#activeSkillState;
|
|
1372
1372
|
if (!active) return undefined;
|
|
1373
|
-
// Path safety: refuse to read mode-state files when the skill or
|
|
1374
|
-
// session-id are not safe path components. The `skill` tool
|
|
1375
|
-
// interprets undefined as a non-terminal phase, so chaining is
|
|
1376
|
-
// refused — there is no risk of bypassing the guard via a custom
|
|
1377
|
-
// skill name with `..` or a session-id with separators.
|
|
1378
1373
|
if (!isCanonicalGjcWorkflowSkill(active.skill)) return undefined;
|
|
1379
|
-
|
|
1380
|
-
return undefined;
|
|
1381
|
-
}
|
|
1374
|
+
const sessionId = active.sessionId ?? this.sessionManager.getSessionId();
|
|
1382
1375
|
try {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
const filePath = path.join(
|
|
1376
|
+
assertNonEmptyGjcSessionId(sessionId, "AgentSession.getActiveSkillPhase");
|
|
1377
|
+
// Keep the session-state-dir construction explicit here so the chain guard
|
|
1378
|
+
// refuses to fall back to a legacy root `.gjc/state` read.
|
|
1379
|
+
const stateDir = sessionStateDir(this.sessionManager.getCwd(), sessionId);
|
|
1380
|
+
const filePath = path.join(
|
|
1381
|
+
stateDir,
|
|
1382
|
+
path.basename(sessionModeStatePath(this.sessionManager.getCwd(), sessionId, active.skill)),
|
|
1383
|
+
);
|
|
1388
1384
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1389
1385
|
const parsed = JSON.parse(raw) as { current_phase?: unknown };
|
|
1390
1386
|
return typeof parsed.current_phase === "string" ? parsed.current_phase : undefined;
|
|
@@ -3763,7 +3759,7 @@ export class AgentSession {
|
|
|
3763
3759
|
* prompts or tool execution can run.
|
|
3764
3760
|
*/
|
|
3765
3761
|
#wrapToolForDeepInterviewMutationGuard<T extends AgentTool>(tool: T): T {
|
|
3766
|
-
if (!["edit", "write", "ast_edit"
|
|
3762
|
+
if (!["edit", "write", "ast_edit"].includes(tool.name)) return tool;
|
|
3767
3763
|
return new Proxy(tool, {
|
|
3768
3764
|
get: (target, prop) => {
|
|
3769
3765
|
if (prop !== "execute") return Reflect.get(target, prop, target);
|
|
@@ -4391,6 +4387,10 @@ export class AgentSession {
|
|
|
4391
4387
|
return this.#workflowGateEmitter;
|
|
4392
4388
|
}
|
|
4393
4389
|
|
|
4390
|
+
getAskAnswerSource(): AskAnswerSource | undefined {
|
|
4391
|
+
return getAskAnswerSourceFromRegistry(this.sessionId);
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
4394
|
setWorkflowGateEmitter(emitter: WorkflowGateEmitter | undefined): void {
|
|
4395
4395
|
this.#workflowGateEmitter = emitter;
|
|
4396
4396
|
if (emitter) {
|
|
@@ -5459,6 +5459,16 @@ export class AgentSession {
|
|
|
5459
5459
|
return;
|
|
5460
5460
|
}
|
|
5461
5461
|
|
|
5462
|
+
// No explicit delivery mode: only a live stream makes prompt() throw
|
|
5463
|
+
// AgentBusyError, so queue the message as steering while streaming.
|
|
5464
|
+
// Compaction is intentionally NOT diverted here: prompt() handles an
|
|
5465
|
+
// in-flight compaction internally, and #queueSteer would otherwise park
|
|
5466
|
+
// the message in the steering queue with no turn to consume it.
|
|
5467
|
+
if (this.isStreaming) {
|
|
5468
|
+
await this.#queueSteer(text, images);
|
|
5469
|
+
return;
|
|
5470
|
+
}
|
|
5471
|
+
|
|
5462
5472
|
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
5463
5473
|
await this.prompt(text, {
|
|
5464
5474
|
expandPromptTemplates: false,
|
|
@@ -5862,12 +5872,46 @@ export class AgentSession {
|
|
|
5862
5872
|
return this.#activeModelProfile;
|
|
5863
5873
|
}
|
|
5864
5874
|
|
|
5875
|
+
/**
|
|
5876
|
+
* The model selector ("provider/id") that resume restores as the session
|
|
5877
|
+
* default — the latest session-log `model_change` with role="default".
|
|
5878
|
+
* Model-profile activation snapshots this before mutating the session so a
|
|
5879
|
+
* failed-activation rollback can restore the pre-activation resume default
|
|
5880
|
+
* instead of promoting a transient runtime model to the resume default.
|
|
5881
|
+
*/
|
|
5882
|
+
getSessionDefaultModelSelector(): string | undefined {
|
|
5883
|
+
return this.sessionManager.buildSessionContext().models.default;
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
/**
|
|
5887
|
+
* Re-assert the session resume default ("provider/id") in the session log
|
|
5888
|
+
* WITHOUT touching the live runtime model. Appends a `model_change` with
|
|
5889
|
+
* role="default"; never writes to global settings (apply-for-this-session
|
|
5890
|
+
* semantics). Used by model-profile activation rollback to neutralize the
|
|
5891
|
+
* profile main model the failed activation already recorded as the default.
|
|
5892
|
+
*/
|
|
5893
|
+
recordResumeDefaultModel(selector: string): void {
|
|
5894
|
+
this.sessionManager.appendModelChange(selector, "default");
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5865
5897
|
/**
|
|
5866
5898
|
* Set model temporarily (for this session only).
|
|
5867
5899
|
* Validates API key, saves to session log but NOT to settings.
|
|
5900
|
+
*
|
|
5901
|
+
* The change is recorded in the session log as `role: "temporary"` by
|
|
5902
|
+
* default, which means it is NOT restored as the session default on resume —
|
|
5903
|
+
* transient retry/fallback/context-promotion/plan switches must not clobber
|
|
5904
|
+
* the user's explicit pick (issue #849). Model-profile activation passes
|
|
5905
|
+
* `persistAsSessionDefault: true` so the profile's main model becomes the
|
|
5906
|
+
* session default and survives resume, while still not being written to
|
|
5907
|
+
* global settings (new sessions keep the global default).
|
|
5868
5908
|
* @throws Error if no API key available for the model
|
|
5869
5909
|
*/
|
|
5870
|
-
async setModelTemporary(
|
|
5910
|
+
async setModelTemporary(
|
|
5911
|
+
model: Model,
|
|
5912
|
+
thinkingLevel?: ThinkingLevel,
|
|
5913
|
+
options?: { persistAsSessionDefault?: boolean },
|
|
5914
|
+
): Promise<void> {
|
|
5871
5915
|
const previousEditMode = this.#resolveActiveEditMode();
|
|
5872
5916
|
const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
|
|
5873
5917
|
if (!apiKey) {
|
|
@@ -5876,7 +5920,10 @@ export class AgentSession {
|
|
|
5876
5920
|
|
|
5877
5921
|
this.#clearActiveRetryFallback();
|
|
5878
5922
|
this.#setModelWithProviderSessionReset(model);
|
|
5879
|
-
this.sessionManager.appendModelChange(
|
|
5923
|
+
this.sessionManager.appendModelChange(
|
|
5924
|
+
`${model.provider}/${model.id}`,
|
|
5925
|
+
options?.persistAsSessionDefault ? "default" : "temporary",
|
|
5926
|
+
);
|
|
5880
5927
|
this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
5881
5928
|
|
|
5882
5929
|
// Apply explicit thinking level if given; otherwise prefer the model's
|
|
@@ -9095,6 +9142,63 @@ export class AgentSession {
|
|
|
9095
9142
|
void this.#emitSessionEvent({ type: "irc_message", message: record });
|
|
9096
9143
|
}
|
|
9097
9144
|
|
|
9145
|
+
emitSubagentSteerObservation(args: { from: string; to: string; body: string; timestamp?: number }): void {
|
|
9146
|
+
const timestamp = args.timestamp ?? Date.now();
|
|
9147
|
+
const observationId = crypto.randomUUID();
|
|
9148
|
+
const message: CustomMessage = {
|
|
9149
|
+
role: "custom",
|
|
9150
|
+
customType: "subagent:steer",
|
|
9151
|
+
content: `[Steer \`${args.from}\` ⇨ \`${args.to}\` (queued)]\n\n${args.body}`,
|
|
9152
|
+
display: true,
|
|
9153
|
+
details: { observationId, from: args.from, to: args.to, body: args.body, state: "queued" },
|
|
9154
|
+
attribution: "agent",
|
|
9155
|
+
timestamp,
|
|
9156
|
+
};
|
|
9157
|
+
void this.#emitSessionEvent({ type: "subagent_steer_message", message });
|
|
9158
|
+
this.#forwardSubagentSteerRelayToMain({
|
|
9159
|
+
from: args.from,
|
|
9160
|
+
to: args.to,
|
|
9161
|
+
body: args.body,
|
|
9162
|
+
observationId,
|
|
9163
|
+
timestamp,
|
|
9164
|
+
});
|
|
9165
|
+
}
|
|
9166
|
+
|
|
9167
|
+
#forwardSubagentSteerRelayToMain(args: {
|
|
9168
|
+
from: string;
|
|
9169
|
+
to: string;
|
|
9170
|
+
body: string;
|
|
9171
|
+
observationId: string;
|
|
9172
|
+
timestamp: number;
|
|
9173
|
+
}): void {
|
|
9174
|
+
const registry = this.#agentRegistry;
|
|
9175
|
+
if (!registry) return;
|
|
9176
|
+
if (this.#agentId === MAIN_AGENT_ID) return;
|
|
9177
|
+
const mainRef = registry.get(MAIN_AGENT_ID);
|
|
9178
|
+
const mainSession = mainRef?.session;
|
|
9179
|
+
if (!mainSession || mainSession === this) return;
|
|
9180
|
+
const record: CustomMessage = {
|
|
9181
|
+
role: "custom",
|
|
9182
|
+
customType: "subagent:steer:relay",
|
|
9183
|
+
content: `[Steer \`${args.from}\` ⇨ \`${args.to}\` (queued)]\n\n${args.body}`,
|
|
9184
|
+
display: true,
|
|
9185
|
+
details: {
|
|
9186
|
+
observationId: args.observationId,
|
|
9187
|
+
from: args.from,
|
|
9188
|
+
to: args.to,
|
|
9189
|
+
body: args.body,
|
|
9190
|
+
state: "queued",
|
|
9191
|
+
},
|
|
9192
|
+
attribution: "agent",
|
|
9193
|
+
timestamp: args.timestamp,
|
|
9194
|
+
};
|
|
9195
|
+
mainSession.emitSubagentSteerRelayObservation(record);
|
|
9196
|
+
}
|
|
9197
|
+
|
|
9198
|
+
emitSubagentSteerRelayObservation(record: CustomMessage): void {
|
|
9199
|
+
void this.#emitSessionEvent({ type: "subagent_steer_message", message: record });
|
|
9200
|
+
}
|
|
9201
|
+
|
|
9098
9202
|
/**
|
|
9099
9203
|
* Run a single ephemeral side-channel turn against this session's current
|
|
9100
9204
|
* model + system prompt + history. No tools are used; the side request
|