@songsid/agend 2.2.0-beta.15 → 2.2.0-beta.17

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.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/README.zh-TW.md +1 -0
  3. package/dist/backend/antigravity.d.ts +25 -0
  4. package/dist/backend/antigravity.js +94 -9
  5. package/dist/backend/antigravity.js.map +1 -1
  6. package/dist/cli.js +19 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/compact-watch.d.ts +78 -0
  9. package/dist/compact-watch.js +247 -0
  10. package/dist/compact-watch.js.map +1 -0
  11. package/dist/daemon.js +46 -6
  12. package/dist/daemon.js.map +1 -1
  13. package/dist/fleet-manager.d.ts +3 -0
  14. package/dist/fleet-manager.js +76 -5
  15. package/dist/fleet-manager.js.map +1 -1
  16. package/dist/locale.js +4 -0
  17. package/dist/locale.js.map +1 -1
  18. package/dist/service-installer.d.ts +24 -0
  19. package/dist/service-installer.js +49 -1
  20. package/dist/service-installer.js.map +1 -1
  21. package/dist/topic-commands.d.ts +23 -2
  22. package/dist/topic-commands.js +48 -19
  23. package/dist/topic-commands.js.map +1 -1
  24. package/dist/transcript-jsonl.d.ts +7 -2
  25. package/dist/transcript-jsonl.js +13 -7
  26. package/dist/transcript-jsonl.js.map +1 -1
  27. package/dist/ui/chat-store.js +32 -2
  28. package/dist/ui/chat-strings.js +8 -2
  29. package/dist/ui/chat-thread.js +75 -12
  30. package/dist/ui/panel-chat.js +4 -1
  31. package/dist/ui/shared/app.css +1 -0
  32. package/dist/usage/providers.d.ts +28 -8
  33. package/dist/usage/providers.js +96 -41
  34. package/dist/usage/providers.js.map +1 -1
  35. package/dist/web-api.js +1 -1
  36. package/dist/web-api.js.map +1 -1
  37. package/dist/web-commands.d.ts +14 -1
  38. package/dist/web-commands.js +6 -3
  39. package/dist/web-commands.js.map +1 -1
  40. package/dist/web-upload.d.ts +7 -3
  41. package/dist/web-upload.js +20 -8
  42. package/dist/web-upload.js.map +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ export declare const COMPACT_WATCH_POLL_MS = 2000;
2
+ /** A long session's compaction can take minutes (one of 1M tokens took about two); ten is well past that. */
3
+ export declare const COMPACT_WATCH_TIMEOUT_MS: number;
4
+ export declare const COMPACT_WATCH_KEEP_MS: number;
5
+ export type CompactFormat = "claude" | "codex";
6
+ export type CompactState = "running" | "done" | "timeout" | "lost";
7
+ export interface CompactWatchView {
8
+ id: string;
9
+ state: CompactState;
10
+ }
11
+ /** The transcript's end just before the command was written (the daemon's). */
12
+ export interface CompactCheckpoint {
13
+ path: string;
14
+ ino: number;
15
+ size: number;
16
+ }
17
+ /** One launch of an instance, as a value: a respawn, pause/wake or new fence is another. */
18
+ export declare function launchIdentity(o: {
19
+ bootId: unknown;
20
+ spawnGeneration: unknown;
21
+ launchAttempt: unknown;
22
+ launchFenceEpoch: unknown;
23
+ }): string;
24
+ /** Whether one whole transcript record says the conversation was compacted (its own top-level fields only). */
25
+ export declare function isCompactionRecord(format: CompactFormat, line: string): boolean;
26
+ export interface CompactWatchDeps {
27
+ /** The instance's transcript now, as the Log resolves it; format null: none AgEnD can read. */
28
+ source(name: string): {
29
+ path: string | null;
30
+ format: CompactFormat | null;
31
+ };
32
+ /** The launch that runs the instance now (launchIdentity); null: none. */
33
+ owner(name: string): string | null;
34
+ /** A watch ended (done, lost or timed out): the status frame should go out again. */
35
+ changed(name: string): void;
36
+ now?(): number;
37
+ setInterval?(fn: () => void, ms: number): unknown;
38
+ clearInterval?(handle: unknown): void;
39
+ }
40
+ export declare class CompactWatch {
41
+ private readonly deps;
42
+ private readonly watches;
43
+ constructor(deps: CompactWatchDeps);
44
+ private now;
45
+ private every;
46
+ private clear;
47
+ /**
48
+ * Before a /compact is handed to the daemon: a watch that waits for the daemon to say the command was written.
49
+ * Returns its id (sent along with the paste), or null when this backend's compaction cannot be seen.
50
+ */
51
+ begin(name: string): string | null;
52
+ /**
53
+ * The daemon wrote (or gave up on) the command with this id. Written: it says under which launch, and where the
54
+ * transcript ended just before the write. Both must be this instance's now — the launch running it and the transcript
55
+ * the watch reads — or the watch is lost.
56
+ */
57
+ pasted(name: string, id: string, written: boolean, report?: {
58
+ owner?: unknown;
59
+ checkpoint?: unknown;
60
+ }): void;
61
+ /** The command was not handed over after all: forget its watch (nothing was announced for it). */
62
+ cancel(name: string, id: string): void;
63
+ /** What the status frame says about the instance's last /compact — or nothing. */
64
+ view(name: string): CompactWatchView | null;
65
+ /** The instance went away, or the fleet stops. */
66
+ forget(name: string): void;
67
+ stopAll(): void;
68
+ private drop;
69
+ private finish;
70
+ /**
71
+ * Whether the watch still stands as `base` was set: the same watch and base, running, the same launch, the source
72
+ * still this format with a transcript (else lost, said here) — and still this transcript: one that moved meanwhile
73
+ * stops this look, and the next look starts at the new one's end. Checked after every await and before it ends.
74
+ */
75
+ private standing;
76
+ /** One look at the transcript: the watch's timer calls it (tests call it directly). */
77
+ poll(name: string, id: string): Promise<void>;
78
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Board 003648ac: whether a /compact sent from the web chat has finished. The chat said only "sent", forever.
3
+ *
4
+ * - **Only what the CLI writes.** Claude Code appends a `system` `compact_boundary` record to the session's JSONL and
5
+ * Codex a `compacted` record to its rollout (the same records the Log tab shows as "compacted", log-steps.ts). Only
6
+ * a record's own top-level fields count: a long record is read whole (≤ WHOLE_LINE_MAX_BYTES, also when it spans
7
+ * several reads) and parsed, never judged by text in its head; one that can't be read whole stays unknown. Other
8
+ * backends (kiro included) record nothing AgEnD can attribute to the instance, so their compaction is never called
9
+ * finished: `begin` answers null.
10
+ * - **From just before the command's own write.** The daemon queues a paste behind its lock and a wake; just before
11
+ * it writes this one, it takes the transcript's end (`checkpoint`), and once written it sends that with the launch it
12
+ * wrote under (`raw_paste_written`). So neither an older boundary nor one written while the command waited counts,
13
+ * and a compaction that finishes fast is not missed.
14
+ * - **One transcript, one launch.** The watch is bound to that launch (boot, spawn generation, attempt, fence) and to
15
+ * the transcript's format and path; it is rechecked before every read and after every await, and before it ends.
16
+ * Another launch, another backend or no transcript ends it as lost. Each look reads through one opened file whose
17
+ * identity was checked; another file at the path, or this one cut short, starts again at its end: a boundary may then
18
+ * be missed (the watch times out), never invented.
19
+ * - **Bounded.** One watch per instance (a new /compact replaces it), a look every COMPACT_WATCH_POLL_MS of at most
20
+ * LOOKS_PER_POLL bounded reads (readNewLines), and an end: done, lost, or timed out after COMPACT_WATCH_TIMEOUT_MS
21
+ * from the command. A finished watch stays in view for COMPACT_WATCH_KEEP_MS so a page that was away still learns it.
22
+ */
23
+ import { randomBytes } from "node:crypto";
24
+ import { open, stat } from "node:fs/promises";
25
+ import { LOOKS_PER_POLL, readNewLines, readWholeLine, WHOLE_LINE_MAX_BYTES } from "./transcript-jsonl.js";
26
+ export const COMPACT_WATCH_POLL_MS = 2_000;
27
+ /** A long session's compaction can take minutes (one of 1M tokens took about two); ten is well past that. */
28
+ export const COMPACT_WATCH_TIMEOUT_MS = 10 * 60_000;
29
+ export const COMPACT_WATCH_KEEP_MS = 2 * 60_000;
30
+ /** One launch of an instance, as a value: a respawn, pause/wake or new fence is another. */
31
+ export function launchIdentity(o) {
32
+ return JSON.stringify([o.bootId, o.spawnGeneration, o.launchAttempt, o.launchFenceEpoch]);
33
+ }
34
+ /** Whether one whole transcript record says the conversation was compacted (its own top-level fields only). */
35
+ export function isCompactionRecord(format, line) {
36
+ let e;
37
+ try {
38
+ e = JSON.parse(line);
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ if (!e || typeof e !== "object" || Array.isArray(e))
44
+ return false;
45
+ const r = e;
46
+ return format === "claude" ? r.type === "system" && r.subtype === "compact_boundary" && r.isSidechain !== true : r.type === "compacted";
47
+ }
48
+ /** A long record worth reading whole: its head mentions the record at all (anything else is skipped unread). */
49
+ const mayBeCompaction = (format, head) => head.includes(format === "claude" ? "compact_boundary" : "compacted");
50
+ export class CompactWatch {
51
+ deps;
52
+ watches = new Map();
53
+ constructor(deps) {
54
+ this.deps = deps;
55
+ }
56
+ now() { return this.deps.now ? this.deps.now() : performance.now(); }
57
+ every(fn, ms) { return this.deps.setInterval ? this.deps.setInterval(fn, ms) : setInterval(fn, ms); }
58
+ clear(handle) {
59
+ if (this.deps.clearInterval)
60
+ this.deps.clearInterval(handle);
61
+ else
62
+ clearInterval(handle);
63
+ }
64
+ /**
65
+ * Before a /compact is handed to the daemon: a watch that waits for the daemon to say the command was written.
66
+ * Returns its id (sent along with the paste), or null when this backend's compaction cannot be seen.
67
+ */
68
+ begin(name) {
69
+ const src = this.deps.source(name);
70
+ if (!src.format || !src.path)
71
+ return null;
72
+ this.drop(name);
73
+ const id = randomBytes(8).toString("hex");
74
+ const w = { id, state: "running", deadline: this.now() + COMPACT_WATCH_TIMEOUT_MS, endedAt: null, timer: null, reading: false, base: null };
75
+ w.timer = this.every(() => { void this.poll(name, id); }, COMPACT_WATCH_POLL_MS);
76
+ this.watches.set(name, w);
77
+ return id;
78
+ }
79
+ /**
80
+ * The daemon wrote (or gave up on) the command with this id. Written: it says under which launch, and where the
81
+ * transcript ended just before the write. Both must be this instance's now — the launch running it and the transcript
82
+ * the watch reads — or the watch is lost.
83
+ */
84
+ pasted(name, id, written, report = {}) {
85
+ const w = this.watches.get(name);
86
+ if (!w || w.id !== id || w.state !== "running" || w.base)
87
+ return;
88
+ if (!written) {
89
+ this.finish(name, w, "lost");
90
+ return;
91
+ }
92
+ const owner = this.deps.owner(name);
93
+ const src = this.deps.source(name);
94
+ const cp = report.checkpoint;
95
+ const valid = !!cp && typeof cp === "object" && typeof cp.path === "string" && Number.isSafeInteger(cp.ino) && Number.isSafeInteger(cp.size) && cp.size >= 0;
96
+ if (owner === null || report.owner !== owner || !src.format || !src.path || !valid || cp.path !== src.path) {
97
+ this.finish(name, w, "lost");
98
+ return;
99
+ }
100
+ w.base = { format: src.format, path: src.path, ino: cp.ino, offset: cp.size, skipping: false, owner, pending: null };
101
+ }
102
+ /** The command was not handed over after all: forget its watch (nothing was announced for it). */
103
+ cancel(name, id) {
104
+ if (this.watches.get(name)?.id === id)
105
+ this.drop(name);
106
+ }
107
+ /** What the status frame says about the instance's last /compact — or nothing. */
108
+ view(name) {
109
+ const w = this.watches.get(name);
110
+ if (!w)
111
+ return null;
112
+ if (w.endedAt !== null && this.now() - w.endedAt >= COMPACT_WATCH_KEEP_MS) {
113
+ this.watches.delete(name);
114
+ return null;
115
+ }
116
+ return { id: w.id, state: w.state };
117
+ }
118
+ /** The instance went away, or the fleet stops. */
119
+ forget(name) { this.drop(name); }
120
+ stopAll() { for (const name of [...this.watches.keys()])
121
+ this.drop(name); }
122
+ drop(name) {
123
+ const w = this.watches.get(name);
124
+ if (!w)
125
+ return;
126
+ if (w.timer !== null)
127
+ this.clear(w.timer);
128
+ this.watches.delete(name);
129
+ }
130
+ finish(name, w, state) {
131
+ if (this.watches.get(name) !== w || w.state !== "running")
132
+ return;
133
+ if (w.timer !== null) {
134
+ this.clear(w.timer);
135
+ w.timer = null;
136
+ }
137
+ w.state = state;
138
+ w.endedAt = this.now();
139
+ this.deps.changed(name);
140
+ }
141
+ /**
142
+ * Whether the watch still stands as `base` was set: the same watch and base, running, the same launch, the source
143
+ * still this format with a transcript (else lost, said here) — and still this transcript: one that moved meanwhile
144
+ * stops this look, and the next look starts at the new one's end. Checked after every await and before it ends.
145
+ */
146
+ standing(name, w, base) {
147
+ if (this.watches.get(name) !== w || w.state !== "running" || w.base !== base)
148
+ return false;
149
+ const src = this.deps.source(name);
150
+ if (this.deps.owner(name) !== base.owner || src.format !== base.format || !src.path) {
151
+ this.finish(name, w, "lost");
152
+ return false;
153
+ }
154
+ return src.path === base.path;
155
+ }
156
+ /** One look at the transcript: the watch's timer calls it (tests call it directly). */
157
+ async poll(name, id) {
158
+ const w = this.watches.get(name);
159
+ if (!w || w.id !== id || w.state !== "running" || w.reading)
160
+ return;
161
+ if (this.now() >= w.deadline) {
162
+ this.finish(name, w, "timeout");
163
+ return;
164
+ }
165
+ const base = w.base;
166
+ if (!base)
167
+ return; // the command has not reached the pane yet
168
+ w.reading = true;
169
+ try {
170
+ const src = this.deps.source(name);
171
+ if (this.deps.owner(name) !== base.owner || src.format !== base.format || !src.path) {
172
+ this.finish(name, w, "lost");
173
+ return;
174
+ }
175
+ if (src.path !== base.path) {
176
+ // Another transcript of the same launch: only what is written to it from now on.
177
+ const moved = src.path;
178
+ const { size, ino } = await stat(moved);
179
+ const now = this.deps.source(name);
180
+ if (this.watches.get(name) !== w || w.state !== "running" || w.base !== base)
181
+ return;
182
+ if (this.deps.owner(name) !== base.owner || now.format !== base.format || !now.path) {
183
+ this.finish(name, w, "lost");
184
+ return;
185
+ }
186
+ if (now.path === moved)
187
+ Object.assign(base, { path: moved, ino, offset: size, skipping: false, pending: null });
188
+ return;
189
+ }
190
+ const fh = await open(base.path, "r");
191
+ try {
192
+ const st = await fh.stat();
193
+ if (!this.standing(name, w, base))
194
+ return;
195
+ if (st.ino !== base.ino || st.size < base.offset) {
196
+ Object.assign(base, { ino: st.ino, offset: st.size, skipping: false, pending: null });
197
+ return;
198
+ }
199
+ // Every read below goes through this handle: the file whose identity was just checked, whatever the path holds now.
200
+ const whole = async (offset, bytes) => (await readWholeLine(base.path, offset, bytes, WHOLE_LINE_MAX_BYTES, fh).catch(() => null)) ?? "";
201
+ for (let look = 0; look < LOOKS_PER_POLL; look++) {
202
+ const read = await readNewLines(base.path, base.offset, { skipping: base.skipping, handle: fh });
203
+ if (!this.standing(name, w, base))
204
+ return;
205
+ let found = read.lines.some(l => isCompactionRecord(base.format, l));
206
+ // A long record that began in an earlier read ends in this one: read it whole now.
207
+ if (base.pending !== null && read.longEnd !== undefined) {
208
+ if (!found && isCompactionRecord(base.format, await whole(base.pending, read.longEnd - 1 - base.pending)))
209
+ found = true;
210
+ base.pending = null;
211
+ }
212
+ for (const l of read.long) {
213
+ if (found || !mayBeCompaction(base.format, l.head))
214
+ continue;
215
+ if (!l.complete) {
216
+ base.pending = l.offset;
217
+ continue;
218
+ }
219
+ if (isCompactionRecord(base.format, await whole(l.offset, l.bytes)))
220
+ found = true;
221
+ }
222
+ if (!this.standing(name, w, base))
223
+ return;
224
+ const moved = read.newOffset !== base.offset;
225
+ base.offset = read.newOffset;
226
+ base.skipping = read.skipping;
227
+ if (found) {
228
+ this.finish(name, w, "done");
229
+ return;
230
+ }
231
+ if (!moved || !read.more)
232
+ break;
233
+ }
234
+ }
235
+ finally {
236
+ await fh.close();
237
+ }
238
+ }
239
+ catch {
240
+ // A look that fails is tried again on the next tick; the deadline still ends the watch.
241
+ }
242
+ finally {
243
+ w.reading = false;
244
+ }
245
+ }
246
+ }
247
+ //# sourceMappingURL=compact-watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-watch.js","sourceRoot":"","sources":["../src/compact-watch.ts"],"names":[],"mappings":"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"}
package/dist/daemon.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AGENT_WHATS_NEW, agendVersion, planWhatsNew, readWhatsNewRecord, recordWhatsNew, whatsNewNotice } from "./agent-whats-new.js";
2
2
  import { join, dirname, basename, resolve } from "node:path";
3
3
  import { mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync, unlinkSync, rmSync, appendFileSync, statSync, renameSync } from "node:fs";
4
+ import { launchIdentity } from "./compact-watch.js";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { ensureInstanceDir } from "./private-dir.js";
6
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
@@ -102,7 +103,7 @@ export function buildInstructionReloadNotice(binaryName, instanceName, instanceD
102
103
  : binaryName === "kiro-cli"
103
104
  ? `.kiro/steering/agend-${instanceName}.md`
104
105
  : binaryName === "agy"
105
- ? ".agents/agents.md"
106
+ ? ".agents/AGENTS.md"
106
107
  : join(instanceDir, "fleet-instructions.md");
107
108
  return `[system] Your AgEnD instructions have been updated. Reload only ${source}; do not scan other instruction directories. Do not reply to this message.`;
108
109
  }
@@ -3782,21 +3783,51 @@ export class Daemon extends EventEmitter {
3782
3783
  isDeliveryEpochCurrent(epoch) {
3783
3784
  return epoch === this.deliveryEpoch;
3784
3785
  }
3785
- queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch, confirmClear = false, durableAttempt, ownSwitch) {
3786
+ queueRawPaste(rawText, deliveryEpoch = this.deliveryEpoch, confirmClear = false, durableAttempt, ownSwitch, notifyWritten) {
3786
3787
  const reportSwitch = (result) => {
3787
3788
  if (!ownSwitch)
3788
3789
  return;
3789
3790
  this.ipcServer?.broadcast({ type: "runtime_switch_result", id: ownSwitch.id, ...result });
3790
3791
  };
3792
+ // Board 003648ac: the fleet follows a web /compact from just before its paste is written: the transcript's end then
3793
+ // (taken here, under this launch, before the write) and the launch it was written under ride with the notice — once.
3794
+ // The checkpoint and the launch are taken together, right before the paste (each attempt takes them again); the
3795
+ // notice carries those — never the launch at notice time, which a respawn in between would relabel.
3796
+ let told = false;
3797
+ let checkpoint = null;
3798
+ let owner = null;
3799
+ const tellWritten = (written) => {
3800
+ if (!notifyWritten || told)
3801
+ return;
3802
+ told = true;
3803
+ this.ipcServer?.broadcast({ type: "raw_paste_written", id: notifyWritten, outcome: written ? "written" : "not_written",
3804
+ ...(written ? { owner, checkpoint } : {}) });
3805
+ };
3806
+ const takeCheckpoint = () => {
3807
+ owner = launchIdentity(this.interactionOwner());
3808
+ const path = this.logTranscriptPath();
3809
+ checkpoint = null;
3810
+ if (!path)
3811
+ return;
3812
+ try {
3813
+ const st = statSync(path);
3814
+ checkpoint = { path, ino: st.ino, size: st.size };
3815
+ }
3816
+ catch {
3817
+ checkpoint = null;
3818
+ }
3819
+ };
3791
3820
  if (!this.tmux || !this.isDeliveryEpochCurrent(deliveryEpoch)) {
3792
3821
  if (durableAttempt)
3793
3822
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste target unavailable before pane queue");
3794
3823
  reportSwitch({ outcome: "not_sent" });
3824
+ tellWritten(false);
3795
3825
  return;
3796
3826
  }
3797
3827
  this.pasteLock = this.pasteLock.then(async () => {
3798
3828
  if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
3799
3829
  reportSwitch({ outcome: "not_sent" });
3830
+ tellWritten(false);
3800
3831
  this.logger.info("Pending raw delivery dropped by user cancel");
3801
3832
  if (durableAttempt)
3802
3833
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste epoch changed while queued");
@@ -3809,6 +3840,7 @@ export class Daemon extends EventEmitter {
3809
3840
  if (durableAttempt)
3810
3841
  this.retryDurableDeliveryBeforeBegin(durableAttempt, "raw_paste epoch changed while waking");
3811
3842
  reportSwitch({ outcome: "not_sent" }); // #1690: cancelled before anything was typed — said at once, nothing saved
3843
+ tellWritten(false);
3812
3844
  return;
3813
3845
  }
3814
3846
  const delivered = await this.deliverMessage(rawText, undefined, {
@@ -3818,8 +3850,11 @@ export class Daemon extends EventEmitter {
3818
3850
  preserveExactBytes: true,
3819
3851
  // #1690: our own /model holds every other delivery from its write on — before the lock is released, so a steer
3820
3852
  // or /btw waiting on it cannot type into the gap before claude paints "Switch model?" (default cursor: Yes).
3821
- ...(ownSwitch ? { onPaneWritten: () => { this.ownSwitch = ownSwitch; } } : {}),
3853
+ ...(ownSwitch || notifyWritten ? { onPaneWritten: () => { if (ownSwitch)
3854
+ this.ownSwitch = ownSwitch; tellWritten(true); } } : {}),
3855
+ ...(notifyWritten ? { beforePaneWrite: takeCheckpoint } : {}),
3822
3856
  });
3857
+ tellWritten(false); // never written (once told, this says nothing)
3823
3858
  if (delivered)
3824
3859
  this.finishDurableSubmission(durableAttempt ?? null, verdict);
3825
3860
  else if (durableAttempt && verdict.paneWriteStarted) {
@@ -3856,6 +3891,7 @@ export class Daemon extends EventEmitter {
3856
3891
  this.ownSwitch = null;
3857
3892
  reportSwitch({ outcome: "not_confirmed" });
3858
3893
  }
3894
+ tellWritten(false);
3859
3895
  return;
3860
3896
  }
3861
3897
  if (confirmClear)
@@ -3897,7 +3933,8 @@ export class Daemon extends EventEmitter {
3897
3933
  const sw = msg.runtime_switch;
3898
3934
  const ownSwitch = sw && typeof sw.id === "string" && sw.kind === "model" && typeof sw.requested === "string"
3899
3935
  ? { id: sw.id, kind: "model", requested: sw.requested } : undefined;
3900
- this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch), msg.confirm_clear === true, durableAttempt ? { ...durableAttempt, submissionMode: "raw_paste", silentSchedule: msg.silent_schedule === true } : undefined, ownSwitch);
3936
+ const notifyWritten = typeof msg.notify_written === "string" && /^[0-9a-f]{16}$/.test(msg.notify_written) ? msg.notify_written : undefined;
3937
+ this.queueRawPaste(msg.content, this.captureDeliveryEpoch(msg.delivery_epoch), msg.confirm_clear === true, durableAttempt ? { ...durableAttempt, submissionMode: "raw_paste", silentSchedule: msg.silent_schedule === true } : undefined, ownSwitch, notifyWritten);
3901
3938
  }
3902
3939
  /**
3903
3940
  * #1690 (the user's choice A): after AgEnD's own `/model <name>`, claude may ask "Switch model?" (a warm cache for
@@ -6969,7 +7006,7 @@ export class Daemon extends EventEmitter {
6969
7006
  return stale();
6970
7007
  if (residue !== "clear")
6971
7008
  return this.failDelivery(verdict, status, "input-residue", residue);
6972
- const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent, opts?.humanVerificationId);
7009
+ const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent, opts?.humanVerificationId, opts?.beforePaneWrite);
6973
7010
  // #1690: still under the lock — whatever waits on it next must already see what this write started.
6974
7011
  if (verdict.paneWriteStarted)
6975
7012
  opts?.onPaneWritten?.();
@@ -8485,7 +8522,9 @@ export class Daemon extends EventEmitter {
8485
8522
  verdict = { reached: false }, durableAttempt, steer = false,
8486
8523
  // #829: the caller's fence, asked once more after the last await before the
8487
8524
  // first side effect. Stale means unwritten: false, with no failure verdict.
8488
- stillCurrent, humanVerificationId) {
8525
+ stillCurrent, humanVerificationId,
8526
+ // Board 003648ac: called synchronously right before each paste — after the last await and fence of that attempt.
8527
+ beforePaste) {
8489
8528
  const signature = this.submissionSignature(formatted, humanVerificationId ?? submissionId, humanVerificationId !== undefined);
8490
8529
  const rawPaste = durableAttempt?.submissionMode === "raw_paste";
8491
8530
  const humanAttempt = verdict.humanReceipt;
@@ -8614,6 +8653,7 @@ export class Daemon extends EventEmitter {
8614
8653
  // same pane; the write-ahead Enter marker later separates safe pre-Enter
8615
8654
  // recovery from a possibly executed command.
8616
8655
  const pasteGeneration = this.spawnGeneration;
8656
+ beforePaste?.();
8617
8657
  const pasted = await this.tmux.pasteBuffer(formatted);
8618
8658
  if (pasted)
8619
8659
  this.rememberPaste(formatted, pasteGeneration);