@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
package/src/adapter.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readHumwickRecord } from "@humwick/sdk/stored-names";
|
|
1
2
|
import type { ServiceAccessHandoff } from "@humwick/sdk";
|
|
2
3
|
import type { AgentProfile, InteractivePrompt, LivenessSignal, Message, MessageFinalityConfidence, MessageTurnEndReason, OperatorDecisionFileInput, ProviderState, ToolCallCategory } from "@humwick/sdk";
|
|
3
4
|
// Leaf subpaths, not the barrel: the Claude monitor bundle imports this module and must stay small.
|
|
@@ -16,7 +17,7 @@ export { resolveRelayContext } from "./relay-instructions";
|
|
|
16
17
|
export type SemanticStatus = "idle" | "busy" | "offline" | "error";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
+
* the independent pane read is THREE-VALUED on its non-busy side, and collapsing it to a
|
|
20
21
|
* boolean was the defect. `false` used to mean both "captured the pane, no spinner" (evidence the
|
|
21
22
|
* turn is over) and "could not read the pane at all" (no evidence of anything), and the busy
|
|
22
23
|
* backstop's force-clear consumed the second as if it were the first: absence of evidence taken
|
|
@@ -51,7 +52,7 @@ export interface ProviderStatusEvent {
|
|
|
51
52
|
label?: string;
|
|
52
53
|
role?: string;
|
|
53
54
|
parentId?: string;
|
|
54
|
-
// OS pids of the process(es) this busy work tracks
|
|
55
|
+
// OS pids of the process(es) this busy work tracks. When supplied, the
|
|
55
56
|
// runner reconciles them against actual liveness and clears the busy marker if
|
|
56
57
|
// every pid is dead — recovering from a tracked process killed out-of-band.
|
|
57
58
|
pids?: number[];
|
|
@@ -62,7 +63,7 @@ export interface ProviderStatusEvent {
|
|
|
62
63
|
*/
|
|
63
64
|
refusedInputIds?: number[];
|
|
64
65
|
metadata?: Record<string, unknown>;
|
|
65
|
-
//
|
|
66
|
+
// the live transcript path for a hookless (monitorless) provider turn. When an
|
|
66
67
|
// adapter that learns turn boundaries WITHOUT Stop/UserPromptSubmit hooks (Claude's tmux
|
|
67
68
|
// monitorless watch) supplies this on a `provider-turn` busy/idle event, the runner drives
|
|
68
69
|
// the transcript reasoning tail natively: start on turn-start, capture the final response on
|
|
@@ -73,9 +74,9 @@ export interface ProviderStatusEvent {
|
|
|
73
74
|
export type ProviderStatusUpdate = SemanticStatus | ProviderStatusEvent;
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
*
|
|
77
|
+
* pane evidence cannot end a turn, clear work, complete claims, decide a restart,
|
|
77
78
|
* or send input. It carries no SemanticStatus or work-kind `clear`. The one display-only
|
|
78
|
-
* exception is a startup-scoped bypass acceptance dialog
|
|
79
|
+
* exception is a startup-scoped bypass acceptance dialog: the runner may show a
|
|
79
80
|
* blocked badge and timeline alert until the modal disappears. This does not mutate work.
|
|
80
81
|
*
|
|
81
82
|
* A detector that wants to say something about the pane emits one of these via
|
|
@@ -122,17 +123,17 @@ export interface ProviderSessionEvent {
|
|
|
122
123
|
replyToUserObligation?: boolean;
|
|
123
124
|
/** Route captured with the submitted inbox turn; null means mirror only, undefined uses the runner cache. */
|
|
124
125
|
replyRoute?: { to: string; replyToMessageId: number; channelTarget?: boolean; replyExpected?: boolean } | null;
|
|
125
|
-
/**
|
|
126
|
+
/** the turn opener, already resolved by the runner (a hookless capture finishing after its idle
|
|
126
127
|
* edge); null = none. Absent ⇒ the runner resolves it from the final's turn id. */
|
|
127
128
|
opener?: ProviderSessionEvent["replyRoute"];
|
|
128
129
|
label?: string;
|
|
129
|
-
/** Provider-independent tool-call category
|
|
130
|
+
/** Provider-independent tool-call category, adapter-resolved; only set when type is "tool". */
|
|
130
131
|
category?: ToolCallCategory;
|
|
131
|
-
/**
|
|
132
|
+
/** absolute paths of the files a tool reads or edits, from its raw input (never `params`,
|
|
132
133
|
* which is capped); only set when type is "tool". The runner matches them to path areas. */
|
|
133
134
|
paths?: string[];
|
|
134
135
|
/**
|
|
135
|
-
* Tool/step lifecycle status. `cancelled`
|
|
136
|
+
* Tool/step lifecycle status. `cancelled` is the terminal outcome for a tool the
|
|
136
137
|
* provider interrupted before it finished (OpenCode's ToolPart error state with
|
|
137
138
|
* `interrupted:true`), distinct from `failed` (ran and errored). Extended ONCE here for every
|
|
138
139
|
* provider — see MessageSessionMeta.status.
|
|
@@ -144,16 +145,16 @@ export interface ProviderSessionEvent {
|
|
|
144
145
|
* appending a duplicate (a tool's running→completed, a streamed reasoning row). */
|
|
145
146
|
stepId?: string;
|
|
146
147
|
/**
|
|
147
|
-
*
|
|
148
|
+
* round 3 — true only for a narration ABOUT the adapter's own retry/failure mechanics
|
|
148
149
|
* (e.g. "retrying in 4s…", "gave up after N retries"), never for anything the model itself
|
|
149
150
|
* produced. `noteOutput` (empty-turn.ts) excludes these from empty-turn evidence the same way it
|
|
150
151
|
* already excludes an echoed `prompt`: a retry storm that ends in terminal failure must not read
|
|
151
|
-
* as forward progress and cancel the
|
|
152
|
+
* as forward progress and cancel the input-armed escalation that wakes the spawn parent.
|
|
152
153
|
* Distinct from `status: "failed"` on purpose — a failed TOOL call is still evidence the model is
|
|
153
154
|
* alive and working; this is evidence only that the TRANSPORT is still failing.
|
|
154
155
|
*/
|
|
155
156
|
diagnostic?: boolean;
|
|
156
|
-
/**
|
|
157
|
+
/** redacted, size-capped tool input/arguments, only set when type is "tool". Carried
|
|
157
158
|
* straight into MessageSessionMeta.params by the adapter-event→session-meta conversion. */
|
|
158
159
|
params?: unknown;
|
|
159
160
|
}
|
|
@@ -185,7 +186,7 @@ export interface ProviderTokenUsageRecord {
|
|
|
185
186
|
reasoningOutputTokens?: number;
|
|
186
187
|
dedupeKey: string;
|
|
187
188
|
createdAt: number;
|
|
188
|
-
/**
|
|
189
|
+
/** the provider recorded usage without counts; buckets are zero and the ledger marks it unknown. */
|
|
189
190
|
usageStatus?: "unknown";
|
|
190
191
|
}
|
|
191
192
|
|
|
@@ -201,7 +202,7 @@ export interface ProviderTokenUsageReporter {
|
|
|
201
202
|
export interface ProviderSessionTurnContext {
|
|
202
203
|
turnId?: string;
|
|
203
204
|
turnStartedAt?: number;
|
|
204
|
-
/**
|
|
205
|
+
/** set only on a hookless capture after the idle edge: the opener snapshotted there. */
|
|
205
206
|
opener?: ProviderSessionEvent["opener"];
|
|
206
207
|
chatCaptureMode: ProviderConfig["chatCaptureMode"];
|
|
207
208
|
reasoningCapture?: boolean;
|
|
@@ -211,7 +212,7 @@ export interface ProviderSessionTurnContext {
|
|
|
211
212
|
pendingSessionEventCount(): number;
|
|
212
213
|
log(message: string): void;
|
|
213
214
|
debug(message: string): void;
|
|
214
|
-
/**
|
|
215
|
+
/** set only on a turn-end capture: input the provider already accepted runs as the next turn
|
|
215
216
|
* with no busy edge of its own, so the runner keeps a turn open across this end. */
|
|
216
217
|
noteQueuedTurn?(): void;
|
|
217
218
|
}
|
|
@@ -257,12 +258,12 @@ export interface ProviderPermissionPromptHandler {
|
|
|
257
258
|
buildView(id: string, body: Record<string, unknown>, reasoningSettled: boolean): InteractivePrompt;
|
|
258
259
|
buildHookResponse(decision: ProviderPermissionDecisionInput, body: Record<string, unknown>): Record<string, unknown>;
|
|
259
260
|
buildResolvedPrompt(view: InteractivePrompt, decision: ProviderPermissionDecisionInput, body: Record<string, unknown>, occurredAt: number): ProviderPermissionResolvedPrompt;
|
|
260
|
-
//
|
|
261
|
+
// for prompt kinds whose PreToolUse hook is answered immediately (deny-then-hold —
|
|
261
262
|
// see control-server.ts's AskUserQuestion branch) rather than held open for the decision,
|
|
262
263
|
// this renders the eventual "answer" decision as the normal user-prompt text to inject back
|
|
263
264
|
// into the session. Absent for prompt kinds that still ride the hook response directly.
|
|
264
265
|
buildAnswerInjectionPrompt?(view: InteractivePrompt, decision: ProviderPermissionDecisionInput, body: Record<string, unknown>): string;
|
|
265
|
-
//
|
|
266
|
+
// maps a "questions" prompt to a durable operator decision a coordinator files instead.
|
|
266
267
|
buildDecisionRequest?(body: Record<string, unknown>): OperatorDecisionFileInput | undefined;
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -279,7 +280,7 @@ export interface ProviderConfig {
|
|
|
279
280
|
// When false, the runner does not stream reasoning/tool steps into chat. Defaults
|
|
280
281
|
// to enabled (steps render discreetly, never as chat bubbles).
|
|
281
282
|
reasoningCapture?: boolean;
|
|
282
|
-
//
|
|
283
|
+
// Phase A — how a provider WITH a mid-turn steer channel treats an ordinary message
|
|
283
284
|
// offered while a turn is active. Optional everywhere: absent resolves through the OWNING
|
|
284
285
|
// adapter's default (see resolveActiveTurnMessageDelivery in mid-turn-delivery.ts — adapters
|
|
285
286
|
// with a mid-turn steer channel, codex and opencode/zai, default to steer; adapters without
|
|
@@ -292,7 +293,7 @@ export interface ProviderConfig {
|
|
|
292
293
|
};
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
/**
|
|
296
|
+
/** Phase A — the mid-turn delivery policy. `"steer"` folds an ordinary message offered
|
|
296
297
|
* mid-turn into the in-flight turn; `"defer"` keeps queue-until-turn-end. */
|
|
297
298
|
export type ActiveTurnMessageDelivery = "steer" | "defer";
|
|
298
299
|
|
|
@@ -305,12 +306,12 @@ export interface RunnerSpawnConfig {
|
|
|
305
306
|
provider: string;
|
|
306
307
|
model?: string;
|
|
307
308
|
effort?: string;
|
|
308
|
-
/**
|
|
309
|
+
/** effective-catalog context window from the spawn payload (incl. DB overrides);
|
|
309
310
|
* adapters prefer it over their static manifest lookup. Absent ⇒ fall back to the manifest. */
|
|
310
311
|
contextWindowTokens?: number;
|
|
311
|
-
/**
|
|
312
|
+
/** the full server-resolved model record (protocol ≥ 2). Precedence over the manifest
|
|
312
313
|
* for window / qualified name / descriptor / threshold; absent ⇒ manifest (or the legacy
|
|
313
|
-
*
|
|
314
|
+
* `contextWindowTokens` above, which the adapter reads only when no record is present). */
|
|
314
315
|
modelRecord?: ResolvedModelRecord;
|
|
315
316
|
runnerId: string;
|
|
316
317
|
instanceId: string;
|
|
@@ -318,6 +319,8 @@ export interface RunnerSpawnConfig {
|
|
|
318
319
|
relayUrl: string;
|
|
319
320
|
/** Runner-owned live credential accessor for adapter accounting RPCs. */
|
|
320
321
|
getRelayToken?: () => string | undefined;
|
|
322
|
+
/** Runner-local warning sink; published through the ordinary durable timeline audit. */
|
|
323
|
+
onLaunchWarning?: (event: import("./turn-reconcile-event").RunnerTimelineEvent) => void;
|
|
321
324
|
cwd: string;
|
|
322
325
|
headless: boolean;
|
|
323
326
|
lifecycle?: "persistent" | "transient";
|
|
@@ -338,15 +341,15 @@ export interface RunnerSpawnConfig {
|
|
|
338
341
|
hostClaudeSettings?: Record<string, unknown>;
|
|
339
342
|
env: Record<string, string>;
|
|
340
343
|
controlPort: number;
|
|
341
|
-
// Stage 2
|
|
344
|
+
// Stage 2: the MCP endpoint the agent connects to — the runner-local proxy URL when the
|
|
342
345
|
// proxy is active. Undefined → the adapter targets the relay's MCP endpoint directly (Stage 1).
|
|
343
346
|
relayMcpEndpoint?: string;
|
|
344
|
-
//
|
|
345
|
-
// same value exported as
|
|
347
|
+
// the per-session proxy secret the agent authenticates to the runner-local proxy with (the
|
|
348
|
+
// same value exported as HUMWICK_SESSION_TOKEN). Set only when the proxy is active; the Codex
|
|
346
349
|
// per-agent metaOnly callmux uses it to reach the relay endpoint on-demand THROUGH the proxy, so
|
|
347
350
|
// identity rides the proxy's live-token injection and no relay token is ever written to disk.
|
|
348
351
|
mcpProxySecret?: string;
|
|
349
|
-
//
|
|
352
|
+
// the shared host-listener URL the per-agent `callmux bridge` connects to, when the
|
|
350
353
|
// profile/spawn opted into `agentProfile.sharedMcp`. Resolved runner-side (env → default);
|
|
351
354
|
// the launch assembler only emits the bridge entry when sharedMcp is on.
|
|
352
355
|
sharedMcpUrl?: string;
|
|
@@ -361,7 +364,7 @@ export interface SpawnArgs {
|
|
|
361
364
|
args: string[];
|
|
362
365
|
cwd: string;
|
|
363
366
|
env: Record<string, string>;
|
|
364
|
-
/**
|
|
367
|
+
/** env keys that must be explicitly UNSET in the launch environment (the tmux
|
|
365
368
|
* launcher script exports only whitelisted keys but still inherits the tmux server's
|
|
366
369
|
* env, so deleting from `env` alone cannot scrub an ambient credential). */
|
|
367
370
|
envUnset?: string[];
|
|
@@ -380,11 +383,11 @@ export interface ManagedProcess {
|
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
/**
|
|
383
|
-
*
|
|
386
|
+
* the terminal verdict of a provider shutdown. `killed` means the provider
|
|
384
387
|
* process (tmux pane / pid tree) is POSITIVELY confirmed gone; `failed-to-kill` means it
|
|
385
388
|
* survived even a SIGKILL of its process group (near-impossible on a same-user Linux host,
|
|
386
389
|
* so it is the operator-visible safety surface, not the happy path). The runner uses this
|
|
387
|
-
* to gate deregistration: a roster removal must imply the process is dead
|
|
390
|
+
* to gate deregistration: a roster removal must imply the process is dead, so
|
|
388
391
|
* a `failed-to-kill` keeps the agent visible rather than turning it into a silent zombie.
|
|
389
392
|
*/
|
|
390
393
|
export type ShutdownOutcome = "killed" | "failed-to-kill";
|
|
@@ -395,7 +398,7 @@ export interface TerminalAttachSpec {
|
|
|
395
398
|
cwd: string;
|
|
396
399
|
command: string[];
|
|
397
400
|
/**
|
|
398
|
-
*
|
|
401
|
+
* round 1 (review A) — REMOVED: a per-key env record let the guest credential ride
|
|
399
402
|
* the attach-spec HTTP response body and the orchestrator's process state. Credentials now
|
|
400
403
|
* travel ONLY via {@link envFile}: the runner pre-stages a 0600 env file it owns and the
|
|
401
404
|
* spec carries its PATH (never its contents); the orchestrator wraps the guest command in a
|
|
@@ -405,14 +408,14 @@ export interface TerminalAttachSpec {
|
|
|
405
408
|
title?: string;
|
|
406
409
|
ttlMs?: number;
|
|
407
410
|
/**
|
|
408
|
-
*
|
|
411
|
+
* absolute path to a 0600 env file written by the runner (same host + user), for
|
|
409
412
|
* the orchestrator's guest wrapper to source into the guest process environment. The path
|
|
410
413
|
* is not a secret; the file contents never travel through the control server, the
|
|
411
414
|
* orchestrator's memory/persistence, logs, or any argv.
|
|
412
415
|
*/
|
|
413
416
|
envFile?: string;
|
|
414
417
|
/**
|
|
415
|
-
*
|
|
418
|
+
* non-secret attachment generation, derived from the provider's ACTIVE session id
|
|
416
419
|
* (a one-way digest — never the session id itself, never a credential). The orchestrator
|
|
417
420
|
* stores it with the guest tmux session and re-reads the runner's current generation before
|
|
418
421
|
* terminal input and on stream connect/reconnect; a mismatch means clearContext()/retry
|
|
@@ -441,7 +444,7 @@ export interface ProviderPermissionDecisionReason {
|
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
// Fresh, per-turn context usage a transcript-backed adapter reads from its
|
|
444
|
-
// session log
|
|
447
|
+
// session log. tokensUsed is the context-window token sum for the turn;
|
|
445
448
|
// occurredAt is the wall-clock ms of the turn when the log stamps one.
|
|
446
449
|
export interface ProviderContextUsage {
|
|
447
450
|
tokensUsed: number;
|
|
@@ -452,7 +455,7 @@ export interface ProviderContextUsage {
|
|
|
452
455
|
/**
|
|
453
456
|
* Runner-owned live turn state is sampled when the adapter offers input.
|
|
454
457
|
*
|
|
455
|
-
*
|
|
458
|
+
* Phase B — the SAME context carries the deferred-settlement handoff for a mid-turn steer whose
|
|
456
459
|
* acceptance is not yet consumption (OpenCode/zai noReply persist: the provider row exists, the model
|
|
457
460
|
* reads it at a LATER step). The callbacks are runner-owned implementations of the ONE settlement seam —
|
|
458
461
|
* `settleMidTurnConsumed` completes an awaiting id's settlement exactly like an immediate ack (mark read,
|
|
@@ -469,23 +472,23 @@ export interface ProviderInputObserver {
|
|
|
469
472
|
|
|
470
473
|
export interface ProviderDeliverContext {
|
|
471
474
|
inputObserver?: ProviderInputObserver;
|
|
472
|
-
/**
|
|
475
|
+
/** for an adapter that runs one provider turn PER message: these messages open the next turn.
|
|
473
476
|
* Returns a disarm for when they open none (failed, refused). Absent call ⇒ the whole batch opens one. */
|
|
474
477
|
openTurnFor?: (messages: Message[]) => () => void;
|
|
475
|
-
/**
|
|
478
|
+
/** this offer's turn is opened by only these messages (a split batch); a no-op unless the offer armed an opener. Returns its disarm. */
|
|
476
479
|
narrowTurnOpener?: (messages: Message[]) => () => void;
|
|
477
480
|
isRecipientMidTurn?: () => boolean;
|
|
478
481
|
/** Text an adapter types as a provider user turn, so its prompt-hook echo is not mirrored as terminal input. */
|
|
479
482
|
recordSubmittedPrompt?: (text: string, imagePaths?: readonly string[]) => void;
|
|
480
483
|
/**
|
|
481
|
-
*
|
|
484
|
+
* Phase B — positively CONSUMED ids for a batch previously reported through
|
|
482
485
|
* {@link DeliverResult.awaitingConsumptionIds}. Fires once per id when the model has read the
|
|
483
486
|
* persisted row (SSE parentID correlation or the bounded idle-wake settle). Absent callback ⇒
|
|
484
487
|
* the adapter must hold the ids in its ledger (fail-safe: never silently settled).
|
|
485
488
|
*/
|
|
486
489
|
settleMidTurnConsumed?: (relayIds: number[]) => void;
|
|
487
490
|
/**
|
|
488
|
-
*
|
|
491
|
+
* Phase B — ids to hand BACK to the normal queue (settled as undelivered, re-offered at
|
|
489
492
|
* the next boundary) because their persisted row can no longer be consumed where it lives.
|
|
490
493
|
* Absent callback ⇒ hold in the ledger, same fail-safe as {@link settleMidTurnConsumed}.
|
|
491
494
|
*/
|
|
@@ -495,7 +498,7 @@ export interface ProviderDeliverContext {
|
|
|
495
498
|
export interface DeliverResult {
|
|
496
499
|
acknowledgedIds: number[];
|
|
497
500
|
/**
|
|
498
|
-
*
|
|
501
|
+
* Phase B — ids the adapter positively accepted for the in-flight turn (or holds in
|
|
499
502
|
* ambiguous custody after ONE timed-out POST whose bounded exact-id reconcile was inconclusive)
|
|
500
503
|
* but whose CONSUMPTION has not happened yet, so their settlement completes LATER through
|
|
501
504
|
* {@link ProviderDeliverContext.settleMidTurnConsumed} / {@link ProviderDeliverContext.reofferMidTurnIds}.
|
|
@@ -505,7 +508,7 @@ export interface DeliverResult {
|
|
|
505
508
|
*/
|
|
506
509
|
awaitingConsumptionIds?: number[];
|
|
507
510
|
/**
|
|
508
|
-
*
|
|
511
|
+
* round 3 review (MED-3r) — ids the adapter positively knows failed a TERMINAL provider
|
|
509
512
|
* error (e.g. OpenCode's retry+reroll budget exhausted against a rate-limited upstream), as
|
|
510
513
|
* opposed to an ordinary un-acknowledged id (recipient mid-turn, transport busy). The runner
|
|
511
514
|
* paces its re-drain of these with bounded exponential backoff instead of an immediate re-drain — without
|
|
@@ -514,7 +517,7 @@ export interface DeliverResult {
|
|
|
514
517
|
*/
|
|
515
518
|
terminalFailureIds?: number[];
|
|
516
519
|
/**
|
|
517
|
-
*
|
|
520
|
+
* ids the provider REFUSED under a rate-limit/quota hold (the turn ended in the shared
|
|
518
521
|
* `blocked/rate_limit` hold, not a terminal failure). Not delivered and not failed: the runner
|
|
519
522
|
* parks them (never re-drained into the wall, claims released so no Task completes) until a
|
|
520
523
|
* provider turn goes through again, then redelivers them. Must be disjoint from `acknowledgedIds`.
|
|
@@ -523,7 +526,7 @@ export interface DeliverResult {
|
|
|
523
526
|
}
|
|
524
527
|
|
|
525
528
|
/**
|
|
526
|
-
*
|
|
529
|
+
* thrown by a synchronous-endpoint adapter (OpenCode/zai) when its outbound prompt was
|
|
527
530
|
* positively ACCEPTED and persisted by the provider, but the turn's COMPLETION await itself
|
|
528
531
|
* failed afterwards (transport drop, turn-timeout abort). The provider is already working on the
|
|
529
532
|
* prompt, so the caller must NOT requeue it: a requeue would run the same assignment a second
|
|
@@ -543,8 +546,8 @@ export interface ProviderAdapter {
|
|
|
543
546
|
statusSourceId?: string;
|
|
544
547
|
stopObligationPath?: string;
|
|
545
548
|
permissionPromptHandler?: ProviderPermissionPromptHandler;
|
|
546
|
-
spawn(config: RunnerSpawnConfig): Promise<ManagedProcess>;
|
|
547
|
-
//
|
|
549
|
+
spawn(config: RunnerSpawnConfig, preparedArgs?: SpawnArgs): Promise<ManagedProcess>;
|
|
550
|
+
// resolves once the provider is terminal. Returns the outcome so the runner can
|
|
548
551
|
// gate deregistration on confirmed death; `void` from a legacy adapter is treated as
|
|
549
552
|
// `killed` (best-effort back-compat), but both first-party adapters now report explicitly.
|
|
550
553
|
shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<ShutdownOutcome | void>;
|
|
@@ -555,51 +558,51 @@ export interface ProviderAdapter {
|
|
|
555
558
|
compact?(process: ManagedProcess, opts?: { instructions?: string; signal?: AbortSignal; onSubmitted?: () => void }): Promise<Record<string, unknown> | void>;
|
|
556
559
|
compactSupportsInstructions?: boolean;
|
|
557
560
|
// Blind submits stamp onSubmitted and need a separate compacted/idle completion barrier.
|
|
558
|
-
/**
|
|
561
|
+
/** the adapter emits its own `compacted` / `context-cleared` timelines; hook copies are not reset facts. */
|
|
559
562
|
reportsContextResets?: boolean;
|
|
560
563
|
compactReportsSubmission?: boolean;
|
|
561
564
|
clearContext?(process: ManagedProcess): Promise<Record<string, unknown> | void>;
|
|
562
|
-
//
|
|
565
|
+
// change effort in place. Resolves with the confirmed `effort`, throws when the provider
|
|
563
566
|
// refused or never confirmed it. Only processes whose meta.effortSwitch is true may receive it.
|
|
564
567
|
setEffort?(process: ManagedProcess, effort: string): Promise<{ effort: string } & Record<string, unknown>>;
|
|
565
|
-
//
|
|
568
|
+
// when true, the runner delivers a self-resume clear-inject continuation IN-PROCESS,
|
|
566
569
|
// coupled to the very same agent.clearContext command (deliverCoupledContinuationAfterClear),
|
|
567
570
|
// rather than leaving it to the separate reactive agent.injectContext command, and only when
|
|
568
571
|
// clearContext reports `clearConfirmed: true`. Set it when a separate inject could race the clear
|
|
569
|
-
// (Claude tmux's async `/clear`, the
|
|
572
|
+
// (Claude tmux's async `/clear`, the silent mind-wipe) or land after a queued inbox message
|
|
570
573
|
// (headless Claude, whose clear resolves on its native conversation_reset boundary). Codex leaves
|
|
571
574
|
// it unset: its awaited threadStart is the barrier and the separate inject (backstopped by the
|
|
572
|
-
// server-side
|
|
575
|
+
// server-side sweep) stays untouched.
|
|
573
576
|
couplesClearInjectContinuation?: boolean;
|
|
574
|
-
//
|
|
577
|
+
// F1 — when true, this provider's `/compact` // `/clear` must be submitted to an IDLE
|
|
575
578
|
// provider: Claude tmux keystrokes typed mid-turn dequeue as literal chat, and headless Claude
|
|
576
579
|
// queues stream-json input behind the live turn. The runner therefore gates the submit on the
|
|
577
580
|
// live provider turn CLEARING first (waitForProviderTurnClear). Providers whose compact/clear is an
|
|
578
581
|
// awaited app-server handshake (Codex) leave this unset — a plain turn-clear wait would time out
|
|
579
582
|
// on their in-turn compaction. Those providers fence the PREDECESSOR turn a different way
|
|
580
|
-
// instead — see interruptsTurnBeforeContextCommand below
|
|
583
|
+
// instead — see interruptsTurnBeforeContextCommand below.
|
|
581
584
|
awaitsIdleBeforeContextCommand?: boolean;
|
|
582
|
-
//
|
|
585
|
+
// when true, the runner must INTERRUPT and SETTLE the predecessor provider turn before
|
|
583
586
|
// this provider's compact/clearContext runs. Set for app-server providers (Codex) whose
|
|
584
587
|
// clearContext starts a FRESH thread (and whose compact rewrites the current one) via an awaited
|
|
585
588
|
// handshake: the await confirms the NEW generation is ready but does NOT settle the OLD turn.
|
|
586
|
-
//
|
|
589
|
+
// humwick_compact_and_resume is called from within the model's own live turn, so that turn keeps
|
|
587
590
|
// running on the previous thread while the successor generation is armed on the new one — two
|
|
588
|
-
// generations then write the single worktree and revert each other
|
|
591
|
+
// generations then write the single worktree and revert each other.
|
|
589
592
|
// A plain wait would time out on the still-live in-turn compaction (why blind-submit providers
|
|
590
593
|
// can't share this path), so the fence actively interrupts the turn, then waits for it to reach
|
|
591
594
|
// a terminal state (interrupt-then-settle, mirroring the redirect path) before the destructive
|
|
592
595
|
// step. Adapters that fence internally (OpenCode's turnQueue) or have no interrupt leave this unset.
|
|
593
596
|
interruptsTurnBeforeContextCommand?: boolean;
|
|
594
597
|
// Derive the current context-window token usage from a transcript-backed
|
|
595
|
-
// provider's session log
|
|
598
|
+
// provider's session log. The runner calls this every heartbeat with the
|
|
596
599
|
// latest transcript so context stats keep advancing per-turn even when a provider's
|
|
597
600
|
// out-of-band probe (Claude's status-line command) goes silent after a
|
|
598
601
|
// compact/clear/native-resume. Pure parse — no process state. Event-stream
|
|
599
602
|
// providers (Codex) report context through `meta.getContext` instead and omit this.
|
|
600
603
|
contextUsageFromTranscript?(jsonl: string): ProviderContextUsage | undefined;
|
|
601
604
|
// Normalize the session so far into the provider-agnostic SessionEvent stream the
|
|
602
|
-
// Insights context-ratio signal
|
|
605
|
+
// Insights context-ratio signal reduces. Called by the runner's
|
|
603
606
|
// pre-session-destroy seam before any compact/clear/restart/shutdown. The runner owns
|
|
604
607
|
// the per-segment cursor (it slices events since the last capture), so this returns the
|
|
605
608
|
// full ordered event list for the current process lifetime. `ctx.transcriptPath` is
|
|
@@ -621,7 +624,7 @@ export interface ProviderAdapter {
|
|
|
621
624
|
// Whether the adapter supports additive input into a running provider turn.
|
|
622
625
|
supportsMidTurnSteer?: boolean;
|
|
623
626
|
/**
|
|
624
|
-
*
|
|
627
|
+
* Phase A — the shared mid-turn steer contract (Phase B: opencode/zai implement against
|
|
625
628
|
* the same shape). Given a batch offered while the recipient is mid-turn, the adapter owns the
|
|
626
629
|
* ONE eligibility decision: fold the steerable subset (ordinary persisted messages,
|
|
627
630
|
* `partitionMidTurnDelivery`) into the in-flight turn and return/ack ONLY those ids;
|
|
@@ -633,7 +636,7 @@ export interface ProviderAdapter {
|
|
|
633
636
|
* `supportsMidTurnSteer`; their `deliver` consults it at the paste instant, so the runner
|
|
634
637
|
* keeps a single call path.
|
|
635
638
|
*
|
|
636
|
-
*
|
|
639
|
+
* Phase B (OpenCode/zai) — the acceptance/consumption split: the ack for this adapter
|
|
637
640
|
* means POSITIVE PERSISTENCE (POST /session/:id/message with a caller messageID +
|
|
638
641
|
* noReply:true — a user row the active loop consumes at its next step; live-proven on pinned
|
|
639
642
|
* 1.18.21), not consumption, so persisted/ambiguous ids are returned as
|
|
@@ -650,21 +653,21 @@ export interface ProviderAdapter {
|
|
|
650
653
|
// turn was interrupted from the web terminal so no Stop hook fired). "unknown"
|
|
651
654
|
// means the provider can't be cheaply probed and the reconciler should defer.
|
|
652
655
|
probeActivity?(process: ManagedProcess): Promise<"busy" | "idle" | "unknown">;
|
|
653
|
-
//
|
|
656
|
+
// P1-A: an independent "is the provider still visibly busy?" read, consulted by the
|
|
654
657
|
// busy backstop — a probeActivity()=idle must not clear a live turn while this reads `busy`
|
|
655
658
|
// (defense-in-depth when the primary probe can momentarily read idle mid-turn, e.g. a
|
|
656
|
-
// connection retry)
|
|
659
|
+
// connection retry).: three-valued on purpose. Only `not-busy` is evidence the turn
|
|
657
660
|
// ended; `no-session`/`unreadable` are unknown and must never authorize a clear.
|
|
658
661
|
probePaneBusy?(process: ManagedProcess): Promise<PaneBusyReading>;
|
|
659
|
-
//
|
|
662
|
+
// resolve a real OS pid for a busy provider-turn whose ManagedProcess carries
|
|
660
663
|
// none directly (e.g. Claude's tmux launch — process.pid is undefined, only a tmux
|
|
661
664
|
// session/socket). The runner calls this as a fallback when a busy report supplies no
|
|
662
|
-
// explicit pids, so the
|
|
665
|
+
// explicit pids, so the liveness reconcile (and the stale-turn backstop) has real
|
|
663
666
|
// proof-of-life instead of only an elapsed-time heuristic. Returning undefined leaves
|
|
664
667
|
// the runner's existing process.pid fallback (or no pid at all) unchanged.
|
|
665
668
|
resolveBusyPid?(process: ManagedProcess): number | undefined;
|
|
666
669
|
terminalAttachSpec?(process: ManagedProcess): Promise<TerminalAttachSpec>;
|
|
667
|
-
//
|
|
670
|
+
// the CURRENT non-secret attachment generation (see TerminalAttachSpec.generation).
|
|
668
671
|
// Read by the runner control server's GET /terminal/generation so the orchestrator can
|
|
669
672
|
// detect a session rotation without ever learning the session id or any credential.
|
|
670
673
|
// Absent ⇒ the provider's attach target cannot go stale (codex) and the endpoint reports
|
|
@@ -677,7 +680,7 @@ export interface ProviderAdapter {
|
|
|
677
680
|
captureSubagentTokenUsage?(transcriptPath: string): Promise<void>;
|
|
678
681
|
/**
|
|
679
682
|
* Bounded final provider-native accounting pass before a session is torn down. `transcriptPath`
|
|
680
|
-
* is `undefined` for a provider with no transcript file to tail (
|
|
683
|
+
* is `undefined` for a provider with no transcript file to tail (round 2 review MED-1 —
|
|
681
684
|
* OpenCode's capture is event-sourced, not file-tailed); the adapter owns whatever precondition
|
|
682
685
|
* it needs (a transcript-tailing adapter bails itself on a missing path, an event-sourced one
|
|
683
686
|
* ignores the argument entirely).
|
|
@@ -686,9 +689,9 @@ export interface ProviderAdapter {
|
|
|
686
689
|
/** Start the provider-owned lifecycle observation for a prompt seeded in the launch command. */
|
|
687
690
|
startLaunchSeededTurn?(process: ManagedProcess): Promise<void>;
|
|
688
691
|
handleUserPrompt?(input: ProviderUserPromptInput, ctx: ProviderSessionTurnContext): Promise<void>;
|
|
689
|
-
/** Whether a prompt the provider reports is one the runner submitted, through the provider's paste transport
|
|
692
|
+
/** Whether a prompt the provider reports is one the runner submitted, through the provider's paste transport. */
|
|
690
693
|
matchesSubmittedPrompt?(providerPrompt: string, submitted: string, imagePaths?: readonly string[]): boolean;
|
|
691
|
-
/** A session-start hook's stamped check-in
|
|
694
|
+
/** A session-start hook's stamped check-in: the provider's hook path works. */
|
|
692
695
|
handleHookCheck?(input: { firedAt?: number }, ctx: ProviderSessionTurnContext): void;
|
|
693
696
|
/** File paths from a synchronous provider tool hook; absent means transcript delivery. */
|
|
694
697
|
toolHookPaths?(input: unknown): string[] | undefined;
|
|
@@ -697,29 +700,29 @@ export interface ProviderAdapter {
|
|
|
697
700
|
stopSessionTrace?(): void;
|
|
698
701
|
publishPassiveTelemetry?(): Promise<void>;
|
|
699
702
|
// `options.readyTimeoutMs` lets the runner widen the provider-ready wait for the
|
|
700
|
-
// first (cold-start) delivery vs. a fast re-attempt after a ready signal
|
|
703
|
+
// first (cold-start) delivery vs. a fast re-attempt after a ready signal.
|
|
701
704
|
// `options.isInitial` (default true) tells the adapter whether this delivery is the
|
|
702
705
|
// agent's genuine first turn — the runner passes false from the dashboard
|
|
703
|
-
// prompt-injection / continue paths
|
|
706
|
+
// prompt-injection / continue paths so a mid-session nudge isn't mislabeled
|
|
704
707
|
// as the initial prompt.
|
|
705
|
-
// Resolves "held"
|
|
708
|
+
// Resolves "held" when the provider REFUSED the prompt under a rate-limit/quota hold:
|
|
706
709
|
// not delivered, not failed — the runner parks it for redelivery (provider-hold.ts).
|
|
707
710
|
deliverInitialPrompt?(process: ManagedProcess, prompt: string, options?: PromptOfferOptions): Promise<void | "held">;
|
|
708
711
|
/** Confirm that a previously ambiguous input was consumed before the runner lifts its delivery hold. */
|
|
709
712
|
confirmSubmitRecovery?(process: ManagedProcess, turnStarted: boolean): Promise<boolean>;
|
|
710
|
-
/** The operator released the runner's submit hold
|
|
713
|
+
/** The operator released the runner's submit hold: drop any adapter-side submit fence. */
|
|
711
714
|
releaseSubmitHold?(process: ManagedProcess): void;
|
|
712
715
|
// When true, the adapter seeds the spawn-time initial prompt as a launch argument
|
|
713
716
|
// (Claude's positional `claude "<prompt>"`), so it's already delivered the instant the
|
|
714
717
|
// session starts. The runner must then NOT also deliver it post-launch via
|
|
715
718
|
// deliverInitialPrompt — that would double-deliver and re-introduce the send-keys
|
|
716
|
-
// onboarding race
|
|
719
|
+
// onboarding race. deliverInitialPrompt stays available for mid-session injection
|
|
717
720
|
// (dashboard chat box) and ongoing message delivery, which have no launch-arg equivalent.
|
|
718
721
|
seedsInitialPromptAtLaunch?: boolean;
|
|
719
722
|
/** Return accepted ids, plus consumption or failure outcomes when the transport reports them. */
|
|
720
723
|
deliver(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<void | number[] | DeliverResult>;
|
|
721
724
|
onStatusChange(cb: (status: ProviderStatusUpdate) => void): void;
|
|
722
|
-
//
|
|
725
|
+
// subscribe to pane-derived DIAGNOSTICS. Separate from onStatusChange on purpose:
|
|
723
726
|
// this channel's payload cannot carry a status, a clear, or a providerState, so wiring a
|
|
724
727
|
// detector here is a structural guarantee that it cannot mutate runner state. Optional;
|
|
725
728
|
// adapters with no pane to scrape simply never call it.
|
|
@@ -728,7 +731,7 @@ export interface ProviderAdapter {
|
|
|
728
731
|
// shared idempotency for multiple trace steps discovered in one provider poll.
|
|
729
732
|
onSessionEvent?(cb: (event: ProviderSessionEvent) => void): void;
|
|
730
733
|
onSessionEvents?(cb: (events: ProviderSessionEventBatch) => void): void;
|
|
731
|
-
//
|
|
734
|
+
// "the model produced something in the current turn", for output an adapter can OBSERVE
|
|
732
735
|
// but cannot mirror. It exists because the runner has to tell a turn that did nothing at all from
|
|
733
736
|
// one that finished work, and mirrored session events do not cover every provider: OpenCode's
|
|
734
737
|
// wire carries tool parts whose internal shape we deliberately do not guess at, so they are
|
|
@@ -736,10 +739,10 @@ export interface ProviderAdapter {
|
|
|
736
739
|
// — nothing is displayed, nothing is persisted. Optional; adapters whose output is fully mirrored
|
|
737
740
|
// never need it.
|
|
738
741
|
onTurnActivity?(cb: () => void): void;
|
|
739
|
-
// §4.1 of the Runner↔provider contract
|
|
742
|
+
// §4.1 of the Runner↔provider contract: fold the runner-tracked raw
|
|
740
743
|
// transport/timeline/token/work-kind signals into ONE normalized LivenessSignal the
|
|
741
744
|
// server reads instead of six raw `meta` fields. Each adapter owns what counts as
|
|
742
|
-
// progress for its provider (Claude
|
|
745
|
+
// progress for its provider (Claude timeline transitions; Codex live
|
|
743
746
|
// subprocess work). Optional so test/mock adapters fall back to the shared
|
|
744
747
|
// {@link computeLivenessSignal} fold — the runner always has a verdict to ship.
|
|
745
748
|
liveness?(inputs: LivenessInputs): LivenessSignal;
|
|
@@ -756,7 +759,7 @@ export interface ProviderAdapter {
|
|
|
756
759
|
buildSpawnArgs(config: RunnerSpawnConfig, providerConfig: ProviderConfig): SpawnArgs;
|
|
757
760
|
}
|
|
758
761
|
|
|
759
|
-
//
|
|
762
|
+
// the strictest base. Beyond the isolated provider home that every
|
|
760
763
|
// non-host base already gets (fresh CLAUDE_CONFIG_DIR / CODEX_HOME, which drops
|
|
761
764
|
// the GLOBAL instruction + host MCP/hooks/plugins cascade), "vanilla" actively
|
|
762
765
|
// suppresses every remaining host customization channel at launch, including the
|
|
@@ -771,7 +774,7 @@ export function relayContext(): string {
|
|
|
771
774
|
return resolveRelayContext({ slug: "relay.context.codex" }).text;
|
|
772
775
|
}
|
|
773
776
|
|
|
774
|
-
//
|
|
777
|
+
// deliver the FULL message body by default. Only a pathological body beyond this
|
|
775
778
|
// high cap truncates (with a get-message hint) so it can't nuke an agent's context; the 99%
|
|
776
779
|
// case (incl. multi-thousand-char handoffs/reports) arrives whole in one shot, no extra
|
|
777
780
|
// round-trip. The mirror flow (user typing directly) already injects full text — this removes
|
|
@@ -789,7 +792,7 @@ export function providerMessageBodyMaxChars(): number {
|
|
|
789
792
|
return DEFAULT_PROVIDER_MESSAGE_BODY_MAX_CHARS;
|
|
790
793
|
}
|
|
791
794
|
|
|
792
|
-
//
|
|
795
|
+
// the Claude Code monitor surfaces each delivered event with a HARD cap on the content that
|
|
793
796
|
// reaches the agent's context; beyond it the harness truncates the event and spills the tail to an
|
|
794
797
|
// output file the agent must re-read (paying for the message ~twice, both copies persisting in
|
|
795
798
|
// context). Two independent bounds, measured 2026-08-02 against the live harness:
|
|
@@ -842,7 +845,7 @@ export interface DeliveredBodyPreview {
|
|
|
842
845
|
guidance: string[];
|
|
843
846
|
}
|
|
844
847
|
|
|
845
|
-
//
|
|
848
|
+
// one home for the delivered-body cap. Head-only truncation silently ate the TAIL, which is
|
|
846
849
|
// exactly where a report-up final puts its verdict / SHIP recommendation. Preserve BOTH ends: keep a
|
|
847
850
|
// head slice for opening context and a tail slice for the conclusion, eliding the middle and pointing
|
|
848
851
|
// at the untrimmed body. The tail is favored (it holds the verdict). Slices snap to whitespace so we
|
|
@@ -854,10 +857,10 @@ export function previewDeliveredBody(
|
|
|
854
857
|
): DeliveredBodyPreview {
|
|
855
858
|
if (rawBody.length <= maxChars) return { body: rawBody, truncated: false, guidance: [] };
|
|
856
859
|
const fetchLines = [
|
|
857
|
-
`Read full:
|
|
858
|
-
`Body only:
|
|
860
|
+
`Read full: humwick get-message ${messageId}`,
|
|
861
|
+
`Body only: humwick get-message ${messageId} --body`,
|
|
859
862
|
];
|
|
860
|
-
const marker = (elided: number) => `[… ${elided} chars elided from the middle — full report:
|
|
863
|
+
const marker = (elided: number) => `[… ${elided} chars elided from the middle — full report: humwick_get_message #${messageId} …]`;
|
|
861
864
|
// Reserve room for the marker and its two blank-line joins; upper-bound the marker length by using
|
|
862
865
|
// the (always-larger) full body length so the reserved budget can never underflow the real marker.
|
|
863
866
|
const budget = maxChars - marker(rawBody.length).length - 4;
|
|
@@ -900,8 +903,8 @@ function attachmentLabel(ref: Record<string, unknown>): string {
|
|
|
900
903
|
|
|
901
904
|
function attachmentCacheMetadata(ref: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
902
905
|
const metadata = isRecord(ref.metadata) ? ref.metadata : undefined;
|
|
903
|
-
const
|
|
904
|
-
return
|
|
906
|
+
const humwick = readHumwickRecord(metadata);
|
|
907
|
+
return humwick;
|
|
905
908
|
}
|
|
906
909
|
|
|
907
910
|
export function isMemoryInjection(message: Message): boolean {
|
|
@@ -921,7 +924,7 @@ export function isPersistedRelayMessage(message: Message): boolean {
|
|
|
921
924
|
return Number.isSafeInteger(message.id) && message.id > 0;
|
|
922
925
|
}
|
|
923
926
|
|
|
924
|
-
//
|
|
927
|
+
// one-line nudge that replaces the reply-scaffold footer for notification-class
|
|
925
928
|
// (replyExpected:false) messages. Deliberately tiny so a bloated context can't drown the
|
|
926
929
|
// no-reply rule established at session start. Shared with the Claude delivery path.
|
|
927
930
|
export const NOTIFICATION_NUDGE = "↪ Notification — no reply needed.";
|
|
@@ -951,7 +954,7 @@ export function dedupePersistedMessages(messages: Message[]): Message[] {
|
|
|
951
954
|
});
|
|
952
955
|
}
|
|
953
956
|
|
|
954
|
-
/** A human wrote it: a human address, or a prompt parked on a human's behalf
|
|
957
|
+
/** A human wrote it: a human address, or a prompt parked on a human's behalf. */
|
|
955
958
|
export function isHumanSenderMessage(message: Message): boolean {
|
|
956
959
|
return isHumanAgentId(heldForSender(message) ?? message.from);
|
|
957
960
|
}
|
|
@@ -969,10 +972,10 @@ export function providerSenderLabel(message: Message): string {
|
|
|
969
972
|
return isHumanChannel ? `human user via ${message.from}` : message.from;
|
|
970
973
|
}
|
|
971
974
|
|
|
972
|
-
/** `imagePaths`: cached attachment paths the prompt text lists
|
|
975
|
+
/** `imagePaths`: cached attachment paths the prompt text lists. */
|
|
973
976
|
export interface PromptOfferOptions { readyTimeoutMs?: number; isInitial?: boolean; imagePaths?: string[]; inputObserver?: ProviderInputObserver }
|
|
974
977
|
|
|
975
|
-
/** A parked prompt's attachment paths, carried on its inbox row so the redelivery keeps them
|
|
978
|
+
/** A parked prompt's attachment paths, carried on its inbox row so the redelivery keeps them. */
|
|
976
979
|
export const HELD_PROMPT_IMAGE_PATHS = "heldPromptImagePaths";
|
|
977
980
|
|
|
978
981
|
/** Local cache paths of the messages' attachments, as providerAttachmentText lists them. */
|
|
@@ -1013,11 +1016,11 @@ export function providerAttachmentText(message: Message): string | undefined {
|
|
|
1013
1016
|
: hasLocalPaths
|
|
1014
1017
|
? [
|
|
1015
1018
|
"Use the local path above when inspecting the attachment.",
|
|
1016
|
-
`Read attachment details:
|
|
1019
|
+
`Read attachment details: humwick get-message ${message.id} --json`,
|
|
1017
1020
|
]
|
|
1018
1021
|
: [
|
|
1019
|
-
`Read attachment details:
|
|
1020
|
-
`Fetch content: curl -fsS -H "X-
|
|
1022
|
+
`Read attachment details: humwick get-message ${message.id} --json`,
|
|
1023
|
+
`Fetch content: curl -fsS -H "X-Humwick-Token: $HUMWICK_TOKEN" "$HUMWICK_URL/api/artifacts/<artifactId>/content" -o <filename>`,
|
|
1021
1024
|
];
|
|
1022
1025
|
return [
|
|
1023
1026
|
"Attachments:",
|
|
@@ -1042,7 +1045,7 @@ export function providerMessageText(messages: Message[]): string {
|
|
|
1042
1045
|
: undefined;
|
|
1043
1046
|
if (isMemoryContext) {
|
|
1044
1047
|
return [
|
|
1045
|
-
`[
|
|
1048
|
+
`[humwick ${isSelfResumeInjection(message) ? "self-resume continuation" : "context"} from ${message.from}]`,
|
|
1046
1049
|
providerAttachmentText(message),
|
|
1047
1050
|
`${subject}${preview.body}`,
|
|
1048
1051
|
].join("\n");
|