@astrosheep/keiyaku 2.9.2 → 2.9.4
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/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +13 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +25 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +47 -4
- package/build/agents/harness/projection.js +42 -59
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +50 -53
- package/build/agents/providers/codex-app-server/events.js +67 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +8 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/arc/handler.js +7 -1
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +34 -7
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/petition/handler.js +4 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +58 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +7 -2
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/task/add/handler.js +5 -13
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/ls/handler.js +3 -5
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/update/handler.js +4 -8
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +4 -11
- package/build/cli/index.js +57 -83
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/parse-flags.js +14 -5
- package/build/cli/parse-metadata.js +3 -3
- package/build/cli/parse-selectors.js +1 -1
- package/build/cli/parse.js +21 -0
- package/build/cli/projection-address.js +7 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +55 -12
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +3 -0
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +37 -14
- package/build/cli/render/success-response.js +77 -0
- package/build/cli/render/task.js +54 -14
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +58 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +38 -36
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +1 -1
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +135 -18
- package/build/core/arc.js +19 -67
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +21 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +20 -3
- package/build/core/draft.js +123 -16
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +2 -38
- package/build/core/projection/generation/model.js +42 -1
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
- package/build/core/projection/generation/store.js +2 -2
- package/build/core/projection/generation/transitions.js +1 -1
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
- package/build/core/projection/projection-terminal-failure.js +104 -0
- package/build/core/projection/projection-wait.js +278 -0
- package/build/core/projection/projection-wake.js +203 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +426 -99
- package/build/core/scope.js +143 -66
- package/build/core/seal.js +139 -35
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
- package/build/core/{petition.js → settlement/petition.js} +12 -10
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +295 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/status/board.js +24 -23
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +3 -0
- package/build/core/task/board.js +40 -4
- package/build/core/task/commands.js +30 -21
- package/build/core/task/index.js +12 -0
- package/build/core/task/settlement-git.js +157 -65
- package/build/core/task/settlement-policy.js +54 -24
- package/build/core/task/task-contract.js +370 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +5 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +85 -39
- package/build/fs/durability.js +27 -0
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +3 -3
- package/build/keiyaku.js +2 -36
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +3 -2
- package/skills/keiyaku-akuma/SKILL.md +10 -6
- package/skills/keiyaku-task/SKILL.md +1 -1
- package/skills/keiyaku-workflow/SKILL.md +25 -4
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
|
@@ -1,91 +1,11 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { getProviderRegistration, isSubagentProvider, } from "../providers/registry.js";
|
|
3
|
+
import { exactKeys, objectAt, optionalString, requiredEnum, requiredString, snapshotError, } from "./parse-helpers.js";
|
|
3
4
|
const ACCESS_VALUES = new Set(["read", "write", "auto"]);
|
|
4
5
|
const NETWORK_VALUES = new Set(["disabled", "enabled"]);
|
|
5
6
|
const WEB_SEARCH_VALUES = new Set(["disabled", "cached", "live"]);
|
|
6
7
|
const AKUMA_SOURCES = new Set(["builtin", "user", "project"]);
|
|
7
8
|
const PROVIDER_SOURCES = new Set(["builtin", "global", "project"]);
|
|
8
|
-
const CLAUDE_SETTING_SOURCES = new Set(["project", "user", "local"]);
|
|
9
|
-
function snapshotError(fieldPath, message) {
|
|
10
|
-
return new Error(`Invalid resolved Akuma launch snapshot at ${fieldPath}: ${message}`);
|
|
11
|
-
}
|
|
12
|
-
function isPlainObject(value) {
|
|
13
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
14
|
-
return false;
|
|
15
|
-
const prototype = Object.getPrototypeOf(value);
|
|
16
|
-
return prototype === Object.prototype || prototype === null;
|
|
17
|
-
}
|
|
18
|
-
function objectAt(value, fieldPath) {
|
|
19
|
-
if (!isPlainObject(value))
|
|
20
|
-
throw snapshotError(fieldPath, "must be a plain JSON object");
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
function exactKeys(value, allowed, fieldPath) {
|
|
24
|
-
const allowedSet = new Set(allowed);
|
|
25
|
-
for (const key of Object.keys(value)) {
|
|
26
|
-
if (!allowedSet.has(key))
|
|
27
|
-
throw snapshotError(`${fieldPath}.${key}`, "is an unknown field");
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function requiredString(value, key, fieldPath, allowBlank = false) {
|
|
31
|
-
const item = value[key];
|
|
32
|
-
if (typeof item !== "string" || (!allowBlank && item.trim().length === 0)) {
|
|
33
|
-
throw snapshotError(`${fieldPath}.${key}`, allowBlank ? "must be a string" : "must be a nonblank string");
|
|
34
|
-
}
|
|
35
|
-
return item;
|
|
36
|
-
}
|
|
37
|
-
function optionalString(value, key, fieldPath) {
|
|
38
|
-
if (!(key in value))
|
|
39
|
-
return undefined;
|
|
40
|
-
return requiredString(value, key, fieldPath);
|
|
41
|
-
}
|
|
42
|
-
function requiredEnum(value, key, fieldPath, values) {
|
|
43
|
-
const item = requiredString(value, key, fieldPath);
|
|
44
|
-
if (!values.has(item))
|
|
45
|
-
throw snapshotError(`${fieldPath}.${key}`, `has invalid value '${item}'`);
|
|
46
|
-
return item;
|
|
47
|
-
}
|
|
48
|
-
function cloneJson(value, fieldPath, ancestors = new Set()) {
|
|
49
|
-
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
50
|
-
return value;
|
|
51
|
-
if (typeof value === "number") {
|
|
52
|
-
if (!Number.isFinite(value))
|
|
53
|
-
throw snapshotError(fieldPath, "contains a non-finite number");
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
if (typeof value !== "object")
|
|
57
|
-
throw snapshotError(fieldPath, "contains a non-JSON value");
|
|
58
|
-
if (ancestors.has(value))
|
|
59
|
-
throw snapshotError(fieldPath, "contains a cycle");
|
|
60
|
-
ancestors.add(value);
|
|
61
|
-
try {
|
|
62
|
-
if (Array.isArray(value)) {
|
|
63
|
-
return Object.freeze(value.map((item, index) => cloneJson(item, `${fieldPath}[${index}]`, ancestors)));
|
|
64
|
-
}
|
|
65
|
-
if (!isPlainObject(value))
|
|
66
|
-
throw snapshotError(fieldPath, "contains a non-plain JSON object");
|
|
67
|
-
const result = {};
|
|
68
|
-
for (const [key, item] of Object.entries(value)) {
|
|
69
|
-
Object.defineProperty(result, key, {
|
|
70
|
-
value: cloneJson(item, `${fieldPath}.${key}`, ancestors),
|
|
71
|
-
enumerable: true,
|
|
72
|
-
configurable: true,
|
|
73
|
-
writable: true,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return Object.freeze(result);
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
ancestors.delete(value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function cloneJsonObject(value, fieldPath) {
|
|
83
|
-
const cloned = cloneJson(value, fieldPath);
|
|
84
|
-
if (!isPlainObject(cloned)) {
|
|
85
|
-
throw snapshotError(fieldPath, "must be a plain JSON object");
|
|
86
|
-
}
|
|
87
|
-
return cloned;
|
|
88
|
-
}
|
|
89
9
|
function effortOptionsFrom(akuma) {
|
|
90
10
|
if (!("effortOptions" in akuma))
|
|
91
11
|
return undefined;
|
|
@@ -118,21 +38,7 @@ function assertEffortRequiresModel(model, effort, kind, akumaName) {
|
|
|
118
38
|
throw new FlowError("INVALID_EFFORT_OVERRIDE", `Effort '${effort}' requires an explicit model.`);
|
|
119
39
|
}
|
|
120
40
|
function providerEffortAndModel(provider) {
|
|
121
|
-
|
|
122
|
-
case "codex-sdk": {
|
|
123
|
-
const model = typeof provider.threadOptions.model === "string" ? provider.threadOptions.model : undefined;
|
|
124
|
-
const effort = typeof provider.threadOptions.modelReasoningEffort === "string"
|
|
125
|
-
? provider.threadOptions.modelReasoningEffort
|
|
126
|
-
: undefined;
|
|
127
|
-
return { model, effort };
|
|
128
|
-
}
|
|
129
|
-
case "codex-app-server":
|
|
130
|
-
case "claude-agent-sdk":
|
|
131
|
-
case "opencode-sdk":
|
|
132
|
-
return { model: provider.model, effort: provider.effort };
|
|
133
|
-
case "pi":
|
|
134
|
-
return { model: provider.model, effort: provider.thinkingLevel };
|
|
135
|
-
}
|
|
41
|
+
return getProviderRegistration(provider.kind).providerEffortAndModel(provider);
|
|
136
42
|
}
|
|
137
43
|
function assertSnapshotEffortRequiresModel(snapshot) {
|
|
138
44
|
const fromAkuma = { model: snapshot.akuma.model, effort: snapshot.akuma.effort };
|
|
@@ -141,7 +47,8 @@ function assertSnapshotEffortRequiresModel(snapshot) {
|
|
|
141
47
|
}
|
|
142
48
|
const fromProvider = providerEffortAndModel(snapshot.provider);
|
|
143
49
|
if (fromProvider.effort !== undefined && fromProvider.model === undefined) {
|
|
144
|
-
|
|
50
|
+
const effortField = getProviderRegistration(snapshot.provider.kind).effortField;
|
|
51
|
+
throw snapshotError(`provider.${effortField}`, "requires an explicit model");
|
|
145
52
|
}
|
|
146
53
|
}
|
|
147
54
|
function parsePolicy(value) {
|
|
@@ -184,133 +91,14 @@ function parseProviderCommon(provider) {
|
|
|
184
91
|
source: requiredEnum(provider, "source", "provider", PROVIDER_SOURCES),
|
|
185
92
|
};
|
|
186
93
|
}
|
|
187
|
-
function optionalJsonObject(provider, key) {
|
|
188
|
-
return key in provider ? cloneJsonObject(provider[key], `provider.${key}`) : undefined;
|
|
189
|
-
}
|
|
190
|
-
function optionalStringRecord(provider, key) {
|
|
191
|
-
if (!(key in provider))
|
|
192
|
-
return undefined;
|
|
193
|
-
const value = provider[key];
|
|
194
|
-
if (!isPlainObject(value))
|
|
195
|
-
throw snapshotError(`provider.${key}`, "must be a plain JSON object");
|
|
196
|
-
const result = {};
|
|
197
|
-
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
198
|
-
if (typeof entryValue !== "string") {
|
|
199
|
-
throw snapshotError(`provider.${key}.${entryKey}`, "must be a string");
|
|
200
|
-
}
|
|
201
|
-
result[entryKey] = entryValue;
|
|
202
|
-
}
|
|
203
|
-
return Object.freeze(result);
|
|
204
|
-
}
|
|
205
|
-
function parseSandboxPolicy(value) {
|
|
206
|
-
const sandbox = objectAt(value, "provider.sandboxPolicy");
|
|
207
|
-
exactKeys(sandbox, ["type", "writableRoots", "networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"], "provider.sandboxPolicy");
|
|
208
|
-
if (sandbox.type !== "workspaceWrite")
|
|
209
|
-
throw snapshotError("provider.sandboxPolicy.type", "must equal 'workspaceWrite'");
|
|
210
|
-
if (!Array.isArray(sandbox.writableRoots) || sandbox.writableRoots.length === 0) {
|
|
211
|
-
throw snapshotError("provider.sandboxPolicy.writableRoots", "must be a nonempty array");
|
|
212
|
-
}
|
|
213
|
-
const roots = sandbox.writableRoots.map((root, index) => {
|
|
214
|
-
if (typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root) {
|
|
215
|
-
throw snapshotError(`provider.sandboxPolicy.writableRoots[${index}]`, "must be a lexically normalized absolute path");
|
|
216
|
-
}
|
|
217
|
-
return root;
|
|
218
|
-
});
|
|
219
|
-
if (new Set(roots).size !== roots.length) {
|
|
220
|
-
throw snapshotError("provider.sandboxPolicy.writableRoots", "must not contain duplicates");
|
|
221
|
-
}
|
|
222
|
-
for (const key of ["networkAccess", "excludeTmpdirEnvVar", "excludeSlashTmp"]) {
|
|
223
|
-
if (typeof sandbox[key] !== "boolean")
|
|
224
|
-
throw snapshotError(`provider.sandboxPolicy.${key}`, "must be a boolean");
|
|
225
|
-
}
|
|
226
|
-
Object.freeze(roots);
|
|
227
|
-
return Object.freeze({
|
|
228
|
-
type: "workspaceWrite",
|
|
229
|
-
writableRoots: roots,
|
|
230
|
-
networkAccess: sandbox.networkAccess,
|
|
231
|
-
excludeTmpdirEnvVar: sandbox.excludeTmpdirEnvVar,
|
|
232
|
-
excludeSlashTmp: sandbox.excludeSlashTmp,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
94
|
function parseProvider(value) {
|
|
236
95
|
const provider = objectAt(value, "provider");
|
|
237
96
|
const kind = requiredString(provider, "kind", "provider");
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
case "codex-sdk": {
|
|
241
|
-
exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "threadOptions"], "provider");
|
|
242
|
-
if (!("threadOptions" in provider))
|
|
243
|
-
throw snapshotError("provider.threadOptions", "is required");
|
|
244
|
-
const executable = optionalString(provider, "executable", "provider");
|
|
245
|
-
const profile = optionalString(provider, "profile", "provider");
|
|
246
|
-
const config = optionalJsonObject(provider, "config");
|
|
247
|
-
return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), threadOptions: cloneJsonObject(provider.threadOptions, "provider.threadOptions") });
|
|
248
|
-
}
|
|
249
|
-
case "codex-app-server": {
|
|
250
|
-
exactKeys(provider, ["name", "source", "kind", "executable", "profile", "config", "model", "effort", "sandboxPolicy", "approvalPolicy", "env"], "provider");
|
|
251
|
-
if ("approvalPolicy" in provider && provider.approvalPolicy !== "never")
|
|
252
|
-
throw snapshotError("provider.approvalPolicy", "must equal 'never'");
|
|
253
|
-
const executable = optionalString(provider, "executable", "provider");
|
|
254
|
-
const profile = optionalString(provider, "profile", "provider");
|
|
255
|
-
const config = optionalJsonObject(provider, "config");
|
|
256
|
-
const model = optionalString(provider, "model", "provider");
|
|
257
|
-
const effort = optionalString(provider, "effort", "provider");
|
|
258
|
-
const sandboxPolicy = "sandboxPolicy" in provider ? parseSandboxPolicy(provider.sandboxPolicy) : undefined;
|
|
259
|
-
const env = optionalStringRecord(provider, "env");
|
|
260
|
-
return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(profile !== undefined ? { profile } : {}), ...(config !== undefined ? { config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}), ...(provider.approvalPolicy === "never" ? { approvalPolicy: "never" } : {}), ...(env !== undefined ? { env } : {}) });
|
|
261
|
-
}
|
|
262
|
-
case "claude-agent-sdk": {
|
|
263
|
-
exactKeys(provider, ["name", "source", "kind", "executable", "model", "effort", "settingSources", "permissionMode", "allowDangerouslySkipPermissions", "disallowedTools", "env"], "provider");
|
|
264
|
-
const modes = new Set(["default", "plan", "acceptEdits", "bypassPermissions"]);
|
|
265
|
-
const permissionMode = requiredEnum(provider, "permissionMode", "provider", modes);
|
|
266
|
-
if (permissionMode === "bypassPermissions") {
|
|
267
|
-
if (provider.allowDangerouslySkipPermissions !== true)
|
|
268
|
-
throw snapshotError("provider.allowDangerouslySkipPermissions", "must be true for bypassPermissions");
|
|
269
|
-
}
|
|
270
|
-
else if ("allowDangerouslySkipPermissions" in provider) {
|
|
271
|
-
throw snapshotError("provider.allowDangerouslySkipPermissions", "is only valid for bypassPermissions");
|
|
272
|
-
}
|
|
273
|
-
let settingSources;
|
|
274
|
-
if ("settingSources" in provider) {
|
|
275
|
-
if (!Array.isArray(provider.settingSources))
|
|
276
|
-
throw snapshotError("provider.settingSources", "must be an array");
|
|
277
|
-
settingSources = Object.freeze(provider.settingSources.map((source, index) => {
|
|
278
|
-
if (typeof source !== "string" || !CLAUDE_SETTING_SOURCES.has(source))
|
|
279
|
-
throw snapshotError(`provider.settingSources[${index}]`, "has an invalid setting source");
|
|
280
|
-
return source;
|
|
281
|
-
}));
|
|
282
|
-
}
|
|
283
|
-
let disallowedTools;
|
|
284
|
-
if ("disallowedTools" in provider) {
|
|
285
|
-
if (!Array.isArray(provider.disallowedTools) || provider.disallowedTools.length !== 1 || provider.disallowedTools[0] !== "WebSearch") {
|
|
286
|
-
throw snapshotError("provider.disallowedTools", "must equal ['WebSearch']");
|
|
287
|
-
}
|
|
288
|
-
disallowedTools = Object.freeze(["WebSearch"]);
|
|
289
|
-
}
|
|
290
|
-
const executable = optionalString(provider, "executable", "provider");
|
|
291
|
-
const model = optionalString(provider, "model", "provider");
|
|
292
|
-
const effort = optionalString(provider, "effort", "provider");
|
|
293
|
-
const env = optionalStringRecord(provider, "env");
|
|
294
|
-
return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(settingSources !== undefined ? { settingSources } : {}), permissionMode, ...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}), ...(disallowedTools !== undefined ? { disallowedTools } : {}), ...(env !== undefined ? { env } : {}) });
|
|
295
|
-
}
|
|
296
|
-
case "opencode-sdk": {
|
|
297
|
-
exactKeys(provider, ["name", "source", "kind", "executable", "model", "effort", "env"], "provider");
|
|
298
|
-
const executable = optionalString(provider, "executable", "provider");
|
|
299
|
-
const model = optionalString(provider, "model", "provider");
|
|
300
|
-
const effort = optionalString(provider, "effort", "provider");
|
|
301
|
-
const env = optionalStringRecord(provider, "env");
|
|
302
|
-
return Object.freeze({ ...common, kind, ...(executable !== undefined ? { executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(env !== undefined ? { env } : {}) });
|
|
303
|
-
}
|
|
304
|
-
case "pi": {
|
|
305
|
-
exactKeys(provider, ["name", "source", "kind", "model", "thinkingLevel", "env"], "provider");
|
|
306
|
-
const model = optionalString(provider, "model", "provider");
|
|
307
|
-
const thinkingLevel = optionalString(provider, "thinkingLevel", "provider");
|
|
308
|
-
const env = optionalStringRecord(provider, "env");
|
|
309
|
-
return Object.freeze({ ...common, kind, ...(model !== undefined ? { model } : {}), ...(thinkingLevel !== undefined ? { thinkingLevel } : {}), ...(env !== undefined ? { env } : {}) });
|
|
310
|
-
}
|
|
311
|
-
default:
|
|
312
|
-
throw snapshotError("provider.kind", `has invalid value '${kind}'`);
|
|
97
|
+
if (!isSubagentProvider(kind)) {
|
|
98
|
+
throw snapshotError("provider.kind", `has invalid value '${kind}'`);
|
|
313
99
|
}
|
|
100
|
+
const common = parseProviderCommon(provider);
|
|
101
|
+
return getProviderRegistration(kind).parseLaunchProvider(provider, common);
|
|
314
102
|
}
|
|
315
103
|
export function parseResolvedAkumaLaunchSnapshot(value) {
|
|
316
104
|
const snapshot = objectAt(value, "resolved Akuma launch snapshot");
|
|
@@ -321,7 +109,11 @@ export function parseResolvedAkumaLaunchSnapshot(value) {
|
|
|
321
109
|
throw snapshotError("akuma", "is required");
|
|
322
110
|
if (!("provider" in snapshot))
|
|
323
111
|
throw snapshotError("provider", "is required");
|
|
324
|
-
const parsed = Object.freeze({
|
|
112
|
+
const parsed = Object.freeze({
|
|
113
|
+
version: 1,
|
|
114
|
+
akuma: parseAkuma(snapshot.akuma),
|
|
115
|
+
provider: parseProvider(snapshot.provider),
|
|
116
|
+
});
|
|
325
117
|
assertSnapshotEffortRequiresModel(parsed);
|
|
326
118
|
return parsed;
|
|
327
119
|
}
|
|
@@ -329,20 +121,7 @@ export function applySnapshotEffort(snapshot, effort) {
|
|
|
329
121
|
assertEffortAllowed(effort, snapshot.akuma.effortOptions);
|
|
330
122
|
assertEffortRequiresModel(snapshot.akuma.model, effort, "override");
|
|
331
123
|
const akuma = { ...snapshot.akuma, effort };
|
|
332
|
-
|
|
333
|
-
switch (snapshot.provider.kind) {
|
|
334
|
-
case "codex-sdk":
|
|
335
|
-
provider = { ...snapshot.provider, threadOptions: { ...snapshot.provider.threadOptions, modelReasoningEffort: effort } };
|
|
336
|
-
break;
|
|
337
|
-
case "codex-app-server":
|
|
338
|
-
case "claude-agent-sdk":
|
|
339
|
-
case "opencode-sdk":
|
|
340
|
-
provider = { ...snapshot.provider, effort };
|
|
341
|
-
break;
|
|
342
|
-
case "pi":
|
|
343
|
-
provider = { ...snapshot.provider, thinkingLevel: effort };
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
124
|
+
const provider = getProviderRegistration(snapshot.provider.kind).applyEffort(snapshot.provider, effort);
|
|
346
125
|
return parseResolvedAkumaLaunchSnapshot({ version: 1, akuma, provider });
|
|
347
126
|
}
|
|
348
127
|
export { assertEffortAllowed, assertEffortRequiresModel };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
export function snapshotError(fieldPath, message) {
|
|
3
|
+
return new Error(`Invalid resolved Akuma launch snapshot at ${fieldPath}: ${message}`);
|
|
4
|
+
}
|
|
5
|
+
export function isPlainObject(value) {
|
|
6
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
7
|
+
return false;
|
|
8
|
+
const prototype = Object.getPrototypeOf(value);
|
|
9
|
+
return prototype === Object.prototype || prototype === null;
|
|
10
|
+
}
|
|
11
|
+
export function objectAt(value, fieldPath) {
|
|
12
|
+
if (!isPlainObject(value))
|
|
13
|
+
throw snapshotError(fieldPath, "must be a plain JSON object");
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export function exactKeys(value, allowed, fieldPath) {
|
|
17
|
+
const allowedSet = new Set(allowed);
|
|
18
|
+
for (const key of Object.keys(value)) {
|
|
19
|
+
if (!allowedSet.has(key))
|
|
20
|
+
throw snapshotError(`${fieldPath}.${key}`, "is an unknown field");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function requiredString(value, key, fieldPath, allowBlank = false) {
|
|
24
|
+
const item = value[key];
|
|
25
|
+
if (typeof item !== "string" || (!allowBlank && item.trim().length === 0)) {
|
|
26
|
+
throw snapshotError(`${fieldPath}.${key}`, allowBlank ? "must be a string" : "must be a nonblank string");
|
|
27
|
+
}
|
|
28
|
+
return item;
|
|
29
|
+
}
|
|
30
|
+
export function optionalString(value, key, fieldPath) {
|
|
31
|
+
if (!(key in value))
|
|
32
|
+
return undefined;
|
|
33
|
+
return requiredString(value, key, fieldPath);
|
|
34
|
+
}
|
|
35
|
+
export function requiredEnum(value, key, fieldPath, values) {
|
|
36
|
+
const item = requiredString(value, key, fieldPath);
|
|
37
|
+
if (!values.has(item))
|
|
38
|
+
throw snapshotError(`${fieldPath}.${key}`, `has invalid value '${item}'`);
|
|
39
|
+
return item;
|
|
40
|
+
}
|
|
41
|
+
function cloneJson(value, fieldPath, ancestors = new Set()) {
|
|
42
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
43
|
+
return value;
|
|
44
|
+
if (typeof value === "number") {
|
|
45
|
+
if (!Number.isFinite(value))
|
|
46
|
+
throw snapshotError(fieldPath, "contains a non-finite number");
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value !== "object")
|
|
50
|
+
throw snapshotError(fieldPath, "contains a non-JSON value");
|
|
51
|
+
if (ancestors.has(value))
|
|
52
|
+
throw snapshotError(fieldPath, "contains a cycle");
|
|
53
|
+
ancestors.add(value);
|
|
54
|
+
try {
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
return Object.freeze(value.map((item, index) => cloneJson(item, `${fieldPath}[${index}]`, ancestors)));
|
|
57
|
+
}
|
|
58
|
+
if (!isPlainObject(value))
|
|
59
|
+
throw snapshotError(fieldPath, "contains a non-plain JSON object");
|
|
60
|
+
const result = {};
|
|
61
|
+
for (const [key, item] of Object.entries(value)) {
|
|
62
|
+
Object.defineProperty(result, key, {
|
|
63
|
+
value: cloneJson(item, `${fieldPath}.${key}`, ancestors),
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return Object.freeze(result);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
ancestors.delete(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function cloneJsonObject(value, fieldPath) {
|
|
76
|
+
const cloned = cloneJson(value, fieldPath);
|
|
77
|
+
if (!isPlainObject(cloned)) {
|
|
78
|
+
throw snapshotError(fieldPath, "must be a plain JSON object");
|
|
79
|
+
}
|
|
80
|
+
return cloned;
|
|
81
|
+
}
|
|
82
|
+
export function optionalJsonObject(provider, key) {
|
|
83
|
+
return key in provider ? cloneJsonObject(provider[key], `provider.${key}`) : undefined;
|
|
84
|
+
}
|
|
85
|
+
export function optionalStringRecord(provider, key) {
|
|
86
|
+
if (!(key in provider))
|
|
87
|
+
return undefined;
|
|
88
|
+
const value = provider[key];
|
|
89
|
+
if (!isPlainObject(value))
|
|
90
|
+
throw snapshotError(`provider.${key}`, "must be a plain JSON object");
|
|
91
|
+
const result = {};
|
|
92
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
93
|
+
if (typeof entryValue !== "string") {
|
|
94
|
+
throw snapshotError(`provider.${key}.${entryKey}`, "must be a string");
|
|
95
|
+
}
|
|
96
|
+
result[entryKey] = entryValue;
|
|
97
|
+
}
|
|
98
|
+
return Object.freeze(result);
|
|
99
|
+
}
|
|
100
|
+
export function parseAbsoluteRootList(value, fieldPath) {
|
|
101
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
102
|
+
throw snapshotError(fieldPath, "must be a nonempty array");
|
|
103
|
+
}
|
|
104
|
+
const roots = value.map((root, index) => {
|
|
105
|
+
if (typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root) {
|
|
106
|
+
throw snapshotError(`${fieldPath}[${index}]`, "must be a lexically normalized absolute path");
|
|
107
|
+
}
|
|
108
|
+
return root;
|
|
109
|
+
});
|
|
110
|
+
if (new Set(roots).size !== roots.length) {
|
|
111
|
+
throw snapshotError(fieldPath, "must not contain duplicates");
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze(roots);
|
|
114
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { nativePolicyOwnership } from "../../config/provider-policy-ownership.js";
|
|
3
|
-
import { buildCodexAppServerWorkspaceWriteSandboxPolicy, configOwnsApprovalPolicy, configOwnsSandboxPolicy, } from "../providers/codex-app-server/adapter.js";
|
|
1
|
+
import { getProviderRegistration } from "../providers/registry.js";
|
|
4
2
|
import { assertEffortAllowed, assertEffortRequiresModel, parseResolvedAkumaLaunchSnapshot, } from "./model.js";
|
|
5
3
|
export function resolveAkumaLaunchSnapshot(selected, input) {
|
|
6
4
|
const model = input.model ?? selected.profile.model;
|
|
@@ -28,45 +26,21 @@ export function resolveAkumaLaunchSnapshot(selected, input) {
|
|
|
28
26
|
policy,
|
|
29
27
|
};
|
|
30
28
|
const instance = selected.profile.providerInstance;
|
|
31
|
-
const common = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
threadOptions.modelReasoningEffort = effort;
|
|
48
|
-
threadOptions.workingDirectory = input.executionCwd;
|
|
49
|
-
threadOptions.skipGitRepoCheck = true;
|
|
50
|
-
provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), threadOptions };
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
case "codex-app-server": {
|
|
54
|
-
const roots = [path.resolve(input.executionCwd), ...(instance.writableRoots ?? []).map((root) => path.resolve(input.projectRoot, root))];
|
|
55
|
-
const writableRoots = [...new Set(roots)];
|
|
56
|
-
provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(instance.profile !== undefined ? { profile: instance.profile } : {}), ...(instance.config !== undefined ? { config: instance.config } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(!configOwnsSandboxPolicy(instance.config) ? { sandboxPolicy: buildCodexAppServerWorkspaceWriteSandboxPolicy({ writableRoots, networkAccess: selected.profile.network === "enabled" }) } : {}), ...(!configOwnsApprovalPolicy(instance.config) ? { approvalPolicy: "never" } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
case "claude-agent-sdk": {
|
|
60
|
-
const permissionMode = selected.profile.access === "read" ? "plan" : selected.profile.access === "write" ? "acceptEdits" : selected.profile.access === "auto" ? "bypassPermissions" : "default";
|
|
61
|
-
provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(instance.settingSources !== undefined ? { settingSources: instance.settingSources } : {}), permissionMode, ...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}), ...(selected.profile.webSearch === "disabled" ? { disallowedTools: ["WebSearch"] } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
case "opencode-sdk":
|
|
65
|
-
provider = { ...common, ...(instance.executable !== undefined ? { executable: instance.executable } : {}), ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { effort } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
|
|
66
|
-
break;
|
|
67
|
-
case "pi":
|
|
68
|
-
provider = { ...common, ...(model !== undefined ? { model } : {}), ...(effort !== undefined ? { thinkingLevel: effort } : {}), ...(instance.env !== undefined ? { env: instance.env } : {}) };
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
29
|
+
const common = {
|
|
30
|
+
name: selected.profile.provider,
|
|
31
|
+
source: selected.profile.providerSource,
|
|
32
|
+
};
|
|
33
|
+
const registration = getProviderRegistration(instance.kind);
|
|
34
|
+
const provider = registration.resolveLaunchProvider({
|
|
35
|
+
common,
|
|
36
|
+
instance,
|
|
37
|
+
model,
|
|
38
|
+
effort,
|
|
39
|
+
executionCwd: input.executionCwd,
|
|
40
|
+
projectRoot: input.projectRoot,
|
|
41
|
+
access: selected.profile.access,
|
|
42
|
+
network: selected.profile.network,
|
|
43
|
+
webSearch: selected.profile.webSearch,
|
|
44
|
+
});
|
|
71
45
|
return parseResolvedAkumaLaunchSnapshot({ version: 1, akuma, provider });
|
|
72
46
|
}
|
|
@@ -7,6 +7,7 @@ const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
|
7
7
|
const DEFAULT_STARTUP_TIMEOUT_MS = 10_000;
|
|
8
8
|
const SERVER_READY_POLL_INTERVAL_MS = 100;
|
|
9
9
|
const SERVER_SHUTDOWN_FORCE_KILL_MS = 1000;
|
|
10
|
+
const SERVER_SHUTDOWN_HARD_TIMEOUT_MS = 2000;
|
|
10
11
|
const OPENCODE_SERVE_SUBCOMMAND = "serve";
|
|
11
12
|
const OPENCODE_HOSTNAME_FLAG = "--hostname";
|
|
12
13
|
const OPENCODE_PORT_FLAG = "--port";
|
|
@@ -33,7 +34,10 @@ async function waitForServerReady(client, directory, timeoutMs, signal) {
|
|
|
33
34
|
if (signal?.aborted)
|
|
34
35
|
throw createAbortError("OpenCode server startup aborted");
|
|
35
36
|
try {
|
|
36
|
-
|
|
37
|
+
// Legacy config endpoints become available before the runtime model
|
|
38
|
+
// catalog. Prompting in that window is admitted, then fails asynchronously
|
|
39
|
+
// with ModelUnavailableError and no terminal event.
|
|
40
|
+
await client.v2.model.list({ location: { directory } }, { throwOnError: true });
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
catch {
|
|
@@ -67,13 +71,23 @@ export async function createOpencodeServer(options = {}) {
|
|
|
67
71
|
return;
|
|
68
72
|
if (mode === "force") {
|
|
69
73
|
forceStop ??= new Promise((resolve, reject) => {
|
|
70
|
-
|
|
74
|
+
let timer;
|
|
75
|
+
const finish = (error) => {
|
|
76
|
+
if (timer)
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
error === undefined ? resolve() : reject(error);
|
|
79
|
+
};
|
|
80
|
+
child.once("close", () => finish());
|
|
81
|
+
timer = setTimeout(() => {
|
|
82
|
+
finish(new Error("OpenCode server forced process exit was not observed"));
|
|
83
|
+
}, SERVER_SHUTDOWN_HARD_TIMEOUT_MS);
|
|
84
|
+
timer.unref?.();
|
|
71
85
|
try {
|
|
72
86
|
if (!child.kill("SIGKILL") && !exited)
|
|
73
|
-
|
|
87
|
+
finish(new Error("OpenCode server force kill failed"));
|
|
74
88
|
}
|
|
75
89
|
catch (error) {
|
|
76
|
-
|
|
90
|
+
finish(error);
|
|
77
91
|
}
|
|
78
92
|
});
|
|
79
93
|
await forceStop;
|
|
@@ -83,24 +97,43 @@ export async function createOpencodeServer(options = {}) {
|
|
|
83
97
|
await forceStop;
|
|
84
98
|
return;
|
|
85
99
|
}
|
|
86
|
-
gracefulStop ??= new Promise((resolve) => {
|
|
100
|
+
gracefulStop ??= new Promise((resolve, reject) => {
|
|
87
101
|
let settled = false;
|
|
88
102
|
let forceKill;
|
|
89
|
-
|
|
103
|
+
let hardTimeout;
|
|
104
|
+
const finish = (error) => {
|
|
90
105
|
if (settled)
|
|
91
106
|
return;
|
|
92
107
|
settled = true;
|
|
93
108
|
if (forceKill)
|
|
94
109
|
clearTimeout(forceKill);
|
|
95
|
-
|
|
110
|
+
if (hardTimeout)
|
|
111
|
+
clearTimeout(hardTimeout);
|
|
112
|
+
error === undefined ? resolve() : reject(error);
|
|
96
113
|
};
|
|
97
|
-
child.once("close", finish);
|
|
98
|
-
|
|
114
|
+
child.once("close", () => finish());
|
|
115
|
+
try {
|
|
116
|
+
child.kill("SIGTERM");
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
finish(error);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
99
122
|
forceKill = setTimeout(() => {
|
|
100
|
-
if (!exited)
|
|
101
|
-
|
|
123
|
+
if (!exited) {
|
|
124
|
+
try {
|
|
125
|
+
child.kill("SIGKILL");
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
finish(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
102
131
|
}, SERVER_SHUTDOWN_FORCE_KILL_MS);
|
|
103
|
-
forceKill.unref();
|
|
132
|
+
forceKill.unref?.();
|
|
133
|
+
hardTimeout = setTimeout(() => {
|
|
134
|
+
finish(new Error("OpenCode server process exit was not observed"));
|
|
135
|
+
}, SERVER_SHUTDOWN_HARD_TIMEOUT_MS);
|
|
136
|
+
hardTimeout.unref?.();
|
|
104
137
|
});
|
|
105
138
|
await gracefulStop;
|
|
106
139
|
};
|
|
@@ -1,33 +1,20 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
1
|
import { appendDebugLog } from "../../../telemetry/debug-log.js";
|
|
3
2
|
import { SUBAGENT_SESSION_VERSION } from "../../session.js";
|
|
4
3
|
import { createProjectionDriver } from "../../harness/execution-handle.js";
|
|
4
|
+
import { createProviderFailure } from "../../harness/provider-adapter.js";
|
|
5
5
|
import { buildSubagentEnv, createAbortError, emitProviderEvidence, linkAbortSignal, sha256Utf8 } from "../../harness/runtime.js";
|
|
6
|
-
import { SubagentFailure } from "../../types.js";
|
|
7
6
|
import { claudeIngressDisposition, emitClaudeThoughts, emitClaudeToolResults, emitClaudeToolStarts, emitClaudeUsage, extractAssistantMessage, extractAssistantMessageId, extractAssistantMessageModel, extractOverloadFallbackWarning, extractResultText, extractResumeToken, isClaudeApiRetryEvent, isClaudeModelRefusalFallbackEvent, isClaudeModelRefusalNoFallbackEvent, mapClaudeApiRetryWarning, } from "./events.js";
|
|
8
7
|
import { CLAUDE_AGENT_SDK_PROVIDER, createPushableUserMessageStream, toClaudeUserMessage, } from "./session.js";
|
|
9
8
|
function toClaudeSdkEffort(effort) {
|
|
10
9
|
return effort; // opacity boundary
|
|
11
10
|
}
|
|
12
|
-
export
|
|
13
|
-
let claudeAgentSdkLoader;
|
|
11
|
+
export { claudeSessionSchema } from "./session-schema.js";
|
|
14
12
|
async function loadClaudeAgentSdk(loader) {
|
|
15
13
|
if (loader) {
|
|
16
14
|
return loader();
|
|
17
15
|
}
|
|
18
|
-
if (claudeAgentSdkLoader) {
|
|
19
|
-
return claudeAgentSdkLoader();
|
|
20
|
-
}
|
|
21
16
|
return (await import("@anthropic-ai/claude-agent-sdk"));
|
|
22
17
|
}
|
|
23
|
-
/** @deprecated Use ClaudeProviderOptions.loader instead. Kept for pipeline-based tests pending loader channel. */
|
|
24
|
-
export function setClaudeAgentSdkLoaderForTests(loader) {
|
|
25
|
-
claudeAgentSdkLoader = loader;
|
|
26
|
-
}
|
|
27
|
-
/** @deprecated Use ClaudeProviderOptions.loader instead. */
|
|
28
|
-
export function resetClaudeAgentSdkLoaderForTests() {
|
|
29
|
-
claudeAgentSdkLoader = undefined;
|
|
30
|
-
}
|
|
31
18
|
export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
32
19
|
const sessionId = options.sessionId?.trim();
|
|
33
20
|
let liveQuery;
|
|
@@ -314,7 +301,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
|
314
301
|
const failCode = unavailable ? "SUBAGENT_PROVIDER_UNAVAILABLE" : "SUBAGENT_EXEC_ERROR";
|
|
315
302
|
builder.emit({ type: "fail", message, code: failCode });
|
|
316
303
|
inputStream?.end();
|
|
317
|
-
throw
|
|
304
|
+
throw createProviderFailure(message, { code: failCode });
|
|
318
305
|
}
|
|
319
306
|
default:
|
|
320
307
|
break;
|
|
@@ -327,7 +314,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
|
327
314
|
if (!resumedSessionId) {
|
|
328
315
|
const message = `${CLAUDE_AGENT_SDK_PROVIDER} exec could not determine session id`;
|
|
329
316
|
builder.emit({ type: "fail", message });
|
|
330
|
-
throw
|
|
317
|
+
throw createProviderFailure(message);
|
|
331
318
|
}
|
|
332
319
|
const session = {
|
|
333
320
|
provider: "claude-agent-sdk",
|
|
@@ -366,3 +353,5 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
|
|
|
366
353
|
},
|
|
367
354
|
});
|
|
368
355
|
}
|
|
356
|
+
// Compiler-level binding to the named ProviderAdapter function contract.
|
|
357
|
+
startClaudeAgentSdk;
|