@humwick/runner 0.130.4 → 0.130.6
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/LICENSE +661 -167
- package/agent-bin/humwick +2 -2
- package/agent-bin/humwick-browser +4 -0
- package/hooks/fff-search-redirect/fff-search-redirect.cjs +1 -1
- package/hooks/web-tools-redirect/web-tools-redirect.cjs +2 -2
- package/package.json +8 -11
- package/plugins/browser/skills/playwright-cli/SKILL.md +8 -6
- package/plugins/claude/.claude-plugin/plugin.json +3 -3
- package/plugins/claude/claude-statusline-probe.cjs +26 -16
- package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +47 -17
- package/plugins/claude/hooks/gate-run-guard.sh +3 -3
- package/plugins/claude/hooks/gate-run-guard.ts +5 -5
- package/plugins/claude/hooks/kill-guard.provisioned.mjs +1 -1
- package/plugins/claude/hooks/kill-guard.sh +4 -4
- package/plugins/claude/hooks/kill-guard.ts +2 -2
- package/plugins/claude/hooks/permission-request.sh +1 -1
- package/plugins/claude/hooks/post-compact.sh +1 -1
- package/plugins/claude/hooks/post-tool-use.sh +3 -3
- package/plugins/claude/hooks/pre-compact.sh +2 -2
- package/plugins/claude/hooks/read-only-bash-guard.sh +4 -4
- package/plugins/claude/hooks/relay-status.sh +32 -32
- package/plugins/claude/hooks/session-end.sh +2 -2
- package/plugins/claude/hooks/session-start.sh +3 -3
- package/plugins/claude/hooks/shell-trust.ts +1 -1
- package/plugins/claude/hooks/stop-failure.sh +2 -2
- package/plugins/claude/hooks/stop.sh +6 -6
- package/plugins/claude/hooks/subagent-skill-policy.cjs +8 -6
- package/plugins/claude/hooks/subagent-stop.sh +3 -3
- package/plugins/claude/hooks/user-prompt-submit.sh +1 -1
- package/plugins/claude/hooks/wait-loop-guard.ts +3 -3
- package/plugins/claude/monitors/monitors.json +2 -2
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +96 -62
- package/plugins/claude/monitors/relay-monitor.ts +5 -5
- package/plugins/claude/skills/label/SKILL.md +3 -3
- package/plugins/claude/skills/message/SKILL.md +5 -5
- package/plugins/claude/skills/react/SKILL.md +1 -1
- package/plugins/claude/skills/read-message/SKILL.md +4 -4
- package/plugins/claude/skills/reply/SKILL.md +5 -5
- package/plugins/claude/skills/send-claimable/SKILL.md +5 -5
- package/plugins/claude/skills/slice-first/SKILL.md +5 -3
- package/plugins/claude/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/claude/skills/status/SKILL.md +3 -3
- package/plugins/claude/skills/tags/SKILL.md +5 -5
- package/plugins/claude/skills/task/SKILL.md +10 -10
- package/plugins/claude-output-style/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-output-style/output-styles/{agent-relay.md → humwick.md} +1 -1
- package/plugins/codex/skills/guide/SKILL.md +3 -3
- package/plugins/codex/skills/label/SKILL.md +3 -3
- package/plugins/codex/skills/message/SKILL.md +5 -5
- package/plugins/codex/skills/react/SKILL.md +1 -1
- package/plugins/codex/skills/read-message/SKILL.md +4 -4
- package/plugins/codex/skills/reply/SKILL.md +5 -5
- package/plugins/codex/skills/send-claimable/SKILL.md +5 -5
- package/plugins/codex/skills/slice-first/SKILL.md +5 -3
- package/plugins/codex/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/codex/skills/status/SKILL.md +3 -3
- package/plugins/codex/skills/tags/SKILL.md +5 -5
- package/plugins/codex/skills/task/SKILL.md +11 -10
- package/src/adapter.ts +95 -92
- package/src/adapters/claude-api.ts +8 -11
- package/src/adapters/claude-context-command.ts +1 -1
- package/src/adapters/claude-credentials.ts +7 -3
- package/src/adapters/claude-delivery-lanes.ts +2 -2
- package/src/adapters/claude-delivery.ts +16 -16
- package/src/adapters/claude-effort.ts +2 -2
- package/src/adapters/claude-exit.ts +2 -2
- package/src/adapters/claude-headless-mirror.ts +18 -2
- package/src/adapters/claude-headless-refusal.ts +3 -3
- package/src/adapters/claude-headless.ts +11 -10
- package/src/adapters/claude-launch-exit.ts +1 -1
- package/src/adapters/claude-monitor-delivery-plan.ts +36 -16
- package/src/adapters/claude-permission.ts +5 -5
- package/src/adapters/claude-prompt-gates.ts +9 -9
- package/src/adapters/claude-prompt-hook.ts +2 -2
- package/src/adapters/claude-session-capture.ts +6 -6
- package/src/adapters/claude-session-probe.ts +25 -25
- package/src/adapters/claude-shutdown.ts +11 -11
- package/src/adapters/claude-status-detectors.ts +31 -31
- package/src/adapters/claude-subagents.ts +7 -7
- package/src/adapters/claude-submit-confirmation.ts +6 -6
- package/src/adapters/claude-tmux.ts +43 -43
- package/src/adapters/claude-token-usage-capture.ts +5 -5
- package/src/adapters/claude-tool-policy.ts +7 -7
- package/src/adapters/claude-transcript.ts +24 -24
- package/src/adapters/claude.ts +100 -100
- package/src/adapters/codex-agent-message-capture.ts +26 -63
- package/src/adapters/codex-app-server.ts +1 -1
- package/src/adapters/codex-client.ts +9 -9
- package/src/adapters/codex-effective-config.ts +1 -1
- package/src/adapters/codex-launch-config.ts +5 -5
- package/src/adapters/codex-rollout-context.ts +2 -2
- package/src/adapters/codex-skill-discovery.ts +1 -1
- package/src/adapters/codex-token-usage-capture.ts +16 -16
- package/src/adapters/codex-tool-output.ts +4 -4
- package/src/adapters/codex-version.ts +3 -3
- package/src/adapters/codex.ts +64 -64
- package/src/adapters/opencode-api.ts +1 -1
- package/src/adapters/opencode-context.ts +20 -22
- package/src/adapters/opencode-model-registry.ts +7 -7
- package/src/adapters/opencode-provider.ts +8 -8
- package/src/adapters/opencode-provisioning.ts +30 -0
- package/src/adapters/opencode-server-retry.ts +1 -1
- package/src/adapters/opencode-soft-steer.ts +5 -5
- package/src/adapters/opencode-spawn.ts +18 -14
- package/src/adapters/opencode-token-usage-capture.ts +12 -12
- package/src/adapters/opencode-tool-events.ts +15 -15
- package/src/adapters/opencode.ts +232 -232
- package/src/adapters/project-shell.ts +11 -11
- package/src/adapters/service-access.ts +6 -6
- package/src/adapters/tmux-offload.ts +9 -9
- package/src/adapters/tool-summary.ts +5 -5
- package/src/agent-redact.ts +1 -1
- package/src/agent-runner/commands/dispatch.ts +10 -10
- package/src/agent-runner/commands/lifecycle.ts +2 -2
- package/src/agent-runner/commands/prompt.ts +19 -19
- package/src/agent-runner/credentials.ts +5 -5
- package/src/agent-runner/inbox.ts +1 -1
- package/src/agent-runner/lifecycle-milestones.ts +3 -3
- package/src/agent-runner/lifecycle-phase.ts +2 -2
- package/src/agent-runner/liveness-exchange.ts +4 -10
- package/src/agent-runner/registration.ts +8 -8
- package/src/agent-runner/runner-context.ts +9 -14
- package/src/agent-runner/session-mirror.ts +12 -12
- package/src/agent-runner/task-claims.ts +2 -2
- package/src/agent-runner/token-usage-coverage.ts +1 -1
- package/src/agent-runner/turn-report.ts +12 -12
- package/src/api-budget-accounting.ts +3 -3
- package/src/area-memory.ts +6 -6
- package/src/artifact-path-materialize.ts +1 -1
- package/src/async-guard.ts +5 -5
- package/src/attachment-cache.ts +17 -22
- package/src/background-script-reconcile.ts +6 -6
- package/src/bounded-log.ts +1 -1
- package/src/busy-reconciler.ts +22 -22
- package/src/claim-tracker.ts +3 -3
- package/src/claude-settings.ts +8 -8
- package/src/claude-system-prompt.ts +1 -1
- package/src/claude-workspace-trust.ts +1 -1
- package/src/config.ts +62 -61
- package/src/context-boundary.ts +5 -5
- package/src/context-command.ts +3 -3
- package/src/control-server.ts +32 -32
- package/src/delivery-result.ts +2 -2
- package/src/effort-command.ts +2 -2
- package/src/empty-turn.ts +22 -14
- package/src/free-port.ts +1 -1
- package/src/gate-run-config.ts +3 -3
- package/src/host-asset-plan.ts +24 -17
- package/src/host-claude-config-worker.ts +2 -2
- package/src/host-sources.ts +2 -2
- package/src/index.ts +16 -16
- package/src/launch-assembly.ts +94 -96
- package/src/liveness.ts +15 -15
- package/src/logger.ts +3 -3
- package/src/mcp-outbox.ts +3 -3
- package/src/mcp-preflight.ts +2 -2
- package/src/mcp-source-selection.ts +2 -2
- package/src/mcp-tool-deferral.ts +1 -1
- package/src/mcp-vault-env.ts +2 -2
- package/src/message-body-config/config.ts +4 -4
- package/src/mid-turn-delivery.ts +2 -2
- package/src/native-memory-lever.ts +8 -8
- package/src/outbox.ts +15 -15
- package/src/pending-permission-store.ts +1 -1
- package/src/profile-home.ts +19 -19
- package/src/profile-projection.ts +26 -24
- package/src/project-instructions.ts +5 -5
- package/src/provider-auth-warning.ts +17 -0
- package/src/provider-double.ts +1 -1
- package/src/provider-hold.ts +3 -3
- package/src/provider-home-marker.ts +1 -1
- package/src/provider-home-tombstone.ts +1 -1
- package/src/provider-input-queue.ts +2 -2
- package/src/provider-integration.ts +15 -13
- package/src/provider-setup.ts +15 -3
- package/src/providers.ts +1 -1
- package/src/provisioning.ts +37 -40
- package/src/question-decision.ts +2 -2
- package/src/quota.ts +0 -6
- package/src/rate-limit.ts +24 -13
- package/src/refused-prompt-store.ts +2 -2
- package/src/relay-injection-events.ts +2 -2
- package/src/relay-instructions.ts +5 -5
- package/src/relay-mcp-proxy.long-turn-e2e.ts +13 -13
- package/src/relay-mcp-proxy.ts +57 -58
- package/src/relay-mcp.ts +55 -69
- package/src/reply-obligation-cache.ts +9 -9
- package/src/response-capture-report.ts +9 -9
- package/src/response-routing.ts +1 -1
- package/src/runner-core.ts +209 -222
- package/src/runner-exit.ts +1 -1
- package/src/runner-helpers.ts +10 -10
- package/src/runner-insights.ts +4 -2
- package/src/runner-mcp-proxy.ts +3 -3
- package/src/runner-spawn-config.ts +3 -3
- package/src/runtime-token-file.ts +1 -1
- package/src/runtime-token-remint.ts +1 -1
- package/src/service-access.ts +2 -2
- package/src/session-destroy.ts +1 -1
- package/src/session-insights.ts +1 -1
- package/src/session-scratch.ts +24 -37
- package/src/spawn-input.ts +6 -6
- package/src/startup-config-fetch.ts +4 -4
- package/src/subreaper.ts +3 -5
- package/src/test-run-dedup.ts +13 -13
- package/src/transcript-context.ts +2 -2
- package/src/transport-errors.ts +1 -1
- package/src/turn-opener.ts +1 -1
- package/src/turn-reconcile-event.ts +2 -2
- package/src/vault-proxy-hop.ts +2 -2
- package/src/web-tools.ts +2 -2
- package/agent-bin/agent-relay-browser +0 -10
- package/plugins/claude/skills/disconnect/SKILL.md +0 -16
- package/plugins/claude/skills/pair/SKILL.md +0 -30
- package/plugins/codex/skills/disconnect/SKILL.md +0 -15
- package/plugins/codex/skills/pair/SKILL.md +0 -29
- package/src/adapters/provider-quota-harvest.ts +0 -76
|
@@ -10,7 +10,7 @@ import { openCodeBackend, openCodeCustomBackendIDs, openCodeHostAuthKey, openCod
|
|
|
10
10
|
import { openCodeMessageModel } from "./opencode-token-usage-capture";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* the host's opencode model registry, used to normalize a launch model before spawn.
|
|
14
14
|
*
|
|
15
15
|
* Primary: `opencode models --pure` run with the launch env but isolated XDG config/data (the
|
|
16
16
|
* spawned server gets fresh isolated homes too), so it lists exactly the providers the server
|
|
@@ -55,7 +55,7 @@ export async function probeOpenCodeModelRegistry(
|
|
|
55
55
|
extraProviders: string[],
|
|
56
56
|
deps: OpenCodeRegistryProbeDeps = {},
|
|
57
57
|
): Promise<OpenCodeModelRegistry | undefined> {
|
|
58
|
-
const scratch = mkdtempSync(join(tmpdir(), "
|
|
58
|
+
const scratch = mkdtempSync(join(tmpdir(), "humwick-opencode-models-"));
|
|
59
59
|
try {
|
|
60
60
|
const { exitCode, stdout } = await (deps.run ?? runProbe)(
|
|
61
61
|
[command, "models", "--pure"],
|
|
@@ -89,7 +89,7 @@ export function cachedOpenCodeModelRegistry(command: string, env: Record<string,
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* normalize the launch model against the host's opencode registry BEFORE the server
|
|
93
93
|
* starts: a registry-listed id passes through unchanged, and an unknown/ambiguous id fails the
|
|
94
94
|
* spawn here with candidates instead of dying later as a provider 400.
|
|
95
95
|
*/
|
|
@@ -99,13 +99,13 @@ export async function resolveOpenCodeLaunchModel(
|
|
|
99
99
|
provider: string,
|
|
100
100
|
registryFor: typeof cachedOpenCodeModelRegistry = cachedOpenCodeModelRegistry,
|
|
101
101
|
): Promise<OpenCodeModelId> {
|
|
102
|
-
const env: Record<string, string> = writeChildEnv({}, { ...config.env, ...providerConfig.env, PATH: config.env.PATH ?? providerConfig.env.PATH ?? process.env.PATH ?? "" });
|
|
103
|
-
//
|
|
102
|
+
const env: Record<string, string> = writeChildEnv({}, { ...config.env, ...providerConfig.env, PATH: config.env.PATH ?? providerConfig.env.PATH ?? process.env.PATH ?? "" }, []);
|
|
103
|
+
// the registry probe runs under fresh XDG roots too, so a hostAuth backend only lists
|
|
104
104
|
// its models when the host login key rides in as the backend's env credential.
|
|
105
105
|
const { backend } = openCodeBackend(provider);
|
|
106
106
|
if (backend.hostAuth && !relayEnv(env, backend.apiKeyEnvVar)) {
|
|
107
107
|
const hostKey = openCodeHostAuthKey(backend.opencodeProviderID, env);
|
|
108
|
-
if (hostKey) writeChildEnv(env, { [backend.apiKeyEnvVar]: hostKey });
|
|
108
|
+
if (hostKey) writeChildEnv(env, { [backend.apiKeyEnvVar]: hostKey }, []);
|
|
109
109
|
}
|
|
110
110
|
const registry = await registryFor(providerConfig.command || "opencode", env, openCodeCustomBackendIDs());
|
|
111
111
|
const launch = openCodeLaunchModel(config.model, provider, openCodeModelRecord(config.modelRecord, provider), registry);
|
|
@@ -114,7 +114,7 @@ export async function resolveOpenCodeLaunchModel(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
117
|
+
* a model the human picked in the attached TUI rides on that prompt's user row
|
|
118
118
|
* (`info.model = {providerID, modelID}`). Adopt it as the session model so the next Relay chat
|
|
119
119
|
* turn keeps it instead of re-sending the launch model.
|
|
120
120
|
*/
|
|
@@ -19,7 +19,7 @@ export function openCodeBackend(provider: string): OpenCodeBackend {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* resolve the payload record for THIS provider, or reject an unknown provider LOUD.
|
|
23
23
|
* A record whose providerModel/alias resolves against a DIFFERENT registered provider is a
|
|
24
24
|
* routing error, not a fallback: silently running it under the wrong backend namespace would
|
|
25
25
|
* launch the wrong model. The manifest fallback only applies when NO record is present at all.
|
|
@@ -36,7 +36,7 @@ export function openCodeBackendApiKeyEnvVar(backendID: string): string | undefin
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* the launch model: THE one normalization of a spawn's model through the shared seam
|
|
40
40
|
* (`resolveOpenCodeModelId`). A server-threaded record's qualified name wins over the alias.
|
|
41
41
|
* With a host registry (spawn), unknown/ambiguous ids throw with candidates; without one
|
|
42
42
|
* (static fallback) a known-backend first segment is structural, anything else is a
|
|
@@ -61,7 +61,7 @@ export function openCodeModelDescriptor(model: string | undefined, provider = "o
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* the per-turn descriptor for an ALREADY-normalized qualified name (process meta.model).
|
|
65
65
|
* Never re-resolves: re-running the static split here re-prefixed live-only backends with the
|
|
66
66
|
* default backend on every chat turn.
|
|
67
67
|
*/
|
|
@@ -72,7 +72,7 @@ export function openCodeTurnDescriptor(qualified: string, provider = "opencode")
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* the config-block + credential-env half of the per-model backend decision: the
|
|
76
76
|
* openCodeProviderID to key `provider.<id>` under, the apiKey env var the config references and
|
|
77
77
|
* the launch env must carry (undefined for a live-registry backend Relay has no credential
|
|
78
78
|
* mapping for — opencode then authenticates it from the inherited env itself), and any
|
|
@@ -111,7 +111,7 @@ export function openCodeContextWindowTokens(_model: string | undefined, provider
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
114
|
+
* the record's explicit threshold, when present (precedence payload > manifest-derived).
|
|
115
115
|
* Returns undefined when the record carries none — the caller then derives from the window.
|
|
116
116
|
*/
|
|
117
117
|
export function openCodeContextThreshold(record: ResolvedModelRecord | undefined, provider = "opencode"): ResolvedModelRecord["contextThreshold"] {
|
|
@@ -121,7 +121,7 @@ export function openCodeContextThreshold(record: ResolvedModelRecord | undefined
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* the selected backend's credential from the runner or provider-config env
|
|
125
125
|
* (`||`: an empty runner var must not mask a provider-config key). A live-registry backend
|
|
126
126
|
* without a Relay credential mapping has none — opencode authenticates it from the inherited env.
|
|
127
127
|
*/
|
|
@@ -133,7 +133,7 @@ export function openCodeBackendCredential(apiKeyEnvVar: string | undefined, runn
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
*
|
|
136
|
+
* the host's own `opencode auth login` key (`<data home>/opencode/auth.json`), which the
|
|
137
137
|
* isolated XDG launch hides from the server. Only for manifests with `opencode.hostAuth`.
|
|
138
138
|
*/
|
|
139
139
|
export function openCodeHostAuthKey(backendID: string, env: Record<string, string>): string | undefined {
|
|
@@ -147,7 +147,7 @@ export function openCodeHostAuthKey(backendID: string, env: Record<string, strin
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
/**
|
|
150
|
+
/** is the session still on its launch model (no terminal model switch adopted)? */
|
|
151
151
|
export function openCodeOnLaunchModel(meta: Record<string, unknown> | undefined, model: string | undefined): boolean {
|
|
152
152
|
const launchModel = typeof meta?.launchModel === "string" ? meta.launchModel : undefined;
|
|
153
153
|
return !launchModel || !model || model === launchModel;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { writeFileSync } from "node:fs";
|
|
3
|
+
import { sanitizeFsName } from "@humwick/sdk/fs-name";
|
|
4
|
+
import type { RunnerSpawnConfig } from "../adapter";
|
|
5
|
+
import { providerHomeRootFromEnv } from "../config";
|
|
6
|
+
import { bundledIntegrationAssets } from "../provider-integration";
|
|
7
|
+
import { materializeResolvedAssets, resolveProvisionedSkillPaths } from "../provisioning";
|
|
8
|
+
import { profileAllowsRelayFeature, resolveRelayContext } from "../relay-instructions";
|
|
9
|
+
import { launchWebTools } from "../web-tools";
|
|
10
|
+
|
|
11
|
+
export function openCodeHome(config: RunnerSpawnConfig, provider = config.provider): string {
|
|
12
|
+
const profile = sanitizeFsName(config.agentProfile?.name || config.profile || "default", { replacement: "-", lowercase: true });
|
|
13
|
+
const instance = sanitizeFsName(config.instanceId || config.runnerId, { replacement: "-", lowercase: true });
|
|
14
|
+
return join(providerHomeRootFromEnv(), provider, profile, instance);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveOpenCodeAssets(config: RunnerSpawnConfig) {
|
|
18
|
+
const configDir = join(openCodeHome(config), "config", "opencode");
|
|
19
|
+
const provisioned = resolveProvisionedSkillPaths(configDir, config);
|
|
20
|
+
const bundled = profileAllowsRelayFeature(config, "skills") ? bundledIntegrationAssets(config) : [];
|
|
21
|
+
const context = resolveRelayContext({ slug: "relay.context.opencode", config, webTools: { names: launchWebTools(config), claude: false } });
|
|
22
|
+
const instructions = [context.text, config.systemPromptAppend?.trim()].filter(Boolean).join("\n\n");
|
|
23
|
+
return { configDir, provisioned, bundled, skills: [...provisioned, ...bundled], context,
|
|
24
|
+
instructions: instructions ? { path: join(configDir, "humwick-instructions.md"), text: instructions } : undefined };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function materializeOpenCodeAssets(assets: ReturnType<typeof resolveOpenCodeAssets>): void {
|
|
28
|
+
materializeResolvedAssets(assets.skills);
|
|
29
|
+
if (assets.instructions) writeFileSync(assets.instructions.path, assets.instructions.text, { mode: 0o600 });
|
|
30
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// OpenCode's OWN in-session retry. On a retryable provider error OpenCode does not end the
|
|
2
2
|
// turn: it reports `session.status` `{ type: "retry", attempt, message, next, action? }` and keeps
|
|
3
3
|
// the synchronous /message POST open until it succeeds. A free-tier wall schedules `next` for the
|
|
4
4
|
// daily reset (hours out), so the agent sat busy and silent. These pure helpers decide when that
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Phase B — OpenCode/ZAI non-destructive soft-steer lane.
|
|
2
2
|
//
|
|
3
3
|
// The soft op is `POST /session/:id/message` with a CALLER messageID + `noReply:true`: it
|
|
4
4
|
// persists a user row WITHOUT joining/cancelling the active run, and the run's loop reloads
|
|
@@ -24,9 +24,9 @@ import { logger } from "../logger";
|
|
|
24
24
|
* The one-row-per-batch marker the design mandates: tells the model the payload is a steer, not
|
|
25
25
|
* a new assignment, and that completed work must not be discarded to act on it.
|
|
26
26
|
*/
|
|
27
|
-
export const OPENCODE_SOFT_STEER_MARKER = "
|
|
27
|
+
export const OPENCODE_SOFT_STEER_MARKER = "Humwick soft steer: incorporate at next safe step; do not discard completed work.";
|
|
28
28
|
|
|
29
|
-
/** Short deadlines only — never the 60m whole-turn budget
|
|
29
|
+
/** Short deadlines only — never the 60m whole-turn budget. */
|
|
30
30
|
export const OPENCODE_SOFT_STEER_POST_TIMEOUT_MS = 5_000;
|
|
31
31
|
export const OPENCODE_SOFT_STEER_RECONCILE_TIMEOUT_MS = 5_000;
|
|
32
32
|
|
|
@@ -41,7 +41,7 @@ export const OPENCODE_SOFT_STEER_MAX_BATCH_BYTES = 48 * 1024;
|
|
|
41
41
|
* `send()` turn gives the wake a real busy/idle pair and the full retry semantics instead of a
|
|
42
42
|
* second, parallel turn machinery.
|
|
43
43
|
*/
|
|
44
|
-
export const OPENCODE_SOFT_STEER_WAKE_TEXT = "
|
|
44
|
+
export const OPENCODE_SOFT_STEER_WAKE_TEXT = "Humwick: soft-steer guidance above is pending. Incorporate it now at this safe step; do not discard completed work. If nothing needs incorporating, reply with a single line: ACK.";
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Caller-side OpenCode messageID for a soft batch — `msg_` prefix (the shape the server accepts
|
|
@@ -99,7 +99,7 @@ export interface SoftSteerEntry {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
102
|
+
* Phase A reconciliation — the ledger's settlement exits are the RUNNER's seams, handed in
|
|
103
103
|
* through ProviderDeliverContext (settleMidTurnConsumed / reofferMidTurnIds). The ledger never
|
|
104
104
|
* settles on its own: an absent callback is a fail-safe hold, never a silent settle.
|
|
105
105
|
*/
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { sanitizeFsName } from "@humwick/sdk/fs-name";
|
|
5
5
|
import { providerScratchEnvOf } from "@humwick/sdk/provider-scratch";
|
|
6
6
|
import { openCodeProjectShellEnv } from "./project-shell";
|
|
7
|
-
import {
|
|
7
|
+
import { openCodeHome, resolveOpenCodeAssets, materializeOpenCodeAssets } from "./opencode-provisioning";
|
|
8
8
|
import { materializeProviderHome } from "../profile-home";
|
|
9
9
|
import { syncOpenCodeRedactPlugin } from "../agent-redact";
|
|
10
10
|
import type { ProviderConfig, RunnerSpawnConfig, SpawnArgs } from "../adapter";
|
|
@@ -40,14 +40,14 @@ export function assertOpenCodeManagedServerArgs(providerArgs: string[]): void {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** Prevent config substitution in caller-supplied model names
|
|
43
|
+
/** Prevent config substitution in caller-supplied model names. */
|
|
44
44
|
export function assertOpenCodeSafeModel(model: string | undefined): void {
|
|
45
45
|
if (model?.includes("{")) {
|
|
46
46
|
throw new Error(`OpenCode model must not contain "{" (would be spliced by OpenCode's own {env:...}/{file:...} config substitution): received ${JSON.stringify(model)}`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/** Preserve host gh auth while isolating OpenCode XDG state
|
|
50
|
+
/** Preserve host gh auth while isolating OpenCode XDG state. */
|
|
51
51
|
export function resolveGhConfigDir(inputEnv: Record<string, string | undefined>): string | undefined {
|
|
52
52
|
const explicit = inputEnv.GH_CONFIG_DIR;
|
|
53
53
|
if (explicit) return explicit;
|
|
@@ -64,14 +64,14 @@ export function buildOpenCodeSpawnArgs(config: RunnerSpawnConfig, providerConfig
|
|
|
64
64
|
assertOpenCodeSafeModel(config.model);
|
|
65
65
|
assertOpenCodeSafeModel(config.modelRecord?.qualifiedModel);
|
|
66
66
|
openCodeBackend(provider);
|
|
67
|
-
// The selected model owns its backend and credential
|
|
67
|
+
// The selected model owns its backend and credential.
|
|
68
68
|
const resolved = openCodeResolvedBackend(launch.backendID, provider);
|
|
69
69
|
// `||`, not `??`: an empty-string runner env var must not mask a valid providerConfig key.
|
|
70
70
|
const apiKey = openCodeApiKey(config, launch.qualified) ?? openCodeBackendCredential(resolved.apiKeyEnvVar, config.env, providerConfig.env, resolved.hostAuth ? resolved.opencodeProviderID : undefined);
|
|
71
71
|
const assembled = assembleLaunch(provider, config, providerConfig);
|
|
72
72
|
const profile = sanitizeFsName(config.agentProfile?.name || config.profile || "default", { replacement: "-", lowercase: true });
|
|
73
73
|
const instanceId = sanitizeFsName(config.instanceId || config.runnerId, { replacement: "-", lowercase: true });
|
|
74
|
-
const root =
|
|
74
|
+
const root = openCodeHome(config, provider);
|
|
75
75
|
materializeProviderHome(root, { provider: provider, profile, instanceId, agentId: config.agentId }, 0o700);
|
|
76
76
|
const xdgConfig = join(root, "config");
|
|
77
77
|
const xdgData = join(root, "data");
|
|
@@ -79,14 +79,18 @@ export function buildOpenCodeSpawnArgs(config: RunnerSpawnConfig, providerConfig
|
|
|
79
79
|
const xdgCache = join(root, "cache");
|
|
80
80
|
const configDir = join(xdgConfig, "opencode");
|
|
81
81
|
for (const path of [xdgConfig, xdgData, xdgState, xdgCache, configDir]) mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
82
|
+
const assets = resolveOpenCodeAssets(config);
|
|
83
|
+
materializeOpenCodeAssets(assets);
|
|
82
84
|
// Permission is pinned in the isolated server config. A guarded/read-only request rejects
|
|
83
85
|
// before this file is written, so a policy downgrade can never become a permissive launch.
|
|
84
86
|
const configFile = join(configDir, "opencode.json");
|
|
85
87
|
const mcp = openCodeMcpConfigForLaunch(config);
|
|
86
|
-
// Keep credentials out of disk config using native env references
|
|
88
|
+
// Keep credentials out of disk config using native env references.
|
|
87
89
|
writeFileSync(configFile, `${JSON.stringify({
|
|
88
90
|
$schema: "https://opencode.ai/config.json",
|
|
89
91
|
model: launch.qualified,
|
|
92
|
+
...(assets.instructions ? { instructions: [assets.instructions.path] } : {}),
|
|
93
|
+
...(assets.skills.length ? { skills: { paths: assets.skills.map(skill => skill.dir) } } : {}),
|
|
90
94
|
...(launch.backendID === "openai" ? { small_model: launch.qualified, enabled_providers: ["openai"] } : {}),
|
|
91
95
|
provider: {
|
|
92
96
|
[resolved.opencodeProviderID]: {
|
|
@@ -100,11 +104,11 @@ export function buildOpenCodeSpawnArgs(config: RunnerSpawnConfig, providerConfig
|
|
|
100
104
|
permission: openCodePermissions(config), ...(Object.keys(mcp).length ? { mcp } : {}), ...openCodeToolsConfig(config, Object.keys(mcp).length > 0),
|
|
101
105
|
}, null, 2)}\n`, { mode: 0o600 });
|
|
102
106
|
chmodSync(configFile, 0o600);
|
|
103
|
-
//
|
|
107
|
+
// `--pure` skips every plugin, so it is dropped only when agent-redact's plugin is in place.
|
|
104
108
|
const pure = syncOpenCodeRedactPlugin(configDir, assembled.redaction) ? [] : ["--pure"];
|
|
105
|
-
// Bridge gh from the host env before overlaying isolated XDG roots
|
|
109
|
+
// Bridge gh from the host env before overlaying isolated XDG roots.
|
|
106
110
|
const ghConfigDir = resolveGhConfigDir({ ...config.env, ...providerConfig.env, ...assembled.env });
|
|
107
|
-
//
|
|
111
|
+
// allocated, never derived from another port. `controlPort + 1_000` overflowed 65535
|
|
108
112
|
// whenever the OS handed the runner a high ephemeral control port (macOS: 49152-65535), and
|
|
109
113
|
// `serve --port 66178` did not fail loudly: opencode bound a different port, so readiness
|
|
110
114
|
// polled the wrong URL for 20s and the runner exited 1 with no report.
|
|
@@ -113,12 +117,12 @@ export function buildOpenCodeSpawnArgs(config: RunnerSpawnConfig, providerConfig
|
|
|
113
117
|
const env: SpawnArgs["env"] = writeChildEnv({}, {
|
|
114
118
|
...config.env,
|
|
115
119
|
...providerConfig.env,
|
|
116
|
-
//
|
|
117
|
-
// the shell hooks keep it (and
|
|
120
|
+
// the runner already nested the agent's private TMPDIR under any provider-config one;
|
|
121
|
+
// the shell hooks keep it (and the toolchain PATH) after a login profile runs.
|
|
118
122
|
...providerScratchEnvOf(config.env),
|
|
119
123
|
...openCodeProjectShellEnv(config.env),
|
|
120
124
|
...assembled.env,
|
|
121
|
-
//
|
|
125
|
+
// gh (and git credential helpers wrapping gh) read the HOST gh config even
|
|
122
126
|
// under the isolated XDG roots below. No other credential surface is bridged.
|
|
123
127
|
...(ghConfigDir ? { GH_CONFIG_DIR: ghConfigDir } : {}),
|
|
124
128
|
XDG_CONFIG_HOME: xdgConfig,
|
|
@@ -131,8 +135,8 @@ export function buildOpenCodeSpawnArgs(config: RunnerSpawnConfig, providerConfig
|
|
|
131
135
|
...(resolved.apiKeyEnvVar && apiKey ? { [resolved.apiKeyEnvVar]: apiKey } : {}),
|
|
132
136
|
OPENCODE_SERVER_PASSWORD: crypto.randomUUID(),
|
|
133
137
|
OPENCODE_SERVER_URL: serverUrl,
|
|
134
|
-
|
|
135
|
-
});
|
|
138
|
+
HUMWICK_PROVIDER: provider,
|
|
139
|
+
}, []);
|
|
136
140
|
if (launch.backendID === "openai") {
|
|
137
141
|
for (const key of ["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN", "OPENCODE_API_KEY"]) writeChildEnv(env, {}, [key]);
|
|
138
142
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
//
|
|
1
|
+
// per-turn OpenCode/Zai token-usage capture, reusing the spine seam
|
|
2
2
|
// (ProviderTokenUsageReporter + POST /api/agents/:id/token-usage, validated live via Claude's
|
|
3
|
-
//
|
|
3
|
+
// capture). Unlike Claude, OpenCode never hands the runner a transcript file to tail: the
|
|
4
4
|
// `message.updated` SSE door already carries a structured per-assistant-message `tokens` object
|
|
5
5
|
// once a message finalizes — `{ input, output, reasoning, cache: { read, write } }`, confirmed
|
|
6
|
-
// live against a real captured v1.18.21 session (see opencode-context.ts's
|
|
6
|
+
// live against a real captured v1.18.21 session (see opencode-context.ts's note beside the
|
|
7
7
|
// same wire shape). So there is no incremental-file tail here: this module is a pure decoder from
|
|
8
8
|
// that object to the spine's required breakdown, called straight from the SSE handler.
|
|
9
9
|
//
|
|
10
|
-
// Dedupe: the door fires more than once per message as it streams toward its final total
|
|
10
|
+
// Dedupe: the door fires more than once per message as it streams toward its final total.
|
|
11
11
|
// Keying on the message id makes every echo collapse onto the SAME spine row via the dedupe_key
|
|
12
12
|
// upsert (`created_at >= existing` replaces) — the row simply refines towards the message's true
|
|
13
13
|
// total rather than posting a new one per frame, exactly like Claude's uuid-keyed entries collapse
|
|
@@ -35,7 +35,7 @@ export function openCodeTokenUsageRecord(input: {
|
|
|
35
35
|
messageId: string;
|
|
36
36
|
tokens: unknown;
|
|
37
37
|
createdAt?: number;
|
|
38
|
-
/**
|
|
38
|
+
/** the message is final, so unreadable counts mean unknown spend, not "not yet". */
|
|
39
39
|
finished?: boolean;
|
|
40
40
|
}): OpenCodeTokenUsageSpineRecord | undefined {
|
|
41
41
|
const unknown = (): OpenCodeTokenUsageSpineRecord | undefined => input.finished ? {
|
|
@@ -53,7 +53,7 @@ export function openCodeTokenUsageRecord(input: {
|
|
|
53
53
|
const cacheReadTokens = numberOrZero(cache?.read);
|
|
54
54
|
const cacheWriteTokens = numberOrZero(cache?.write);
|
|
55
55
|
// A recognised-but-entirely-empty object carries no signal — the same no-signal rule
|
|
56
|
-
// `openCodeContextState` applies to a `tokensUsed:0` reading of this identical shape (
|
|
56
|
+
// `openCodeContextState` applies to a `tokensUsed:0` reading of this identical shape (
|
|
57
57
|
// review MED-1: a genuinely engaged assistant message always has input > 0).
|
|
58
58
|
if (inputTokens === 0 && outputTokens === 0 && reasoningOutputTokens === 0 && cacheReadTokens === 0 && cacheWriteTokens === 0) return unknown();
|
|
59
59
|
return {
|
|
@@ -71,9 +71,9 @@ export function openCodeTokenUsageRecord(input: {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
*
|
|
74
|
+
* round 1 review (F6) — the message's OWN observed usage time: `time.completed` once
|
|
75
75
|
* finalized, else `time.created` (present on every AssistantMessage, per opencode-context.ts's
|
|
76
|
-
*
|
|
76
|
+
* note on this same wire family). Reading this instead of `Date.now` at report time
|
|
77
77
|
* removes the equal-millisecond upsert race for the TERMINAL echo specifically: every
|
|
78
78
|
* streaming echo of a still-open message shares the SAME `time.created`, and the terminal
|
|
79
79
|
* echo's own `time.completed` is always `>=` it — so the spine's `created_at >= existing`
|
|
@@ -98,10 +98,10 @@ export function openCodeMessageFinished(info: unknown): boolean {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
*
|
|
101
|
+
* round 1 review (F5) — the model the message was actually generated with, read off its own
|
|
102
102
|
* `providerID`/`modelID` (the same shape `openCodeModelDescriptor` sends on the request) rather
|
|
103
103
|
* than the session's currently-configured model, which can have moved on (a mid-session
|
|
104
|
-
* model/provider swap, a
|
|
104
|
+
* model/provider swap, a retry re-roll) by the time this echo is processed.
|
|
105
105
|
*/
|
|
106
106
|
export function openCodeMessageModel(info: unknown): string | undefined {
|
|
107
107
|
if (!isRecord(info)) return undefined;
|
|
@@ -113,7 +113,7 @@ export function openCodeMessageModel(info: unknown): string | undefined {
|
|
|
113
113
|
const TOKEN_USAGE_DEBOUNCE_MS = 300;
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
116
|
+
* round 1 review (F2/F3) — the `message.updated` door fires repeatedly as one message
|
|
117
117
|
* streams to completion; posting on every echo means an HTTP POST + SQLite write per frame,
|
|
118
118
|
* fleet-wide, for a value the very next echo immediately supersedes. Trailing-debounce per
|
|
119
119
|
* message id: each new echo replaces the pending record and resets the timer, so a whole
|
|
@@ -137,7 +137,7 @@ export class OpenCodeTokenUsagePoster {
|
|
|
137
137
|
this.pending.set(record.dedupeKey, { record, timer });
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
//
|
|
140
|
+
// round 2 review (MED-1) — returns the in-flight delivery so a teardown caller can
|
|
141
141
|
// actually await it (previously fire-and-forget: a caller that returned right after calling
|
|
142
142
|
// this had no guarantee the POST had even started, let alone landed — the repo's
|
|
143
143
|
// persisted-is-not-delivered shape). A live turn-boundary call (closeSuppressedTurn/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* decode OpenCode's `message.part.updated` tool parts (v1.18.21 `ToolPart`) into the
|
|
3
3
|
* provider-neutral tool-step shape the chat/timeline already renders (TraceView `ToolCallRow`).
|
|
4
4
|
*
|
|
5
5
|
* `opencode.ts` is near the module-size ceiling, so the wire-decoding lives here as a small,
|
|
@@ -31,16 +31,16 @@ export interface OpenCodeToolStep {
|
|
|
31
31
|
status: OpenCodeToolStepStatus;
|
|
32
32
|
/** Safe one-line summary (redacted + clipped) of what the tool is doing. */
|
|
33
33
|
body: string;
|
|
34
|
-
/**
|
|
34
|
+
/** redacted, size-capped tool input. Only present on the "running" state (the ToolPart
|
|
35
35
|
* wire shape drops `input` once the tool completes) — undefined here means "not captured on
|
|
36
36
|
* THIS decode", not "no params exist"; see `emitToolStep`'s stepId cache for the carry-forward
|
|
37
37
|
* onto the terminal state. */
|
|
38
38
|
params?: unknown;
|
|
39
|
-
/**
|
|
39
|
+
/** absolute paths a file tool reads or edits, from the raw input (running state only). */
|
|
40
40
|
paths?: string[];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** OpenCode built-in tool name → the provider-independent category taxonomy
|
|
43
|
+
/** OpenCode built-in tool name → the provider-independent category taxonomy. Anything
|
|
44
44
|
* not a known built-in (an MCP tool, a future built-in) falls to the mcp/generic default —
|
|
45
45
|
* never force-fit, so nothing is hidden from the trace. */
|
|
46
46
|
const OPENCODE_TOOL_CATEGORIES: Record<string, ToolCallCategory> = {
|
|
@@ -56,7 +56,7 @@ const OPENCODE_TOOL_CATEGORIES: Record<string, ToolCallCategory> = {
|
|
|
56
56
|
todoread: "plan",
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/** OpenCode's file tools; each names its file in `input.filePath`. */
|
|
60
60
|
const OPENCODE_FILE_TOOLS = new Set(["read", "edit", "write", "multiedit"]);
|
|
61
61
|
|
|
62
62
|
/** Extra input-field names OpenCode's built-in tools use that aren't in the shared candidate
|
|
@@ -67,7 +67,7 @@ export function openCodeToolCategory(tool: string): ToolCallCategory {
|
|
|
67
67
|
const known = OPENCODE_TOOL_CATEGORIES[tool.toLowerCase()];
|
|
68
68
|
if (known) return known;
|
|
69
69
|
// MCP tools surface through OpenCode with a namespaced name; the `__` convention (shared with
|
|
70
|
-
// Claude's `mcp__server__tool`) is the safe signal. Everything else is generic (
|
|
70
|
+
// Claude's `mcp__server__tool`) is the safe signal. Everything else is generic (rule:
|
|
71
71
|
// unknown tool → generic), so a mystery tool still renders rather than vanishing.
|
|
72
72
|
if (tool.includes("__") || tool.toLowerCase().startsWith("mcp")) return "mcp-tool-call";
|
|
73
73
|
return "generic";
|
|
@@ -169,7 +169,7 @@ export function openCodeToolSessionEvent(step: OpenCodeToolStep, turnId: string
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
|
-
*
|
|
172
|
+
* concatenate the terminal assistant message's text parts (wire order) into the turn-final
|
|
173
173
|
* response text. Non-text parts (tool, step-start/finish, reasoning) are skipped; the `\n\n` join
|
|
174
174
|
* matches the dashboard's block delimiter, so a multi-part response's leading blocks dedupe against
|
|
175
175
|
* the streamed narration. `undefined` when there is no assistant text.
|
|
@@ -186,7 +186,7 @@ export function openCodeAssistantText(parts: unknown[] | undefined): string | un
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
*
|
|
189
|
+
* decode one `message.part.updated` reasoning part (`{ type: "reasoning", text }`,
|
|
190
190
|
* pinned v1.18.21, live-verified in the provider's own SQLite: opencode/minimax-m2.5-free AND
|
|
191
191
|
* kimi-for-coding sessions persist `{type:"reasoning",text}` parts) into the provider-neutral
|
|
192
192
|
* emit fields. Pure — the adapter owns the dedup/emission, exactly like decodeOpenCodeToolPart.
|
|
@@ -202,7 +202,7 @@ export function decodeOpenCodeReasoningPart(part: unknown): { stepId: string; bo
|
|
|
202
202
|
return { stepId, body };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
/**
|
|
205
|
+
/** build the `reasoning` session event the chat/timeline consumes (TraceView reasoning
|
|
206
206
|
* row): `stepId` = the stable part id, so the server upserts the row in place on growth instead
|
|
207
207
|
* of appending a duplicate — the same stable-key shape the Codex adapter flushes with.
|
|
208
208
|
* `streaming` stays true while the part is unfinished (`time.end` unset), false on finalize. */
|
|
@@ -218,7 +218,7 @@ export function openCodeReasoningSessionEvent(step: { stepId: string; body: stri
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* one decoded reasoning part with its provider-side identity. The `id` is the wire that
|
|
222
222
|
* makes the turn-final backstop upsert instead of duplicate: the server keys step rows on
|
|
223
223
|
* `stepId` ONLY, so every turn-final reasoning row must carry the SAME stepId the live SSE door
|
|
224
224
|
* already emitted for that part (see openCodeReasoningSessionEvent) — a merged/concatenated
|
|
@@ -231,7 +231,7 @@ export interface OpenCodeReasoningStep {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
*
|
|
234
|
+
* decode the completed assistant message's reasoning parts (wire order) into
|
|
235
235
|
* per-part steps. Mirrors openCodeAssistantText's part walk, but keeps identity: one entry per
|
|
236
236
|
* non-empty reasoning part (empty text is backend muzak — dropped by decodeOpenCodeReasoningPart
|
|
237
237
|
* — so it never occupies a row). `[]` when the message carried no reasoning.
|
|
@@ -247,7 +247,7 @@ export function openCodeAssistantReasoningSteps(parts: unknown[] | undefined): O
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
|
-
*
|
|
250
|
+
* concatenate the terminal assistant message's reasoning parts (wire order) into the
|
|
251
251
|
* turn-final reasoning text. `undefined` when the completed message carried no (non-empty)
|
|
252
252
|
* reasoning. Kept for the completion log line / describeCompletion only — the EMIT path uses
|
|
253
253
|
* openCodeAssistantReasoningSteps (per-part identity) so live+final collapse to one row.
|
|
@@ -257,7 +257,7 @@ export function openCodeAssistantReasoning(parts: unknown[] | undefined): string
|
|
|
257
257
|
return steps.length ? steps.map((step) => step.body).join("\n\n") : undefined;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
/**
|
|
260
|
+
/** build the turn-final `response` event (marks it for Final Response capture / auto-forward
|
|
261
261
|
* and styles it apart from reasoning). `undefined` when the turn produced no assistant text. */
|
|
262
262
|
export function openCodeTurnFinalResponseEvent(text: string | undefined, turnId: string | undefined): ProviderSessionEvent | undefined {
|
|
263
263
|
const body = text?.trim();
|
|
@@ -266,7 +266,7 @@ export function openCodeTurnFinalResponseEvent(text: string | undefined, turnId:
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
/**
|
|
269
|
-
*
|
|
269
|
+
* emit the turn-final `reasoning` rows (`completion.reasoningSteps`, read from the SAME
|
|
270
270
|
* synchronous POST body as the response below) plus that response, in order. Each reasoning row
|
|
271
271
|
* carries its OWN part id as stepId — the exact key the live SSE door already emitted — so the
|
|
272
272
|
* server upserts live+final into ONE row instead of storing the turn's thinking twice. Pure
|
|
@@ -281,7 +281,7 @@ export function openCodeTurnFinalResponseEvent(text: string | undefined, turnId:
|
|
|
281
281
|
* path) — comparison is scoped to THOSE part ids, never to a global body pool, so an identical
|
|
282
282
|
* body in a later turn is never misread as already-seen.
|
|
283
283
|
*
|
|
284
|
-
* The text is ground truth
|
|
284
|
+
* The text is ground truth: the POST returning IS the provider-native turn-end edge,
|
|
285
285
|
* hence `confidence:"definitive"` on the response; `final:true` is what makes it a turn-final
|
|
286
286
|
* capture downstream. Emits reasoning first so the trace reads thinking-then-answer, the order
|
|
287
287
|
* the model produced them in. Either half may be absent (reasoning-only or text-only turns).
|