@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
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { errMessage } from "@humwick/sdk";
|
|
3
|
+
import type { ManagedProcess, ProviderSessionEvent, ProviderSessionEventBatch, ProviderSessionTurnContext, ProviderSessionTurnInput, ProviderSessionTurnResult, ProviderUserPromptInput } from "../adapter";
|
|
4
|
+
import type { SessionEvent } from "../session-insights";
|
|
5
|
+
import {
|
|
6
|
+
collectClaudeSessionEvents,
|
|
7
|
+
countTranscriptEntries,
|
|
8
|
+
extractAssistantTurnForPrompt,
|
|
9
|
+
extractHookAssistantMessage,
|
|
10
|
+
extractLastAssistantTurn,
|
|
11
|
+
extractLastAssistantTurnAfterEntry,
|
|
12
|
+
extractLatestTurnSteps,
|
|
13
|
+
pendingQueuedPrompts,
|
|
14
|
+
stepDedupKeys,
|
|
15
|
+
transcriptHasToolUseAnchor,
|
|
16
|
+
transcriptLooksComplete,
|
|
17
|
+
transcriptToolUseOccurredAt,
|
|
18
|
+
turnStepEvent,
|
|
19
|
+
} from "./claude-transcript";
|
|
20
|
+
import { IncrementalClaudeTranscriptTail } from "./claude-transcript-tail";
|
|
21
|
+
import { ClaudePromptHookLedger } from "./claude-prompt-hook";
|
|
22
|
+
import { readIncrementalFileRange, statIncrementalFile } from "./incremental-file";
|
|
23
|
+
|
|
24
|
+
interface ReasoningTailState {
|
|
25
|
+
timer: ReturnType<typeof setInterval>;
|
|
26
|
+
emittedNarrationKeys: Set<string>;
|
|
27
|
+
poll(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const REASONING_POLL_MS = 1_200;
|
|
31
|
+
// Bounded the same way as control-server.ts's resolvedApprovals so a long session's dedup
|
|
32
|
+
// guard (see emittedStepKeys) can't grow without limit.
|
|
33
|
+
const EMITTED_STEP_KEY_LIMIT = 2048;
|
|
34
|
+
// #2919 — a prompt still queued at a Stop was enqueued after the turn's last tool boundary, so it
|
|
35
|
+
// sits in the transcript's last entries; a bounded tail read finds it.
|
|
36
|
+
const QUEUE_SCAN_BYTES = 256 * 1024;
|
|
37
|
+
const HOOKED_PROMPT_LIMIT = 8;
|
|
38
|
+
|
|
39
|
+
export class ClaudeSessionCapture {
|
|
40
|
+
// #2664 — every UserPromptSubmit hook; the proof that Claude accepted a submitted prompt.
|
|
41
|
+
readonly promptHooks = new ClaudePromptHookLedger();
|
|
42
|
+
private narrativeFlushEntryCount = 0;
|
|
43
|
+
private reasoningTail?: ReasoningTailState;
|
|
44
|
+
// Step signatures already published to chat — by the deny-then-hold pre-flush harvest or a
|
|
45
|
+
// prior reasoning-tail instance — kept for the life of this capture (not reset when a new
|
|
46
|
+
// tail starts). A fresh tail is created on every new prompt (see startReasoningTail),
|
|
47
|
+
// including the runner's own injected AskUserQuestion answer; it re-reads the transcript
|
|
48
|
+
// from byte 0 and recomputes "latest turn" steps from scratch, so if that first poll lands
|
|
49
|
+
// before the injected prompt's own transcript entry has advanced the "latest turn" window,
|
|
50
|
+
// it can re-derive the exact same step signatures the earlier tail already emitted. Seeding
|
|
51
|
+
// each new tail's `seen` set from this closes that gap regardless of the exact timing.
|
|
52
|
+
private emittedStepKeys = new Set<string>();
|
|
53
|
+
// #2919 — recent UserPromptSubmit prompts. Claude hooks a prompt submitted mid-turn when it queues
|
|
54
|
+
// it and never again when it runs it. Pruned at every Stop to the ones still queued.
|
|
55
|
+
private hookedPrompts: string[] = [];
|
|
56
|
+
private sessionEventCb: (event: ProviderSessionEvent) => void = () => {};
|
|
57
|
+
private sessionEventsCb: (events: ProviderSessionEventBatch) => void = (events) => { for (const event of events) this.sessionEventCb(event); };
|
|
58
|
+
|
|
59
|
+
onSessionEvent(cb: (event: ProviderSessionEvent) => void): void {
|
|
60
|
+
this.sessionEventCb = cb;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onSessionEvents(cb: (events: ProviderSessionEventBatch) => void): void {
|
|
64
|
+
this.sessionEventsCb = cb;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async captureSessionTurn(input: ProviderSessionTurnInput, ctx: ProviderSessionTurnContext): Promise<ProviderSessionTurnResult | void> {
|
|
68
|
+
if (input.isPreFlush) {
|
|
69
|
+
if (!input.transcriptPath) return { reasoningSettled: true };
|
|
70
|
+
const settled = await this.settlePollForToolUse(input.transcriptPath, input.toolName, input.toolInput, ctx);
|
|
71
|
+
const jsonl = settled.jsonl;
|
|
72
|
+
if (jsonl === undefined) return { reasoningSettled: false };
|
|
73
|
+
const body = extractLastAssistantTurnAfterEntry(jsonl, this.narrativeFlushEntryCount);
|
|
74
|
+
await this.drainReasoningTail(ctx);
|
|
75
|
+
this.narrativeFlushEntryCount = countTranscriptEntries(jsonl);
|
|
76
|
+
// With reasoningCapture off the mirror dropped the tail's narration rows, so none were mirrored.
|
|
77
|
+
if (body && !(ctx.reasoningCapture !== false && this.preFlushBodyAlreadyMirroredByReasoningTail(jsonl, body))) {
|
|
78
|
+
ctx.log(`pre-flush narrative for turn ${ctx.turnId ?? "?"} (${body.length} chars)`);
|
|
79
|
+
this.emit({ type: "response", origin: "provider", body, final: false, ...(ctx.turnId ? { turnId: ctx.turnId } : {}) });
|
|
80
|
+
}
|
|
81
|
+
await ctx.flushSessionEvents(2_000).then(
|
|
82
|
+
(ok) => { if (!ok) ctx.log(`pre-flush session outbox incomplete before blocking control (${ctx.pendingSessionEventCount()} pending)`); },
|
|
83
|
+
(error) => ctx.log(`pre-flush session outbox failed before blocking control: ${errMessage(error)}`),
|
|
84
|
+
);
|
|
85
|
+
const toolUseOccurredAt = input.toolName ? transcriptToolUseOccurredAt(jsonl, input.toolName, input.toolInput) : undefined;
|
|
86
|
+
return { reasoningSettled: settled.settled, ...(toolUseOccurredAt ? { toolUseOccurredAt } : {}) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const turnId = input.promptId ?? ctx.turnId;
|
|
90
|
+
await this.drainReasoningTail(ctx);
|
|
91
|
+
this.stopSessionTrace();
|
|
92
|
+
if (input.transcriptPath && await this.hookedPromptStillQueued(input.transcriptPath)) ctx.noteQueuedTurn?.();
|
|
93
|
+
const flushCount = this.narrativeFlushEntryCount;
|
|
94
|
+
this.narrativeFlushEntryCount = 0;
|
|
95
|
+
|
|
96
|
+
// #2840 — the reasoning tail already mirrors each narration line as its own row, so the
|
|
97
|
+
// turn-final (and the report built from it) is the closing text only.
|
|
98
|
+
const closingOnly = ctx.chatCaptureMode === "final" || ctx.reasoningCapture !== false;
|
|
99
|
+
let body = extractHookAssistantMessage(input.lastAssistantMessage);
|
|
100
|
+
if (!closingOnly && input.transcriptPath) {
|
|
101
|
+
let jsonl: string | undefined;
|
|
102
|
+
try { jsonl = await readFile(input.transcriptPath, "utf8"); } catch { jsonl = undefined; }
|
|
103
|
+
if (jsonl !== undefined && transcriptLooksComplete(jsonl)) {
|
|
104
|
+
let full = turnId ? extractAssistantTurnForPrompt(jsonl, turnId, flushCount) : "";
|
|
105
|
+
if (!full) full = flushCount > 0 ? extractLastAssistantTurnAfterEntry(jsonl, flushCount) : extractLastAssistantTurn(jsonl);
|
|
106
|
+
if (full) body = full;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!body && closingOnly && input.transcriptPath) {
|
|
110
|
+
let jsonl: string | undefined;
|
|
111
|
+
try { jsonl = await readFile(input.transcriptPath, "utf8"); } catch { jsonl = undefined; }
|
|
112
|
+
if (jsonl !== undefined) {
|
|
113
|
+
const fallback = turnId ? extractAssistantTurnForPrompt(jsonl, turnId, flushCount, true) : "";
|
|
114
|
+
body = fallback || (flushCount > 0 ? extractLastAssistantTurnAfterEntry(jsonl, flushCount, true) : extractLastAssistantTurn(jsonl, true));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!body) {
|
|
118
|
+
ctx.log(`response capture: no closing text for turn ${turnId ?? "?"} (skipped)`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ctx.log(`response captured for turn ${turnId ?? "?"} (${body.length} chars)`);
|
|
123
|
+
this.emit({
|
|
124
|
+
type: "response",
|
|
125
|
+
origin: "provider",
|
|
126
|
+
body,
|
|
127
|
+
final: true,
|
|
128
|
+
confidence: "definitive",
|
|
129
|
+
endedBy: "completed",
|
|
130
|
+
replyToUserObligation: true,
|
|
131
|
+
...(turnId ? { turnId } : {}),
|
|
132
|
+
...(ctx.opener !== undefined ? { opener: ctx.opener } : {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async handleUserPrompt(input: ProviderUserPromptInput, ctx: ProviderSessionTurnContext): Promise<void> {
|
|
137
|
+
const prompt = input.prompt?.trim();
|
|
138
|
+
if (prompt && this.promptHooks.observe(input.prompt, input.firedAt) === "unstamped") ctx.log("UserPromptSubmit hook carried no firedAt; not submit evidence");
|
|
139
|
+
if (prompt) this.hookedPrompts = [...this.hookedPrompts, prompt].slice(-HOOKED_PROMPT_LIMIT);
|
|
140
|
+
if (input.transcriptPath) this.startReasoningTail(input.transcriptPath, ctx);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
handleHookCheck(input: { firedAt?: number }, ctx: ProviderSessionTurnContext): void {
|
|
144
|
+
this.promptHooks.hookCheckedIn(input.firedAt);
|
|
145
|
+
ctx.log(input.firedAt === undefined ? "hook check-in carried no firedAt; submits stay on composer confirmation" : "hook check-in: stamped hook path verified");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** #2919 — whether a prompt Claude already hooked is still queued, so it runs next with no hook of its own. */
|
|
149
|
+
private async hookedPromptStillQueued(transcriptPath: string): Promise<boolean> {
|
|
150
|
+
if (!this.hookedPrompts.length) return false;
|
|
151
|
+
const queued = pendingQueuedPrompts(await readTranscriptTail(transcriptPath)).map((prompt) => prompt.trim());
|
|
152
|
+
this.hookedPrompts = this.hookedPrompts.filter((prompt) => queued.includes(prompt));
|
|
153
|
+
return this.hookedPrompts.length > 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async drainReasoningTail(_ctx: ProviderSessionTurnContext): Promise<void> {
|
|
157
|
+
await this.reasoningTail?.poll();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
stopSessionTrace(): void {
|
|
161
|
+
if (this.reasoningTail) clearInterval(this.reasoningTail.timer);
|
|
162
|
+
this.reasoningTail = undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async settlePollForToolUse(transcriptPath: string, toolName: string | undefined, toolInput: unknown, ctx: ProviderSessionTurnContext): Promise<{ settled: boolean; jsonl?: string }> {
|
|
166
|
+
const deadline = Date.now() + ctx.settlePollTimeoutMs;
|
|
167
|
+
for (;;) {
|
|
168
|
+
let jsonl: string | undefined;
|
|
169
|
+
try { jsonl = await readFile(transcriptPath, "utf8"); } catch { jsonl = undefined; }
|
|
170
|
+
if (!toolName) return { settled: Boolean(jsonl), jsonl };
|
|
171
|
+
if (jsonl !== undefined && transcriptHasToolUseAnchor(jsonl, toolName, toolInput)) return { settled: true, jsonl };
|
|
172
|
+
if (Date.now() >= deadline) return { settled: false, jsonl };
|
|
173
|
+
await Bun.sleep(ctx.settlePollIntervalMs);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// #3411 — runs whatever reasoningCapture says: its tool steps carry the paths area memory matches,
|
|
178
|
+
// and the session mirror drops the steps from chat when reasoningCapture is off.
|
|
179
|
+
private startReasoningTail(transcriptPath: string, ctx: ProviderSessionTurnContext): void {
|
|
180
|
+
this.stopSessionTrace();
|
|
181
|
+
const seen = new Set<string>(this.emittedStepKeys);
|
|
182
|
+
const emittedNarrationKeys = new Set<string>();
|
|
183
|
+
const transcriptTail = new IncrementalClaudeTranscriptTail();
|
|
184
|
+
const turnIdAtStart = ctx.turnId;
|
|
185
|
+
let seeded = false;
|
|
186
|
+
let pendingPoll = Promise.resolve();
|
|
187
|
+
const pollBody = async (): Promise<void> => {
|
|
188
|
+
const result = await transcriptTail.poll(transcriptPath);
|
|
189
|
+
if (!result.available) return;
|
|
190
|
+
if (result.reset) { seen.clear(); emittedNarrationKeys.clear(); seeded = false; }
|
|
191
|
+
if (!result.changed && (seeded || (!result.steps.length && !result.transcriptLooksComplete))) return;
|
|
192
|
+
const keyed = stepDedupKeys(result.steps).map((sig, i) => ({ sig, step: result.steps[i]! }));
|
|
193
|
+
if (!seeded) {
|
|
194
|
+
seeded = true;
|
|
195
|
+
if (result.transcriptLooksComplete) {
|
|
196
|
+
for (const { sig } of keyed) seen.add(sig);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const turnId = ctx.turnId ?? turnIdAtStart ?? result.promptId;
|
|
200
|
+
let emitted = 0;
|
|
201
|
+
const events: ProviderSessionEvent[] = [];
|
|
202
|
+
for (const { sig, step } of keyed) {
|
|
203
|
+
if (seen.has(sig)) continue;
|
|
204
|
+
seen.add(sig);
|
|
205
|
+
this.rememberEmittedStepKey(sig);
|
|
206
|
+
emitted += 1;
|
|
207
|
+
events.push(turnStepEvent(step, turnId));
|
|
208
|
+
if (step.type === "narration") emittedNarrationKeys.add(sig);
|
|
209
|
+
}
|
|
210
|
+
if (events.length) this.emitBatch(events);
|
|
211
|
+
if (emitted) ctx.debug(`reasoning tail emitted ${emitted} step(s) (turn ${turnId ?? "?"}, ${seen.size} total)`);
|
|
212
|
+
};
|
|
213
|
+
const poll = (): Promise<void> => {
|
|
214
|
+
const next = pendingPoll.then(pollBody, pollBody);
|
|
215
|
+
pendingPoll = next.catch(() => {});
|
|
216
|
+
return next;
|
|
217
|
+
};
|
|
218
|
+
// The serialized pendingPoll deliberately catches so later polls may continue, but poll()
|
|
219
|
+
// still returns the original rejection to its caller. Both timer-driven calls below have no
|
|
220
|
+
// caller, so catch them here rather than turning a rotating transcript into a process-fatal
|
|
221
|
+
// unhandled rejection.
|
|
222
|
+
const pollInBackground = (): void => {
|
|
223
|
+
void poll().catch((error) => {
|
|
224
|
+
try { ctx.log(`reasoning tail poll failed: ${errMessage(error)}`); } catch {}
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
this.reasoningTail = { emittedNarrationKeys, poll, timer: setInterval(pollInBackground, REASONING_POLL_MS) };
|
|
228
|
+
ctx.log(`reasoning tail started (turn ${turnIdAtStart ?? "?"})`);
|
|
229
|
+
pollInBackground();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private rememberEmittedStepKey(sig: string): void {
|
|
233
|
+
this.emittedStepKeys.delete(sig);
|
|
234
|
+
this.emittedStepKeys.add(sig);
|
|
235
|
+
while (this.emittedStepKeys.size > EMITTED_STEP_KEY_LIMIT) {
|
|
236
|
+
const oldest = this.emittedStepKeys.values().next().value;
|
|
237
|
+
if (oldest === undefined) break;
|
|
238
|
+
this.emittedStepKeys.delete(oldest);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private preFlushBodyAlreadyMirroredByReasoningTail(jsonl: string, body: string): boolean {
|
|
243
|
+
const tail = this.reasoningTail;
|
|
244
|
+
if (!tail || !tail.emittedNarrationKeys.size) return false;
|
|
245
|
+
const steps = extractLatestTurnSteps(jsonl);
|
|
246
|
+
const keys = stepDedupKeys(steps);
|
|
247
|
+
const mirrored = steps
|
|
248
|
+
.map((step, index) => ({ step, key: keys[index]! }))
|
|
249
|
+
.filter(({ step, key }) => step.type === "narration" && tail.emittedNarrationKeys.has(key))
|
|
250
|
+
.map(({ step }) => step.text)
|
|
251
|
+
.join("\n\n")
|
|
252
|
+
.trim();
|
|
253
|
+
const trimmed = body.trim();
|
|
254
|
+
return mirrored === trimmed || mirrored.endsWith(`\n\n${trimmed}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private emit(event: ProviderSessionEvent): void {
|
|
258
|
+
this.sessionEventCb(event);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private emitBatch(events: ProviderSessionEventBatch): void {
|
|
262
|
+
this.sessionEventsCb(events);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function readTranscriptTail(path: string): Promise<string> {
|
|
267
|
+
try {
|
|
268
|
+
const { size } = await statIncrementalFile(path);
|
|
269
|
+
return new TextDecoder().decode(await readIncrementalFileRange(path, Math.max(0, size - QUEUE_SCAN_BYTES), size));
|
|
270
|
+
} catch {
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function collectClaudeTranscriptSessionEvents(_process: ManagedProcess, ctx: { transcriptPath?: string }): Promise<SessionEvent[] | null> {
|
|
276
|
+
if (!ctx.transcriptPath) return null;
|
|
277
|
+
let jsonl: string;
|
|
278
|
+
try {
|
|
279
|
+
jsonl = await readFile(ctx.transcriptPath, "utf8");
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
return collectClaudeSessionEvents(jsonl);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function collectClaudeTranscriptArchiveSegment(_process: ManagedProcess, ctx: { transcriptPath?: string }): Promise<string | null> {
|
|
287
|
+
if (!ctx.transcriptPath) return null;
|
|
288
|
+
try {
|
|
289
|
+
return await readFile(ctx.transcriptPath, "utf8");
|
|
290
|
+
} catch {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
}
|