@gajae-code/coding-agent 0.14.2 → 0.15.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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
|
@@ -81,15 +81,8 @@ export function createProviderSelectionPolicy(input: ProviderSelectionPolicyInpu
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
for (const provider of input.catalogProviders) {
|
|
87
|
-
if (seen.has(provider)) {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
seen.add(provider);
|
|
91
|
-
orderedProviders.push(provider);
|
|
92
|
-
}
|
|
84
|
+
// One shared implementation with the standalone accessor; see projectProviderOrder.
|
|
85
|
+
const orderedProviders = projectProviderOrder(explicitProviders, input.catalogProviders);
|
|
93
86
|
|
|
94
87
|
return {
|
|
95
88
|
rank(provider: string): number {
|
|
@@ -113,6 +106,35 @@ export function createProviderSelectionPolicy(input: ProviderSelectionPolicyInpu
|
|
|
113
106
|
};
|
|
114
107
|
}
|
|
115
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Project the deterministic provider order: normalized explicit order first, then
|
|
111
|
+
* first-wins catalog order for everything else.
|
|
112
|
+
*
|
|
113
|
+
* This is the single implementation of that ordering. It reads no credentials and
|
|
114
|
+
* takes no auth input at all, so any consumer that only needs "which providers, in
|
|
115
|
+
* what priority" cannot accidentally acquire auth sensitivity. Auth-aware banding
|
|
116
|
+
* lives exclusively in {@link ProviderSelectionPolicy.rank}.
|
|
117
|
+
*/
|
|
118
|
+
export function projectProviderOrder(
|
|
119
|
+
explicitProviderOrder: readonly string[],
|
|
120
|
+
catalogProviders: readonly string[],
|
|
121
|
+
): string[] {
|
|
122
|
+
const ordered: string[] = [];
|
|
123
|
+
const seen = new Set<string>();
|
|
124
|
+
for (const raw of explicitProviderOrder) {
|
|
125
|
+
const normalized = raw.trim().toLowerCase();
|
|
126
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
127
|
+
seen.add(normalized);
|
|
128
|
+
ordered.push(normalized);
|
|
129
|
+
}
|
|
130
|
+
for (const provider of catalogProviders) {
|
|
131
|
+
if (!provider || seen.has(provider)) continue;
|
|
132
|
+
seen.add(provider);
|
|
133
|
+
ordered.push(provider);
|
|
134
|
+
}
|
|
135
|
+
return ordered;
|
|
136
|
+
}
|
|
137
|
+
|
|
116
138
|
export interface ProviderSelectionCatalog {
|
|
117
139
|
/** Lowercased provider ids in first-wins registry catalog order. */
|
|
118
140
|
readonly catalogProviders: readonly string[];
|
|
@@ -144,3 +166,32 @@ export function buildProviderSelectionCatalog(models: readonly Model<Api>[]): Pr
|
|
|
144
166
|
}
|
|
145
167
|
return { catalogProviders, catalogModels };
|
|
146
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Deterministic provider priority for a catalog, returned in the catalog's own
|
|
171
|
+
* spelling.
|
|
172
|
+
*
|
|
173
|
+
* Ordering and de-duplication run on normalized ids, but the result restores each
|
|
174
|
+
* provider's first-seen catalog spelling because the autorouting generator matches
|
|
175
|
+
* provider prefixes with case-sensitive exact strings — a lowercased id would
|
|
176
|
+
* silently empty that provider's tiers. Providers absent from the catalog are
|
|
177
|
+
* dropped so a dead declaration cannot pollute a generated declarationFingerprint.
|
|
178
|
+
*
|
|
179
|
+
* Reads no credentials: it takes a catalog and an explicit order, nothing else.
|
|
180
|
+
*/
|
|
181
|
+
export function projectCatalogProviderOrder(
|
|
182
|
+
explicitProviderOrder: readonly string[],
|
|
183
|
+
models: readonly Model<Api>[],
|
|
184
|
+
): string[] {
|
|
185
|
+
const { catalogProviders } = buildProviderSelectionCatalog(models);
|
|
186
|
+
const spelling = new Map<string, string>();
|
|
187
|
+
for (const model of models) {
|
|
188
|
+
const normalized = model.provider.trim().toLowerCase();
|
|
189
|
+
if (normalized && !spelling.has(normalized)) spelling.set(normalized, model.provider);
|
|
190
|
+
}
|
|
191
|
+
const restored: string[] = [];
|
|
192
|
+
for (const provider of projectProviderOrder(explicitProviderOrder, catalogProviders)) {
|
|
193
|
+
const spelled = spelling.get(provider);
|
|
194
|
+
if (spelled !== undefined) restored.push(spelled);
|
|
195
|
+
}
|
|
196
|
+
return restored;
|
|
197
|
+
}
|
|
@@ -4,6 +4,18 @@ import { TASK_SIMPLE_MODES } from "../task/simple-mode";
|
|
|
4
4
|
import { getThinkingLevelMetadata } from "../thinking-metadata";
|
|
5
5
|
import { DEFAULT_EDIT_MODE_SETTING, EDIT_MODE_SETTINGS, EDIT_MODES, type EditMode } from "../utils/edit-mode";
|
|
6
6
|
import { CONFIGURABLE_SEARCH_PROVIDER_IDS } from "../web/search/types";
|
|
7
|
+
import {
|
|
8
|
+
AUTOROUTING_SELECTOR_DESCRIPTION,
|
|
9
|
+
AUTOROUTING_SELECTOR_PATTERN,
|
|
10
|
+
AUTOROUTING_TIERS,
|
|
11
|
+
type AutoroutingLocalIssue,
|
|
12
|
+
type AutoroutingProvenance,
|
|
13
|
+
type AutoroutingSetup,
|
|
14
|
+
type AutoroutingTierMapInput,
|
|
15
|
+
validateAutoroutingLocal,
|
|
16
|
+
validateAutoroutingProvenance,
|
|
17
|
+
validateAutoroutingSetup,
|
|
18
|
+
} from "./autorouting-contract";
|
|
7
19
|
import type { ModelSelectorValue } from "./model-selector-value";
|
|
8
20
|
import { UPDATE_CHANNELS } from "./update-channel";
|
|
9
21
|
|
|
@@ -148,6 +160,22 @@ export type AnyUiMetadata = UiBase & {
|
|
|
148
160
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
149
161
|
};
|
|
150
162
|
|
|
163
|
+
/** JSON Schema fragment carried by settings definitions that own nested validation. */
|
|
164
|
+
export type JsonSchemaObject = {
|
|
165
|
+
[key: string]: unknown;
|
|
166
|
+
type?: string;
|
|
167
|
+
properties?: Record<string, JsonSchemaObject>;
|
|
168
|
+
additionalProperties?: boolean | JsonSchemaObject;
|
|
169
|
+
items?: JsonSchemaObject;
|
|
170
|
+
required?: readonly string[];
|
|
171
|
+
pattern?: string;
|
|
172
|
+
minItems?: number;
|
|
173
|
+
minLength?: number;
|
|
174
|
+
uniqueItems?: boolean;
|
|
175
|
+
minimum?: number;
|
|
176
|
+
const?: unknown;
|
|
177
|
+
};
|
|
178
|
+
|
|
151
179
|
interface BooleanDef {
|
|
152
180
|
type: "boolean";
|
|
153
181
|
default?: boolean;
|
|
@@ -186,6 +214,21 @@ type RecordValueDef =
|
|
|
186
214
|
| { type: "string-enum"; values: readonly string[] }
|
|
187
215
|
| { type: "credential-selector" };
|
|
188
216
|
|
|
217
|
+
interface ConstrainedRecordValueDef {
|
|
218
|
+
type: "autorouting-selector-value";
|
|
219
|
+
pattern: string;
|
|
220
|
+
description: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface ConstrainedRecordDef<T> {
|
|
224
|
+
type: "constrained-record";
|
|
225
|
+
default: T;
|
|
226
|
+
keys: readonly string[];
|
|
227
|
+
valueSchema: ConstrainedRecordValueDef;
|
|
228
|
+
description?: string;
|
|
229
|
+
ui?: UiBase;
|
|
230
|
+
}
|
|
231
|
+
|
|
189
232
|
interface RecordDef<T> {
|
|
190
233
|
type: "record";
|
|
191
234
|
default: Record<string, T>;
|
|
@@ -193,13 +236,23 @@ interface RecordDef<T> {
|
|
|
193
236
|
ui?: UiBase;
|
|
194
237
|
}
|
|
195
238
|
|
|
196
|
-
|
|
239
|
+
export interface OptionalObjectDef<T> {
|
|
240
|
+
type: "optional-object";
|
|
241
|
+
default: undefined;
|
|
242
|
+
jsonSchema: JsonSchemaObject;
|
|
243
|
+
validate: (value: unknown) => AutoroutingLocalIssue[];
|
|
244
|
+
_value?: T;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export type SettingDef =
|
|
197
248
|
| BooleanDef
|
|
198
249
|
| StringDef
|
|
199
250
|
| NumberDef
|
|
200
251
|
| EnumDef<readonly string[]>
|
|
201
252
|
| ArrayDef<unknown>
|
|
202
|
-
| RecordDef<unknown
|
|
253
|
+
| RecordDef<unknown>
|
|
254
|
+
| ConstrainedRecordDef<unknown>
|
|
255
|
+
| OptionalObjectDef<unknown>;
|
|
203
256
|
|
|
204
257
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
205
258
|
// Schema Definition
|
|
@@ -261,6 +314,56 @@ export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
|
|
|
261
314
|
},
|
|
262
315
|
];
|
|
263
316
|
|
|
317
|
+
const AUTOROUTING_SETUP_JSON_SCHEMA: JsonSchemaObject = {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
schema: { type: "integer", const: 1 },
|
|
321
|
+
providers: {
|
|
322
|
+
type: "array",
|
|
323
|
+
minItems: 1,
|
|
324
|
+
uniqueItems: true,
|
|
325
|
+
items: {
|
|
326
|
+
type: "string",
|
|
327
|
+
minLength: 1,
|
|
328
|
+
pattern: "^[^\\s](?:.*[^\\s])?$",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
models: {
|
|
332
|
+
type: "array",
|
|
333
|
+
items: {
|
|
334
|
+
type: "string",
|
|
335
|
+
minLength: 1,
|
|
336
|
+
maxLength: 256,
|
|
337
|
+
pattern: AUTOROUTING_SELECTOR_PATTERN,
|
|
338
|
+
not: { pattern: "^\\s*[pP][iI]/" },
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
additionalProperties: false,
|
|
343
|
+
required: ["schema", "providers"],
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const AUTOROUTING_PROVENANCE_JSON_SCHEMA: JsonSchemaObject = {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
schema: { type: "integer", const: 1 },
|
|
350
|
+
source: {
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: {
|
|
353
|
+
catalogFingerprint: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
354
|
+
mapFingerprint: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
355
|
+
generatorVersion: { type: "integer", minimum: 1 },
|
|
356
|
+
},
|
|
357
|
+
additionalProperties: false,
|
|
358
|
+
required: ["catalogFingerprint", "mapFingerprint", "generatorVersion"],
|
|
359
|
+
},
|
|
360
|
+
declarationFingerprint: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
361
|
+
tiersFingerprint: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
362
|
+
},
|
|
363
|
+
additionalProperties: false,
|
|
364
|
+
required: ["schema", "source", "declarationFingerprint", "tiersFingerprint"],
|
|
365
|
+
};
|
|
366
|
+
|
|
264
367
|
export const SETTINGS_SCHEMA = {
|
|
265
368
|
// ────────────────────────────────────────────────────────────────────────
|
|
266
369
|
// General settings (no UI)
|
|
@@ -618,7 +721,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
618
721
|
},
|
|
619
722
|
"gjc.ralplan.autoHandoff": {
|
|
620
723
|
type: "enum",
|
|
621
|
-
values: ["off", "ultragoal", "
|
|
724
|
+
values: ["off", "ultragoal", "autoresearch"],
|
|
622
725
|
default: "off",
|
|
623
726
|
},
|
|
624
727
|
"gjc.ralplan.maxIterations": {
|
|
@@ -3510,6 +3613,39 @@ export const SETTINGS_SCHEMA = {
|
|
|
3510
3613
|
valueSchema: MODEL_SELECTOR_VALUE_SCHEMA,
|
|
3511
3614
|
},
|
|
3512
3615
|
|
|
3616
|
+
"task.autorouting.enabled": {
|
|
3617
|
+
type: "boolean",
|
|
3618
|
+
default: false,
|
|
3619
|
+
},
|
|
3620
|
+
|
|
3621
|
+
"task.autorouting.tiers": {
|
|
3622
|
+
type: "constrained-record",
|
|
3623
|
+
default: {} as AutoroutingTierMapInput,
|
|
3624
|
+
keys: AUTOROUTING_TIERS,
|
|
3625
|
+
valueSchema: {
|
|
3626
|
+
type: "autorouting-selector-value",
|
|
3627
|
+
minLength: 1,
|
|
3628
|
+
maxLength: 256,
|
|
3629
|
+
pattern: AUTOROUTING_SELECTOR_PATTERN,
|
|
3630
|
+
description: AUTOROUTING_SELECTOR_DESCRIPTION,
|
|
3631
|
+
},
|
|
3632
|
+
description: AUTOROUTING_SELECTOR_DESCRIPTION,
|
|
3633
|
+
},
|
|
3634
|
+
|
|
3635
|
+
"task.autorouting.setup": {
|
|
3636
|
+
type: "optional-object",
|
|
3637
|
+
default: undefined,
|
|
3638
|
+
jsonSchema: AUTOROUTING_SETUP_JSON_SCHEMA,
|
|
3639
|
+
validate: validateAutoroutingSetup,
|
|
3640
|
+
} as OptionalObjectDef<AutoroutingSetup>,
|
|
3641
|
+
|
|
3642
|
+
"task.autorouting.provenance": {
|
|
3643
|
+
type: "optional-object",
|
|
3644
|
+
default: undefined,
|
|
3645
|
+
jsonSchema: AUTOROUTING_PROVENANCE_JSON_SCHEMA,
|
|
3646
|
+
validate: validateAutoroutingProvenance,
|
|
3647
|
+
} as OptionalObjectDef<AutoroutingProvenance>,
|
|
3648
|
+
|
|
3513
3649
|
"tasks.todoClearDelay": {
|
|
3514
3650
|
type: "number",
|
|
3515
3651
|
default: 60,
|
|
@@ -3856,7 +3992,11 @@ export type SettingValue<P extends SettingPath> = Schema[P] extends { type: "boo
|
|
|
3856
3992
|
? D
|
|
3857
3993
|
: Schema[P] extends { type: "record"; default: infer D }
|
|
3858
3994
|
? D
|
|
3859
|
-
:
|
|
3995
|
+
: Schema[P] extends { type: "constrained-record"; default: infer D }
|
|
3996
|
+
? D
|
|
3997
|
+
: Schema[P] extends OptionalObjectDef<infer D>
|
|
3998
|
+
? D | undefined
|
|
3999
|
+
: never;
|
|
3860
4000
|
|
|
3861
4001
|
/** Get the default value for a setting path */
|
|
3862
4002
|
export function getDefault<P extends SettingPath>(path: P): SettingValue<P> {
|
|
@@ -3930,7 +4070,11 @@ function schemaPaths(value: Record<string, unknown>, prefix = ""): string[] {
|
|
|
3930
4070
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
3931
4071
|
const definition = SETTINGS_SCHEMA[path as SettingPath];
|
|
3932
4072
|
// Records intentionally accept user-defined keys; validate their entries below.
|
|
3933
|
-
if (
|
|
4073
|
+
if (
|
|
4074
|
+
definition?.type === "record" ||
|
|
4075
|
+
definition?.type === "constrained-record" ||
|
|
4076
|
+
definition?.type === "optional-object"
|
|
4077
|
+
) {
|
|
3934
4078
|
paths.push(path);
|
|
3935
4079
|
} else if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
3936
4080
|
paths.push(...schemaPaths(child as Record<string, unknown>, path));
|
|
@@ -3974,7 +4118,15 @@ function validSettingValue(definition: (typeof SETTINGS_SCHEMA)[SettingPath], va
|
|
|
3974
4118
|
(definition.values as readonly string[]).includes(value)) ||
|
|
3975
4119
|
(definition.type === "array" &&
|
|
3976
4120
|
validArraySettingValue(value, "items" in definition ? definition.items?.enum : undefined)) ||
|
|
3977
|
-
(definition.type === "record"
|
|
4121
|
+
((definition.type === "record" || definition.type === "constrained-record") &&
|
|
4122
|
+
!!value &&
|
|
4123
|
+
typeof value === "object" &&
|
|
4124
|
+
!Array.isArray(value)) ||
|
|
4125
|
+
(definition.type === "optional-object" &&
|
|
4126
|
+
!!value &&
|
|
4127
|
+
typeof value === "object" &&
|
|
4128
|
+
!Array.isArray(value) &&
|
|
4129
|
+
definition.validate(value).length === 0)
|
|
3978
4130
|
);
|
|
3979
4131
|
}
|
|
3980
4132
|
|
|
@@ -4022,6 +4174,16 @@ export function validateSettingPatch(patch: Record<string, unknown>): Array<{ pa
|
|
|
4022
4174
|
issues.push({ path, detail });
|
|
4023
4175
|
continue;
|
|
4024
4176
|
}
|
|
4177
|
+
if (definition.type === "constrained-record" && path === "task.autorouting.tiers") {
|
|
4178
|
+
for (const issue of validateAutoroutingLocal({ tiers: value })) {
|
|
4179
|
+
const nestedPath = issue.path.replace(/^tiers\./u, "");
|
|
4180
|
+
issues.push({
|
|
4181
|
+
path: nestedPath ? `task.autorouting.tiers.${nestedPath}` : "task.autorouting.tiers",
|
|
4182
|
+
detail: issue.detail,
|
|
4183
|
+
});
|
|
4184
|
+
}
|
|
4185
|
+
continue;
|
|
4186
|
+
}
|
|
4025
4187
|
if (definition.type === "record" && "valueSchema" in definition && definition.valueSchema) {
|
|
4026
4188
|
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
4027
4189
|
if (!validRecordValue(definition.valueSchema, entry)) {
|
|
@@ -4065,6 +4227,33 @@ export function reconcileSettingsSchema(raw: Record<string, unknown>): {
|
|
|
4065
4227
|
schemaSetAtPath(settings, path, next);
|
|
4066
4228
|
issues.push({ path, kind: "coerced", detail: `Coerced ${typeof value} to ${definition.type}.` });
|
|
4067
4229
|
}
|
|
4230
|
+
if (definition.type === "optional-object") {
|
|
4231
|
+
for (const localIssue of definition.validate(next)) {
|
|
4232
|
+
issues.push({
|
|
4233
|
+
path: localIssue.path ? `${path}.${localIssue.path}` : path,
|
|
4234
|
+
kind: "invalid",
|
|
4235
|
+
detail: localIssue.detail,
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
if (definition.type === "constrained-record") {
|
|
4241
|
+
const autorouting = schemaValueAtPath(settings, "task.autorouting");
|
|
4242
|
+
const tiersFragment =
|
|
4243
|
+
autorouting && typeof autorouting === "object" && !Array.isArray(autorouting)
|
|
4244
|
+
? Object.fromEntries(
|
|
4245
|
+
Object.entries(autorouting).filter(([key]) => key !== "setup" && key !== "provenance"),
|
|
4246
|
+
)
|
|
4247
|
+
: autorouting;
|
|
4248
|
+
for (const localIssue of validateAutoroutingLocal(tiersFragment)) {
|
|
4249
|
+
issues.push({
|
|
4250
|
+
path: localIssue.path ? `task.autorouting.${localIssue.path}` : "task.autorouting",
|
|
4251
|
+
kind: "invalid",
|
|
4252
|
+
detail: localIssue.detail,
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
continue;
|
|
4256
|
+
}
|
|
4068
4257
|
if (!validSettingValue(definition, next)) {
|
|
4069
4258
|
const arrayItemEnum =
|
|
4070
4259
|
definition.type === "array" && Array.isArray(next) && "items" in definition
|
package/src/config/settings.ts
CHANGED
|
@@ -57,6 +57,11 @@ import {
|
|
|
57
57
|
setByPath,
|
|
58
58
|
withAtomicYamlConfigTransaction,
|
|
59
59
|
} from "./atomic-yaml-patch";
|
|
60
|
+
import {
|
|
61
|
+
type AutoroutingEffective,
|
|
62
|
+
validateAutoroutingEffective,
|
|
63
|
+
validateAutoroutingLocal,
|
|
64
|
+
} from "./autorouting-contract";
|
|
60
65
|
import { isModelSelectorValue, type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
|
|
61
66
|
|
|
62
67
|
import {
|
|
@@ -68,6 +73,7 @@ import {
|
|
|
68
73
|
reconcileSettingsSchema,
|
|
69
74
|
SETTINGS_SCHEMA,
|
|
70
75
|
type SettingPath,
|
|
76
|
+
type SettingsSchemaIssue,
|
|
71
77
|
type SettingsSchemaReport,
|
|
72
78
|
type SettingValue,
|
|
73
79
|
} from "./settings-schema";
|
|
@@ -524,6 +530,9 @@ export class Settings implements NotificationSettingsReader {
|
|
|
524
530
|
#legacyCustomImageProviderDiagnosticLogged = false;
|
|
525
531
|
|
|
526
532
|
#schemaReport: SettingsSchemaReport = { issues: [], valid: true };
|
|
533
|
+
|
|
534
|
+
#autoroutingEffective: AutoroutingEffective = { active: false };
|
|
535
|
+
#autoroutingLocalIssues: SettingsSchemaIssue[] = [];
|
|
527
536
|
#schemaMigrationPending = false;
|
|
528
537
|
/** A newer config schema must never be rewritten by legacy migrations. */
|
|
529
538
|
#futureSchemaVersion = false;
|
|
@@ -692,7 +701,34 @@ export class Settings implements NotificationSettingsReader {
|
|
|
692
701
|
|
|
693
702
|
/** Diagnostics from schema reconciliation during the most recent load. */
|
|
694
703
|
getSchemaReport(): SettingsSchemaReport {
|
|
695
|
-
|
|
704
|
+
const issues = [
|
|
705
|
+
...this.#schemaReport.issues.filter(
|
|
706
|
+
issue =>
|
|
707
|
+
!(
|
|
708
|
+
(issue.kind === "invalid" || issue.kind === "unknown") &&
|
|
709
|
+
(issue.path === "task.autorouting" || issue.path.startsWith("task.autorouting."))
|
|
710
|
+
),
|
|
711
|
+
),
|
|
712
|
+
...this.#autoroutingLocalIssues,
|
|
713
|
+
...(this.#autoroutingEffective.active || !this.#autoroutingEffective.issue
|
|
714
|
+
? []
|
|
715
|
+
: [
|
|
716
|
+
{
|
|
717
|
+
path: "task.autorouting",
|
|
718
|
+
kind: "invalid" as const,
|
|
719
|
+
detail: this.#autoroutingEffective.issue.detail,
|
|
720
|
+
},
|
|
721
|
+
]),
|
|
722
|
+
];
|
|
723
|
+
return {
|
|
724
|
+
issues: structuredClone(issues),
|
|
725
|
+
valid: !issues.some(issue => issue.kind === "invalid"),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Effective merged autorouting state shared by settings diagnostics and routing policy. */
|
|
730
|
+
getEffectiveAutorouting(): AutoroutingEffective {
|
|
731
|
+
return structuredClone(this.#autoroutingEffective);
|
|
696
732
|
}
|
|
697
733
|
|
|
698
734
|
onChanged(listener: (path: SettingPath) => void): () => void {
|
|
@@ -1066,6 +1102,9 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1066
1102
|
});
|
|
1067
1103
|
cloned.#storage = this.#storage;
|
|
1068
1104
|
cloned.#schemaReport = structuredClone(this.#schemaReport);
|
|
1105
|
+
|
|
1106
|
+
cloned.#autoroutingEffective = structuredClone(this.#autoroutingEffective);
|
|
1107
|
+
cloned.#autoroutingLocalIssues = structuredClone(this.#autoroutingLocalIssues);
|
|
1069
1108
|
cloned.#schemaMigrationPending = this.#schemaMigrationPending;
|
|
1070
1109
|
cloned.#futureSchemaVersion = this.#futureSchemaVersion;
|
|
1071
1110
|
cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
|
|
@@ -1601,6 +1640,15 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1601
1640
|
}
|
|
1602
1641
|
setByPath(source, pathSegments, sanitized);
|
|
1603
1642
|
}
|
|
1643
|
+
|
|
1644
|
+
const tiersPath = ["task", "autorouting", "tiers"];
|
|
1645
|
+
const tiers = getByPath(source, tiersPath);
|
|
1646
|
+
if (tiers === undefined) continue;
|
|
1647
|
+
if (!tiers || typeof tiers !== "object" || Array.isArray(tiers)) {
|
|
1648
|
+
logger.warn("Settings: retained malformed autorouting tier record for schema diagnostics", {
|
|
1649
|
+
path: tiersPath.join("."),
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1604
1652
|
}
|
|
1605
1653
|
}
|
|
1606
1654
|
|
|
@@ -5860,6 +5908,18 @@ export class Settings implements NotificationSettingsReader {
|
|
|
5860
5908
|
throw error;
|
|
5861
5909
|
}
|
|
5862
5910
|
}
|
|
5911
|
+
#recomputeAutoroutingDiagnostic(): void {
|
|
5912
|
+
this.#autoroutingLocalIssues = [this.#global, this.#project, this.#overrides].flatMap(source => {
|
|
5913
|
+
const fragment = getByPath(source, ["task", "autorouting"]);
|
|
5914
|
+
return validateAutoroutingLocal(fragment).map(localIssue => ({
|
|
5915
|
+
path: localIssue.path ? `task.autorouting.${localIssue.path}` : "task.autorouting",
|
|
5916
|
+
kind: "invalid" as const,
|
|
5917
|
+
detail: localIssue.detail,
|
|
5918
|
+
}));
|
|
5919
|
+
});
|
|
5920
|
+
this.#autoroutingEffective = validateAutoroutingEffective(getByPath(this.#merged, ["task", "autorouting"]));
|
|
5921
|
+
}
|
|
5922
|
+
|
|
5863
5923
|
#rebuildMerged(): void {
|
|
5864
5924
|
const project = structuredClone(this.#project);
|
|
5865
5925
|
const overrides = structuredClone(this.#overrides);
|
|
@@ -5870,6 +5930,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
5870
5930
|
}
|
|
5871
5931
|
this.#merged = this.#deepMerge(this.#deepMerge({}, this.#global), project);
|
|
5872
5932
|
this.#merged = this.#deepMerge(this.#merged, overrides);
|
|
5933
|
+
this.#recomputeAutoroutingDiagnostic();
|
|
5873
5934
|
}
|
|
5874
5935
|
|
|
5875
5936
|
#fireAllHooks(): void {
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
4
|
-
import { CONFIG_DIR_NAME, getConfigAgentDirName, getProjectDir } from "@gajae-code/utils";
|
|
3
|
+
import { CONFIG_DIR_NAME, getConfigAgentDirName, getProjectDir, getTrustedHomeDir } from "@gajae-code/utils";
|
|
5
4
|
import { expandTilde } from "./tools/path-utils";
|
|
6
5
|
|
|
7
6
|
export * from "./config/config-file";
|
|
@@ -51,7 +50,7 @@ export function getChangelogPath(): string {
|
|
|
51
50
|
* Project-level: .gjc, .gemini
|
|
52
51
|
*/
|
|
53
52
|
const USER_CONFIG_BASES = USER_CONFIG_PRIORITY.map(({ dir, globalAgentDir }) => ({
|
|
54
|
-
base: () => path.join(
|
|
53
|
+
base: () => path.join(getTrustedHomeDir(), globalAgentDir ? globalAgentDir() : dir),
|
|
55
54
|
name: dir,
|
|
56
55
|
}));
|
|
57
56
|
|
|
@@ -3,6 +3,12 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { withFileLock } from "../config/file-lock";
|
|
5
5
|
import { assertSafeCodexEndpoint } from "./codex-wake-publisher";
|
|
6
|
+
import {
|
|
7
|
+
ensureCoordinatorDirectory,
|
|
8
|
+
syncCoordinatorDirectory,
|
|
9
|
+
syncCoordinatorFile,
|
|
10
|
+
writeCoordinatorAtomic,
|
|
11
|
+
} from "./durability";
|
|
6
12
|
|
|
7
13
|
export const CODEX_WAKE_EVENT_KINDS = [
|
|
8
14
|
"question.opened",
|
|
@@ -108,52 +114,71 @@ function wakeEventPath(namespaceDir: string, workUnit: string, eventSeq: number)
|
|
|
108
114
|
return path.join(namespaceDir, "codex-wake-events", `${assertWorkUnit(workUnit)}__${assertEventSeq(eventSeq)}.json`);
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
async function fsyncDirectory(directory: string): Promise<void> {
|
|
112
|
-
const handle = await fs.open(directory, "r");
|
|
113
|
-
try {
|
|
114
|
-
await handle.sync();
|
|
115
|
-
} finally {
|
|
116
|
-
await handle.close();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
117
|
async function writeAtomic(file: string, value: unknown): Promise<void> {
|
|
121
|
-
await
|
|
122
|
-
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
123
|
-
const handle = await fs.open(temp, "wx", 0o600);
|
|
124
|
-
try {
|
|
125
|
-
await handle.writeFile(JSON.stringify(value));
|
|
126
|
-
await handle.sync();
|
|
127
|
-
} finally {
|
|
128
|
-
await handle.close();
|
|
129
|
-
}
|
|
130
|
-
await fs.rename(temp, file);
|
|
131
|
-
await fsyncDirectory(path.dirname(file));
|
|
118
|
+
await writeCoordinatorAtomic(file, JSON.stringify(value));
|
|
132
119
|
}
|
|
133
120
|
|
|
134
121
|
async function writeExclusive(file: string, value: unknown): Promise<boolean> {
|
|
135
|
-
await
|
|
122
|
+
await ensureCoordinatorDirectory(path.dirname(file));
|
|
136
123
|
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
} finally {
|
|
142
|
-
await handle.close();
|
|
143
|
-
}
|
|
124
|
+
let linked = false;
|
|
125
|
+
let result = false;
|
|
126
|
+
let primaryError: unknown;
|
|
127
|
+
let tempCreated = false;
|
|
144
128
|
try {
|
|
145
|
-
await fs.
|
|
129
|
+
const handle = await fs.open(temp, "wx", 0o600);
|
|
130
|
+
tempCreated = true;
|
|
131
|
+
let writeError: unknown;
|
|
132
|
+
try {
|
|
133
|
+
await handle.writeFile(JSON.stringify(value));
|
|
134
|
+
await syncCoordinatorFile(handle);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
writeError = error;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await handle.close();
|
|
140
|
+
} catch (closeError) {
|
|
141
|
+
if (writeError) throw new AggregateError([writeError, closeError], "exclusive write and close failed");
|
|
142
|
+
throw closeError;
|
|
143
|
+
}
|
|
144
|
+
if (writeError) throw writeError;
|
|
145
|
+
try {
|
|
146
|
+
await fs.link(temp, file);
|
|
147
|
+
linked = true;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
150
|
+
await syncCoordinatorDirectory(path.dirname(file));
|
|
151
|
+
result = false;
|
|
152
|
+
}
|
|
153
|
+
if (linked) {
|
|
154
|
+
await syncCoordinatorDirectory(path.dirname(file));
|
|
155
|
+
result = true;
|
|
156
|
+
}
|
|
146
157
|
} catch (error) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
primaryError = error;
|
|
159
|
+
}
|
|
160
|
+
let cleanupError: unknown;
|
|
161
|
+
if (tempCreated) {
|
|
162
|
+
try {
|
|
163
|
+
await fs.unlink(temp);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") cleanupError = error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
let cleanupBarrierError: unknown;
|
|
169
|
+
if (tempCreated && !cleanupError) {
|
|
170
|
+
try {
|
|
171
|
+
await syncCoordinatorDirectory(path.dirname(file));
|
|
172
|
+
} catch (error) {
|
|
173
|
+
cleanupBarrierError = error;
|
|
151
174
|
}
|
|
152
|
-
throw error;
|
|
153
175
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
const failures = [primaryError, cleanupError, cleanupBarrierError].filter(
|
|
177
|
+
(error): error is unknown => error !== undefined,
|
|
178
|
+
);
|
|
179
|
+
if (failures.length > 1) throw new AggregateError(failures, "exclusive publication and cleanup failed");
|
|
180
|
+
if (failures.length === 1) throw failures[0];
|
|
181
|
+
return result;
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
async function readJson<T>(file: string): Promise<T | null> {
|
|
@@ -161,7 +186,8 @@ async function readJson<T>(file: string): Promise<T | null> {
|
|
|
161
186
|
return JSON.parse(await fs.readFile(file, "utf8")) as T;
|
|
162
187
|
} catch (error) {
|
|
163
188
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
164
|
-
throw new Error("state_corrupt");
|
|
189
|
+
if (error instanceof SyntaxError) throw new Error("state_corrupt");
|
|
190
|
+
throw error;
|
|
165
191
|
}
|
|
166
192
|
}
|
|
167
193
|
|
|
@@ -196,7 +222,7 @@ function assertCodexHandoffOrigin(value: unknown): asserts value is CodexHandoff
|
|
|
196
222
|
(typeof origin.codex_host_session_id === "string" && SAFE_ID.test(origin.codex_host_session_id))
|
|
197
223
|
) ||
|
|
198
224
|
!(typeof origin.delegation_id === "string" && SAFE_ID.test(origin.delegation_id)) ||
|
|
199
|
-
!["plan", "execute"
|
|
225
|
+
!["plan", "execute"].includes(origin.workflow as string) ||
|
|
200
226
|
!(typeof origin.bound_at === "string" && Number.isFinite(Date.parse(origin.bound_at)))
|
|
201
227
|
)
|
|
202
228
|
throw new Error("state_corrupt");
|
|
@@ -343,7 +369,7 @@ export async function listCodexHandoffs(namespaceDir: string): Promise<CodexHand
|
|
|
343
369
|
names = await fs.readdir(directory);
|
|
344
370
|
} catch (error) {
|
|
345
371
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
346
|
-
throw
|
|
372
|
+
throw error;
|
|
347
373
|
}
|
|
348
374
|
const handoffs: CodexHandoffRegistrationV1[] = [];
|
|
349
375
|
for (const name of names) {
|
|
@@ -418,7 +444,7 @@ export async function updateCodexWakeEvent(
|
|
|
418
444
|
const file = eventPathForKey(namespaceDir, key);
|
|
419
445
|
if (patch.status !== undefined && !["pending", "published", "acked", "failed"].includes(patch.status))
|
|
420
446
|
throw new Error("invalid_wake_event_status");
|
|
421
|
-
if (patch.attempts_delta !== undefined && !Number.
|
|
447
|
+
if (patch.attempts_delta !== undefined && (!Number.isSafeInteger(patch.attempts_delta) || patch.attempts_delta < 0))
|
|
422
448
|
throw new Error("invalid_attempts_delta");
|
|
423
449
|
return await withFileLock(file, async () => {
|
|
424
450
|
const event = await readJson<CodexWakeEventV1>(file);
|
|
@@ -457,7 +483,7 @@ export async function listCodexWakeEvents(namespaceDir: string, workUnit?: strin
|
|
|
457
483
|
names = await fs.readdir(directory);
|
|
458
484
|
} catch (error) {
|
|
459
485
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
460
|
-
throw
|
|
486
|
+
throw error;
|
|
461
487
|
}
|
|
462
488
|
const events: CodexWakeEventV1[] = [];
|
|
463
489
|
for (const name of names) {
|