@songsid/agend 2.1.12-beta.8 → 2.1.12-beta.9
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/dist/daemon.d.ts +16 -1
- package/dist/daemon.js +144 -43
- package/dist/daemon.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +65 -0
- package/dist/kiro-db-reader.js +258 -0
- package/dist/kiro-db-reader.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +56 -0
- package/dist/kiro-transcript-lane.js +141 -0
- package/dist/kiro-transcript-lane.js.map +1 -0
- package/dist/kiro-transcript-worker.d.ts +1 -0
- package/dist/kiro-transcript-worker.js +28 -0
- package/dist/kiro-transcript-worker.js.map +1 -0
- package/dist/transcript-delta-reader.d.ts +28 -0
- package/dist/transcript-delta-reader.js +148 -0
- package/dist/transcript-delta-reader.js.map +1 -0
- package/dist/transcript-monitor.d.ts +2 -0
- package/dist/transcript-monitor.js +19 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +12 -47
- package/dist/transcript-sources.js +54 -235
- package/dist/transcript-sources.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ import type { StormWindow } from "./storm-window.js";
|
|
|
12
12
|
import type { BackendOutageView } from "./backend-outage.js";
|
|
13
13
|
import { type DaemonDeliveryPort } from "./delivery-outbox.js";
|
|
14
14
|
import type { InteractionSnapshot } from "./backend/types.js";
|
|
15
|
-
/** Point a resumed CLI at its one backend-native instruction source. */
|
|
16
15
|
export declare function buildInstructionReloadNotice(binaryName: string, instanceName: string, instanceDir: string): string;
|
|
17
16
|
/**
|
|
18
17
|
* Whether this backend has to be told in its pane that its instructions moved.
|
|
@@ -1209,6 +1208,8 @@ export declare class Daemon extends EventEmitter {
|
|
|
1209
1208
|
pause(reason?: PauseReason): Promise<void>;
|
|
1210
1209
|
/** Respawn the CLI in the preserved window and block until its prompt is ready. */
|
|
1211
1210
|
wake(timeoutMs?: number): Promise<void>;
|
|
1211
|
+
/** A reset baseline must settle before a launch admits new work. */
|
|
1212
|
+
private resetTranscriptBeforeAdmission;
|
|
1212
1213
|
/**
|
|
1213
1214
|
* One line per edge, until the instance is flapping. The edges themselves are
|
|
1214
1215
|
* still published to every observer; only the log is limited, so the next
|
|
@@ -1757,6 +1758,20 @@ export declare class Daemon extends EventEmitter {
|
|
|
1757
1758
|
*/
|
|
1758
1759
|
private confirmAfterEnter;
|
|
1759
1760
|
private confirmSubmitted;
|
|
1761
|
+
/**
|
|
1762
|
+
* The CLI's transcript, from this attempt's checkpoint, holds this delivery's exact marker: a user entry (read) or
|
|
1763
|
+
* one it queued (taken into its queue). Either is the CLI having taken the
|
|
1764
|
+
* message, so it outranks the pane — including a box that still shows the paste a moment after Enter (Claude paints
|
|
1765
|
+
* the submit late, captured live on 2.1.293), which on its own reads as a strand. Positive only; never a downgrade.
|
|
1766
|
+
*/
|
|
1767
|
+
private transcriptLook;
|
|
1768
|
+
/**
|
|
1769
|
+
* A write's transcript proof: bounded looks from its checkpoint, fenced to the write — its spawn and launch, a stop,
|
|
1770
|
+
* and the caller's own fence — so a look that lands after any of them is never used (#1380 review).
|
|
1771
|
+
*/
|
|
1772
|
+
private transcriptProofFor;
|
|
1773
|
+
/** The CLI this daemon drives, for log lines shared by every backend ("claude-code", "codex", …). */
|
|
1774
|
+
private cliLabel;
|
|
1760
1775
|
/**
|
|
1761
1776
|
* The verdict one snapshot supports — the live viewport, or (for a unique signature) the scrollback a late proof
|
|
1762
1777
|
* reads. One judge for both, so a history read cannot vouch for what the live one refuses: an unreadable box, or our
|
package/dist/daemon.js
CHANGED
|
@@ -37,6 +37,7 @@ import { MuseUsageRelay, clearMuseUsageSnapshot } from "./muse-usage-relay.js";
|
|
|
37
37
|
import { deliveryContentDigest } from "./delivery-outbox.js";
|
|
38
38
|
import { queueResumePolicyForAttempt } from "./delivery-queue-evidence.js";
|
|
39
39
|
import { scanTranscriptForDeliveryMarker } from "./delivery-reconciliation.js";
|
|
40
|
+
import { TranscriptDeltaReader } from "./transcript-delta-reader.js";
|
|
40
41
|
import { buildResumeContinuation, clearInFlightTurnMarker, consumeInFlightTurnMarker, decideTurnResume, normalizeResumeBackend, readResumeFingerprint, RESUME_BLOCKING_DELIVERY_STATES, writeInFlightTurnMarker, } from "./turn-resume.js";
|
|
41
42
|
import { codexLiveRowMatches } from "./backend/codex.js";
|
|
42
43
|
import { InteractionObservation, INTERACTION_CONFIRM_MS, sameInteractionOwner } from "./interaction-observation.js";
|
|
@@ -68,6 +69,10 @@ const REPLY_DROP_WARNING_COOLDOWN_MS = 5 * 60_000;
|
|
|
68
69
|
const REPLY_GUARD_IDLE_CONFIRM_MS = 60_000;
|
|
69
70
|
const REPLY_RECOVERY_PROMPT = "[system:reply-required] The previous human-facing turn ended without a successfully delivered reply. Do not redo the work. React with an emoji or use the reply tool exactly once now to send the user a concise conclusion. If no substantive answer is needed, a brief react is sufficient. If you already replied to this message, do nothing. Do not reply to this system instruction except through the react or reply tool.";
|
|
70
71
|
/** Point a resumed CLI at its one backend-native instruction source. */
|
|
72
|
+
/** The outbox evidence for a delivery the CLI's transcript shows taken (#758). */
|
|
73
|
+
function transcriptMarkerEvidence(kind) {
|
|
74
|
+
return kind === "user" ? "transcript-marker" : "transcript-marker-queued";
|
|
75
|
+
}
|
|
71
76
|
export function buildInstructionReloadNotice(binaryName, instanceName, instanceDir) {
|
|
72
77
|
const source = binaryName === "codex" || binaryName === "grok" || binaryName === "muse"
|
|
73
78
|
? "AGENTS.md"
|
|
@@ -1765,7 +1770,8 @@ export class Daemon extends EventEmitter {
|
|
|
1765
1770
|
this.finishDurableDelivery(delivery, "delivered", OUTPUT_EDGE_ONLY_EVIDENCE);
|
|
1766
1771
|
return;
|
|
1767
1772
|
}
|
|
1768
|
-
this.finishDurableDelivery(delivery, "delivered",
|
|
1773
|
+
this.finishDurableDelivery(delivery, "delivered", verdict.transcriptProof
|
|
1774
|
+
? `positive submission proof; ${transcriptMarkerEvidence(verdict.transcriptProof)}` : "positive submission proof");
|
|
1769
1775
|
}
|
|
1770
1776
|
/**
|
|
1771
1777
|
* Finish a delivery that only the output edge vouched for, by the delivery's own marker in the CLI's transcript (#758).
|
|
@@ -1794,7 +1800,7 @@ export class Daemon extends EventEmitter {
|
|
|
1794
1800
|
const found = await scanTranscriptForDeliveryMarker(checkpoint.path, checkpoint.offset, checkpoint.backend, delivery.deliveryId);
|
|
1795
1801
|
if (found === "user" || found === "queued") {
|
|
1796
1802
|
outcome = "delivered";
|
|
1797
|
-
evidence = found
|
|
1803
|
+
evidence = transcriptMarkerEvidence(found);
|
|
1798
1804
|
break;
|
|
1799
1805
|
}
|
|
1800
1806
|
last = found;
|
|
@@ -2073,6 +2079,15 @@ export class Daemon extends EventEmitter {
|
|
|
2073
2079
|
// conversation store via a pluggable source. Backends with no known
|
|
2074
2080
|
// source stay inert exactly as before.
|
|
2075
2081
|
this.transcriptMonitor = new TranscriptMonitor(this.instanceDir, this.logger, createTranscriptSource(this.config.backend ?? "claude-code", this.config.working_directory, this.credentialProfileStore()));
|
|
2082
|
+
const transcriptMonitor = this.transcriptMonitor;
|
|
2083
|
+
const transcriptFence = this.launchFenceEpoch;
|
|
2084
|
+
// Baseline in the isolate before this daemon accepts new work. A
|
|
2085
|
+
// stopped/replaced launch may never re-arm polling after this await.
|
|
2086
|
+
await transcriptMonitor.initialize();
|
|
2087
|
+
if (this.startupAborted || this.launchFenceEpoch !== transcriptFence || this.transcriptMonitor !== transcriptMonitor) {
|
|
2088
|
+
transcriptMonitor.stop();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2076
2091
|
// 5. Wire transcript events
|
|
2077
2092
|
const ackIfPending = () => {
|
|
2078
2093
|
if (!this.pendingAckMessage || !this.adapter)
|
|
@@ -2843,7 +2858,13 @@ export class Daemon extends EventEmitter {
|
|
|
2843
2858
|
// was just stopped or paused (#1160 review).
|
|
2844
2859
|
if (this.runtimeMonitorsFrozen || this.healthCheckPaused)
|
|
2845
2860
|
return;
|
|
2846
|
-
|
|
2861
|
+
// Reset is synchronous for file sources, but Kiro's baseline is
|
|
2862
|
+
// read off-thread. Finish it before starting the replacement CLI,
|
|
2863
|
+
// otherwise its first work could become part of that baseline.
|
|
2864
|
+
if (!await this.resetTranscriptBeforeAdmission())
|
|
2865
|
+
return;
|
|
2866
|
+
if (this.runtimeMonitorsFrozen || this.healthCheckPaused)
|
|
2867
|
+
return;
|
|
2847
2868
|
// Kill orphan MCP server from the crashed CLI session.
|
|
2848
2869
|
// MCP server writes its PID to channel.mcp.pid on startup.
|
|
2849
2870
|
try {
|
|
@@ -4252,50 +4273,77 @@ export class Daemon extends EventEmitter {
|
|
|
4252
4273
|
// concurrency coordination, first output + idle is `budgetMs`, the dialog
|
|
4253
4274
|
// scan is STARTUP_DIALOG_BUDGET_MS — so the outcome is always trySpawn's
|
|
4254
4275
|
// own verdict. A soft timer only makes a slow wake visible in the log.
|
|
4255
|
-
const
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4276
|
+
const wakeFence = this.launchFenceEpoch;
|
|
4277
|
+
const wakeGeneration = this.spawnGeneration;
|
|
4278
|
+
const isCurrent = () => !this.startupAborted && this.launchFenceEpoch === wakeFence
|
|
4279
|
+
&& this.spawnGeneration === wakeGeneration && this.pauseWakeState === "waking";
|
|
4280
|
+
// The shared transition covers baseline initialization as well as spawn.
|
|
4281
|
+
// Every wake().then(deliver) caller must wait for the same admission edge.
|
|
4282
|
+
const transition = (async () => {
|
|
4260
4283
|
try {
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4284
|
+
await this.autoPauseController.wakeOnDeliver(async () => {
|
|
4285
|
+
const slow = setTimeout(() => {
|
|
4286
|
+
this.logger.warn({ budgetMs }, "Wake is exceeding its budget — still waiting for the spawn (it cannot be cancelled)");
|
|
4287
|
+
}, budgetMs + STARTUP_DIALOG_BUDGET_MS);
|
|
4288
|
+
slow.unref?.();
|
|
4289
|
+
try {
|
|
4290
|
+
const ready = await this.trySpawn(true, budgetMs);
|
|
4291
|
+
if (!ready)
|
|
4292
|
+
throw new Error(`Wake failed: the CLI did not become ready (budget ${budgetMs}ms)`);
|
|
4293
|
+
}
|
|
4294
|
+
finally {
|
|
4295
|
+
clearTimeout(slow);
|
|
4296
|
+
}
|
|
4297
|
+
});
|
|
4298
|
+
if (!isCurrent())
|
|
4299
|
+
throw new Error("Wake cancelled: the launch was superseded");
|
|
4300
|
+
if (!await this.resetTranscriptBeforeAdmission() || !isCurrent()) {
|
|
4301
|
+
throw new Error("Wake cancelled: transcript initialization was superseded");
|
|
4302
|
+
}
|
|
4303
|
+
this.pauseWakeState = "active";
|
|
4304
|
+
this.healthCheckPaused = false;
|
|
4305
|
+
this.pauseRequested = false;
|
|
4306
|
+
// trySpawn resolved only after the new CLI reached its ready prompt.
|
|
4307
|
+
// Discard any recovery gate retained while monitors were frozen.
|
|
4308
|
+
this.clearErrorRecoveryGate();
|
|
4309
|
+
clearPausedMarker(this.instanceDir);
|
|
4310
|
+
this.resumeRuntimeMonitors();
|
|
4311
|
+
this.logger.info("Instance auto-woke");
|
|
4312
|
+
this.ipcServer?.broadcast({
|
|
4313
|
+
type: "instance_state", instanceName: this.name, state: this.instanceState, pausedAt: null,
|
|
4314
|
+
});
|
|
4315
|
+
this.emit("auto_woke", { name: this.name });
|
|
4316
|
+
}
|
|
4317
|
+
catch (err) {
|
|
4318
|
+
if (isCurrent()) {
|
|
4319
|
+
this.pauseWakeState = "paused";
|
|
4320
|
+
this.healthCheckPaused = true;
|
|
4321
|
+
}
|
|
4322
|
+
this.logger.error({ err: err.message }, "Instance wake failed");
|
|
4323
|
+
throw err;
|
|
4264
4324
|
}
|
|
4265
4325
|
finally {
|
|
4266
|
-
|
|
4326
|
+
this.endSpawn();
|
|
4267
4327
|
}
|
|
4268
|
-
});
|
|
4328
|
+
})();
|
|
4269
4329
|
this.pauseWakeTransition = transition;
|
|
4270
4330
|
try {
|
|
4271
4331
|
await transition;
|
|
4272
|
-
this.pauseWakeState = "active";
|
|
4273
|
-
this.healthCheckPaused = false;
|
|
4274
|
-
this.pauseRequested = false;
|
|
4275
|
-
// trySpawn resolved only after the new CLI reached its ready prompt.
|
|
4276
|
-
// Discard any recovery gate retained while monitors were frozen.
|
|
4277
|
-
this.clearErrorRecoveryGate();
|
|
4278
|
-
clearPausedMarker(this.instanceDir);
|
|
4279
|
-
this.transcriptMonitor?.resetOffset();
|
|
4280
|
-
this.resumeRuntimeMonitors();
|
|
4281
|
-
this.logger.info("Instance auto-woke");
|
|
4282
|
-
this.ipcServer?.broadcast({
|
|
4283
|
-
type: "instance_state", instanceName: this.name, state: this.instanceState, pausedAt: null,
|
|
4284
|
-
});
|
|
4285
|
-
this.emit("auto_woke", { name: this.name });
|
|
4286
|
-
}
|
|
4287
|
-
catch (err) {
|
|
4288
|
-
this.pauseWakeState = "paused";
|
|
4289
|
-
this.healthCheckPaused = true;
|
|
4290
|
-
this.logger.error({ err: err.message }, "Instance wake failed");
|
|
4291
|
-
throw err;
|
|
4292
4332
|
}
|
|
4293
4333
|
finally {
|
|
4294
|
-
this.endSpawn();
|
|
4295
4334
|
if (this.pauseWakeTransition === transition)
|
|
4296
4335
|
this.pauseWakeTransition = null;
|
|
4297
4336
|
}
|
|
4298
4337
|
}
|
|
4338
|
+
/** A reset baseline must settle before a launch admits new work. */
|
|
4339
|
+
async resetTranscriptBeforeAdmission() {
|
|
4340
|
+
const monitor = this.transcriptMonitor;
|
|
4341
|
+
const fence = this.launchFenceEpoch, generation = this.spawnGeneration;
|
|
4342
|
+
monitor?.resetOffset();
|
|
4343
|
+
await monitor?.initialize();
|
|
4344
|
+
return !this.startupAborted && this.launchFenceEpoch === fence
|
|
4345
|
+
&& this.spawnGeneration === generation && this.transcriptMonitor === monitor;
|
|
4346
|
+
}
|
|
4299
4347
|
/**
|
|
4300
4348
|
* One line per edge, until the instance is flapping. The edges themselves are
|
|
4301
4349
|
* still published to every observer; only the log is limited, so the next
|
|
@@ -7553,6 +7601,11 @@ export class Daemon extends EventEmitter {
|
|
|
7553
7601
|
path: attemptEvidence.transcriptPath,
|
|
7554
7602
|
offset: attemptEvidence.transcriptOffset,
|
|
7555
7603
|
};
|
|
7604
|
+
// From here every proof of this write may also ask the CLI's transcript (the pane can lose the echo to the
|
|
7605
|
+
// reply, or still be painting the paste) — this delivery's exact marker only.
|
|
7606
|
+
if (["claude-code", "codex"].includes(attemptEvidence.backend)) {
|
|
7607
|
+
signature.transcript = this.transcriptProofFor(verdict.transcriptCheckpoint, durableAttempt.deliveryId, spawnAtWrite, stillCurrent, kind => { verdict.transcriptProof = kind; });
|
|
7608
|
+
}
|
|
7556
7609
|
}
|
|
7557
7610
|
}
|
|
7558
7611
|
if (rawPaste)
|
|
@@ -7745,7 +7798,7 @@ export class Daemon extends EventEmitter {
|
|
|
7745
7798
|
// message nobody submitted. Output is corroboration; text sitting in
|
|
7746
7799
|
// the input row is disqualifying, and disqualifying evidence wins.
|
|
7747
7800
|
if (this.structuredInputEvidence() && afterEnter !== "stranded") {
|
|
7748
|
-
this.logger.warn({ phase: "native-queue-submit", proof: afterEnter, strandedAt },
|
|
7801
|
+
this.logger.warn({ phase: "native-queue-submit", proof: afterEnter, strandedAt }, `${this.cliLabel()} recovery Enter outcome uncertain — no hard failure or duplicate paste`);
|
|
7749
7802
|
verdict.phase = "native-queue-submit";
|
|
7750
7803
|
verdict.proof = afterEnter;
|
|
7751
7804
|
return false;
|
|
@@ -7761,7 +7814,7 @@ export class Daemon extends EventEmitter {
|
|
|
7761
7814
|
// In Codex a missing viewport echo is inconclusive, not a proof of
|
|
7762
7815
|
// loss. Another paste could run the same request twice. Leave the
|
|
7763
7816
|
// already-pasted delivery at 👀 and let the next observation decide.
|
|
7764
|
-
this.logger.warn({ phase: "native-queue-proof", proof: settled },
|
|
7817
|
+
this.logger.warn({ phase: "native-queue-proof", proof: settled }, `${this.cliLabel()} native-queue outcome uncertain — not re-pasting`);
|
|
7765
7818
|
verdict.phase = "native-queue-proof";
|
|
7766
7819
|
verdict.proof = settled;
|
|
7767
7820
|
return false;
|
|
@@ -7871,7 +7924,7 @@ export class Daemon extends EventEmitter {
|
|
|
7871
7924
|
// although its echo had not appeared within the proof window. A
|
|
7872
7925
|
// missing viewport signature cannot establish non-delivery; keep
|
|
7873
7926
|
// 👀 and never emit the sender's hard ❌ or re-paste blindly.
|
|
7874
|
-
this.logger.warn({ phase: "post-submit-proof", proof },
|
|
7927
|
+
this.logger.warn({ phase: "post-submit-proof", proof }, `${this.cliLabel()} delivery outcome uncertain — no hard failure or duplicate paste`);
|
|
7875
7928
|
verdict.phase = "post-submit-proof";
|
|
7876
7929
|
verdict.proof = proof;
|
|
7877
7930
|
return false;
|
|
@@ -7988,11 +8041,15 @@ export class Daemon extends EventEmitter {
|
|
|
7988
8041
|
// here: the CLI may already be processing the unique message_id.
|
|
7989
8042
|
let proof = "unproven";
|
|
7990
8043
|
const firstDeadline = Date.now() + POST_ENTER_PROOF_WINDOW_MS;
|
|
8044
|
+
// A structural box reader (claude-code) sees its box still holding the paste for a moment after an Enter it did
|
|
8045
|
+
// take — Claude paints the submit late (captured live on 2.1.293: ~40 captures in a row). So for it a strand
|
|
8046
|
+
// proves a swallowed Enter only once it outlasts the window; an early one is polled through. Codex keeps its rule.
|
|
8047
|
+
const strandMustPersist = !!this.backend?.readInputRow;
|
|
7991
8048
|
for (;;) {
|
|
7992
8049
|
proof = await this.confirmSubmitted(signature, baseline);
|
|
7993
8050
|
if (proof === "submitted")
|
|
7994
8051
|
return true;
|
|
7995
|
-
if (proof === "stranded" || Date.now() >= firstDeadline)
|
|
8052
|
+
if ((proof === "stranded" && !strandMustPersist) || Date.now() >= firstDeadline)
|
|
7996
8053
|
break;
|
|
7997
8054
|
await new Promise(r => setTimeout(r, POST_ENTER_PROOF_POLL_MS));
|
|
7998
8055
|
}
|
|
@@ -8042,14 +8099,58 @@ export class Daemon extends EventEmitter {
|
|
|
8042
8099
|
async confirmSubmitted(signature, baseline) {
|
|
8043
8100
|
if (!this.tmux)
|
|
8044
8101
|
return "unproven";
|
|
8045
|
-
let pane;
|
|
8102
|
+
let pane = null;
|
|
8046
8103
|
try {
|
|
8047
8104
|
pane = await this.tmux.capturePane();
|
|
8048
8105
|
}
|
|
8049
|
-
catch {
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8106
|
+
catch { /* the transcript may still say */ }
|
|
8107
|
+
const onPane = pane === null ? "unproven" : this.judgeSubmission(pane, signature, baseline);
|
|
8108
|
+
if (onPane === "submitted")
|
|
8109
|
+
return onPane;
|
|
8110
|
+
const t = signature.transcript;
|
|
8111
|
+
const found = await this.transcriptLook(signature);
|
|
8112
|
+
// One synchronous stretch from here: the fence is asked after the last await, and only then is the hit kept and the
|
|
8113
|
+
// spawn's guard retired — a stop or respawn that lands between the look and this line leaves both untouched.
|
|
8114
|
+
if (!found || !t?.current())
|
|
8115
|
+
return onPane;
|
|
8116
|
+
if (!t.provenBy) {
|
|
8117
|
+
t.provenBy = found;
|
|
8118
|
+
t.onProof?.(found);
|
|
8119
|
+
}
|
|
8120
|
+
return this.submittedProof();
|
|
8121
|
+
}
|
|
8122
|
+
/**
|
|
8123
|
+
* The CLI's transcript, from this attempt's checkpoint, holds this delivery's exact marker: a user entry (read) or
|
|
8124
|
+
* one it queued (taken into its queue). Either is the CLI having taken the
|
|
8125
|
+
* message, so it outranks the pane — including a box that still shows the paste a moment after Enter (Claude paints
|
|
8126
|
+
* the submit late, captured live on 2.1.293), which on its own reads as a strand. Positive only; never a downgrade.
|
|
8127
|
+
*/
|
|
8128
|
+
async transcriptLook(signature) {
|
|
8129
|
+
const t = signature.transcript;
|
|
8130
|
+
if (!t || !t.current())
|
|
8131
|
+
return null;
|
|
8132
|
+
if (t.provenBy)
|
|
8133
|
+
return t.provenBy;
|
|
8134
|
+
const found = await t.reader.look();
|
|
8135
|
+
// No side effect here: the caller keeps the hit and retires the guard only after its own fence check.
|
|
8136
|
+
return found === "user" || found === "queued" ? found : null;
|
|
8137
|
+
}
|
|
8138
|
+
/**
|
|
8139
|
+
* A write's transcript proof: bounded looks from its checkpoint, fenced to the write — its spawn and launch, a stop,
|
|
8140
|
+
* and the caller's own fence — so a look that lands after any of them is never used (#1380 review).
|
|
8141
|
+
*/
|
|
8142
|
+
transcriptProofFor(checkpoint, deliveryId, spawnAtWrite, stillCurrent, onProof) {
|
|
8143
|
+
const launchAtWrite = this.launchFenceEpoch;
|
|
8144
|
+
return {
|
|
8145
|
+
reader: new TranscriptDeltaReader(checkpoint.path, checkpoint.offset, checkpoint.backend, deliveryId),
|
|
8146
|
+
current: () => !this.deliveryWritesStopping && this.spawnGeneration === spawnAtWrite
|
|
8147
|
+
&& this.launchFenceEpoch === launchAtWrite && (stillCurrent?.() ?? true),
|
|
8148
|
+
onProof,
|
|
8149
|
+
};
|
|
8150
|
+
}
|
|
8151
|
+
/** The CLI this daemon drives, for log lines shared by every backend ("claude-code", "codex", …). */
|
|
8152
|
+
cliLabel() {
|
|
8153
|
+
return this.config.backend ?? this.backend?.binaryName ?? "CLI";
|
|
8053
8154
|
}
|
|
8054
8155
|
/**
|
|
8055
8156
|
* The verdict one snapshot supports — the live viewport, or (for a unique signature) the scrollback a late proof
|