@songsid/agend 2.1.12-beta.6 → 2.1.12-beta.8
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/agent-cli.js +13 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/backend/claude-code.d.ts +34 -1
- package/dist/backend/claude-code.js +123 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/types.d.ts +21 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +13 -0
- package/dist/channel/adapters/discord.js +43 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +7 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +4 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cpu-profile.d.ts +32 -0
- package/dist/cpu-profile.js +221 -0
- package/dist/cpu-profile.js.map +1 -0
- package/dist/daemon.d.ts +27 -1
- package/dist/daemon.js +131 -30
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +22 -0
- package/dist/delivery-outbox.js +111 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -1
- package/dist/fleet-manager.d.ts +66 -2
- package/dist/fleet-manager.js +409 -62
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +26 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +15 -1
- package/dist/outbound-schemas.js +9 -3
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-input-residue.d.ts +2 -0
- package/dist/pane-input-residue.js +5 -1
- package/dist/pane-input-residue.js.map +1 -1
- package/dist/profile-control.d.ts +20 -0
- package/dist/profile-control.js +308 -0
- package/dist/profile-control.js.map +1 -0
- package/dist/runtime-cpu-profile.d.ts +37 -0
- package/dist/runtime-cpu-profile.js +123 -0
- package/dist/runtime-cpu-profile.js.map +1 -0
- package/dist/scheduler/db.d.ts +29 -4
- package/dist/scheduler/db.js +96 -6
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/types.d.ts +33 -0
- package/dist/service-installer.d.ts +6 -0
- package/dist/service-installer.js +18 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/topic-commands.d.ts +12 -1
- package/dist/topic-commands.js +33 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -30,7 +30,7 @@ import { writeSecretFile } from "./secret-file.js";
|
|
|
30
30
|
import { PaneWriteLock } from "./pane-write-lock.js";
|
|
31
31
|
import { buildFleetInstructions } from "./instructions.js";
|
|
32
32
|
import { formatCrossInstanceInboundMessage, renderCrossInstanceHandoffMetadata, } from "./cross-instance-envelope.js";
|
|
33
|
-
import { bottomRowIsReady, inputAreaText, inputShowsPastedText, pastedTextSignature, pasteLeftInInput, strandedAgendMessageInInput } from "./pane-input-residue.js";
|
|
33
|
+
import { agendMessageInInput, bottomRowIsReady, inputAreaText, inputShowsPastedText, pastedTextSignature, pasteLeftInInput, strandedAgendMessageInInput } from "./pane-input-residue.js";
|
|
34
34
|
import { TurnReplyGuard } from "./turn-reply-guard.js";
|
|
35
35
|
import { t } from "./locale.js";
|
|
36
36
|
import { MuseUsageRelay, clearMuseUsageSnapshot } from "./muse-usage-relay.js";
|
|
@@ -6143,7 +6143,7 @@ export class Daemon extends EventEmitter {
|
|
|
6143
6143
|
const canHandOff = (supportsQueuedInput || opts?.steer)
|
|
6144
6144
|
&& readiness === "busy"
|
|
6145
6145
|
&& (await this.probeBlockingDialog()).state === "clear"
|
|
6146
|
-
&& await this.hasPositiveDeliveryInput();
|
|
6146
|
+
&& await this.hasPositiveDeliveryInput(true);
|
|
6147
6147
|
if (canHandOff) {
|
|
6148
6148
|
// Native queue (codex), or an explicit /steer: hand the complete
|
|
6149
6149
|
// paste+Enter transaction to the busy CLI now. For steer this is the
|
|
@@ -6240,7 +6240,7 @@ export class Daemon extends EventEmitter {
|
|
|
6240
6240
|
if (probe.state !== "clear")
|
|
6241
6241
|
return "dialog";
|
|
6242
6242
|
}
|
|
6243
|
-
if (!(await this.hasPositiveDeliveryInput()))
|
|
6243
|
+
if (!(await this.hasPositiveDeliveryInput(handingOffToNativeQueue)))
|
|
6244
6244
|
return "dialog";
|
|
6245
6245
|
// #829: a CLI that restores a cancelled prompt into its input box would
|
|
6246
6246
|
// have this message pasted onto it and both submitted as one. Clear it
|
|
@@ -6262,6 +6262,9 @@ export class Daemon extends EventEmitter {
|
|
|
6262
6262
|
if (residue !== "clear")
|
|
6263
6263
|
return this.failDelivery(verdict, status, "input-residue", residue);
|
|
6264
6264
|
const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent);
|
|
6265
|
+
// The hand-off's last capture had no box: nothing was begun or written — wait for readiness, then try again.
|
|
6266
|
+
if (written === "handoff-box-unread")
|
|
6267
|
+
return "dialog";
|
|
6265
6268
|
// Fenced before its first write: not attempted — redo after a spawn, else drop without a ❌.
|
|
6266
6269
|
return written === false && verdict.fenced ? stale() : written;
|
|
6267
6270
|
});
|
|
@@ -6809,8 +6812,26 @@ export class Daemon extends EventEmitter {
|
|
|
6809
6812
|
}
|
|
6810
6813
|
return true;
|
|
6811
6814
|
}
|
|
6812
|
-
/**
|
|
6813
|
-
|
|
6815
|
+
/**
|
|
6816
|
+
* Codex can paint a prompt before the TTY enters raw mode. Both are required.
|
|
6817
|
+
*
|
|
6818
|
+
* A hand-off into a busy pane (native queue or steer) pastes and presses Enter at once, so a backend with a structural
|
|
6819
|
+
* box reader must see its box on a fresh capture first: a pane whose box cannot be read — a modal the dialog table
|
|
6820
|
+
* does not know, a frame the reader refuses, a failed capture — is not one to type into (#1169 review). Asked
|
|
6821
|
+
* before the hand-off is chosen and again under the pane lock, right before the write; false waits for readiness.
|
|
6822
|
+
*/
|
|
6823
|
+
async hasPositiveDeliveryInput(handOff = false) {
|
|
6824
|
+
if (handOff && this.backend?.readInputRow) {
|
|
6825
|
+
if (!this.tmux)
|
|
6826
|
+
return false;
|
|
6827
|
+
try {
|
|
6828
|
+
if (this.backend.readInputRow(await this.tmux.capturePane()) === null)
|
|
6829
|
+
return false;
|
|
6830
|
+
}
|
|
6831
|
+
catch {
|
|
6832
|
+
return false;
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6814
6835
|
if (!this.needsStartupInputProof())
|
|
6815
6836
|
return true;
|
|
6816
6837
|
const check = this.backend?.isDeliveryInputReadyPane;
|
|
@@ -7494,6 +7515,15 @@ export class Daemon extends EventEmitter {
|
|
|
7494
7515
|
verdict.fenced = !verdict.durableBeginCommitted;
|
|
7495
7516
|
return false;
|
|
7496
7517
|
}
|
|
7518
|
+
// A hand-off pastes and presses Enter into a busy pane: the box must still be readable on this, the last capture
|
|
7519
|
+
// before the write (#1169 review) — a modal that appeared after the under-lock check would take that Enter. Before
|
|
7520
|
+
// the durable begin the caller waits for readiness outside the lock and tries again; after it (a paste retry),
|
|
7521
|
+
// nothing was written and the attempt ends like a paste that failed.
|
|
7522
|
+
if (handingOffToNativeQueue && this.backend?.readInputRow && !pasteBaseline?.inputReadable) {
|
|
7523
|
+
if (!verdict.durableBeginCommitted)
|
|
7524
|
+
return "handoff-box-unread";
|
|
7525
|
+
return this.failDelivery(verdict, status, "paste", "handoff-input-unreadable");
|
|
7526
|
+
}
|
|
7497
7527
|
// Readiness, idle-gate, dialog, spawn and pane-lock waits have all ended.
|
|
7498
7528
|
// Commit the submission fence at the last possible point before the
|
|
7499
7529
|
// first side effect; a crash during those waits remains safely replayable.
|
|
@@ -7700,7 +7730,7 @@ export class Daemon extends EventEmitter {
|
|
|
7700
7730
|
// The recovery Enter can be accepted before Codex paints its new
|
|
7701
7731
|
// transcript. One immediate capture is not a failure verdict: give
|
|
7702
7732
|
// that echo/queue a bounded chance to appear, without re-pasting.
|
|
7703
|
-
const afterEnter = this.
|
|
7733
|
+
const afterEnter = this.structuredInputEvidence()
|
|
7704
7734
|
? await this.lateCodexSubmissionProof(signature, pasteBaseline, true)
|
|
7705
7735
|
: await this.confirmSubmitted(signature, pasteBaseline);
|
|
7706
7736
|
if (afterEnter === "submitted") {
|
|
@@ -7714,7 +7744,7 @@ export class Daemon extends EventEmitter {
|
|
|
7714
7744
|
// another turn's output — and treating that as proof re-confirms a
|
|
7715
7745
|
// message nobody submitted. Output is corroboration; text sitting in
|
|
7716
7746
|
// the input row is disqualifying, and disqualifying evidence wins.
|
|
7717
|
-
if (this.
|
|
7747
|
+
if (this.structuredInputEvidence() && afterEnter !== "stranded") {
|
|
7718
7748
|
this.logger.warn({ phase: "native-queue-submit", proof: afterEnter, strandedAt }, "Codex recovery Enter outcome uncertain — no hard failure or duplicate paste");
|
|
7719
7749
|
verdict.phase = "native-queue-submit";
|
|
7720
7750
|
verdict.proof = afterEnter;
|
|
@@ -7723,7 +7753,11 @@ export class Daemon extends EventEmitter {
|
|
|
7723
7753
|
this.logger.error({ afterEnter, strandedAt }, "Stranded message could not be submitted by Enter");
|
|
7724
7754
|
return this.failDelivery(verdict, status, "native-queue-submit", afterEnter);
|
|
7725
7755
|
}
|
|
7726
|
-
|
|
7756
|
+
// A structural box reader (claude-code, #1200) is in the same position as Codex once the turn has run: the
|
|
7757
|
+
// message may have been taken and its echo scrolled out, the queue drained, the box empty — or the capture
|
|
7758
|
+
// failed. None of that proves the paste was lost, so nothing short of positive evidence is a reason to paste
|
|
7759
|
+
// again (#1353 review); "unverifiable" used to, and could submit the message twice.
|
|
7760
|
+
if (this.backend?.isDeliveryInputReadyPane || this.backend?.readInputRow) {
|
|
7727
7761
|
// In Codex a missing viewport echo is inconclusive, not a proof of
|
|
7728
7762
|
// loss. Another paste could run the same request twice. Leave the
|
|
7729
7763
|
// already-pasted delivery at 👀 and let the next observation decide.
|
|
@@ -7824,7 +7858,7 @@ export class Daemon extends EventEmitter {
|
|
|
7824
7858
|
verdict.proof = "output-edge";
|
|
7825
7859
|
}
|
|
7826
7860
|
else {
|
|
7827
|
-
const proof = this.
|
|
7861
|
+
const proof = this.structuredInputEvidence()
|
|
7828
7862
|
? await this.lateCodexSubmissionProof(signature, pasteBaseline)
|
|
7829
7863
|
: await this.confirmSubmitted(signature, pasteBaseline);
|
|
7830
7864
|
if (proof === "submitted") {
|
|
@@ -7832,7 +7866,7 @@ export class Daemon extends EventEmitter {
|
|
|
7832
7866
|
this.emit("message_confirmed", status);
|
|
7833
7867
|
return true;
|
|
7834
7868
|
}
|
|
7835
|
-
if (this.
|
|
7869
|
+
if (this.structuredInputEvidence() && proof !== "stranded") {
|
|
7836
7870
|
// This is the observed #910 race: the CLI processed the message
|
|
7837
7871
|
// although its echo had not appeared within the proof window. A
|
|
7838
7872
|
// missing viewport signature cannot establish non-delivery; keep
|
|
@@ -7910,7 +7944,7 @@ export class Daemon extends EventEmitter {
|
|
|
7910
7944
|
return dialogClear && this.spawnGeneration === spawnGeneration && this.getWindowId() === windowId;
|
|
7911
7945
|
}
|
|
7912
7946
|
canProveSubmission() {
|
|
7913
|
-
return
|
|
7947
|
+
return this.readsInput();
|
|
7914
7948
|
}
|
|
7915
7949
|
/** Positive submission retires the startup transient guard for this spawn. */
|
|
7916
7950
|
submittedProof() {
|
|
@@ -7947,7 +7981,7 @@ export class Daemon extends EventEmitter {
|
|
|
7947
7981
|
}
|
|
7948
7982
|
return busy;
|
|
7949
7983
|
}
|
|
7950
|
-
if (this.
|
|
7984
|
+
if (this.structuredInputEvidence()) {
|
|
7951
7985
|
// Codex's first post-wake redraw may hide the echo for a few seconds.
|
|
7952
7986
|
// Absence from a viewport is NOT proof the paste was lost, so only a
|
|
7953
7987
|
// positively identified strand authorizes another Enter. Never re-paste
|
|
@@ -8015,6 +8049,14 @@ export class Daemon extends EventEmitter {
|
|
|
8015
8049
|
catch {
|
|
8016
8050
|
return "unproven";
|
|
8017
8051
|
}
|
|
8052
|
+
return this.judgeSubmission(pane, signature, baseline);
|
|
8053
|
+
}
|
|
8054
|
+
/**
|
|
8055
|
+
* The verdict one snapshot supports — the live viewport, or (for a unique signature) the scrollback a late proof
|
|
8056
|
+
* reads. One judge for both, so a history read cannot vouch for what the live one refuses: an unreadable box, or our
|
|
8057
|
+
* text still in it (#1353 review).
|
|
8058
|
+
*/
|
|
8059
|
+
judgeSubmission(pane, signature, baseline) {
|
|
8018
8060
|
// Without a way to tell the input row from the transcript, "the text is on
|
|
8019
8061
|
// screen" cannot distinguish submitted from stranded — that ambiguity IS
|
|
8020
8062
|
// the bug. Such a backend gets an explicit verdict rather than a guess:
|
|
@@ -8024,8 +8066,7 @@ export class Daemon extends EventEmitter {
|
|
|
8024
8066
|
// for the first and recovers on the second, which is what it did before —
|
|
8025
8067
|
// the fix here is for backends that CAN be read, not a new guess for those
|
|
8026
8068
|
// that cannot.
|
|
8027
|
-
|
|
8028
|
-
if (!prompt) {
|
|
8069
|
+
if (!this.readsInput()) {
|
|
8029
8070
|
const seen = this.paneEvidence(pane, signature);
|
|
8030
8071
|
if (signature.unique && seen.payload > 0)
|
|
8031
8072
|
return "unverifiable";
|
|
@@ -8042,6 +8083,11 @@ export class Daemon extends EventEmitter {
|
|
|
8042
8083
|
&& !this.backend.getBusyPattern?.()?.test(pane)) {
|
|
8043
8084
|
return after.payload > 0 ? "unverifiable" : "unproven";
|
|
8044
8085
|
}
|
|
8086
|
+
// A structural reader (claude-code) that finds no input box on this screen — a dialog in front of it, a layout it
|
|
8087
|
+
// does not know — cannot place our text on either side of the box: the same verdicts as a readerless backend.
|
|
8088
|
+
if (this.backend?.readInputRow && !after.inputReadable) {
|
|
8089
|
+
return after.payload > 0 ? "unverifiable" : "unproven";
|
|
8090
|
+
}
|
|
8045
8091
|
// 1. Disqualifying evidence, checked FIRST and never overridden by the
|
|
8046
8092
|
// corroborating evidence below: our text is sitting in the input row, so
|
|
8047
8093
|
// it was not submitted — whatever else is on screen.
|
|
@@ -8052,8 +8098,22 @@ export class Daemon extends EventEmitter {
|
|
|
8052
8098
|
// Otherwise an older stranded message with the same opening would be
|
|
8053
8099
|
// read as ours, we would press Enter to "recover" it, and the turn IT
|
|
8054
8100
|
// starts would confirm a message that never reached the pane.
|
|
8055
|
-
|
|
8101
|
+
//
|
|
8102
|
+
// For a structural box reader (claude-code), "did not already show it" needs a baseline whose box was READ: an
|
|
8103
|
+
// unreadable one (a dialog, a redraw the reader refused) says nothing about what the box held, and counting it as
|
|
8104
|
+
// empty would claim an older message as ours and press Enter on it (#1353 review). The prompt-row backends (codex,
|
|
8105
|
+
// kiro) keep their existing attribution.
|
|
8106
|
+
const attributable = baseline != null
|
|
8107
|
+
&& (!this.backend?.readInputRow || (baseline.inputReadable && after.inputReadable));
|
|
8108
|
+
if (after.strandedInput && (signature.unique || (attributable && baseline.strandedInput === false)))
|
|
8109
|
+
return "stranded";
|
|
8110
|
+
// A paste the CLI shows collapsed carries no signature at all. One the box did not hold before we pasted is
|
|
8111
|
+
// ours, and it is still in the box — whatever echo or output is on screen (#1200).
|
|
8112
|
+
if (attributable && after.collapsedPastes > baseline.collapsedPastes)
|
|
8056
8113
|
return "stranded";
|
|
8114
|
+
// Something sits in the box that cannot be attributed either way. Only a unique signature seen outside the box
|
|
8115
|
+
// (below) can still prove this delivery; nothing else on screen may.
|
|
8116
|
+
const unattributedResidue = !attributable && (after.strandedInput || after.collapsedPastes > 0);
|
|
8057
8117
|
// 2. Positive evidence. A unique signature needs no baseline: no earlier
|
|
8058
8118
|
// message can carry this delivery's message_id, so finding it outside
|
|
8059
8119
|
// the input row is proof in itself — which also means a momentary
|
|
@@ -8061,12 +8121,16 @@ export class Daemon extends EventEmitter {
|
|
|
8061
8121
|
// message into a re-paste.
|
|
8062
8122
|
if (signature.unique && after.payload > 0)
|
|
8063
8123
|
return this.submittedProof();
|
|
8124
|
+
if (unattributedResidue)
|
|
8125
|
+
return "unverifiable";
|
|
8064
8126
|
// 3. Otherwise the evidence must be NEW relative to the pane as it was
|
|
8065
8127
|
// before we pasted: a queue marker left by an earlier message, or an
|
|
8066
8128
|
// older transcript entry that opens the same way, are on screen either
|
|
8067
8129
|
// way and would otherwise confirm a paste that never landed.
|
|
8068
8130
|
if (baseline) {
|
|
8069
|
-
|
|
8131
|
+
// A queue is new only against a box that was read before the paste (a reader backend): an unreadable baseline
|
|
8132
|
+
// says nothing about whether the queue was already there.
|
|
8133
|
+
if (after.queued > baseline.queued && (!this.backend?.readInputRow || baseline.inputReadable))
|
|
8070
8134
|
return this.submittedProof();
|
|
8071
8135
|
if (after.payload > baseline.payload)
|
|
8072
8136
|
return this.submittedProof();
|
|
@@ -8094,12 +8158,10 @@ export class Daemon extends EventEmitter {
|
|
|
8094
8158
|
return proof;
|
|
8095
8159
|
if (signature.unique && this.tmux?.capturePaneWithHistory) {
|
|
8096
8160
|
try {
|
|
8161
|
+
// Scrollback can hold the echo the viewport lost — but it is judged exactly like the viewport: a box that
|
|
8162
|
+
// cannot be read, or that still holds our text, is never upgraded by an echo further up.
|
|
8097
8163
|
const history = await this.tmux.capturePaneWithHistory(300);
|
|
8098
|
-
|
|
8099
|
-
const known = !this.backend?.isDeliveryInputReadyPane
|
|
8100
|
-
|| this.backend.isDeliveryInputReadyPane(history)
|
|
8101
|
-
|| this.backend.getBusyPattern?.()?.test(history);
|
|
8102
|
-
if (known && seen.payload > 0 && !seen.strandedInput)
|
|
8164
|
+
if (this.judgeSubmission(history, signature, baseline) === "submitted")
|
|
8103
8165
|
return this.submittedProof();
|
|
8104
8166
|
}
|
|
8105
8167
|
catch { /* a failed history read proves neither delivery nor loss */ }
|
|
@@ -8123,9 +8185,8 @@ export class Daemon extends EventEmitter {
|
|
|
8123
8185
|
try {
|
|
8124
8186
|
const pane = await this.tmux.capturePane();
|
|
8125
8187
|
const evidence = this.paneEvidence(pane, signature);
|
|
8126
|
-
const
|
|
8127
|
-
if (
|
|
8128
|
-
&& strandedAgendMessageInInput(pane, prompt)) {
|
|
8188
|
+
const input = this.inputRegion(pane);
|
|
8189
|
+
if (input && agendMessageInInput(input.text)) {
|
|
8129
8190
|
// Whatever we paste now lands after it, and one Enter submits both as
|
|
8130
8191
|
// a single message. Nothing here can undo that; saying so beats
|
|
8131
8192
|
// letting two messages silently merge.
|
|
@@ -8144,16 +8205,45 @@ export class Daemon extends EventEmitter {
|
|
|
8144
8205
|
return null;
|
|
8145
8206
|
}
|
|
8146
8207
|
paneEvidence(pane, signature) {
|
|
8147
|
-
const
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8208
|
+
const input = this.inputRegion(pane);
|
|
8209
|
+
// A structural box reader vouches for the queue itself (InputBox.queued): a marker's words elsewhere on the pane —
|
|
8210
|
+
// a reply quoting it, an old block — are not a queue (#1169 review). Other backends count their marker rows.
|
|
8211
|
+
const marker = this.backend?.readInputRow ? null : this.backend?.getQueuedInputMarker?.();
|
|
8151
8212
|
return {
|
|
8152
|
-
queued:
|
|
8213
|
+
queued: this.backend?.readInputRow
|
|
8214
|
+
? (input?.queued ? 1 : 0)
|
|
8215
|
+
: marker ? pane.split(/\r?\n/).filter(row => marker.test(row)).length : 0,
|
|
8153
8216
|
payload: countOccurrences(pane.replace(/\s+/g, ""), signature.value),
|
|
8154
|
-
strandedInput: input != null && inputShowsPastedText(input, signature.value),
|
|
8217
|
+
strandedInput: input != null && inputShowsPastedText(input.text, signature.value),
|
|
8218
|
+
collapsedPastes: input?.collapsedPastes ?? 0,
|
|
8219
|
+
inputReadable: input != null,
|
|
8155
8220
|
};
|
|
8156
8221
|
}
|
|
8222
|
+
/**
|
|
8223
|
+
* The input box as it stands on this screen: the backend's structural reader when it has one (claude-code, #1200),
|
|
8224
|
+
* else the prompt-row heuristic (codex — only on a screen it vouches for — and kiro). null: no box to read here.
|
|
8225
|
+
*/
|
|
8226
|
+
inputRegion(pane) {
|
|
8227
|
+
if (this.backend?.readInputRow)
|
|
8228
|
+
return this.backend.readInputRow(pane);
|
|
8229
|
+
const prompt = this.backend?.getBottomReadyPattern?.();
|
|
8230
|
+
if (!prompt || (this.backend?.isDeliveryInputReadyPane && !this.backend.isDeliveryInputReadyPane(pane)))
|
|
8231
|
+
return null;
|
|
8232
|
+
const text = inputAreaText(pane, prompt);
|
|
8233
|
+
return text == null ? null : { text, collapsedPastes: 0 };
|
|
8234
|
+
}
|
|
8235
|
+
/** Whether this backend's input box can be read at all (a given screen may still have none). */
|
|
8236
|
+
readsInput() {
|
|
8237
|
+
return !!this.backend?.readInputRow || !!this.backend?.getBottomReadyPattern?.();
|
|
8238
|
+
}
|
|
8239
|
+
/**
|
|
8240
|
+
* Backends whose pane evidence is structured (codex's input/footer pair, a structural box reader): there, our text
|
|
8241
|
+
* missing from the viewport is inconclusive — an echo can arrive late or scroll away — and never a loss to re-paste
|
|
8242
|
+
* over. Only text positively seen in the box (a strand) authorizes another Enter.
|
|
8243
|
+
*/
|
|
8244
|
+
structuredInputEvidence() {
|
|
8245
|
+
return !!this.backend?.isDeliveryInputReadyPane || !!this.backend?.readInputRow;
|
|
8246
|
+
}
|
|
8157
8247
|
/**
|
|
8158
8248
|
* What identifies THIS message on screen. The routing envelope carries a
|
|
8159
8249
|
* message_id that no earlier transcript entry can share, so prefer it: the
|
|
@@ -8220,6 +8310,17 @@ export class Daemon extends EventEmitter {
|
|
|
8220
8310
|
if (!(await this.sendDeliveryEnter(label, guard?.current)))
|
|
8221
8311
|
return false;
|
|
8222
8312
|
let proof = await this.confirmSubmitted(signature, baseline);
|
|
8313
|
+
// A structural box reader sees the paste in the box until the CLI digests the Enter (claude-code repaints a moment
|
|
8314
|
+
// later): give that a bounded window before calling it stranded or lost, as the delivery ladder does.
|
|
8315
|
+
if (this.backend?.readInputRow) {
|
|
8316
|
+
const deadline = Date.now() + POST_ENTER_PROOF_WINDOW_MS;
|
|
8317
|
+
while (proof !== "submitted" && Date.now() < deadline) {
|
|
8318
|
+
await new Promise(r => setTimeout(r, POST_ENTER_PROOF_POLL_MS));
|
|
8319
|
+
if (guard && !guard.current())
|
|
8320
|
+
return false;
|
|
8321
|
+
proof = await this.confirmSubmitted(signature, baseline);
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8223
8324
|
if (proof === "unverifiable") {
|
|
8224
8325
|
if (this.backend?.isDeliveryInputReadyPane) {
|
|
8225
8326
|
// A Codex screen without a structurally current input/footer pair is
|