@gajae-code/coding-agent 0.18.5 → 0.18.7
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 +140 -0
- package/dist/types/capability/types.d.ts +4 -0
- package/dist/types/cli-commands.d.ts +10 -0
- package/dist/types/cli-ordinary.d.ts +13 -0
- package/dist/types/cli.d.ts +12 -3
- package/dist/types/commands/contribution-prep.d.ts +0 -3
- package/dist/types/commands/sdk.d.ts +3 -0
- package/dist/types/commands/update.d.ts +1 -0
- package/dist/types/config/model-profile-activation.d.ts +19 -0
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/coordinator-mcp/session-reaper.d.ts +5 -0
- package/dist/types/discovery/helpers.d.ts +2 -2
- package/dist/types/edit/modes/replace.d.ts +3 -3
- package/dist/types/edit/streaming.d.ts +2 -2
- package/dist/types/exec/bash-shell-supervisor.d.ts +12 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/sdk/broker/discovery.d.ts +8 -0
- package/dist/types/sdk/broker/ensure.d.ts +1 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +4 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +4 -2
- package/dist/types/sdk/cli/diagnostics-cli.d.ts +18 -0
- package/dist/types/sdk/diagnostics/observe-broker.d.ts +185 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +13 -0
- package/dist/types/session/agent-session.d.ts +5 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -1
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +17 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +3 -0
- package/dist/types/session/session-retirement.d.ts +47 -0
- package/dist/types/session/session-storage.d.ts +0 -46
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +107 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/tools/ask-contract.d.ts +75 -254
- package/dist/types/tools/bash.d.ts +1 -1
- package/package.json +7 -7
- package/scripts/build-sdk-package-smoke.ts +5 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/src/capability/index.ts +1 -0
- package/src/capability/types.ts +4 -0
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/public-command-entry.ts +5 -0
- package/src/cli/ssh-cli.ts +6 -3
- package/src/cli/web-search-cli.ts +9 -3
- package/src/cli-commands.ts +14 -0
- package/src/cli-main.ts +7 -8
- package/src/cli-ordinary.ts +197 -0
- package/src/cli.ts +25 -182
- package/src/commands/contribution-prep.ts +1 -3
- package/src/commands/sdk.ts +171 -21
- package/src/commands/update.ts +15 -1
- package/src/config/file-lock.ts +91 -2
- package/src/config/model-profile-activation.ts +70 -4
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +23 -31
- package/src/config/model-resolver.ts +9 -1
- package/src/config/settings.ts +44 -4
- package/src/coordinator-mcp/event-webhook.ts +19 -14
- package/src/coordinator-mcp/policy.ts +14 -4
- package/src/coordinator-mcp/server.ts +142 -7
- package/src/coordinator-mcp/session-reaper.ts +9 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/discovery/helpers.ts +9 -3
- package/src/discovery/ssh.ts +8 -3
- package/src/edit/modes/replace.ts +6 -6
- package/src/edit/streaming.ts +15 -15
- package/src/exec/bash-shell-supervisor.ts +60 -3
- package/src/gjc-runtime/gc-runtime.ts +2 -1
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +11 -10
- package/src/memories/index.ts +8 -2
- package/src/modes/acp/acp-agent.ts +724 -370
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/prompts/tools/ask.md +2 -0
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/sdk/broker/broker.ts +54 -1
- package/src/sdk/broker/daemon-entry.ts +67 -6
- package/src/sdk/broker/discovery.ts +8 -0
- package/src/sdk/broker/ensure.ts +156 -20
- package/src/sdk/broker/lifecycle-ledger.ts +110 -12
- package/src/sdk/broker/lifecycle.ts +334 -348
- package/src/sdk/broker/runtime.ts +5 -1
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/broker/transport.ts +5 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +3 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +15 -12
- package/src/sdk/bus/telegram-daemon.ts +38 -3
- package/src/sdk/cli/diagnostics-cli.ts +247 -0
- package/src/sdk/diagnostics/observe-broker.ts +654 -0
- package/src/sdk/host/control/dispatch.ts +52 -4
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/session-runtime.test.ts +83 -4
- package/src/sdk/host/session-runtime.ts +15 -7
- package/src/sdk/index.ts +1 -0
- package/src/sdk/lifecycle-session.ts +1 -0
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/router/session-router.ts +26 -3
- package/src/sdk/session.ts +51 -8
- package/src/session/agent-session.ts +169 -43
- package/src/session/blob-store.ts +78 -33
- package/src/session/internal/managed-session-storage.ts +203 -4
- package/src/session/internal/task-artifact-owner-access.ts +229 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +68 -0
- package/src/session/session-retirement.ts +155 -0
- package/src/session/session-storage.ts +1 -160
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +578 -0
- package/src/session/task-artifact-owner-retirement.ts +475 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +19 -1
- package/src/setup/hermes-setup.ts +4 -1
- package/src/slash-commands/builtin-registry.ts +8 -5
- package/src/tools/ask-contract.test.ts +76 -1
- package/src/tools/ask-contract.ts +57 -6
- package/src/tools/ask.ts +16 -7
- package/src/tools/bash.ts +38 -29
- package/src/tools/descriptors.test.ts +29 -0
- package/src/tools/tool-catalog.generated.ts +7 -4
- package/src/web/search/index.ts +2 -2
|
@@ -73,32 +73,25 @@ const profile = (
|
|
|
73
73
|
|
|
74
74
|
export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
75
75
|
profile("codex-eco", ["openai-codex"], {
|
|
76
|
-
default: "openai-codex/gpt-
|
|
76
|
+
default: "openai-codex/gpt-6-luna:low",
|
|
77
77
|
executor: "openai-codex/gpt-6-luna:low",
|
|
78
78
|
planner: "openai-codex/gpt-6-luna:high",
|
|
79
|
-
critic: "openai-codex/gpt-
|
|
80
|
-
architect: "openai-codex/gpt-
|
|
79
|
+
critic: "openai-codex/gpt-6-luna:xhigh",
|
|
80
|
+
architect: "openai-codex/gpt-6-luna:high",
|
|
81
81
|
}),
|
|
82
82
|
profile("codex-medium", ["openai-codex"], {
|
|
83
|
-
default: "openai-codex/gpt-6-sol:low",
|
|
84
|
-
executor: "openai-codex/gpt-
|
|
85
|
-
planner: "openai-codex/gpt-
|
|
86
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
87
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
83
|
+
default: "openai-codex/gpt-6.1-sol:low",
|
|
84
|
+
executor: "openai-codex/gpt-6.1-sol:low",
|
|
85
|
+
planner: "openai-codex/gpt-6.1-sol:high",
|
|
86
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
87
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
88
88
|
}),
|
|
89
89
|
profile("codex-pro", ["openai-codex"], {
|
|
90
|
-
default: "openai-codex/gpt-6-sol:medium",
|
|
91
|
-
executor: "openai-codex/gpt-5.6-terra:medium",
|
|
92
|
-
planner: "openai-codex/gpt-6-sol:high",
|
|
93
|
-
critic: "openai-codex/gpt-6-sol:max",
|
|
94
|
-
architect: "openai-codex/gpt-6-sol:xhigh",
|
|
95
|
-
}),
|
|
96
|
-
profile("codex-sol61", ["openai-codex"], {
|
|
97
90
|
default: "openai-codex/gpt-6.1-sol:medium",
|
|
98
91
|
executor: "openai-codex/gpt-6.1-sol:medium",
|
|
99
92
|
planner: "openai-codex/gpt-6.1-sol:high",
|
|
100
|
-
critic: "openai-codex/gpt-6.1-sol:
|
|
101
|
-
architect: "openai-codex/gpt-6-
|
|
93
|
+
critic: "openai-codex/gpt-6.1-sol:max",
|
|
94
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
102
95
|
}),
|
|
103
96
|
profile("lunamaxxing", ["openai-codex"], {
|
|
104
97
|
default: "openai-codex/gpt-6-luna:medium",
|
|
@@ -111,20 +104,20 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
111
104
|
default: "openai-codex/gpt-6-astra:medium",
|
|
112
105
|
executor: "openai-codex/gpt-6-luna:xhigh",
|
|
113
106
|
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
114
|
-
critic: "openai-codex/gpt-6-sol:high",
|
|
115
|
-
architect: "openai-codex/gpt-
|
|
107
|
+
critic: "openai-codex/gpt-6.1-sol:high",
|
|
108
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
116
109
|
}),
|
|
117
110
|
profile("astra-default", ["openai-codex"], {
|
|
118
111
|
default: "openai-codex/gpt-6-astra:medium",
|
|
119
112
|
executor: "openai-codex/gpt-6-luna:max",
|
|
120
113
|
planner: "openai-codex/gpt-6-astra:xhigh",
|
|
121
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
122
|
-
architect: "openai-codex/gpt-
|
|
114
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
115
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
123
116
|
}),
|
|
124
117
|
profile("astra-heavy", ["openai-codex"], {
|
|
125
118
|
default: "openai-codex/gpt-6-astra:medium",
|
|
126
|
-
executor: "openai-codex/gpt-6-sol:max",
|
|
127
|
-
planner: "openai-codex/gpt-6-sol:max",
|
|
119
|
+
executor: "openai-codex/gpt-6.1-sol:max",
|
|
120
|
+
planner: "openai-codex/gpt-6.1-sol:max",
|
|
128
121
|
critic: "openai-codex/gpt-6-astra:max",
|
|
129
122
|
architect: "openai-codex/gpt-6-astra:max",
|
|
130
123
|
}),
|
|
@@ -497,24 +490,24 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
497
490
|
}),
|
|
498
491
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
499
492
|
default: "anthropic/claude-opus-5-5:medium",
|
|
500
|
-
executor: "openai-codex/gpt-
|
|
493
|
+
executor: "openai-codex/gpt-6.1-sol:low",
|
|
501
494
|
planner: "anthropic/claude-sonnet-5-5",
|
|
502
|
-
critic: "openai-codex/gpt-6-sol:xhigh",
|
|
503
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
495
|
+
critic: "openai-codex/gpt-6.1-sol:xhigh",
|
|
496
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
504
497
|
}),
|
|
505
498
|
profile("codex-opencodego", ["openai-codex", "opencode-go"], {
|
|
506
|
-
default: "openai-codex/gpt-6-sol:low",
|
|
499
|
+
default: "openai-codex/gpt-6.1-sol:low",
|
|
507
500
|
executor: "opencode-go/deepseek-v4-pro",
|
|
508
501
|
planner: "opencode-go/kimi-k3",
|
|
509
502
|
critic: "opencode-go/mimo-v2.5-pro",
|
|
510
|
-
architect: "openai-codex/gpt-6-sol:high",
|
|
503
|
+
architect: "openai-codex/gpt-6.1-sol:high",
|
|
511
504
|
}),
|
|
512
505
|
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
513
506
|
default: "anthropic/claude-fable-5-1:high",
|
|
514
|
-
executor: "openai-codex/gpt-
|
|
507
|
+
executor: "openai-codex/gpt-6.1-sol:medium",
|
|
515
508
|
planner: "anthropic/claude-opus-5-5:medium",
|
|
516
509
|
critic: "anthropic/claude-opus-5-5:high",
|
|
517
|
-
architect: "openai-codex/gpt-6-sol:xhigh",
|
|
510
|
+
architect: "openai-codex/gpt-6.1-sol:xhigh",
|
|
518
511
|
}),
|
|
519
512
|
profile("astra-fable", ["openai-codex", "anthropic"], {
|
|
520
513
|
default: "openai-codex/gpt-6-astra:medium",
|
|
@@ -562,7 +555,6 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
562
555
|
"codex-eco": { displayName: "Codex Eco", providerGroup: "CODEX" },
|
|
563
556
|
"codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
|
|
564
557
|
"codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
|
|
565
|
-
"codex-sol61": { displayName: "Codex Sol 6.1", providerGroup: "CODEX" },
|
|
566
558
|
lunamaxxing: { displayName: "LunaMaxxing", providerGroup: "CODEX" },
|
|
567
559
|
"astra-lite": { displayName: "ASTRA-Lite", providerGroup: "CODEX" },
|
|
568
560
|
"astra-default": { displayName: "ASTRA-Default", providerGroup: "CODEX" },
|
|
@@ -1189,7 +1189,15 @@ async function resolveModelChainEntries(
|
|
|
1189
1189
|
skips.push({ selector, reason: "credential_unavailable" });
|
|
1190
1190
|
break;
|
|
1191
1191
|
}
|
|
1192
|
-
|
|
1192
|
+
let key: string | undefined;
|
|
1193
|
+
try {
|
|
1194
|
+
key = await modelRegistry.getApiKey(candidate.model, credentialSessionId, { signal: options?.signal });
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
// OAuth selection can invalidate a pin after the preflight check.
|
|
1197
|
+
if (!options?.isCredentialUnavailable?.(candidate.model.provider)) throw error;
|
|
1198
|
+
skips.push({ selector, reason: "credential_unavailable" });
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1193
1201
|
if (isAuthenticatedOrKeyless(key)) {
|
|
1194
1202
|
return { ...candidate, activeIndex, skips };
|
|
1195
1203
|
}
|
package/src/config/settings.ts
CHANGED
|
@@ -1104,6 +1104,41 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1104
1104
|
if (saveError !== undefined) throw saveError;
|
|
1105
1105
|
}
|
|
1106
1106
|
|
|
1107
|
+
/** Copy execution settings without reloading, persisting, or sharing mutable layers. */
|
|
1108
|
+
snapshot(): Settings {
|
|
1109
|
+
const cloned = new Settings({ cwd: this.#cwd, agentDir: this.#agentDir, inMemory: true });
|
|
1110
|
+
cloned.#global = structuredClone(this.#global);
|
|
1111
|
+
cloned.#project = structuredClone(this.#project);
|
|
1112
|
+
cloned.#overrides = structuredClone(this.#overrides);
|
|
1113
|
+
cloned.#merged = structuredClone(this.#merged);
|
|
1114
|
+
cloned.#schemaReport = structuredClone(this.#schemaReport);
|
|
1115
|
+
cloned.#autoroutingEffective = structuredClone(this.#autoroutingEffective);
|
|
1116
|
+
cloned.#autoroutingLocalIssues = structuredClone(this.#autoroutingLocalIssues);
|
|
1117
|
+
cloned.#schemaMigrationPending = this.#schemaMigrationPending;
|
|
1118
|
+
cloned.#futureSchemaVersion = this.#futureSchemaVersion;
|
|
1119
|
+
cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
|
|
1120
|
+
cloned.#hasRecoveredConfigSyntax = this.#hasRecoveredConfigSyntax;
|
|
1121
|
+
cloned.#hasInvalidNotificationGlobal = this.#hasInvalidNotificationGlobal;
|
|
1122
|
+
cloned.#notificationValidationGeneration = this.#notificationValidationGeneration;
|
|
1123
|
+
cloned.#rawNotificationConfig = structuredClone(this.#rawNotificationConfig);
|
|
1124
|
+
cloned.#durableRawNotificationConfig = structuredClone(this.#durableRawNotificationConfig);
|
|
1125
|
+
cloned.#durableNotificationFingerprint = this.#durableNotificationFingerprint;
|
|
1126
|
+
return cloned;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/** Resolve a retained execution's project layer without persistence or global hooks. */
|
|
1130
|
+
async snapshotForCwd(cwd: string): Promise<Settings> {
|
|
1131
|
+
const cloned = this.snapshot();
|
|
1132
|
+
if (path.resolve(cwd) === path.resolve(this.#cwd)) return cloned;
|
|
1133
|
+
cloned.#cwd = path.resolve(cwd);
|
|
1134
|
+
cloned.#readonly = true;
|
|
1135
|
+
cloned.#project = {};
|
|
1136
|
+
cloned.#rebuildMerged();
|
|
1137
|
+
cloned.#project = await cloned.#loadProjectSettings(true);
|
|
1138
|
+
await cloned.#normalizeAfterLoad(false);
|
|
1139
|
+
return cloned;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1107
1142
|
async cloneForCwd(cwd: string): Promise<Settings> {
|
|
1108
1143
|
// A clone shares the same config queue. Settle an already-reserved local
|
|
1109
1144
|
// debounce before the clone can enqueue a durable selector, preventing it
|
|
@@ -1572,9 +1607,14 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1572
1607
|
return reconciled.settings;
|
|
1573
1608
|
}
|
|
1574
1609
|
|
|
1575
|
-
async #loadProjectSettings(): Promise<RawSettings> {
|
|
1610
|
+
async #loadProjectSettings(bypassCache = false): Promise<RawSettings> {
|
|
1576
1611
|
try {
|
|
1577
|
-
const result = await loadCapability(settingsCapability.id, {
|
|
1612
|
+
const result = await loadCapability(settingsCapability.id, {
|
|
1613
|
+
cwd: this.#cwd,
|
|
1614
|
+
agentDir: this.#agentDir,
|
|
1615
|
+
settings: this,
|
|
1616
|
+
bypassCache,
|
|
1617
|
+
});
|
|
1578
1618
|
let merged: RawSettings = {};
|
|
1579
1619
|
for (const item of result.items as SettingsCapabilityItem[]) {
|
|
1580
1620
|
if (item.level !== "project") continue;
|
|
@@ -1637,7 +1677,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1637
1677
|
return settings;
|
|
1638
1678
|
}
|
|
1639
1679
|
|
|
1640
|
-
async #normalizeAfterLoad(): Promise<void> {
|
|
1680
|
+
async #normalizeAfterLoad(publishHooks = true): Promise<void> {
|
|
1641
1681
|
this.#sanitizeModelSelectorRecords();
|
|
1642
1682
|
this.#rebuildMerged();
|
|
1643
1683
|
if (!this.#futureSchemaVersion) {
|
|
@@ -1653,7 +1693,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1653
1693
|
await this.flush();
|
|
1654
1694
|
this.#sanitizeModelSelectorRecords();
|
|
1655
1695
|
this.#rebuildMerged();
|
|
1656
|
-
this.#fireAllHooks();
|
|
1696
|
+
if (publishHooks) this.#fireAllHooks();
|
|
1657
1697
|
}
|
|
1658
1698
|
|
|
1659
1699
|
#sanitizeModelSelectorRecords(): void {
|
|
@@ -116,6 +116,17 @@ function webhookEnvValue(explicitEnv: NodeJS.ProcessEnv | undefined, name: strin
|
|
|
116
116
|
return $credentialEnv(name)?.trim() || undefined;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// Number.parseInt reads a leading digit prefix ("1.5" -> 1, "1e4" -> 1,
|
|
120
|
+
// "10s" -> 10), which would silently turn a timeout into one millisecond or a
|
|
121
|
+
// retry budget into a single attempt. Only plain decimal digits count; anything
|
|
122
|
+
// else falls back to the default, matching the coordinator policy env values.
|
|
123
|
+
function boundedWebhookEnvInt(raw: string | undefined, fallback: number, max: number): number {
|
|
124
|
+
if (raw === undefined || !/^\d+$/.test(raw)) return fallback;
|
|
125
|
+
const parsed = Number.parseInt(raw, 10);
|
|
126
|
+
// An oversized digit string still means "as large as allowed", so it caps.
|
|
127
|
+
return parsed > 0 ? Math.min(parsed, max) : fallback;
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
export function parseEventWebhookConfig(explicitEnv?: NodeJS.ProcessEnv): EventWebhookConfig | null {
|
|
120
131
|
const rawUrl = webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_URL");
|
|
121
132
|
if (!rawUrl) return null;
|
|
@@ -139,22 +150,16 @@ export function parseEventWebhookConfig(explicitEnv?: NodeJS.ProcessEnv): EventW
|
|
|
139
150
|
: null;
|
|
140
151
|
const tokenFile = webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TOKEN_FILE") ?? "";
|
|
141
152
|
if (tokenFile !== "" && !path.isAbsolute(tokenFile)) throw new Error("coordinator_event_webhook_token_file_invalid");
|
|
142
|
-
const
|
|
143
|
-
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TIMEOUT_MS")
|
|
144
|
-
|
|
153
|
+
const timeoutMs = boundedWebhookEnvInt(
|
|
154
|
+
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_TIMEOUT_MS"),
|
|
155
|
+
DEFAULT_EVENT_WEBHOOK_TIMEOUT_MS,
|
|
156
|
+
MAX_EVENT_WEBHOOK_TIMEOUT_MS,
|
|
145
157
|
);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const attemptsRaw = Number.parseInt(
|
|
151
|
-
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_MAX_ATTEMPTS") ?? "",
|
|
152
|
-
10,
|
|
158
|
+
const maxAttempts = boundedWebhookEnvInt(
|
|
159
|
+
webhookEnvValue(explicitEnv, "GJC_COORDINATOR_MCP_EVENT_WEBHOOK_MAX_ATTEMPTS"),
|
|
160
|
+
DEFAULT_EVENT_WEBHOOK_MAX_ATTEMPTS,
|
|
161
|
+
MAX_EVENT_WEBHOOK_MAX_ATTEMPTS,
|
|
153
162
|
);
|
|
154
|
-
const maxAttempts =
|
|
155
|
-
Number.isFinite(attemptsRaw) && attemptsRaw > 0
|
|
156
|
-
? Math.min(attemptsRaw, MAX_EVENT_WEBHOOK_MAX_ATTEMPTS)
|
|
157
|
-
: DEFAULT_EVENT_WEBHOOK_MAX_ATTEMPTS;
|
|
158
163
|
return { url: rawUrl, tokenFile: tokenFile || null, sessionIds, timeoutMs, maxAttempts };
|
|
159
164
|
}
|
|
160
165
|
|
|
@@ -86,9 +86,19 @@ const LEGACY_MUTATION_CLASS_ALIASES = new Map<string, CoordinatorMutationClass>(
|
|
|
86
86
|
["report", "reports"],
|
|
87
87
|
]);
|
|
88
88
|
|
|
89
|
+
// Number.parseInt reads a leading digit prefix ("64KB" -> 64, "1e6" -> 1,
|
|
90
|
+
// "1.5" -> 1), which would silently shrink a cap or TTL to a tiny value. Only
|
|
91
|
+
// plain decimal digits count; anything else falls back to the default.
|
|
92
|
+
function parsePositiveInt(value: string | undefined): number | undefined {
|
|
93
|
+
const trimmed = (value ?? "").trim();
|
|
94
|
+
if (!/^\d+$/.test(trimmed)) return undefined;
|
|
95
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
96
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
function parsePositiveIntMs(value: string | undefined, fallback: number, floor: number): number {
|
|
90
|
-
const parsed =
|
|
91
|
-
if (
|
|
100
|
+
const parsed = parsePositiveInt(value);
|
|
101
|
+
if (parsed === undefined) return fallback;
|
|
92
102
|
return Math.max(floor, parsed);
|
|
93
103
|
}
|
|
94
104
|
|
|
@@ -133,8 +143,8 @@ function parseMutationClasses(value: string | undefined): Set<CoordinatorMutatio
|
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
function parseByteCap(value: string | undefined): number {
|
|
136
|
-
const parsed =
|
|
137
|
-
if (
|
|
146
|
+
const parsed = parsePositiveInt(value);
|
|
147
|
+
if (parsed === undefined) return DEFAULT_ARTIFACT_BYTE_CAP;
|
|
138
148
|
return Math.min(parsed, MAX_ARTIFACT_BYTE_CAP);
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -740,13 +740,18 @@ function toolSchema(
|
|
|
740
740
|
return {
|
|
741
741
|
name,
|
|
742
742
|
description:
|
|
743
|
-
"Create a durable turn
|
|
743
|
+
"Create a durable turn, or set steer:true with the active turn_id to deliver requested input to that running turn. queue:true is only for a separate later task; it is not consumption acknowledgement.",
|
|
744
744
|
inputSchema: {
|
|
745
745
|
type: "object",
|
|
746
746
|
properties: {
|
|
747
747
|
session_id: sessionId,
|
|
748
748
|
prompt: { type: "string" },
|
|
749
749
|
queue: { type: "boolean" },
|
|
750
|
+
steer: { type: "boolean" },
|
|
751
|
+
turn_id: {
|
|
752
|
+
type: "string",
|
|
753
|
+
description: "Required with steer:true; the existing active coordinator turn.",
|
|
754
|
+
},
|
|
750
755
|
force: { type: "boolean" },
|
|
751
756
|
idempotency_key: idempotencyKey,
|
|
752
757
|
allow_mutation: allowMutation,
|
|
@@ -829,7 +834,12 @@ function toolSchema(
|
|
|
829
834
|
summary: { type: "string" },
|
|
830
835
|
blocker: { type: "string" },
|
|
831
836
|
pr_url: { type: "string" },
|
|
832
|
-
evidence_paths: {
|
|
837
|
+
evidence_paths: {
|
|
838
|
+
type: "array",
|
|
839
|
+
items: { type: "string" },
|
|
840
|
+
description:
|
|
841
|
+
"Evidence must be inside configured artifact roots. Agent session logs are not automatically allowed.",
|
|
842
|
+
},
|
|
833
843
|
idempotency_key: idempotencyKey,
|
|
834
844
|
allow_mutation: allowMutation,
|
|
835
845
|
},
|
|
@@ -1250,8 +1260,10 @@ const COORDINATOR_IDEMPOTENCY_RESPONSE_BYTE_CAP = 64 * 1024;
|
|
|
1250
1260
|
const COORDINATOR_IDEMPOTENCY_STRING_BYTE_CAP = 8 * 1024;
|
|
1251
1261
|
const PUBLIC_ERROR_MESSAGES: Record<string, string> = {
|
|
1252
1262
|
invalid_input: "Coordinator request input is invalid.",
|
|
1263
|
+
turn_not_active: "Feedback requires the selected active turn; no new coordinator turn was created.",
|
|
1253
1264
|
invalid_request: "Coordinator request is invalid.",
|
|
1254
1265
|
invalid_session_id: "Coordinator session id is invalid.",
|
|
1266
|
+
invalid_turn_id: "Coordinator turn id is invalid.",
|
|
1255
1267
|
unknown_operation: "Coordinator operation is unsupported.",
|
|
1256
1268
|
not_found: "Coordinator resource was not found.",
|
|
1257
1269
|
resource_gone: "Coordinator resource is no longer available.",
|
|
@@ -2608,8 +2620,20 @@ export async function appendCoordinatorEventForTest(
|
|
|
2608
2620
|
return appendCoordinatorEvent(namespaceDir, input);
|
|
2609
2621
|
}
|
|
2610
2622
|
|
|
2623
|
+
// Tool arguments and env values arrive untyped. `Number.parseInt(String(v))`
|
|
2624
|
+
// reads a leading digit prefix ("1e4" -> 1, "5s" -> 5) and flattens arrays
|
|
2625
|
+
// ([50] -> "50"), so a malformed value silently became a 1 ms timeout or a
|
|
2626
|
+
// one-row limit. Numbers pass through; strings must be plain decimal digits;
|
|
2627
|
+
// anything else is NaN so each caller falls back to its default.
|
|
2628
|
+
function integerArg(value: unknown): number {
|
|
2629
|
+
if (typeof value === "number") return value;
|
|
2630
|
+
if (typeof value !== "string") return Number.NaN;
|
|
2631
|
+
const trimmed = value.trim();
|
|
2632
|
+
return /^\d+$/.test(trimmed) ? Number.parseInt(trimmed, 10) : Number.NaN;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2611
2635
|
function boundedEventLimit(value: unknown): number {
|
|
2612
|
-
const parsed =
|
|
2636
|
+
const parsed = integerArg(value);
|
|
2613
2637
|
if (!Number.isFinite(parsed) || parsed <= 0) return 100;
|
|
2614
2638
|
return Math.min(parsed, 100);
|
|
2615
2639
|
}
|
|
@@ -3178,7 +3202,7 @@ export const COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS = 30_000;
|
|
|
3178
3202
|
export const COORDINATOR_POLL_INTERVAL_MAX_MS = 10_000;
|
|
3179
3203
|
|
|
3180
3204
|
function parsePositiveIntegerMs(value: unknown, fallback: number): number {
|
|
3181
|
-
const parsed =
|
|
3205
|
+
const parsed = integerArg(value);
|
|
3182
3206
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
3183
3207
|
}
|
|
3184
3208
|
|
|
@@ -3194,7 +3218,7 @@ export function boundedRuntimePromptAckTimeoutMs(value: unknown): number {
|
|
|
3194
3218
|
}
|
|
3195
3219
|
|
|
3196
3220
|
export function boundedEventWatchTimeoutMs(value: unknown): number {
|
|
3197
|
-
const parsed =
|
|
3221
|
+
const parsed = integerArg(value);
|
|
3198
3222
|
if (parsed === 0) return 0;
|
|
3199
3223
|
return Math.min(Number.isFinite(parsed) && parsed > 0 ? parsed : 1000, COORDINATOR_EVENT_WATCH_TIMEOUT_MAX_MS);
|
|
3200
3224
|
}
|
|
@@ -3204,7 +3228,7 @@ export function boundedPollIntervalMs(value: unknown): number {
|
|
|
3204
3228
|
}
|
|
3205
3229
|
|
|
3206
3230
|
function boundedLineCount(value: unknown): number {
|
|
3207
|
-
const parsed =
|
|
3231
|
+
const parsed = integerArg(value);
|
|
3208
3232
|
if (!Number.isFinite(parsed) || parsed <= 0) return 80;
|
|
3209
3233
|
return Math.min(parsed, 400);
|
|
3210
3234
|
}
|
|
@@ -5178,7 +5202,20 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5178
5202
|
}
|
|
5179
5203
|
|
|
5180
5204
|
async function queryLastAssistant(session: Record<string, unknown>): Promise<string | null> {
|
|
5181
|
-
|
|
5205
|
+
let item: unknown;
|
|
5206
|
+
try {
|
|
5207
|
+
item = sdkQueryPageItem(await querySession(session, "session.last_assistant"), "session.last_assistant");
|
|
5208
|
+
} catch (error) {
|
|
5209
|
+
// Compatibility with running older hosts: this exact Q17 diagnostic
|
|
5210
|
+
// denotes an absent text payload, not a missing session or closed store.
|
|
5211
|
+
if (
|
|
5212
|
+
error instanceof SdkClientError &&
|
|
5213
|
+
error.code === "resource_gone" &&
|
|
5214
|
+
error.message === "snapshot payload is unavailable"
|
|
5215
|
+
)
|
|
5216
|
+
return null;
|
|
5217
|
+
throw error;
|
|
5218
|
+
}
|
|
5182
5219
|
if (typeof item === "string") return item;
|
|
5183
5220
|
const message = asRecord(item);
|
|
5184
5221
|
return typeof message?.text === "string"
|
|
@@ -5227,6 +5264,19 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
5227
5264
|
function publicError(error: unknown): Record<string, unknown> {
|
|
5228
5265
|
if (error instanceof Error && error.message.startsWith("coordinator_mutation_call_not_allowed:"))
|
|
5229
5266
|
return { ok: false, reason: error.message };
|
|
5267
|
+
if (error instanceof Error) {
|
|
5268
|
+
const reason = error.message.startsWith("coordinator_artifact_outside_allowed_roots:")
|
|
5269
|
+
? "artifact_outside_allowed_roots"
|
|
5270
|
+
: error.message === "coordinator_artifact_state_root_denied"
|
|
5271
|
+
? "artifact_state_root_denied"
|
|
5272
|
+
: undefined;
|
|
5273
|
+
if (reason)
|
|
5274
|
+
return {
|
|
5275
|
+
ok: false,
|
|
5276
|
+
reason,
|
|
5277
|
+
error: { code: "invalid_input", message: PUBLIC_ERROR_MESSAGES.invalid_input },
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5230
5280
|
const directCode = error instanceof SdkClientError ? error.code : sinkErrorCode(error);
|
|
5231
5281
|
const messageCode =
|
|
5232
5282
|
error instanceof Error && Object.hasOwn(PUBLIC_ERROR_MESSAGES, error.message) ? error.message : undefined;
|
|
@@ -10354,6 +10404,91 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
|
|
|
10354
10404
|
const idempotencyKey = requiredIdempotencyKey(args);
|
|
10355
10405
|
const sessionId = safeExternalId("session", args.session_id);
|
|
10356
10406
|
const prompt = args.prompt;
|
|
10407
|
+
if (args.steer === true) {
|
|
10408
|
+
if (args.queue === true || args.force === true)
|
|
10409
|
+
return {
|
|
10410
|
+
ok: false,
|
|
10411
|
+
error: { code: "invalid_input", message: "steer cannot be combined with queue or force." },
|
|
10412
|
+
};
|
|
10413
|
+
const turnId = safeTurnId(args.turn_id);
|
|
10414
|
+
return await withToolIdempotency(
|
|
10415
|
+
name,
|
|
10416
|
+
idempotencyKey,
|
|
10417
|
+
{ session_id: sessionId, turn_id: turnId, prompt, steer: true, allow_mutation: true },
|
|
10418
|
+
async () =>
|
|
10419
|
+
await withSessionTransition(sessionId, async () => {
|
|
10420
|
+
const transaction = await readSessionTransaction(questionPaths, sessionId);
|
|
10421
|
+
const turn = transaction?.canonical.turns[turnId];
|
|
10422
|
+
if (
|
|
10423
|
+
!transaction ||
|
|
10424
|
+
!turn ||
|
|
10425
|
+
transaction.canonical.queue.active_turn_id !== turnId ||
|
|
10426
|
+
(turn.status !== "active" && turn.status !== "waiting_for_answer")
|
|
10427
|
+
)
|
|
10428
|
+
return {
|
|
10429
|
+
ok: false,
|
|
10430
|
+
error: {
|
|
10431
|
+
code: "turn_not_active",
|
|
10432
|
+
message: "Feedback requires the selected active turn; no new turn was created.",
|
|
10433
|
+
},
|
|
10434
|
+
};
|
|
10435
|
+
const runtimeCommandId = turn.delivery.runtime_command_id;
|
|
10436
|
+
const runtimeTurnId = turn.delivery.runtime_turn_id;
|
|
10437
|
+
if (
|
|
10438
|
+
typeof runtimeCommandId !== "string" ||
|
|
10439
|
+
runtimeCommandId.length === 0 ||
|
|
10440
|
+
typeof runtimeTurnId !== "string" ||
|
|
10441
|
+
runtimeTurnId.length === 0
|
|
10442
|
+
)
|
|
10443
|
+
return {
|
|
10444
|
+
ok: false,
|
|
10445
|
+
error: {
|
|
10446
|
+
code: "turn_not_active",
|
|
10447
|
+
message:
|
|
10448
|
+
"Feedback requires the selected active turn's runtime identity; no new turn was created.",
|
|
10449
|
+
},
|
|
10450
|
+
};
|
|
10451
|
+
const session = asRecord(await readJsonFile(sessionFile(sessionId)));
|
|
10452
|
+
if (!session) throw new Error("resource_gone");
|
|
10453
|
+
await assertPersistedSessionAuthority(transaction.canonical.session);
|
|
10454
|
+
const clientRef = `coordinator-feedback:${createHash("sha256")
|
|
10455
|
+
.update(JSON.stringify([sessionId, turnId, idempotencyKey]))
|
|
10456
|
+
.digest("hex")}`;
|
|
10457
|
+
const expectedSdkRunToken = `${runtimeCommandId}:${runtimeTurnId}`;
|
|
10458
|
+
const response = asRecord(
|
|
10459
|
+
await controlSession(
|
|
10460
|
+
session,
|
|
10461
|
+
"turn.steer",
|
|
10462
|
+
{ text: prompt, clientRef, expectedSdkRunToken },
|
|
10463
|
+
idempotencyKey,
|
|
10464
|
+
),
|
|
10465
|
+
);
|
|
10466
|
+
const result = asRecord(response?.result);
|
|
10467
|
+
return {
|
|
10468
|
+
ok: result?.accepted === true,
|
|
10469
|
+
session_id: sessionId,
|
|
10470
|
+
turn_id: turnId,
|
|
10471
|
+
delivery: "steer",
|
|
10472
|
+
queued: false,
|
|
10473
|
+
consumption_verified: false,
|
|
10474
|
+
steer_client_ref: clientRef,
|
|
10475
|
+
result,
|
|
10476
|
+
};
|
|
10477
|
+
}),
|
|
10478
|
+
true,
|
|
10479
|
+
response => {
|
|
10480
|
+
const result = asRecord(response.result);
|
|
10481
|
+
const resultError = asRecord(result?.error);
|
|
10482
|
+
return (
|
|
10483
|
+
["ambiguous", "uncertain_after_send", "connection_closed", "timeout"].includes(
|
|
10484
|
+
String(asRecord(response.error)?.code),
|
|
10485
|
+
) ||
|
|
10486
|
+
result?.status === "uncertain" ||
|
|
10487
|
+
resultError?.code === "delivery_uncertain"
|
|
10488
|
+
);
|
|
10489
|
+
},
|
|
10490
|
+
);
|
|
10491
|
+
}
|
|
10357
10492
|
return await withToolIdempotency(
|
|
10358
10493
|
name,
|
|
10359
10494
|
idempotencyKey,
|
|
@@ -22,6 +22,11 @@ export const DEFAULT_SESSION_IDLE_TTL_MS = 30 * 60_000; // 30 min idle → reap
|
|
|
22
22
|
export const DEFAULT_SESSION_SWEEP_INTERVAL_MS = 5 * 60_000; // sweep every 5 min
|
|
23
23
|
export const MIN_SESSION_IDLE_TTL_MS = 60_000; // never reap a <1min-idle session
|
|
24
24
|
export const MIN_SESSION_SWEEP_INTERVAL_MS = 30_000;
|
|
25
|
+
/**
|
|
26
|
+
* Longest delay setTimeout honors. Bun, like Node, treats any larger delay as 1 ms,
|
|
27
|
+
* so an operator's very long sweep interval would otherwise re-sweep back to back.
|
|
28
|
+
*/
|
|
29
|
+
export const MAX_SESSION_SWEEP_INTERVAL_MS = 2 ** 31 - 1;
|
|
25
30
|
/** After this many consecutive reap failures the session is force-evicted from the index. */
|
|
26
31
|
export const MAX_REAP_FAILURES = 3;
|
|
27
32
|
|
|
@@ -92,7 +97,10 @@ export interface SessionReaper {
|
|
|
92
97
|
|
|
93
98
|
export function createSessionReaper(deps: SessionReaperDeps, policy: SessionReaperPolicy): SessionReaper {
|
|
94
99
|
const idleTtlMs = Math.max(MIN_SESSION_IDLE_TTL_MS, policy.idleTtlMs);
|
|
95
|
-
const sweepIntervalMs = Math.
|
|
100
|
+
const sweepIntervalMs = Math.min(
|
|
101
|
+
MAX_SESSION_SWEEP_INTERVAL_MS,
|
|
102
|
+
Math.max(MIN_SESSION_SWEEP_INTERVAL_MS, policy.sweepIntervalMs),
|
|
103
|
+
);
|
|
96
104
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
97
105
|
let generation = 0;
|
|
98
106
|
let inProgress = false;
|
|
@@ -265,7 +265,7 @@ Is that topology and locked intent right? Should any component or intent be adde
|
|
|
265
265
|
|
|
266
266
|
Options should include contextually relevant choices such as **Looks right**, **Add/remove/merge components**, **Defer one or more components**, plus free-text, translated/localized according to `language.instruction` when present. This is the only pre-scoring question and preserves the one-question-per-round rule.
|
|
267
267
|
|
|
268
|
-
The Round 0 `ask` call MUST include `deepInterview.round = 0`, `deepInterview.component = "review-topology"`, `deepInterview.dimension = "topology"`, `deepInterview.ambiguity` (the observed 0..1 ambiguity at ask time; `1` before Round 1 scoring), `deepInterview.intent_contract.items` containing the exact displayed locked-intent items, and `deepInterview.intent_contract.confirmation_options` listing only the displayed affirmative labels that lock the proposal (normally **Looks right**). The runtime recorder canonicalizes and locks this contract only when the user selects one of those labels; correction, deferral, free-text, and clarification answers never lock the pre-question proposal. Do not manually copy raw free text into intent evidence, and do not continue if this required recorder write fails. An incomplete Round 0 object (for example missing `deepInterview.ambiguity` or the whole `deepInterview.intent_contract`) is rejected before coercion with a correction naming the omitted fields; retry once with every required field populated from the visible payload context, never by re-sending the incomplete object or inventing contract items — the recorder never locks intent without an affirmative user answer.
|
|
268
|
+
While state is in the `interviewing` phase, every `ask` MUST contain exactly one non-empty question with structured `deepInterview` metadata on that question; never add an empty `metadata` or `ignore` question. The entire `deepInterview` object is required, not merely the prose round header or `workflowGate`. After native final-spec persistence transitions to `handoff`, Phase 5 next-workflow/approval asks are ordinary asks without interview round metadata. The Round 0 `ask` call MUST include `deepInterview.round = 0`, `deepInterview.component = "review-topology"`, `deepInterview.dimension = "topology"`, `deepInterview.ambiguity` (the observed 0..1 ambiguity at ask time; `1` before Round 1 scoring), `deepInterview.intent_contract.items` containing the exact displayed locked-intent items, and `deepInterview.intent_contract.confirmation_options` listing only the displayed affirmative labels that lock the proposal (normally **Looks right**). The runtime recorder canonicalizes and locks this contract only when the user selects one of those labels; correction, deferral, free-text, and clarification answers never lock the pre-question proposal. Do not manually copy raw free text into intent evidence, and do not continue if this required recorder write fails. An incomplete Round 0 object (for example missing `deepInterview.ambiguity` or the whole `deepInterview.intent_contract`) is rejected before coercion with a correction naming the omitted fields; retry once with every required field populated from the visible payload context, never by re-sending the incomplete object or inventing contract items — the recorder never locks intent without an affirmative user answer.
|
|
269
269
|
|
|
270
270
|
3. **Lock topology into state** after the answer. Store a normalized component list and confirmation timestamp:
|
|
271
271
|
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -203,10 +203,13 @@ export function getProjectPath(ctx: LoadContext, source: SourceId, subpath: stri
|
|
|
203
203
|
|
|
204
204
|
/** Build the filesystem authority for a provider read. */
|
|
205
205
|
export function getReadOptions(
|
|
206
|
-
ctx: Pick<
|
|
206
|
+
ctx: Pick<
|
|
207
|
+
LoadContext,
|
|
208
|
+
"home" | "isolatedHome" | "userAgentDir" | "homeIdentity" | "userAgentIdentity" | "bypassCache"
|
|
209
|
+
>,
|
|
207
210
|
scope: ReadScope,
|
|
208
211
|
): ReadFileOptions | undefined {
|
|
209
|
-
if (!ctx.isolatedHome) return undefined;
|
|
212
|
+
if (!ctx.isolatedHome) return ctx.bypassCache ? { bypassCache: true } : undefined;
|
|
210
213
|
return {
|
|
211
214
|
isolatedHome: true,
|
|
212
215
|
home: ctx.home,
|
|
@@ -219,7 +222,10 @@ export function getReadOptions(
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
export async function getReadOptionsForContainment(
|
|
222
|
-
ctx: Pick<
|
|
225
|
+
ctx: Pick<
|
|
226
|
+
LoadContext,
|
|
227
|
+
"home" | "isolatedHome" | "userAgentDir" | "homeIdentity" | "userAgentIdentity" | "bypassCache"
|
|
228
|
+
>,
|
|
223
229
|
scope: ReadScope,
|
|
224
230
|
containmentRoot?: string,
|
|
225
231
|
): Promise<ReadFileOptions | undefined> {
|
package/src/discovery/ssh.ts
CHANGED
|
@@ -34,9 +34,14 @@ interface SSHConfigFile {
|
|
|
34
34
|
|
|
35
35
|
function parsePort(value: number | string | undefined): number | undefined {
|
|
36
36
|
if (value === undefined) return undefined;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// The JSON is not validated against SSHConfigFile, so anything else (e.g. an
|
|
38
|
+
// array, which parseInt would coerce: parseInt([2222]) === 2222) is rejected.
|
|
39
|
+
if (typeof value !== "number" && typeof value !== "string") return undefined;
|
|
40
|
+
// `Number.parseInt` accepts trailing garbage (parseInt("22oops") === 22), so a
|
|
41
|
+
// string must be plain digits.
|
|
42
|
+
if (typeof value === "string" && !/^\d+$/.test(value)) return undefined;
|
|
43
|
+
const port = typeof value === "number" ? value : Number.parseInt(value, 10);
|
|
44
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : undefined;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
function parseCompat(value: boolean | string | undefined): boolean | undefined {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fuzzy matching utilities for the edit tool.
|
|
3
3
|
*
|
|
4
|
-
* Provides both character-level and line-level fuzzy matching with
|
|
5
|
-
*
|
|
4
|
+
* Provides both character-level and line-level fuzzy matching with staged
|
|
5
|
+
* strategies for finding text in files.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentToolResult } from "@gajae-code/agent-core";
|
|
8
8
|
import { markDesignedError } from "@gajae-code/utils/error-classification";
|
|
@@ -430,7 +430,7 @@ export function findContextLine(
|
|
|
430
430
|
lines: string[],
|
|
431
431
|
context: string,
|
|
432
432
|
startFrom: number,
|
|
433
|
-
options?: { allowFuzzy?: boolean;
|
|
433
|
+
options?: { allowFuzzy?: boolean; skipParenRetry?: boolean },
|
|
434
434
|
): ContextLineResult {
|
|
435
435
|
const allowFuzzy = options?.allowFuzzy ?? true;
|
|
436
436
|
const trimmedContext = context.trim();
|
|
@@ -570,14 +570,14 @@ export function findContextLine(
|
|
|
570
570
|
};
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
if (!options?.
|
|
573
|
+
if (!options?.skipParenRetry && trimmedContext.endsWith("()")) {
|
|
574
574
|
const withParen = trimmedContext.replace(/\(\)\s*$/u, "(");
|
|
575
575
|
const withoutParen = trimmedContext.replace(/\(\)\s*$/u, "");
|
|
576
|
-
const parenResult = findContextLine(lines, withParen, startFrom, { allowFuzzy,
|
|
576
|
+
const parenResult = findContextLine(lines, withParen, startFrom, { allowFuzzy, skipParenRetry: true });
|
|
577
577
|
if (parenResult.index !== undefined || (parenResult.matchCount ?? 0) > 0) {
|
|
578
578
|
return parenResult;
|
|
579
579
|
}
|
|
580
|
-
return findContextLine(lines, withoutParen, startFrom, { allowFuzzy,
|
|
580
|
+
return findContextLine(lines, withoutParen, startFrom, { allowFuzzy, skipParenRetry: true });
|
|
581
581
|
}
|
|
582
582
|
|
|
583
583
|
return { index: undefined, confidence: bestScore };
|