@humwick/runner 0.0.0-stage → 0.130.1
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 +42 -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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { isRecord } from "@humwick/sdk";
|
|
2
|
+
import type { ManagedProcess, ProviderAdapter } from "./adapter";
|
|
3
|
+
import type { NativeSelfResumeHandoff, NativeSelfResumeTracker } from "./native-self-resume";
|
|
4
|
+
|
|
5
|
+
export const LATE_CLEAR_GRACE_MS = 120_000;
|
|
6
|
+
// Claude caps each hook context string at 10k; leave room for the review reminder.
|
|
7
|
+
// https://code.claude.com/docs/en/hooks#json-output
|
|
8
|
+
export const LATE_CLEAR_HOOK_MAX_CHARS = 8_000;
|
|
9
|
+
|
|
10
|
+
/** Hold pre-clear inbox work until its continuation arrives or the settled clear's deadline. */
|
|
11
|
+
export class ClearInjectInboxFence {
|
|
12
|
+
private pending?: { commandId: string; heldMessageIds: Set<number>; clearing: boolean; continuationAccepted?: boolean; timer?: ReturnType<typeof setTimeout> };
|
|
13
|
+
|
|
14
|
+
constructor(private readonly onRelease: () => void) {}
|
|
15
|
+
|
|
16
|
+
get active(): boolean { return this.pending !== undefined; }
|
|
17
|
+
|
|
18
|
+
begin(type: string, params: Record<string, unknown>, commandId: string, messageIds: Iterable<number>): void {
|
|
19
|
+
const resume = isRecord(params.selfResume) ? params.selfResume : undefined;
|
|
20
|
+
if (type !== "agent.clearContext" || resume?.mode !== "clear-inject" || typeof resume.continuation !== "string" || !resume.continuation.trim()) return;
|
|
21
|
+
this.reset();
|
|
22
|
+
this.pending = { commandId, heldMessageIds: new Set(messageIds), clearing: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
finish(commandId: string, awaitingLateClear: boolean): void {
|
|
26
|
+
const pending = this.pending;
|
|
27
|
+
if (pending?.commandId !== commandId) return;
|
|
28
|
+
pending.clearing = awaitingLateClear;
|
|
29
|
+
// #2854: the continuation backstop may never arrive; wake the inbox independently.
|
|
30
|
+
pending.timer = setTimeout(() => {
|
|
31
|
+
if (this.pending !== pending) return;
|
|
32
|
+
this.reset();
|
|
33
|
+
this.onRelease();
|
|
34
|
+
}, LATE_CLEAR_GRACE_MS);
|
|
35
|
+
pending.timer.unref?.();
|
|
36
|
+
if (!awaitingLateClear) this.onRelease();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
accept(commandId: string, awaitingLateClear: boolean): void {
|
|
40
|
+
if (this.pending?.commandId !== commandId) return;
|
|
41
|
+
this.pending.continuationAccepted = true;
|
|
42
|
+
if (awaitingLateClear) return;
|
|
43
|
+
this.reset();
|
|
44
|
+
this.onRelease();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
endLateClearGrace(commandId: string): void {
|
|
48
|
+
const pending = this.pending;
|
|
49
|
+
if (pending?.commandId !== commandId) return;
|
|
50
|
+
pending.clearing = false;
|
|
51
|
+
if (pending.continuationAccepted) this.reset();
|
|
52
|
+
this.onRelease();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
holds(messageId: number): boolean { return Boolean(this.pending && (this.pending.clearing || this.pending.heldMessageIds.has(messageId))); }
|
|
56
|
+
|
|
57
|
+
reset(): void {
|
|
58
|
+
if (this.pending?.timer) clearTimeout(this.pending.timer);
|
|
59
|
+
this.pending = undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface LateClearContinuation {
|
|
64
|
+
clearCommandId: string;
|
|
65
|
+
content: string;
|
|
66
|
+
fullContextMessageId?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function lateClearHookContent(recovery: LateClearContinuation): string {
|
|
70
|
+
if (recovery.content.length <= LATE_CLEAR_HOOK_MAX_CHARS) return recovery.content;
|
|
71
|
+
const fetch = recovery.fullContextMessageId
|
|
72
|
+
? `relay_get_message #${recovery.fullContextMessageId} (CLI: agent-relay get-message ${recovery.fullContextMessageId})`
|
|
73
|
+
: 'relay_recall with query "latest working state"';
|
|
74
|
+
return `A delayed /clear committed after its confirmation timed out. Your saved continuation exceeds the hook context limit. Retrieve it with ${fetch} before resuming work; the full continuation was not injected here.`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Own the uncertainty window between submitting /clear and its actual SessionStart. */
|
|
78
|
+
export class LateClearRecovery {
|
|
79
|
+
private pending?: LateClearContinuation & { observed: boolean; failed: boolean; settled: Promise<void>; resolve(): void; cancel?: () => void };
|
|
80
|
+
|
|
81
|
+
constructor(private readonly onGraceEnd: (commandId: string) => void, private readonly schedule = (fn: () => void, ms: number): (() => void) => {
|
|
82
|
+
const timer = setTimeout(fn, ms);
|
|
83
|
+
timer.unref?.();
|
|
84
|
+
return () => clearTimeout(timer);
|
|
85
|
+
}) {}
|
|
86
|
+
|
|
87
|
+
begin(commandId: string, params: Record<string, unknown>, couples: boolean | undefined): void {
|
|
88
|
+
this.reset();
|
|
89
|
+
const resume = isRecord(params.selfResume) ? params.selfResume : undefined;
|
|
90
|
+
if (!couples || resume?.mode !== "clear-inject" || typeof resume.continuation !== "string" || !resume.continuation.trim()) return;
|
|
91
|
+
let resolve!: () => void;
|
|
92
|
+
const settled = new Promise<void>((done) => { resolve = done; });
|
|
93
|
+
this.pending = { clearCommandId: commandId, content: resume.continuation.trim(), observed: false, failed: false, settled, resolve,
|
|
94
|
+
...(Number.isSafeInteger(resume.fullContextMessageId) && Number(resume.fullContextMessageId) > 0 ? { fullContextMessageId: Number(resume.fullContextMessageId) } : {}) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
finish(commandId: string, confirmed: boolean): void {
|
|
98
|
+
const pending = this.pending;
|
|
99
|
+
if (pending?.clearCommandId !== commandId) return;
|
|
100
|
+
if (confirmed || pending.observed) { this.reset(); return; }
|
|
101
|
+
pending.failed = true;
|
|
102
|
+
pending.cancel = this.schedule(() => {
|
|
103
|
+
if (this.pending !== pending) return;
|
|
104
|
+
this.endGrace();
|
|
105
|
+
}, LATE_CLEAR_GRACE_MS);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
observe(): LateClearContinuation | undefined {
|
|
109
|
+
const pending = this.pending;
|
|
110
|
+
if (!pending) return;
|
|
111
|
+
if (!pending.failed) { pending.observed = true; return; }
|
|
112
|
+
this.reset();
|
|
113
|
+
return pending;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
holds(commandId: string): boolean { return this.pending?.clearCommandId === commandId; }
|
|
117
|
+
ready(): Promise<void> { return this.pending?.settled ?? Promise.resolve(); }
|
|
118
|
+
|
|
119
|
+
endGrace(): void {
|
|
120
|
+
const pending = this.pending;
|
|
121
|
+
this.reset();
|
|
122
|
+
if (pending?.failed) this.onGraceEnd(pending.clearCommandId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
reset(): void {
|
|
126
|
+
this.pending?.cancel?.();
|
|
127
|
+
this.pending?.resolve();
|
|
128
|
+
this.pending = undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function executeCompactCommand(adapter: ProviderAdapter, process: ManagedProcess | undefined, params: Record<string, unknown>, tracker: NativeSelfResumeTracker, handoff: NativeSelfResumeHandoff | undefined, awaitIdle: () => Promise<void>, resetContext: () => void): Promise<Record<string, unknown> | void> {
|
|
133
|
+
if (!adapter.compact || !process) throw new Error("provider does not support compact");
|
|
134
|
+
await tracker.run(handoff, awaitIdle);
|
|
135
|
+
resetContext();
|
|
136
|
+
if (handoff && !adapter.compactReportsSubmission) handoff.submittedAt = Date.now();
|
|
137
|
+
const compact = () => adapter.compact!(process, {
|
|
138
|
+
instructions: typeof params.instructions === "string" ? params.instructions : undefined,
|
|
139
|
+
signal: handoff?.abort.signal,
|
|
140
|
+
onSubmitted: () => { if (handoff) handoff.submittedAt = Date.now(); },
|
|
141
|
+
});
|
|
142
|
+
const result = handoff ? await tracker.run(handoff, compact) : await compact();
|
|
143
|
+
return handoff ? { ...(result ?? {}), nativeSelfResume: await handoff.promise } : result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function assertClearInjectCommitted(couples: boolean | undefined, params: Record<string, unknown>, result: Record<string, unknown> | undefined, log: (message: string) => void): void {
|
|
147
|
+
const resume = isRecord(params.selfResume) ? params.selfResume : undefined;
|
|
148
|
+
if (!couples || resume?.mode !== "clear-inject" || typeof resume.continuation !== "string" || !resume.continuation.trim() || result?.clearConfirmed === true) return;
|
|
149
|
+
log("self-resume clear-inject commit was NOT confirmed; failing the command so the relay re-injects the continuation instead of silently no-oping");
|
|
150
|
+
throw new Error("clear-inject clear commit not confirmed (possible no-op); failing so the continuation is re-injected rather than silently lost");
|
|
151
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { HumwickHttpClient } from "@humwick/sdk";
|
|
2
|
+
import type { OutboxRecord } from "./outbox";
|
|
3
|
+
|
|
4
|
+
const CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES = 64 * 1024;
|
|
5
|
+
const CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES = 56 * 1024;
|
|
6
|
+
const CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const fatalUtf8Decoder = new TextDecoder("utf-8", { fatal: true });
|
|
9
|
+
|
|
10
|
+
interface ContinuationArchiveOutboxPayload {
|
|
11
|
+
agentId: string;
|
|
12
|
+
segment: string;
|
|
13
|
+
generation?: number;
|
|
14
|
+
deliveredChunks?: number;
|
|
15
|
+
totalChunks?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ContinuationArchiveResponse {
|
|
19
|
+
archive?: { generation?: unknown };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function boundContinuationArchiveSegment(segment: string): { segment: string; keptBytes: number; droppedBytes: number } {
|
|
23
|
+
const encoded = encoder.encode(segment);
|
|
24
|
+
const bytes = encoded.byteLength;
|
|
25
|
+
if (bytes <= CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES) {
|
|
26
|
+
return { segment, keptBytes: bytes, droppedBytes: 0 };
|
|
27
|
+
}
|
|
28
|
+
const bounded = decodeUtf8Prefix(encoded, CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES);
|
|
29
|
+
const keptBytes = utf8Bytes(bounded);
|
|
30
|
+
return { segment: bounded.trimEnd(), keptBytes, droppedBytes: bytes - keptBytes };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function deliverContinuationArchiveRecord(input: {
|
|
34
|
+
record: OutboxRecord;
|
|
35
|
+
http: Pick<HumwickHttpClient, "recordContinuationArchive">;
|
|
36
|
+
updatePayload: (seq: number, payload: unknown) => void;
|
|
37
|
+
sessionLog: (message: string) => void;
|
|
38
|
+
}): Promise<void> {
|
|
39
|
+
const payload = input.record.payload as ContinuationArchiveOutboxPayload;
|
|
40
|
+
let segment = payload.segment;
|
|
41
|
+
if (!payload.agentId || typeof segment !== "string") throw new Error("invalid continuation archive outbox payload");
|
|
42
|
+
|
|
43
|
+
const bounded = boundContinuationArchiveSegment(segment);
|
|
44
|
+
if (bounded.droppedBytes > 0) {
|
|
45
|
+
segment = bounded.segment;
|
|
46
|
+
input.sessionLog(`continuation archive truncated at ${bounded.keptBytes} bytes; dropped ${bounded.droppedBytes} bytes before delivery`);
|
|
47
|
+
input.updatePayload(input.record.seq, { ...payload, segment, deliveredChunks: 0, totalChunks: undefined, generation: undefined });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const chunks = splitContinuationArchiveSegment({
|
|
51
|
+
agentId: payload.agentId,
|
|
52
|
+
segment,
|
|
53
|
+
occurredAt: input.record.occurredAt,
|
|
54
|
+
});
|
|
55
|
+
const knownGeneration = Number.isSafeInteger(payload.generation) ? payload.generation : undefined;
|
|
56
|
+
const deliveredChunks = knownGeneration === undefined ? 0 : Math.max(0, Math.min(payload.deliveredChunks ?? 0, chunks.length));
|
|
57
|
+
let generation = knownGeneration;
|
|
58
|
+
|
|
59
|
+
for (let index = deliveredChunks; index < chunks.length; index += 1) {
|
|
60
|
+
const request = {
|
|
61
|
+
agentId: payload.agentId,
|
|
62
|
+
segment: chunks[index]!,
|
|
63
|
+
occurredAt: input.record.occurredAt,
|
|
64
|
+
...(generation !== undefined ? { generation } : {}),
|
|
65
|
+
};
|
|
66
|
+
assertContinuationArchivePostFits(request);
|
|
67
|
+
const response = await input.http.recordContinuationArchive(request) as ContinuationArchiveResponse;
|
|
68
|
+
const returnedGeneration = archiveGeneration(response);
|
|
69
|
+
if (generation === undefined) {
|
|
70
|
+
if (returnedGeneration === undefined) throw new Error("continuation archive response missing generation");
|
|
71
|
+
generation = returnedGeneration;
|
|
72
|
+
} else if (returnedGeneration !== undefined && returnedGeneration !== generation) {
|
|
73
|
+
throw new Error(`continuation archive generation mismatch: expected ${generation}, got ${returnedGeneration}`);
|
|
74
|
+
}
|
|
75
|
+
input.updatePayload(input.record.seq, {
|
|
76
|
+
...payload,
|
|
77
|
+
segment,
|
|
78
|
+
generation,
|
|
79
|
+
deliveredChunks: index + 1,
|
|
80
|
+
totalChunks: chunks.length,
|
|
81
|
+
} satisfies ContinuationArchiveOutboxPayload);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function splitContinuationArchiveSegment(input: { agentId: string; segment: string; occurredAt: number }): string[] {
|
|
86
|
+
const chunks: string[] = [];
|
|
87
|
+
let current = "";
|
|
88
|
+
for (const line of transcriptLines(input.segment)) {
|
|
89
|
+
if (!line) continue;
|
|
90
|
+
if (fitsContinuationArchiveChunk(input.agentId, line, input.occurredAt)) {
|
|
91
|
+
const next = current ? current + line : line;
|
|
92
|
+
if (utf8Bytes(next) <= CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES && fitsContinuationArchiveChunk(input.agentId, next, input.occurredAt)) {
|
|
93
|
+
current = next;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (current) chunks.push(current.trimEnd());
|
|
97
|
+
current = line;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (current) {
|
|
101
|
+
chunks.push(current.trimEnd());
|
|
102
|
+
current = "";
|
|
103
|
+
}
|
|
104
|
+
chunks.push(...splitLongContinuationArchiveLine(input.agentId, line, input.occurredAt));
|
|
105
|
+
}
|
|
106
|
+
if (current) chunks.push(current.trimEnd());
|
|
107
|
+
return chunks.filter((chunk) => chunk.length > 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function transcriptLines(segment: string): string[] {
|
|
111
|
+
const lines = segment.match(/[^\n]*(?:\n|$)/g) ?? [segment];
|
|
112
|
+
return lines.filter((line) => line.length > 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function splitLongContinuationArchiveLine(agentId: string, line: string, occurredAt: number): string[] {
|
|
116
|
+
const chars = Array.from(line);
|
|
117
|
+
const chunks: string[] = [];
|
|
118
|
+
let offset = 0;
|
|
119
|
+
while (offset < chars.length) {
|
|
120
|
+
let low = 1;
|
|
121
|
+
let high = chars.length - offset;
|
|
122
|
+
let best = 0;
|
|
123
|
+
while (low <= high) {
|
|
124
|
+
const mid = Math.floor((low + high) / 2);
|
|
125
|
+
const candidate = chars.slice(offset, offset + mid).join("");
|
|
126
|
+
if (utf8Bytes(candidate) <= CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES && fitsContinuationArchiveChunk(agentId, candidate, occurredAt)) {
|
|
127
|
+
best = mid;
|
|
128
|
+
low = mid + 1;
|
|
129
|
+
} else {
|
|
130
|
+
high = mid - 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (best === 0) throw new Error("continuation archive chunk budget cannot fit one character");
|
|
134
|
+
chunks.push(chars.slice(offset, offset + best).join(""));
|
|
135
|
+
offset += best;
|
|
136
|
+
}
|
|
137
|
+
return chunks;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function fitsContinuationArchiveChunk(agentId: string, segment: string, occurredAt: number): boolean {
|
|
141
|
+
return continuationArchivePostBytes({
|
|
142
|
+
agentId,
|
|
143
|
+
segment,
|
|
144
|
+
occurredAt,
|
|
145
|
+
generation: Number.MAX_SAFE_INTEGER,
|
|
146
|
+
}) < CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function assertContinuationArchivePostFits(input: { agentId: string; segment: string; occurredAt: number; generation?: number }): void {
|
|
150
|
+
const bytes = continuationArchivePostBytes(input);
|
|
151
|
+
if (bytes >= CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES) {
|
|
152
|
+
throw new Error(`continuation archive chunk JSON body is ${bytes} bytes; max is ${CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES - 1}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function continuationArchivePostBytes(input: { agentId: string; segment: string; occurredAt: number; generation?: number }): number {
|
|
157
|
+
return utf8Bytes(JSON.stringify(input));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function archiveGeneration(response: ContinuationArchiveResponse): number | undefined {
|
|
161
|
+
const generation = response.archive?.generation;
|
|
162
|
+
return typeof generation === "number" && Number.isSafeInteger(generation) && generation >= 0 ? generation : undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function decodeUtf8Prefix(bytes: Uint8Array, maxBytes: number): string {
|
|
166
|
+
let end = Math.min(maxBytes, bytes.byteLength);
|
|
167
|
+
while (end > 0) {
|
|
168
|
+
try {
|
|
169
|
+
return fatalUtf8Decoder.decode(bytes.subarray(0, end));
|
|
170
|
+
} catch {
|
|
171
|
+
end -= 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function utf8Bytes(value: string): number {
|
|
178
|
+
return encoder.encode(value).byteLength;
|
|
179
|
+
}
|