agent-dag 1.35.13 → 1.35.15
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/web/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
|
|
41
41
|
})();
|
|
42
42
|
</script>
|
|
43
|
-
<script type="module" crossorigin src="/assets/index-
|
|
43
|
+
<script type="module" crossorigin src="/assets/index-CdgG2b5Q.js"></script>
|
|
44
44
|
<link rel="stylesheet" crossorigin href="/assets/index-hLBidJXz.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.35.
|
|
3
|
+
"version": "1.35.15",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/server/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { dirname } from "node:path";
|
|
|
10
10
|
import { createInterface } from "node:readline";
|
|
11
11
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { claudeConfigDir } from "./claude-dir.mjs";
|
|
13
|
-
import { CODEX_SESSIONS_DIR, STOP, walkRolloutDays } from "./codex-dir.mjs";
|
|
13
|
+
import { CODEX_HOME, CODEX_SESSIONS_DIR, STOP, walkRolloutDays } from "./codex-dir.mjs";
|
|
14
14
|
import { PRODUCT } from "./brand.mjs";
|
|
15
15
|
import { invokedName, renameNotice } from "./invoked-as.mjs";
|
|
16
16
|
import { codexCwdInWorkspace, writesCodexLog } from "./log-writer.mjs";
|
|
@@ -550,38 +550,79 @@ export function ccProjectSlug(cwd) {
|
|
|
550
550
|
return `${slug.slice(0, CC_SLUG_MAX)}-${Math.abs(ccPathHash(abs)).toString(36)}`;
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
-
|
|
554
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Candidate memory-file paths on the walk from `cwd` up to the filesystem root.
|
|
555
|
+
*
|
|
556
|
+
* Both CLIs load their memory file the same way — nearest-first from the
|
|
557
|
+
* working directory outwards — and differ only in what the file is CALLED and
|
|
558
|
+
* in what else they add on top, so the walk is written once here and the two
|
|
559
|
+
* scanners below supply their own names. `rels` is a list of paths RELATIVE to
|
|
560
|
+
* each directory on the walk rather than bare filenames, because CC also honours
|
|
561
|
+
* `.claude/CLAUDE.md` at every level and Codex does not.
|
|
562
|
+
*
|
|
563
|
+
* Sixteen levels is the same depth this has always used: deep enough for any
|
|
564
|
+
* real checkout, shallow enough that a cwd on a network mount cannot turn one
|
|
565
|
+
* context read into an unbounded number of stat() calls.
|
|
566
|
+
*
|
|
567
|
+
* Returns paths without touching the disk. Statting them is collectMemoryFiles'
|
|
568
|
+
* job, so a caller that wants to add its own paths — a user-global file, a
|
|
569
|
+
* per-project memory directory — can splice them into one ordered list and get
|
|
570
|
+
* a single de-duplicated, existence-checked answer back.
|
|
571
|
+
*/
|
|
572
|
+
function memoryWalkPaths(cwd, rels) {
|
|
573
|
+
const out = [];
|
|
574
|
+
let dir = resolve(cwd);
|
|
575
|
+
for (let depth = 0; depth < 16; depth++) {
|
|
576
|
+
for (const rel of rels) out.push(join(dir, rel));
|
|
577
|
+
const parent = pdirname(dir);
|
|
578
|
+
if (parent === dir) break;
|
|
579
|
+
dir = parent;
|
|
580
|
+
}
|
|
581
|
+
return out;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Which of `paths` are real, non-empty files, in the order given and with
|
|
586
|
+
* duplicates dropped.
|
|
587
|
+
*
|
|
588
|
+
* A zero-byte file is skipped on purpose: it contributes nothing to the model's
|
|
589
|
+
* context, and listing it in the modal would have the reader looking for the
|
|
590
|
+
* bytes it claims to cost. A path that cannot be stat()ed is simply absent —
|
|
591
|
+
* this runs against a tree another process is editing, and a permissions error
|
|
592
|
+
* on one candidate is no reason to lose the other fifteen.
|
|
593
|
+
*/
|
|
594
|
+
async function collectMemoryFiles(paths) {
|
|
555
595
|
const found = [];
|
|
556
596
|
const seen = new Set();
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
if (seen.has(p)) return;
|
|
597
|
+
for (const p of paths) {
|
|
598
|
+
if (seen.has(p)) continue;
|
|
560
599
|
seen.add(p);
|
|
561
600
|
try {
|
|
562
601
|
const s = await stat(p);
|
|
563
602
|
if (s.isFile() && s.size > 0) found.push({ path: p, bytes: s.size });
|
|
564
603
|
} catch {}
|
|
565
|
-
};
|
|
566
|
-
// Walk up from cwd to filesystem root. At each dir, check for the
|
|
567
|
-
// canonical CC memory filenames plus CLAUDE.local.md (user-private).
|
|
568
|
-
let dir = resolve(cwd);
|
|
569
|
-
for (let depth = 0; depth < 16; depth++) {
|
|
570
|
-
for (const rel of [
|
|
571
|
-
"CLAUDE.md",
|
|
572
|
-
"CLAUDE.local.md",
|
|
573
|
-
join(".claude", "CLAUDE.md"),
|
|
574
|
-
join(".claude", "CLAUDE.local.md"),
|
|
575
|
-
]) {
|
|
576
|
-
await push(join(dir, rel));
|
|
577
|
-
}
|
|
578
|
-
const parent = pdirname(dir);
|
|
579
|
-
if (parent === dir) break;
|
|
580
|
-
dir = parent;
|
|
581
604
|
}
|
|
605
|
+
return found;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** The memory files a CLAUDE session has in scope. Exported alongside its Codex
|
|
609
|
+
* counterpart below so a test can check the pair together — the two must agree
|
|
610
|
+
* on the walk and disagree on the filename, and only one of them showing up in
|
|
611
|
+
* a test is how they would drift. */
|
|
612
|
+
export async function scanClaudeMdFiles(cwd) {
|
|
613
|
+
if (!cwd || typeof cwd !== "string") return [];
|
|
614
|
+
const home = homedir();
|
|
615
|
+
// Walk up from cwd to filesystem root, checking the canonical CC memory
|
|
616
|
+
// filenames plus CLAUDE.local.md (user-private) at each level.
|
|
617
|
+
const paths = memoryWalkPaths(cwd, [
|
|
618
|
+
"CLAUDE.md",
|
|
619
|
+
"CLAUDE.local.md",
|
|
620
|
+
join(".claude", "CLAUDE.md"),
|
|
621
|
+
join(".claude", "CLAUDE.local.md"),
|
|
622
|
+
]);
|
|
582
623
|
// User-global memory.
|
|
583
|
-
|
|
584
|
-
|
|
624
|
+
paths.push(join(home, ".claude", "CLAUDE.md"));
|
|
625
|
+
paths.push(join(home, ".claude", "CLAUDE.local.md"));
|
|
585
626
|
// Per-project auto-memory: ~/.claude/projects/<slug>/memory/*.md
|
|
586
627
|
// (plus MEMORY.md index). CC injects these into context for sessions
|
|
587
628
|
// whose cwd matches the slug.
|
|
@@ -591,11 +632,54 @@ async function scanClaudeMdFiles(cwd) {
|
|
|
591
632
|
try {
|
|
592
633
|
const entries = await readdir(memDir);
|
|
593
634
|
for (const f of entries) {
|
|
594
|
-
if (f.toLowerCase().endsWith(".md"))
|
|
635
|
+
if (f.toLowerCase().endsWith(".md")) paths.push(join(memDir, f));
|
|
595
636
|
}
|
|
596
637
|
} catch {}
|
|
597
638
|
}
|
|
598
|
-
return
|
|
639
|
+
return collectMemoryFiles(paths);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* The memory files a CODEX session has in scope: AGENTS.md, not CLAUDE.md.
|
|
644
|
+
*
|
|
645
|
+
* WHY THIS FUNCTION EXISTS AT ALL (#399). The context modal's third section was
|
|
646
|
+
* fed by scanClaudeMdFiles for every session regardless of provider, so the
|
|
647
|
+
* moment the donut became reachable for Codex the modal would have told a Codex
|
|
648
|
+
* user "No CLAUDE.md files found on the path from cwd to ~/.claude" — naming a
|
|
649
|
+
* file and a directory Codex does not read. Before this, `AGENTS.md` did not
|
|
650
|
+
* appear anywhere in this repository.
|
|
651
|
+
*
|
|
652
|
+
* That Codex reads it is not an assumption. Sampled every rollout under this
|
|
653
|
+
* machine's CODEX_HOME (structural search, no record content printed): the
|
|
654
|
+
* literal string `AGENTS.md` appears on 9 lines across 5 of the 8 files — in the
|
|
655
|
+
* `response_item/message` role=user preamble Codex prepends to a turn, in a
|
|
656
|
+
* role=developer message, and in a `world_state` record — while `CLAUDE.md`
|
|
657
|
+
* appears on zero lines in any of them.
|
|
658
|
+
*
|
|
659
|
+
* WHY THE FILESYSTEM AND NOT THE ROLLOUT. The rollout does name the files, but
|
|
660
|
+
* only inside message TEXT, and reading the text of a user's conversation to
|
|
661
|
+
* find a filename is not a trade this deck makes anywhere else — the Claude side
|
|
662
|
+
* has always answered the same question by walking the filesystem, and the two
|
|
663
|
+
* halves of one modal section should be derived the same way or the reader
|
|
664
|
+
* cannot compare them.
|
|
665
|
+
*
|
|
666
|
+
* CODEX_HOME comes from codex-dir.mjs like every other Codex path in the
|
|
667
|
+
* process (#375), so a relocated Codex home is honoured here without this
|
|
668
|
+
* module growing a sixth spelling of the rule.
|
|
669
|
+
*
|
|
670
|
+
* Exported for the tests, like readContextFromTranscript beside it: the rule for
|
|
671
|
+
* which files a session has in scope is worth pinning directly, rather than
|
|
672
|
+
* through a watcher, a temp home and a 1.5s poll.
|
|
673
|
+
*/
|
|
674
|
+
export async function scanAgentsMdFiles(cwd) {
|
|
675
|
+
if (!cwd || typeof cwd !== "string") return [];
|
|
676
|
+
// Codex has no `.codex/AGENTS.md` per-directory convention to mirror CC's
|
|
677
|
+
// `.claude/CLAUDE.md`, so the per-level list is the single filename.
|
|
678
|
+
const paths = memoryWalkPaths(cwd, ["AGENTS.md"]);
|
|
679
|
+
// The user-global instructions file, which Codex loads for every session
|
|
680
|
+
// whatever the cwd — the counterpart of ~/.claude/CLAUDE.md.
|
|
681
|
+
paths.push(join(CODEX_HOME, "AGENTS.md"));
|
|
682
|
+
return collectMemoryFiles(paths);
|
|
599
683
|
}
|
|
600
684
|
|
|
601
685
|
function maybeResolveContext(payload) {
|
|
@@ -611,14 +695,14 @@ function maybeResolveContext(payload) {
|
|
|
611
695
|
lastContextReadAt.set(sid, now);
|
|
612
696
|
pendingContextReads.add(sid);
|
|
613
697
|
Promise.all([readContextFromTranscript(tp), scanClaudeMdFiles(cwd)])
|
|
614
|
-
.then(([breakdown,
|
|
615
|
-
if (!breakdown && (!
|
|
698
|
+
.then(([breakdown, memoryFiles]) => {
|
|
699
|
+
if (!breakdown && (!memoryFiles || memoryFiles.length === 0)) return;
|
|
616
700
|
pushEvent({
|
|
617
701
|
hook_event_name: "ContextObserved",
|
|
618
702
|
session_id: sid,
|
|
619
703
|
context: {
|
|
620
704
|
...(breakdown ?? {}),
|
|
621
|
-
|
|
705
|
+
memoryFiles: memoryFiles ?? [],
|
|
622
706
|
},
|
|
623
707
|
}, "internal");
|
|
624
708
|
})
|
|
@@ -626,6 +710,61 @@ function maybeResolveContext(payload) {
|
|
|
626
710
|
.finally(() => pendingContextReads.delete(sid));
|
|
627
711
|
}
|
|
628
712
|
|
|
713
|
+
// Throttle state for the Codex half of the same question. Separate maps rather
|
|
714
|
+
// than sharing maybeResolveContext's, because the two run on different triggers
|
|
715
|
+
// — a hook payload there, a batch of appended rollout lines here — and one
|
|
716
|
+
// session cannot be both.
|
|
717
|
+
const lastCodexMemoryReadAt = new Map();
|
|
718
|
+
const pendingCodexMemoryReads = new Set();
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Emit the memory files a Codex session has in scope, throttled per session.
|
|
722
|
+
*
|
|
723
|
+
* WHY THIS IS NOT maybeResolveContext (#399). That function early-returns
|
|
724
|
+
* without `payload.transcript_path`, a field only Claude Code sends, and it
|
|
725
|
+
* would scan for CLAUDE.md if it got that far. It is also unreachable from here
|
|
726
|
+
* on a second count: pushEvent gates all of its enrichment on `source ===
|
|
727
|
+
* "hook"`, and the Codex rollout watcher emits with source "codex" because it is
|
|
728
|
+
* not a hook stream at all. So this is called from the watcher's own scan loop,
|
|
729
|
+
* next to the lazy root, rather than off the back of an event.
|
|
730
|
+
*
|
|
731
|
+
* There is no structural breakdown alongside the file list, and that is the
|
|
732
|
+
* honest answer rather than a gap: the counts the Claude side reports —
|
|
733
|
+
* user/assistant messages, tool uses, tool results, system-reminders — come from
|
|
734
|
+
* a regex scan of a transcript this deck has read from byte zero, and the
|
|
735
|
+
* watcher deliberately SKIPS a pre-existing session's history at startup
|
|
736
|
+
* (`state.offset = st.size`). Counting from the moment the deck attached would
|
|
737
|
+
* produce five confident numbers that are all short by however much of the
|
|
738
|
+
* session happened first, on a panel whose whole purpose is to say what is in
|
|
739
|
+
* the window. ContextModal says so in the slot those counts would have used;
|
|
740
|
+
* see codex-approval.ts for the same move on a different unanswerable question.
|
|
741
|
+
*/
|
|
742
|
+
function maybeResolveCodexMemory(sid, cwd) {
|
|
743
|
+
if (!sid || !cwd) return;
|
|
744
|
+
if (pendingCodexMemoryReads.has(sid)) return;
|
|
745
|
+
const now = Date.now();
|
|
746
|
+
const last = lastCodexMemoryReadAt.get(sid) ?? 0;
|
|
747
|
+
if (now - last < CONTEXT_READ_THROTTLE_MS) return;
|
|
748
|
+
lastCodexMemoryReadAt.set(sid, now);
|
|
749
|
+
pendingCodexMemoryReads.add(sid);
|
|
750
|
+
scanAgentsMdFiles(cwd)
|
|
751
|
+
.then(memoryFiles => {
|
|
752
|
+
// Nothing found is not a fact worth an event: the reducer merges a
|
|
753
|
+
// ContextObserved into whatever the session already had, and an empty list
|
|
754
|
+
// would only ever overwrite a real one with nothing. A repo that grows its
|
|
755
|
+
// first AGENTS.md mid-session is picked up by the next throttled pass.
|
|
756
|
+
if (!memoryFiles.length) return;
|
|
757
|
+
pushEvent({
|
|
758
|
+
hook_event_name: "ContextObserved",
|
|
759
|
+
session_id: sid,
|
|
760
|
+
provider: "codex",
|
|
761
|
+
context: { memoryFiles },
|
|
762
|
+
}, "internal");
|
|
763
|
+
})
|
|
764
|
+
.catch(() => {})
|
|
765
|
+
.finally(() => pendingCodexMemoryReads.delete(sid));
|
|
766
|
+
}
|
|
767
|
+
|
|
629
768
|
// ─── Codex transcript enrichment ──────────────────────────────────────────
|
|
630
769
|
// Codex CLI hook payloads carry `session_id` but no transcript path. Sessions
|
|
631
770
|
// are persisted to ~/.codex/sessions/YYYY/MM/DD/rollout-<sid>.jsonl with one
|
|
@@ -777,7 +916,9 @@ function maybeResolveCodex(payload) {
|
|
|
777
916
|
// response_item/function_call_output → PostToolUse / PostToolUseFailure,
|
|
778
917
|
// decided by the outcome line Codex
|
|
779
918
|
// prepends to the output (codexCallFailed)
|
|
780
|
-
// event_msg/token_count → UsageObserved
|
|
919
|
+
// event_msg/token_count → UsageObserved, and riding on it the
|
|
920
|
+
// session's live context occupancy and
|
|
921
|
+
// the CLI's own window (#399)
|
|
781
922
|
// event_msg/task_started (+window) → ModelObserved (context window)
|
|
782
923
|
// event_msg/task_complete → Stop (the turn finished)
|
|
783
924
|
// event_msg/turn_aborted → Stop (the turn was interrupted)
|
|
@@ -970,8 +1111,68 @@ export function codexObjToPayload(obj, sid, cwd) {
|
|
|
970
1111
|
if (pl.type === "item_completed" && pl.item && pl.item.type === "UserMessage") {
|
|
971
1112
|
return { ...base, hook_event_name: "UserPromptSubmit", prompt: codexItemText(pl.item), model };
|
|
972
1113
|
}
|
|
973
|
-
|
|
974
|
-
|
|
1114
|
+
// Codex states THREE numbers on every `token_count` and this branch used to
|
|
1115
|
+
// take one of them (#399). The other two are what the context donut is drawn
|
|
1116
|
+
// from, so the deck rendered no context readout at all for the only provider
|
|
1117
|
+
// that reports its window exactly.
|
|
1118
|
+
//
|
|
1119
|
+
// Measured across every rollout under this machine's CODEX_HOME — 178
|
|
1120
|
+
// `token_count` records, 164 on Codex 0.144.5 and 14 on 0.147.0 — all 178
|
|
1121
|
+
// carry `info.last_token_usage`, `info.total_token_usage` and
|
|
1122
|
+
// `info.model_context_window`. Nothing here is a new read: the line is
|
|
1123
|
+
// already parsed and the object already destructured.
|
|
1124
|
+
//
|
|
1125
|
+
// total_token_usage cumulative SPEND for the session. Every request's
|
|
1126
|
+
// prompt summed, so it counts the cached prefix again
|
|
1127
|
+
// on every turn and passes the context window many
|
|
1128
|
+
// times over inside one session (5,238,700 against a
|
|
1129
|
+
// 258,400 window in the longest file here). Correct
|
|
1130
|
+
// for cost, meaningless as an occupancy figure.
|
|
1131
|
+
//
|
|
1132
|
+
// last_token_usage the MOST RECENT request: `input_tokens` is the whole
|
|
1133
|
+
// conversation Codex sent (it already contains the
|
|
1134
|
+
// cached prefix — see billedInputTokens), plus that
|
|
1135
|
+
// request's completion. `total_tokens` is exactly
|
|
1136
|
+
// input + output on 177 of the 178 records.
|
|
1137
|
+
//
|
|
1138
|
+
// model_context_window the CLI's own ceiling, 258,400 for gpt-5.6 against
|
|
1139
|
+
// the 1,050,000 the static table guesses.
|
|
1140
|
+
//
|
|
1141
|
+
// WHY `last_token_usage.total_tokens` AND NOT `input_tokens`. The prompt-only
|
|
1142
|
+
// figure is the closer analogue of the Claude side, which sums the last usage
|
|
1143
|
+
// block's input + cache_read + cache_creation and leaves the completion out.
|
|
1144
|
+
// The difference is one response — 20 to 2,288 tokens in this sample, under
|
|
1145
|
+
// 1% of the window — and `total_tokens` wins on the case where they diverge
|
|
1146
|
+
// for real: on `thread_rolled_back` (the user rewinding the conversation)
|
|
1147
|
+
// Codex writes a `token_count` whose per-request components are all zero and
|
|
1148
|
+
// whose `last_token_usage.total_tokens` is the RECOMPUTED context size —
|
|
1149
|
+
// 47,355, down from 58,516 — while `total_token_usage` does not move at all,
|
|
1150
|
+
// because no request was made. Codex is using that field as "tokens in the
|
|
1151
|
+
// window", and reading `input_tokens` there would collapse the donut to 0%
|
|
1152
|
+
// at precisely the moment the number changed most.
|
|
1153
|
+
//
|
|
1154
|
+
// The window rides along too. `task_started` below is the only other carrier
|
|
1155
|
+
// and it fires once per turn, so a deck that attached mid-turn — the ordinary
|
|
1156
|
+
// case, since the watcher skips a pre-existing session's history at startup —
|
|
1157
|
+
// had to wait for the next turn before the donut could be scaled against
|
|
1158
|
+
// anything but the wrong static default.
|
|
1159
|
+
if (pl.type === "token_count" && pl.info) {
|
|
1160
|
+
const info = pl.info;
|
|
1161
|
+
const last = info.last_token_usage;
|
|
1162
|
+
const contextTokens = last && typeof last.total_tokens === "number" ? last.total_tokens : undefined;
|
|
1163
|
+
const window = typeof info.model_context_window === "number" ? info.model_context_window : undefined;
|
|
1164
|
+
// A record that states none of the three says nothing, and emitting an
|
|
1165
|
+
// event for it would put an empty envelope in the ring buffer and in the
|
|
1166
|
+
// persisted log for every reader to skip forever.
|
|
1167
|
+
if (!info.total_token_usage && contextTokens === undefined && window === undefined) return null;
|
|
1168
|
+
return {
|
|
1169
|
+
...base,
|
|
1170
|
+
hook_event_name: "UsageObserved",
|
|
1171
|
+
usage: info.total_token_usage,
|
|
1172
|
+
model,
|
|
1173
|
+
model_context_window: window,
|
|
1174
|
+
context_tokens: contextTokens,
|
|
1175
|
+
};
|
|
975
1176
|
}
|
|
976
1177
|
if (pl.type === "task_started" && typeof pl.model_context_window === "number") {
|
|
977
1178
|
return { ...base, hook_event_name: "ModelObserved", model, model_context_window: pl.model_context_window };
|
|
@@ -1244,6 +1445,14 @@ async function codexScanOnce(firstRun) {
|
|
|
1244
1445
|
emitCodexEvent(payload, persist);
|
|
1245
1446
|
}
|
|
1246
1447
|
}
|
|
1448
|
+
|
|
1449
|
+
// Once per batch of appended lines rather than once per line — the scan
|
|
1450
|
+
// throttles itself per session, but the cheapest call is the one that is
|
|
1451
|
+
// never made, and a batch can be hundreds of lines. Gated on the root
|
|
1452
|
+
// existing so a session the deck has decided not to draw does not cost a
|
|
1453
|
+
// directory walk, and repeated rather than done once at root creation so
|
|
1454
|
+
// an AGENTS.md written after the session started is still found (#399).
|
|
1455
|
+
if (state.rootEmitted) maybeResolveCodexMemory(state.sid, state.cwd);
|
|
1247
1456
|
}
|
|
1248
1457
|
|
|
1249
1458
|
// Rollout files fall out of the newest-2-days listing and never come back,
|
|
@@ -1308,6 +1517,7 @@ function forgetSession(sid) {
|
|
|
1308
1517
|
lastContextReadAt.delete(sid);
|
|
1309
1518
|
codexRolloutPathBySid.delete(sid);
|
|
1310
1519
|
lastCodexUsageReadAt.delete(sid);
|
|
1520
|
+
lastCodexMemoryReadAt.delete(sid);
|
|
1311
1521
|
codexSessionModel.delete(sid);
|
|
1312
1522
|
codexSessionApproval.delete(sid);
|
|
1313
1523
|
}
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
// Only ever touches its own entry, tagged `__agent-dag-sound`. Hooks the user
|
|
11
11
|
// wrote themselves are left exactly as found — including the platform-specific
|
|
12
12
|
// ones this replaces, which are reported rather than deleted.
|
|
13
|
+
//
|
|
14
|
+
// Claude Code only, and deliberately: everything here is one entry in Claude
|
|
15
|
+
// Code's settings.json, which Claude Code alone reads and executes. There is no
|
|
16
|
+
// Codex equivalent — the deck installs no Codex hooks and tails the rollout
|
|
17
|
+
// files instead — so a Codex turn ends in silence and no amount of writing to
|
|
18
|
+
// this file changes that. The browser is where that is said rather than
|
|
19
|
+
// guessed: the topbar button is drawn only where Claude Code is, and its
|
|
20
|
+
// tooltip names the limit and the mechanism behind it. If this module ever does
|
|
21
|
+
// learn a second provider, src/web/provider-copy.ts's finishSoundTitle is the
|
|
22
|
+
// sentence that has to move with it, and finish-sound-scope.test.ts fails until
|
|
23
|
+
// it does (#394).
|
|
13
24
|
import { readFile, mkdir } from "node:fs/promises";
|
|
14
25
|
import { existsSync } from "node:fs";
|
|
15
26
|
import { join, dirname } from "node:path";
|