@humwick/runner 0.0.0-stage → 0.130.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +167 -0
- package/agent-bin/agent-relay-browser +10 -0
- package/agent-bin/humwick +10 -0
- package/hooks/fff-search-redirect/fff-search-redirect.cjs +161 -0
- package/hooks/fff-search-redirect/hooks.json +13 -0
- package/hooks/rg-r-guard/hooks.json +18 -0
- package/hooks/rg-r-guard/rg-r-guard.cjs +144 -0
- package/hooks/web-tools-redirect/hooks.json +13 -0
- package/hooks/web-tools-redirect/web-tools-redirect.cjs +84 -0
- package/node_modules/@humwick/offload/LICENSE +167 -0
- package/node_modules/@humwick/offload/README.md +57 -0
- package/node_modules/@humwick/offload/dist/async-runner.d.ts +18 -0
- package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/async-runner.js +158 -0
- package/node_modules/@humwick/offload/dist/async-runner.js.map +1 -0
- package/node_modules/@humwick/offload/dist/envelope.d.ts +25 -0
- package/node_modules/@humwick/offload/dist/envelope.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/envelope.js +80 -0
- package/node_modules/@humwick/offload/dist/envelope.js.map +1 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +10 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +59 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +1 -0
- package/node_modules/@humwick/offload/dist/index.d.ts +5 -0
- package/node_modules/@humwick/offload/dist/index.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/index.js +5 -0
- package/node_modules/@humwick/offload/dist/index.js.map +1 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +62 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.js +295 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +1 -0
- package/node_modules/@humwick/offload/dist/types.d.ts +111 -0
- package/node_modules/@humwick/offload/dist/types.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/types.js +176 -0
- package/node_modules/@humwick/offload/dist/types.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts +23 -0
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-pool.js +435 -0
- package/node_modules/@humwick/offload/dist/worker-pool.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +2 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.js +27 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker.d.ts +5 -0
- package/node_modules/@humwick/offload/dist/worker.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker.js +1 -0
- package/node_modules/@humwick/offload/dist/worker.js.map +1 -0
- package/node_modules/@humwick/offload/package.json +46 -0
- package/node_modules/@humwick/offload/src/async-runner.ts +170 -0
- package/node_modules/@humwick/offload/src/envelope.ts +92 -0
- package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +49 -0
- package/node_modules/@humwick/offload/src/index.ts +4 -0
- package/node_modules/@humwick/offload/src/subprocess-runner.ts +334 -0
- package/node_modules/@humwick/offload/src/types.ts +235 -0
- package/node_modules/@humwick/offload/src/worker-pool.ts +463 -0
- package/node_modules/@humwick/offload/src/worker-runtime.ts +32 -0
- package/node_modules/@humwick/offload/src/worker.ts +5 -0
- package/package.json +47 -4
- package/plugins/browser/skills/playwright-cli/SKILL.md +53 -0
- package/plugins/claude/.claude-plugin/plugin.json +8 -0
- package/plugins/claude/claude-statusline-probe.cjs +144 -0
- package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +914 -0
- package/plugins/claude/hooks/gate-run-guard.sh +9 -0
- package/plugins/claude/hooks/gate-run-guard.ts +237 -0
- package/plugins/claude/hooks/hooks.json +158 -0
- package/plugins/claude/hooks/kill-guard.provisioned.mjs +7027 -0
- package/plugins/claude/hooks/kill-guard.sh +341 -0
- package/plugins/claude/hooks/kill-guard.ts +337 -0
- package/plugins/claude/hooks/permission-request.sh +22 -0
- package/plugins/claude/hooks/post-compact.sh +10 -0
- package/plugins/claude/hooks/post-tool-use.sh +8 -0
- package/plugins/claude/hooks/pre-compact.sh +12 -0
- package/plugins/claude/hooks/read-only-bash-guard.sh +587 -0
- package/plugins/claude/hooks/relay-status.sh +373 -0
- package/plugins/claude/hooks/session-end.sh +26 -0
- package/plugins/claude/hooks/session-start.sh +31 -0
- package/plugins/claude/hooks/shell-trust.ts +28 -0
- package/plugins/claude/hooks/stop-failure.sh +47 -0
- package/plugins/claude/hooks/stop.sh +48 -0
- package/plugins/claude/hooks/subagent-guard.cjs +26 -0
- package/plugins/claude/hooks/subagent-skill-policy.cjs +160 -0
- package/plugins/claude/hooks/subagent-start.sh +13 -0
- package/plugins/claude/hooks/subagent-stop.sh +19 -0
- package/plugins/claude/hooks/user-prompt-submit.sh +18 -0
- package/plugins/claude/hooks/wait-loop-guard.ts +120 -0
- package/plugins/claude/monitors/monitors.json +7 -0
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +786 -0
- package/plugins/claude/monitors/relay-monitor.ts +58 -0
- package/plugins/claude/skills/disconnect/SKILL.md +16 -0
- package/plugins/claude/skills/label/SKILL.md +23 -0
- package/plugins/claude/skills/message/SKILL.md +40 -0
- package/plugins/claude/skills/pair/SKILL.md +30 -0
- package/plugins/claude/skills/react/SKILL.md +18 -0
- package/plugins/claude/skills/read-message/SKILL.md +24 -0
- package/plugins/claude/skills/reply/SKILL.md +44 -0
- package/plugins/claude/skills/send-claimable/SKILL.md +39 -0
- package/plugins/claude/skills/slice-first/SKILL.md +32 -0
- package/plugins/claude/skills/slice-first/references/plan-recipe.md +46 -0
- package/plugins/claude/skills/status/SKILL.md +16 -0
- package/plugins/claude/skills/tags/SKILL.md +25 -0
- package/plugins/claude/skills/task/SKILL.md +28 -0
- package/plugins/claude-output-style/.claude-plugin/plugin.json +5 -0
- package/plugins/claude-output-style/output-styles/agent-relay.md +5 -0
- package/plugins/codex/skills/disconnect/SKILL.md +15 -0
- package/plugins/codex/skills/guide/SKILL.md +15 -0
- package/plugins/codex/skills/label/SKILL.md +22 -0
- package/plugins/codex/skills/message/SKILL.md +40 -0
- package/plugins/codex/skills/pair/SKILL.md +29 -0
- package/plugins/codex/skills/react/SKILL.md +17 -0
- package/plugins/codex/skills/read-message/SKILL.md +23 -0
- package/plugins/codex/skills/reply/SKILL.md +44 -0
- package/plugins/codex/skills/send-claimable/SKILL.md +39 -0
- package/plugins/codex/skills/slice-first/SKILL.md +32 -0
- package/plugins/codex/skills/slice-first/references/plan-recipe.md +46 -0
- package/plugins/codex/skills/status/SKILL.md +15 -0
- package/plugins/codex/skills/tags/SKILL.md +24 -0
- package/plugins/codex/skills/task/SKILL.md +28 -0
- package/src/adapter.ts +1061 -0
- package/src/adapters/__fixtures__/codex-app-server.ts +47 -0
- package/src/adapters/claude-api.ts +243 -0
- package/src/adapters/claude-context-command.ts +16 -0
- package/src/adapters/claude-credentials.ts +22 -0
- package/src/adapters/claude-delivery-lanes.ts +28 -0
- package/src/adapters/claude-delivery.ts +166 -0
- package/src/adapters/claude-effort.ts +94 -0
- package/src/adapters/claude-exit.ts +290 -0
- package/src/adapters/claude-headless-mirror.ts +88 -0
- package/src/adapters/claude-headless-refusal.ts +51 -0
- package/src/adapters/claude-headless.ts +617 -0
- package/src/adapters/claude-monitor-delivery-plan.ts +317 -0
- package/src/adapters/claude-permission.ts +243 -0
- package/src/adapters/claude-prompt-gates.ts +269 -0
- package/src/adapters/claude-prompt-hook.ts +145 -0
- package/src/adapters/claude-session-capture.ts +293 -0
- package/src/adapters/claude-session-probe.ts +414 -0
- package/src/adapters/claude-shutdown.ts +190 -0
- package/src/adapters/claude-status-detectors.ts +426 -0
- package/src/adapters/claude-subagents.ts +93 -0
- package/src/adapters/claude-submit-confirmation.ts +287 -0
- package/src/adapters/claude-tmux.ts +548 -0
- package/src/adapters/claude-token-usage-capture.ts +253 -0
- package/src/adapters/claude-tool-policy.ts +165 -0
- package/src/adapters/claude-transcript-tail.ts +193 -0
- package/src/adapters/claude-transcript.ts +812 -0
- package/src/adapters/claude.ts +1097 -0
- package/src/adapters/codex-agent-message-capture.ts +189 -0
- package/src/adapters/codex-app-server.ts +70 -0
- package/src/adapters/codex-client.ts +362 -0
- package/src/adapters/codex-effective-config.ts +87 -0
- package/src/adapters/codex-launch-config.ts +153 -0
- package/src/adapters/codex-rollout-context.ts +272 -0
- package/src/adapters/codex-skill-discovery.ts +18 -0
- package/src/adapters/codex-token-usage-capture.ts +163 -0
- package/src/adapters/codex-tool-output.ts +53 -0
- package/src/adapters/codex-turn-outcome.ts +27 -0
- package/src/adapters/codex-version.ts +64 -0
- package/src/adapters/codex.ts +1442 -0
- package/src/adapters/incremental-file.ts +110 -0
- package/src/adapters/opencode-api.ts +71 -0
- package/src/adapters/opencode-context.ts +277 -0
- package/src/adapters/opencode-model-registry.ts +126 -0
- package/src/adapters/opencode-provider.ts +169 -0
- package/src/adapters/opencode-server-retry.ts +50 -0
- package/src/adapters/opencode-soft-steer.ts +302 -0
- package/src/adapters/opencode-spawn.ts +145 -0
- package/src/adapters/opencode-subscribe.ts +62 -0
- package/src/adapters/opencode-token-usage-capture.ts +187 -0
- package/src/adapters/opencode-tool-events.ts +302 -0
- package/src/adapters/opencode-tools-config.ts +17 -0
- package/src/adapters/opencode.ts +2264 -0
- package/src/adapters/project-shell.ts +80 -0
- package/src/adapters/provider-launch-config.ts +21 -0
- package/src/adapters/provider-quota-harvest.ts +127 -0
- package/src/adapters/service-access.ts +153 -0
- package/src/adapters/tmux-offload.ts +207 -0
- package/src/adapters/tool-summary.ts +214 -0
- package/src/agent-redact.ts +75 -0
- package/src/agent-runner/commands/dispatch.ts +185 -0
- package/src/agent-runner/commands/lifecycle.ts +84 -0
- package/src/agent-runner/commands/prompt.ts +284 -0
- package/src/agent-runner/credentials.ts +208 -0
- package/src/agent-runner/http-liveness.ts +85 -0
- package/src/agent-runner/lifecycle-milestones.ts +207 -0
- package/src/agent-runner/lifecycle-phase.ts +80 -0
- package/src/agent-runner/registration.ts +163 -0
- package/src/agent-runner/runner-context.ts +158 -0
- package/src/agent-runner/session-mirror.ts +278 -0
- package/src/agent-runner/task-claims.ts +116 -0
- package/src/agent-runner/token-usage-coverage.ts +54 -0
- package/src/agent-runner/turn-report.ts +269 -0
- package/src/api-budget-accounting.ts +91 -0
- package/src/area-memory.ts +255 -0
- package/src/artifact-path-materialize.ts +52 -0
- package/src/async-guard.ts +35 -0
- package/src/attachment-cache-sweep.ts +31 -0
- package/src/attachment-cache.ts +195 -0
- package/src/background-script-reconcile.ts +106 -0
- package/src/bounded-log.ts +59 -0
- package/src/bounded-spawn-sync.ts +18 -0
- package/src/busy-reconciler.ts +282 -0
- package/src/claim-tracker.ts +140 -0
- package/src/claude-settings.ts +167 -0
- package/src/claude-workspace-trust.ts +289 -0
- package/src/config.ts +547 -0
- package/src/context-boundary.ts +101 -0
- package/src/context-command.ts +151 -0
- package/src/context-probe-state.ts +6 -0
- package/src/continuation-archive.ts +179 -0
- package/src/control-server.ts +730 -0
- package/src/delivery-policy.ts +9 -0
- package/src/delivery-result.ts +24 -0
- package/src/effort-command.ts +125 -0
- package/src/empty-turn.ts +816 -0
- package/src/free-port.ts +25 -0
- package/src/fs-offload.ts +26 -0
- package/src/gate-run-config.ts +35 -0
- package/src/gate-run-lease-probe.ts +38 -0
- package/src/host-asset-plan.ts +450 -0
- package/src/host-claude-config-worker.ts +34 -0
- package/src/host-sources.ts +40 -0
- package/src/inbox-carrier.ts +7 -0
- package/src/index.ts +386 -0
- package/src/launch-assembly.ts +955 -0
- package/src/liveness.ts +105 -0
- package/src/logger.ts +108 -0
- package/src/mcp-outbox.ts +204 -0
- package/src/mcp-preflight.ts +86 -0
- package/src/mcp-source-selection.ts +54 -0
- package/src/mcp-tool-deferral.ts +35 -0
- package/src/mcp-vault-env.ts +25 -0
- package/src/message-body-config/config.ts +16 -0
- package/src/message-body-config.ts +1 -0
- package/src/mid-turn-delivery.ts +43 -0
- package/src/native-memory-lever.ts +329 -0
- package/src/native-self-resume.ts +244 -0
- package/src/outbox.ts +453 -0
- package/src/park-post.ts +17 -0
- package/src/pending-permission-store.ts +148 -0
- package/src/process-meta.ts +9 -0
- package/src/profile-home.ts +419 -0
- package/src/profile-projection.ts +411 -0
- package/src/project-hooks.ts +65 -0
- package/src/project-instructions.ts +119 -0
- package/src/provider-double.ts +59 -0
- package/src/provider-hold.ts +228 -0
- package/src/provider-home-marker.ts +3 -0
- package/src/provider-home-tombstone.ts +4 -0
- package/src/provider-input-queue.ts +134 -0
- package/src/provider-integration.ts +93 -0
- package/src/provider-processes.ts +17 -0
- package/src/provider-submit-failure.ts +15 -0
- package/src/providers.ts +35 -0
- package/src/provisioning.ts +703 -0
- package/src/question-decision.ts +18 -0
- package/src/quota.ts +22 -0
- package/src/rate-limit.ts +351 -0
- package/src/refused-prompt-store.ts +36 -0
- package/src/relay-injection-events.ts +161 -0
- package/src/relay-instructions.ts +107 -0
- package/src/relay-mcp-proxy.long-turn-e2e.ts +183 -0
- package/src/relay-mcp-proxy.ts +704 -0
- package/src/relay-mcp.ts +583 -0
- package/src/reply-obligation-cache.ts +211 -0
- package/src/response-capture-report.ts +111 -0
- package/src/response-routing.ts +6 -0
- package/src/runner-core.ts +2955 -0
- package/src/runner-exit.ts +12 -0
- package/src/runner-helpers.ts +582 -0
- package/src/runner-insights.ts +105 -0
- package/src/runner-mcp-proxy.ts +46 -0
- package/src/runner-spawn-config.ts +68 -0
- package/src/runner-stale-scratch-sweep.ts +21 -0
- package/src/runner.ts +9 -0
- package/src/runtime-token-file.ts +45 -0
- package/src/runtime-token-remint.ts +56 -0
- package/src/service-access.ts +88 -0
- package/src/session-destroy.ts +27 -0
- package/src/session-insights.ts +118 -0
- package/src/session-scratch.ts +296 -0
- package/src/spawn-input.ts +147 -0
- package/src/startup-config-fetch.ts +48 -0
- package/src/subreaper.ts +113 -0
- package/src/template-resolver.ts +29 -0
- package/src/test-run-dedup.ts +435 -0
- package/src/transcript-context.ts +99 -0
- package/src/transport-errors.ts +48 -0
- package/src/turn-opener.ts +55 -0
- package/src/turn-reconcile-event.ts +88 -0
- package/src/vault-proxy-hop.ts +99 -0
- package/src/version.ts +44 -0
- package/src/web-tool-call-proxy.ts +128 -0
- package/src/web-tools.ts +59 -0
- package/README.md +0 -3
package/src/index.ts
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun --no-env-file
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { disableCoreDumps } from "@humwick/sdk/core-dumps";
|
|
4
|
+
import { AgentRunner } from "./runner";
|
|
5
|
+
import {
|
|
6
|
+
agentProfileJsonFromEnv,
|
|
7
|
+
harnessSubagentsFromEnv,
|
|
8
|
+
agentProfileNameFromEnv,
|
|
9
|
+
automationIdFromEnv,
|
|
10
|
+
automationRunIdFromEnv,
|
|
11
|
+
lifecycleFromEnv,
|
|
12
|
+
loadGlobalConfig,
|
|
13
|
+
loadProviderConfigFromRelay,
|
|
14
|
+
policyNameFromEnv,
|
|
15
|
+
promptFromEnv,
|
|
16
|
+
providerConfigLookupFromEnv,
|
|
17
|
+
relayInjectionEventsJsonFromEnv,
|
|
18
|
+
resolveCwd,
|
|
19
|
+
runnerId,
|
|
20
|
+
runtimeTokenExpiresAtFromEnv,
|
|
21
|
+
runtimeTokenJtiFromEnv,
|
|
22
|
+
runtimeTokenProfileFromEnv,
|
|
23
|
+
spawnRequestIdFromEnv,
|
|
24
|
+
systemPromptAppendFromEnv,
|
|
25
|
+
taskIdFromEnv,
|
|
26
|
+
tmuxSessionFromEnv,
|
|
27
|
+
workspaceJsonFromEnv,
|
|
28
|
+
projectToolchainFromEnv,
|
|
29
|
+
} from "./config";
|
|
30
|
+
import { createProviderAdapter } from "./providers";
|
|
31
|
+
import { parseRunnerCliArgs, runnerHelp } from "./spawn-input";
|
|
32
|
+
import { VERSION } from "./version";
|
|
33
|
+
import type { AgentProfile, WorkspaceMetadata } from "@humwick/sdk";
|
|
34
|
+
import { exitRunner } from "./runner-exit";
|
|
35
|
+
import { codexConfigOverrides, errMessage, formatRunnerExitLine, isRecord, isRelayInjectionCategory, normalizeAgentLifecycle, RELAY_TOKEN_HEADER, providerArgsSelectStreamJson, startArtifactCheckoutLease } from "@humwick/sdk";
|
|
36
|
+
import { supportedLaunchModesForProvider } from "@humwick/providers";
|
|
37
|
+
import { installUnhandledRejectionGuard } from "./async-guard";
|
|
38
|
+
import type { RunnerRelayInjectionEvent } from "./relay-injection-events";
|
|
39
|
+
import { enableRunnerSubreaper } from "./subreaper";
|
|
40
|
+
|
|
41
|
+
export async function main(argv = process.argv): Promise<void> {
|
|
42
|
+
disableCoreDumps();
|
|
43
|
+
// #2443 — last-resort observability, installed before anything can schedule work. Two managed
|
|
44
|
+
// OpenCode sessions died with a clean `exit(1)` and an EMPTY stderr capture: the actual cause
|
|
45
|
+
// (a terminal provider failure resolving through onProviderExit, see runner-core.ts) turned out
|
|
46
|
+
// not to be a crash at all, but this guard closes the still-real, long-documented gap (#1665)
|
|
47
|
+
// for any genuine unhandled rejection or uncaught exception elsewhere in the runner — neither
|
|
48
|
+
// currently leaves a trace beyond "exited after Ns". `unhandledRejection` mirrors the
|
|
49
|
+
// orchestrator's non-fatal "log and survive" backstop (see the SDK function's doc): nothing
|
|
50
|
+
// here respawns a dead runner (#1618), so surviving beats dying silently. `uncaughtException`
|
|
51
|
+
// stays fatal (a synchronous throw can leave a half-applied mutation behind — same ruling as
|
|
52
|
+
// the orchestrator) but is logged deterministically via multiple paths first, so a crash always
|
|
53
|
+
// leaves a stack trace regardless of whether Bun's own default reporter would have shown one.
|
|
54
|
+
// Installed inside `main()`, not at module scope: this file is imported directly by tests
|
|
55
|
+
// (config.test.ts, runner.test.ts) that never call `main()`, and a top-level `process.on(...)`
|
|
56
|
+
// would leak a real, process-fatal `uncaughtException` handler into the whole `bun test` run.
|
|
57
|
+
installUnhandledRejectionGuard();
|
|
58
|
+
process.on("uncaughtException", (error) => {
|
|
59
|
+
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
60
|
+
try { process.stderr.write(`[runner] uncaughtException (fatal): ${detail}\n`); } catch { /* best-effort */ }
|
|
61
|
+
try { console.error(`[runner] uncaughtException (fatal): ${errMessage(error)}`); } catch { /* best-effort */ }
|
|
62
|
+
exitRunner(1, `uncaughtException: ${errMessage(error)}`);
|
|
63
|
+
});
|
|
64
|
+
if (isVersionRequest(argv)) {
|
|
65
|
+
console.log(VERSION);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const opts = parseRunnerCliArgs(argv);
|
|
69
|
+
if (opts.help) {
|
|
70
|
+
console.log(runnerHelp(opts.provider));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
enableRunnerSubreaper();
|
|
74
|
+
// Started only once we know this is a real launch, not --version/--help: the lease holds an
|
|
75
|
+
// un-refed setInterval alive (see sdk/src/artifact-checkout-gc.ts), so starting it earlier
|
|
76
|
+
// stops --version/--help from ever exiting on a host whose runtime is a checkout symlink
|
|
77
|
+
// (e.g. the wbox self-hosted runner) — the process hangs until externally killed. #2122
|
|
78
|
+
startArtifactCheckoutLease();
|
|
79
|
+
const globalConfig = loadGlobalConfig();
|
|
80
|
+
const relayUrl = opts.relayUrl ?? globalConfig.relayUrl;
|
|
81
|
+
const providerConfig = await loadRunnerProviderConfig(opts.provider, relayUrl, opts.token ?? globalConfig.token);
|
|
82
|
+
const cwd = resolveCwd(opts.cwd, globalConfig.defaultCwd);
|
|
83
|
+
const id = runnerId(opts.provider, cwd, opts.label);
|
|
84
|
+
// Providers offering both modes always record the one they run in; raw stream-json args stay a legacy headless selector.
|
|
85
|
+
const launchMode = opts.launchMode ?? (supportedLaunchModesForProvider(opts.provider).length
|
|
86
|
+
? (providerArgsSelectStreamJson(opts.providerArgs) ? "headless" : "interactive")
|
|
87
|
+
: undefined);
|
|
88
|
+
const adapter = createProviderAdapter(opts.provider, opts.providerArgs, launchMode);
|
|
89
|
+
// Resolved BEFORE the interactive token exchange so the mint can stamp the posture into the signed
|
|
90
|
+
// constraints (#2392): `agent.meta.approvalMode` is authz-relevant and therefore stripped at
|
|
91
|
+
// registration unless the token can prove it (src/authz-meta-keys.ts).
|
|
92
|
+
const approvalMode = opts.approvalMode
|
|
93
|
+
?? detectApprovalModeFromArgs(opts.provider, providerConfig.defaultArgs, opts.providerArgs)
|
|
94
|
+
?? providerConfig.defaultApprovalMode;
|
|
95
|
+
const runtimeAuth = await resolveRunnerToken({
|
|
96
|
+
interactive: opts.interactive,
|
|
97
|
+
relayUrl,
|
|
98
|
+
token: opts.token ?? globalConfig.token,
|
|
99
|
+
runtimeTokenProfile: runtimeTokenProfileFromEnv(),
|
|
100
|
+
runtimeTokenJti: runtimeTokenJtiFromEnv(),
|
|
101
|
+
runtimeTokenExpiresAt: parseTokenExpiresAt(runtimeTokenExpiresAtFromEnv()),
|
|
102
|
+
provider: opts.provider,
|
|
103
|
+
cwd,
|
|
104
|
+
runnerId: id,
|
|
105
|
+
agentId: opts.agentId ?? id,
|
|
106
|
+
label: opts.label,
|
|
107
|
+
approvalMode,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
let exitResolve: (code: number) => void;
|
|
111
|
+
let exitReason: string | undefined;
|
|
112
|
+
const exitPromise = new Promise<number>((resolve) => { exitResolve = resolve; });
|
|
113
|
+
|
|
114
|
+
// Provider-owned async preload reaches registration projection before any synchronous launch
|
|
115
|
+
// assembly runs, without leaking provider-specific host config parsing into this CLI boundary.
|
|
116
|
+
const launchConfig = await adapter.preloadRunnerConfig?.();
|
|
117
|
+
|
|
118
|
+
const runner = new AgentRunner({
|
|
119
|
+
provider: opts.provider,
|
|
120
|
+
model: opts.model,
|
|
121
|
+
effort: opts.effort,
|
|
122
|
+
// #2595 — effective-catalog window resolved server-side; the OpenCode adapter prefers it
|
|
123
|
+
// over its static manifest so override-defined models (muse-spark) report context usage.
|
|
124
|
+
...(opts.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {}),
|
|
125
|
+
// #2599 — the full record (protocol ≥ 2); the adapter applies payload > manifest precedence.
|
|
126
|
+
...(opts.modelRecord ? { modelRecord: opts.modelRecord } : {}),
|
|
127
|
+
runnerId: id,
|
|
128
|
+
instanceId: crypto.randomUUID(),
|
|
129
|
+
agentId: opts.agentId,
|
|
130
|
+
relayUrl,
|
|
131
|
+
token: runtimeAuth.token,
|
|
132
|
+
tokenJti: runtimeAuth.jti,
|
|
133
|
+
tokenProfileId: runtimeAuth.profileId,
|
|
134
|
+
tokenExpiresAt: runtimeAuth.expiresAt,
|
|
135
|
+
rootTokenFallback: runtimeAuth.rootFallback,
|
|
136
|
+
cwd,
|
|
137
|
+
headless: opts.headless,
|
|
138
|
+
...(launchMode ? { launchMode } : {}),
|
|
139
|
+
approvalMode,
|
|
140
|
+
label: opts.label,
|
|
141
|
+
rig: opts.rig,
|
|
142
|
+
profile: opts.profile ?? agentProfileNameFromEnv(),
|
|
143
|
+
harnessSubagents: harnessSubagentsFromEnv(),
|
|
144
|
+
agentProfile: parseAgentProfileEnv(agentProfileJsonFromEnv()),
|
|
145
|
+
workspace: parseWorkspaceEnv(workspaceJsonFromEnv()),
|
|
146
|
+
toolchain: projectToolchainFromEnv(),
|
|
147
|
+
prompt: opts.prompt ?? promptFromEnv(),
|
|
148
|
+
systemPromptAppend: opts.systemPromptAppend ?? systemPromptAppendFromEnv(),
|
|
149
|
+
relayInjectionEvents: parseRelayInjectionEventsEnv(relayInjectionEventsJsonFromEnv()),
|
|
150
|
+
tmuxSession: tmuxSessionFromEnv(),
|
|
151
|
+
tags: opts.tags,
|
|
152
|
+
capabilities: opts.caps,
|
|
153
|
+
providerArgs: opts.providerArgs,
|
|
154
|
+
policyName: policyNameFromEnv(),
|
|
155
|
+
spawnRequestId: spawnRequestIdFromEnv(),
|
|
156
|
+
taskId: taskIdFromEnv(),
|
|
157
|
+
automationId: automationIdFromEnv(),
|
|
158
|
+
automationRunId: automationRunIdFromEnv(),
|
|
159
|
+
lifecycle: normalizeAgentLifecycle(lifecycleFromEnv()) ?? "persistent",
|
|
160
|
+
startedAt: Date.now(),
|
|
161
|
+
providerConfig,
|
|
162
|
+
...(launchConfig ? { launchConfig } : {}),
|
|
163
|
+
adapter,
|
|
164
|
+
// #2443 — printed via console.error (captured into the systemd-redirected stdout/stderr log
|
|
165
|
+
// the orchestrator tails on exit, see logFileDiagnostics/readLogTail) BEFORE process.exit runs
|
|
166
|
+
// below, so a terminal-provider-failure exit reads "model-unavailable: APIError 404 ..." in
|
|
167
|
+
// that log instead of leaving it empty.
|
|
168
|
+
onProviderExit: (code, reason) => {
|
|
169
|
+
exitReason = reason;
|
|
170
|
+
exitResolve(code ?? 1);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
await runner.run();
|
|
174
|
+
|
|
175
|
+
const code = await Promise.race([exitPromise, signalPromise(runner)]);
|
|
176
|
+
// #2619 — the structured exit record is written before stop() can stall: the orchestrator parses
|
|
177
|
+
// it back from the log tail when systemd has already collected the unit. exitRunner then exits.
|
|
178
|
+
const reason = exitReason ?? (code === 130 || code === 143 ? `signal ${code === 130 ? "SIGINT" : "SIGTERM"}` : undefined);
|
|
179
|
+
console.error(formatRunnerExitLine(code, reason));
|
|
180
|
+
await runner.stop();
|
|
181
|
+
exitRunner(code, reason);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The runner main-path central-config bootstrap, kept callable for seam tests. */
|
|
185
|
+
export function loadRunnerProviderConfig(provider: string, relayUrl: string, token?: string) {
|
|
186
|
+
return loadProviderConfigFromRelay(provider, {
|
|
187
|
+
relayUrl,
|
|
188
|
+
token,
|
|
189
|
+
...providerConfigLookupFromEnv(),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function resolveRunnerToken(input: {
|
|
194
|
+
interactive: boolean;
|
|
195
|
+
relayUrl: string;
|
|
196
|
+
token?: string;
|
|
197
|
+
runtimeTokenProfile?: string;
|
|
198
|
+
runtimeTokenJti?: string;
|
|
199
|
+
runtimeTokenExpiresAt?: number;
|
|
200
|
+
provider: string;
|
|
201
|
+
cwd: string;
|
|
202
|
+
runnerId: string;
|
|
203
|
+
agentId: string;
|
|
204
|
+
label?: string;
|
|
205
|
+
/** #2392 — the posture this interactive session actually launches with. Sent so the relay can stamp
|
|
206
|
+
* it into the signed constraints; without it the session's `meta.approvalMode` is unprovable and
|
|
207
|
+
* gets stripped at registration, silently downgrading spawned children to `guarded`. */
|
|
208
|
+
approvalMode?: string;
|
|
209
|
+
}): Promise<{ token?: string; jti?: string; profileId?: string; expiresAt?: number; rootFallback?: boolean }> {
|
|
210
|
+
if (!input.token) return {};
|
|
211
|
+
const claims = decodeComponentTokenClaims(input.token);
|
|
212
|
+
if (input.runtimeTokenProfile) {
|
|
213
|
+
return {
|
|
214
|
+
token: input.token,
|
|
215
|
+
jti: input.runtimeTokenJti ?? claims?.jti,
|
|
216
|
+
profileId: input.runtimeTokenProfile,
|
|
217
|
+
expiresAt: input.runtimeTokenExpiresAt ?? claims?.exp,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const inferredRuntime = inferRuntimeTokenMetadata(claims);
|
|
221
|
+
if (!input.interactive && inferredRuntime) return { token: input.token, ...inferredRuntime };
|
|
222
|
+
if (!input.interactive) return { token: input.token };
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const url = new URL("/api/runtime-tokens/interactive-runner", input.relayUrl);
|
|
226
|
+
const res = await fetch(url, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: {
|
|
229
|
+
"Content-Type": "application/json",
|
|
230
|
+
[RELAY_TOKEN_HEADER]: input.token,
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
provider: input.provider,
|
|
234
|
+
cwd: input.cwd,
|
|
235
|
+
runnerId: input.runnerId,
|
|
236
|
+
agentId: input.agentId,
|
|
237
|
+
label: input.label,
|
|
238
|
+
host: hostname(),
|
|
239
|
+
...(input.approvalMode ? { approvalMode: input.approvalMode } : {}),
|
|
240
|
+
}),
|
|
241
|
+
signal: AbortSignal.timeout(5_000),
|
|
242
|
+
});
|
|
243
|
+
const body = await res.json().catch(() => null) as { token?: string; record?: { jti?: string; profileId?: string; expiresAt?: number }; error?: string } | null;
|
|
244
|
+
if (res.ok && body?.token) return { token: body.token, jti: body.record?.jti, profileId: body.record?.profileId ?? "provider-interactive", expiresAt: body.record?.expiresAt };
|
|
245
|
+
const detail = body?.error ? `: ${body.error}` : "";
|
|
246
|
+
console.warn(`[agent-relay] interactive scoped-token exchange failed (${res.status}${detail}); continuing with existing token. Root-token runtime fallback is deprecated.`);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.warn(`[agent-relay] interactive scoped-token exchange failed (${errMessage(error)}); continuing with existing token. Root-token runtime fallback is deprecated.`);
|
|
249
|
+
}
|
|
250
|
+
return { token: input.token, rootFallback: true };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function decodeComponentTokenClaims(token: string | undefined): { sub?: string; role?: string; exp?: number; jti?: string } | undefined {
|
|
254
|
+
const payload = token?.split(".")[1];
|
|
255
|
+
if (!payload) return undefined;
|
|
256
|
+
try {
|
|
257
|
+
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
258
|
+
const decoded = JSON.parse(Buffer.from(normalized, "base64").toString("utf8")) as Record<string, unknown>;
|
|
259
|
+
const exp = typeof decoded.exp === "number" && Number.isSafeInteger(decoded.exp) && decoded.exp > 0 ? decoded.exp : undefined;
|
|
260
|
+
return {
|
|
261
|
+
...(typeof decoded.sub === "string" ? { sub: decoded.sub } : {}),
|
|
262
|
+
...(typeof decoded.role === "string" ? { role: decoded.role } : {}),
|
|
263
|
+
...(typeof decoded.jti === "string" ? { jti: decoded.jti } : {}),
|
|
264
|
+
...(exp ? { exp } : {}),
|
|
265
|
+
};
|
|
266
|
+
} catch {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function inferRuntimeTokenMetadata(claims: { sub?: string; role?: string; exp?: number; jti?: string } | undefined): { jti?: string; profileId?: string; expiresAt?: number } | undefined {
|
|
272
|
+
if (claims?.role !== "provider" || !claims.exp) return undefined;
|
|
273
|
+
const sub = claims.sub ?? "";
|
|
274
|
+
const profileId = sub.startsWith("runner:interactive:")
|
|
275
|
+
? "provider-interactive"
|
|
276
|
+
: sub.startsWith("runner:spawn:") || sub.startsWith("runner:policy:") || /^runner:(codex|claude|provider):/.test(sub)
|
|
277
|
+
? "provider-agent"
|
|
278
|
+
: undefined;
|
|
279
|
+
if (!profileId) return undefined;
|
|
280
|
+
return {
|
|
281
|
+
...(claims.jti ? { jti: claims.jti } : {}),
|
|
282
|
+
profileId,
|
|
283
|
+
expiresAt: claims.exp,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function parseTokenExpiresAt(value: string | undefined): number | undefined {
|
|
288
|
+
if (!value) return undefined;
|
|
289
|
+
const parsed = Number(value);
|
|
290
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function isVersionRequest(argv: string[]): boolean {
|
|
294
|
+
const relayArgs = argv.slice(2);
|
|
295
|
+
const providerSep = relayArgs.indexOf("--");
|
|
296
|
+
const ownArgs = providerSep >= 0 ? relayArgs.slice(0, providerSep) : relayArgs;
|
|
297
|
+
return ownArgs.some((arg) => arg === "--version" || arg === "-v");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
function parseAgentProfileEnv(raw: string | undefined): AgentProfile | undefined {
|
|
302
|
+
if (!raw) return undefined;
|
|
303
|
+
try {
|
|
304
|
+
const parsed = JSON.parse(raw) as Partial<AgentProfile>;
|
|
305
|
+
return parsed && typeof parsed === "object" && typeof parsed.name === "string" ? parsed as AgentProfile : undefined;
|
|
306
|
+
} catch {
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function parseWorkspaceEnv(raw: string | undefined): WorkspaceMetadata | undefined {
|
|
312
|
+
if (!raw) return undefined;
|
|
313
|
+
try {
|
|
314
|
+
const parsed = JSON.parse(raw) as Partial<WorkspaceMetadata>;
|
|
315
|
+
return parsed && typeof parsed === "object" && typeof parsed.mode === "string" ? parsed as WorkspaceMetadata : undefined;
|
|
316
|
+
} catch {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function parseRelayInjectionEventsEnv(raw: string | undefined): RunnerRelayInjectionEvent[] | undefined {
|
|
322
|
+
if (!raw) return undefined;
|
|
323
|
+
try {
|
|
324
|
+
const parsed = JSON.parse(raw);
|
|
325
|
+
if (!Array.isArray(parsed)) return undefined;
|
|
326
|
+
const events = parsed.flatMap((item): RunnerRelayInjectionEvent[] => {
|
|
327
|
+
if (!isRecord(item) || typeof item.summary !== "string" || !isRelayInjectionCategory(item.category)) return [];
|
|
328
|
+
const detail = isRecord(item.detail) ? item.detail : {};
|
|
329
|
+
return [{ category: item.category, summary: item.summary, detail }];
|
|
330
|
+
});
|
|
331
|
+
return events.length ? events : undefined;
|
|
332
|
+
} catch {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// #2414 — arm the `shuttingDown` restart-suppression latch synchronously, in the signal handler itself, BEFORE resolving this
|
|
338
|
+
// promise (which only unblocks `main()`'s `await Promise.race(...)`, itself an extra tick before
|
|
339
|
+
// `await runner.stop()` ever runs). See `markShuttingDown`'s doc comment in runner-core.ts for the
|
|
340
|
+
// race this closes: a direct-child provider (Codex/OpenCode) dying from the SAME process-group
|
|
341
|
+
// signal the orchestrator just sent this process can otherwise race `handleAdapterStatusChange`
|
|
342
|
+
// ahead of `stop()` and get treated as an unexpected crash worth restarting.
|
|
343
|
+
export function signalPromise(runner: Pick<AgentRunner, "markShuttingDown">): Promise<number> {
|
|
344
|
+
return new Promise<number>((resolve) => {
|
|
345
|
+
process.on("SIGINT", () => { runner.markShuttingDown(); resolve(130); });
|
|
346
|
+
process.on("SIGTERM", () => { runner.markShuttingDown(); resolve(143); });
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
type ApprovalModeDetector = (allArgs: string[]) => string | undefined;
|
|
351
|
+
|
|
352
|
+
const APPROVAL_MODE_DETECTORS: Record<string, ApprovalModeDetector> = {
|
|
353
|
+
claude(allArgs) {
|
|
354
|
+
if (allArgs.includes("--dangerously-skip-permissions")) return "open";
|
|
355
|
+
const modeIdx = allArgs.lastIndexOf("--permission-mode");
|
|
356
|
+
if (modeIdx >= 0) {
|
|
357
|
+
const mode = allArgs[modeIdx + 1];
|
|
358
|
+
if (mode === "dontAsk") return "read-only";
|
|
359
|
+
if (mode === "default") return "guarded";
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
},
|
|
363
|
+
codex(allArgs) {
|
|
364
|
+
if (allArgs.includes("--yolo")) return "open";
|
|
365
|
+
for (const value of codexConfigOverrides(allArgs)) {
|
|
366
|
+
if (/sandbox_mode\s*=\s*"?danger-full-access"?/.test(value)) return "open";
|
|
367
|
+
if (/sandbox_mode\s*=\s*"?read-only"?/.test(value)) return "read-only";
|
|
368
|
+
}
|
|
369
|
+
return undefined;
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export function detectApprovalModeFromArgs(provider: string, defaultArgs: string[], providerArgs: string[]): string | undefined {
|
|
374
|
+
const detector = APPROVAL_MODE_DETECTORS[provider];
|
|
375
|
+
return detector ? detector([...defaultArgs, ...providerArgs]) : undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (import.meta.main) {
|
|
379
|
+
main().catch((error) => {
|
|
380
|
+
// A fatal startup failure (e.g. the runner could not register with the relay within the
|
|
381
|
+
// deadline) must exit non-zero, not hang or swallow — the orchestrator's exit detection
|
|
382
|
+
// then reports it and the spawn-parent is notified instead of waiting on a silent zombie.
|
|
383
|
+
console.error(`[agent-relay] runner fatal: ${errMessage(error)}`);
|
|
384
|
+
exitRunner(1, `runner fatal: ${errMessage(error)}`);
|
|
385
|
+
});
|
|
386
|
+
}
|