agent-dag 1.4.0 → 1.4.1
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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agent-dag</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DXdvsUWV.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-DnQUMgzS.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
package/src/server/index.mjs
CHANGED
|
@@ -33,7 +33,73 @@ const sseClients = new Set(); // res handles
|
|
|
33
33
|
|
|
34
34
|
let persistPath = null; // absolute path to events.jsonl, or null
|
|
35
35
|
|
|
36
|
+
// ─── Model enrichment ────────────────────────────────────────────────────
|
|
37
|
+
// CC's hook payloads never carry the `model` field — but every hook
|
|
38
|
+
// references a `transcript_path` JSONL that contains lines like
|
|
39
|
+
// `"model":"claude-opus-4-7"`. We read the tail of that file once per
|
|
40
|
+
// session, cache the result, and (a) inject `model` into subsequent
|
|
41
|
+
// payloads for that session before broadcasting, (b) emit a synthetic
|
|
42
|
+
// `ModelObserved` event so the client backfills agents created before
|
|
43
|
+
// the model was resolved.
|
|
44
|
+
const modelBySession = new Map(); // sessionId -> "claude-…"
|
|
45
|
+
const pendingTranscriptReads = new Set(); // sessionId currently being read
|
|
46
|
+
|
|
47
|
+
async function readModelFromTranscript(path) {
|
|
48
|
+
try {
|
|
49
|
+
const s = await stat(path);
|
|
50
|
+
if (s.size === 0) return null;
|
|
51
|
+
// Read up to last 128 KB — plenty for the most-recent model
|
|
52
|
+
// declaration. Reading from the tail handles sessions that switched
|
|
53
|
+
// model mid-conversation (we want the current one).
|
|
54
|
+
const TAIL = 128 * 1024;
|
|
55
|
+
const start = Math.max(0, s.size - TAIL);
|
|
56
|
+
const fh = await open(path, "r");
|
|
57
|
+
try {
|
|
58
|
+
const len = s.size - start;
|
|
59
|
+
const buf = Buffer.alloc(len);
|
|
60
|
+
await fh.read(buf, 0, len, start);
|
|
61
|
+
const text = buf.toString("utf8");
|
|
62
|
+
// Scan all matches and return the LAST one — most recent model used.
|
|
63
|
+
const re = /"model"\s*:\s*"(claude[-_][^"]+)"/gi;
|
|
64
|
+
let last = null;
|
|
65
|
+
for (const m of text.matchAll(re)) last = m[1];
|
|
66
|
+
return last;
|
|
67
|
+
} finally {
|
|
68
|
+
await fh.close();
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function maybeResolveModel(payload) {
|
|
76
|
+
if (!payload || typeof payload !== "object") return;
|
|
77
|
+
const sid = payload.session_id;
|
|
78
|
+
const tp = payload.transcript_path;
|
|
79
|
+
if (!sid || !tp) return;
|
|
80
|
+
if (modelBySession.has(sid)) return;
|
|
81
|
+
if (pendingTranscriptReads.has(sid)) return;
|
|
82
|
+
pendingTranscriptReads.add(sid);
|
|
83
|
+
readModelFromTranscript(tp)
|
|
84
|
+
.then(model => {
|
|
85
|
+
if (!model) return;
|
|
86
|
+
modelBySession.set(sid, model);
|
|
87
|
+
// Synthetic enrichment event — reducer applies to every agent in
|
|
88
|
+
// this session, including ones created before we resolved.
|
|
89
|
+
pushEvent({ hook_event_name: "ModelObserved", session_id: sid, model }, "internal");
|
|
90
|
+
})
|
|
91
|
+
.catch(() => {})
|
|
92
|
+
.finally(() => pendingTranscriptReads.delete(sid));
|
|
93
|
+
}
|
|
94
|
+
|
|
36
95
|
function pushEvent(raw, source, opts = {}) {
|
|
96
|
+
// Synchronous enrichment: if we already know this session's model, stamp
|
|
97
|
+
// it on the payload so the client's recursive scanner picks it up.
|
|
98
|
+
if (raw && typeof raw === "object" && raw.session_id && !raw.model) {
|
|
99
|
+
const cached = modelBySession.get(raw.session_id);
|
|
100
|
+
if (cached) raw.model = cached;
|
|
101
|
+
}
|
|
102
|
+
|
|
37
103
|
const seq = nextSeq++;
|
|
38
104
|
const evt = {
|
|
39
105
|
seq,
|
|
@@ -54,6 +120,11 @@ function pushEvent(raw, source, opts = {}) {
|
|
|
54
120
|
appendFile(persistPath, JSON.stringify(evt) + "\n", "utf8").catch(() => {});
|
|
55
121
|
}
|
|
56
122
|
|
|
123
|
+
// Kick off async transcript scan for unknown sessions. The result lands
|
|
124
|
+
// as a synthetic ModelObserved event a few ms later that backfills any
|
|
125
|
+
// agents already on the canvas.
|
|
126
|
+
if (source === "hook" && !opts.replay) maybeResolveModel(raw);
|
|
127
|
+
|
|
57
128
|
return evt;
|
|
58
129
|
}
|
|
59
130
|
|