@letta-ai/letta-code 0.30.27 → 0.30.29
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-presets.js +17 -17
- package/dist/agent-presets.js.map +1 -1
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/types/agent/client-skills.d.ts +3 -0
- package/dist/types/agent/client-skills.d.ts.map +1 -1
- package/dist/types/agent/memory-git.d.ts +2 -0
- package/dist/types/agent/memory-git.d.ts.map +1 -1
- package/dist/types/agent/shared-memory-skills.d.ts +18 -0
- package/dist/types/agent/shared-memory-skills.d.ts.map +1 -0
- package/dist/types/backend/dev/pi-model-factory.d.ts +6 -0
- package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
- package/dist/types/backend/dev/pi-provider-registry.d.ts.map +1 -1
- package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
- package/dist/types/tools/impl/skill.d.ts +10 -5
- package/dist/types/tools/impl/skill.d.ts.map +1 -1
- package/dist/types/tools/secret-substitution.d.ts.map +1 -1
- package/dist/types/types/loop-status-protocol.d.ts +17 -0
- package/dist/types/types/loop-status-protocol.d.ts.map +1 -0
- package/dist/types/types/protocol_v2.d.ts +2 -19
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/dist/types/websocket/listener/inbound-queue.d.ts +5 -0
- package/dist/types/websocket/listener/inbound-queue.d.ts.map +1 -0
- package/dist/types/websocket/listener/protocol-outbound-routing.d.ts +9 -0
- package/dist/types/websocket/listener/protocol-outbound-routing.d.ts.map +1 -0
- package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
- package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
- package/dist/types/websocket/listener/turn-correlation.d.ts +10 -0
- package/dist/types/websocket/listener/turn-correlation.d.ts.map +1 -0
- package/dist/types/websocket/listener/types.d.ts +4 -0
- package/dist/types/websocket/listener/types.d.ts.map +1 -1
- package/image-resize-worker.js +42 -20
- package/letta.js +85237 -138922
- package/package.json +4 -3
- package/scripts/claude-watch/agent-watch.ts +622 -0
- package/scripts/claude-watch/docs-snapshot.test.ts +259 -0
- package/scripts/claude-watch/docs-snapshot.ts +672 -0
- package/scripts/claude-watch/fixtures/historical-replays.json +52 -0
- package/scripts/claude-watch/github.ts +137 -0
- package/scripts/claude-watch/release-analysis.test.ts +235 -0
- package/scripts/claude-watch/release-analysis.ts +297 -0
- package/scripts/claude-watch/release-source.test.ts +179 -0
- package/scripts/claude-watch/release-source.ts +369 -0
- package/scripts/claude-watch/runtime-observations.ts +98 -0
- package/scripts/claude-watch/runtime-probe.test.ts +576 -0
- package/scripts/claude-watch/runtime-probe.ts +911 -0
- package/scripts/claude-watch/runtime-sandbox.ts +170 -0
- package/scripts/claude-watch/state-branch.test.ts +211 -0
- package/scripts/claude-watch/state-branch.ts +316 -0
- package/scripts/claude-watch/tracker.test.ts +148 -0
- package/scripts/claude-watch/tracker.ts +325 -0
- package/scripts/claude-watch/types.ts +186 -0
- package/scripts/claude-watch/update-tracker.ts +201 -0
- package/scripts/codex-watch/agent-watch.ts +65 -10
- package/scripts/codex-watch/release-analysis.test.ts +57 -0
- package/scripts/codex-watch/release-analysis.ts +45 -3
- package/scripts/codex-watch/tracker.test.ts +243 -6
- package/scripts/codex-watch/tracker.ts +222 -23
- package/scripts/pi-ai-watch/agent-watch.ts +253 -0
- package/scripts/pi-ai-watch/github.ts +145 -0
- package/scripts/pi-ai-watch/release-analysis.test.ts +137 -0
- package/scripts/pi-ai-watch/release-analysis.ts +371 -0
- package/scripts/pi-ai-watch/tracker.test.ts +138 -0
- package/scripts/pi-ai-watch/tracker.ts +303 -0
- package/scripts/pi-ai-watch/update-tracker.ts +215 -0
- package/scripts/run-unit-tests.cjs +2 -0
- package/scripts/source-file-size-baseline.json +3 -3
- package/skills/creating-mods/references/commands.md +1 -1
- package/skills/creating-mods/references/ui.md +1 -1
- package/skills/customizing-commands/SKILL.md +1 -1
- package/skills/initializing-memory/SKILL.md +7 -7
- package/skills/self-configuration/SKILL.md +4 -4
- package/scripts/codex-watch/check-release.ts +0 -128
- package/scripts/codex-watch/render-issue.ts +0 -273
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import type { CodexWatchAnalysis } from "./release-analysis.ts";
|
|
3
3
|
import {
|
|
4
|
+
advanceCodexAuditCursor,
|
|
4
5
|
emptyTrackerState,
|
|
5
|
-
|
|
6
|
+
getCodexAuditCursorTag,
|
|
7
|
+
hasProcessedRange,
|
|
6
8
|
parseTrackerState,
|
|
7
9
|
recordAnalysis,
|
|
8
10
|
renderTrackerBody,
|
|
9
11
|
serializeTrackerState,
|
|
10
12
|
type TrackerEntry,
|
|
11
13
|
upsertTrackerEntry,
|
|
14
|
+
validateLegacyCodexAuditCursor,
|
|
12
15
|
} from "./tracker.ts";
|
|
13
16
|
|
|
14
17
|
function analysis(
|
|
@@ -18,6 +21,7 @@ function analysis(
|
|
|
18
21
|
return {
|
|
19
22
|
previous_tag: "rust-v0.1.0",
|
|
20
23
|
current_tag: tag,
|
|
24
|
+
is_adjacent_release: true,
|
|
21
25
|
release_url: `https://github.com/openai/codex/releases/tag/${tag}`,
|
|
22
26
|
release_notes_md: "",
|
|
23
27
|
verdict,
|
|
@@ -31,6 +35,12 @@ function analysis(
|
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
function withoutAuditCursor(body: string): string {
|
|
39
|
+
return body
|
|
40
|
+
.replace(/ "audit_cursor_tag": (?:null|"[^"]+"),\n/, "")
|
|
41
|
+
.replace(' "audit_cursor_validated": true,\n', "");
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
function entry(index: number, outcome: TrackerEntry["outcome"]): TrackerEntry {
|
|
35
45
|
const verdict =
|
|
36
46
|
outcome === "recorded_noop" ? "no-op" : "tool-surface review needed";
|
|
@@ -51,11 +61,38 @@ function entry(index: number, outcome: TrackerEntry["outcome"]): TrackerEntry {
|
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
describe("tracker state", () => {
|
|
54
|
-
test("
|
|
55
|
-
expect(parseTrackerState("plain body")).
|
|
56
|
-
|
|
64
|
+
test("fails closed when hidden state is absent or malformed", () => {
|
|
65
|
+
expect(() => parseTrackerState("plain body")).toThrow(
|
|
66
|
+
"Codex tracker hidden state is missing",
|
|
67
|
+
);
|
|
68
|
+
expect(() =>
|
|
57
69
|
parseTrackerState("<!-- codex-agent-watch-state\nnot json\n-->"),
|
|
58
|
-
).
|
|
70
|
+
).toThrow("Codex tracker hidden state is invalid");
|
|
71
|
+
expect(() =>
|
|
72
|
+
parseTrackerState(
|
|
73
|
+
'<!-- codex-agent-watch-state\n{"last_checked_tag":null,"last_checked_at":null,"processed":[{}]}\n-->',
|
|
74
|
+
),
|
|
75
|
+
).toThrow("Codex tracker hidden state is invalid");
|
|
76
|
+
expect(() =>
|
|
77
|
+
parseTrackerState(
|
|
78
|
+
'<!-- codex-agent-watch-state\n{"audit_cursor_tag":null,"last_checked_tag":"rust-v0.2.0","last_checked_at":"2026-08-20T00:00:00Z","processed":[]}\n-->',
|
|
79
|
+
),
|
|
80
|
+
).toThrow("Codex tracker hidden state is invalid");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("fails closed on an unsupported durable cursor", () => {
|
|
84
|
+
const state = upsertTrackerEntry(
|
|
85
|
+
emptyTrackerState(),
|
|
86
|
+
entry(1, "no_local_impact"),
|
|
87
|
+
);
|
|
88
|
+
const body = renderTrackerBody(state).replace(
|
|
89
|
+
'"audit_cursor_tag": "rust-v0.1.0"',
|
|
90
|
+
'"audit_cursor_tag": "rust-v0.5.0"',
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(() => parseTrackerState(body)).toThrow(
|
|
94
|
+
"Codex tracker hidden state is invalid",
|
|
95
|
+
);
|
|
59
96
|
});
|
|
60
97
|
|
|
61
98
|
test("round-trips hidden state through rendered body", () => {
|
|
@@ -72,6 +109,85 @@ describe("tracker state", () => {
|
|
|
72
109
|
expect(body).toContain("upstream-only router change");
|
|
73
110
|
});
|
|
74
111
|
|
|
112
|
+
test("derives the durable cursor from legacy hidden state", () => {
|
|
113
|
+
const state = recordAnalysis(emptyTrackerState(), {
|
|
114
|
+
analysis: analysis("rust-v0.2.0"),
|
|
115
|
+
outcome: "recorded_noop",
|
|
116
|
+
notes: "no watched changes",
|
|
117
|
+
processedAt: "2026-07-02T00:00:00.000Z",
|
|
118
|
+
});
|
|
119
|
+
const legacyBody = withoutAuditCursor(renderTrackerBody(state));
|
|
120
|
+
|
|
121
|
+
const legacy = parseTrackerState(legacyBody);
|
|
122
|
+
expect(legacy.audit_cursor_validated).toBe(false);
|
|
123
|
+
const migrated = validateLegacyCodexAuditCursor(legacy, [
|
|
124
|
+
"rust-v0.1.0",
|
|
125
|
+
"rust-v0.2.0",
|
|
126
|
+
]);
|
|
127
|
+
expect(migrated.audit_cursor_validated).toBe(true);
|
|
128
|
+
expect(getCodexAuditCursorTag(migrated)).toBe("rust-v0.2.0");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("migrates a legacy cursor independently of an older replay", () => {
|
|
132
|
+
let state = upsertTrackerEntry(
|
|
133
|
+
emptyTrackerState(),
|
|
134
|
+
entry(1, "no_local_impact"),
|
|
135
|
+
);
|
|
136
|
+
state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
|
|
137
|
+
state = upsertTrackerEntry(state, entry(3, "no_local_impact"));
|
|
138
|
+
state = upsertTrackerEntry(state, entry(1, "no_local_impact"));
|
|
139
|
+
|
|
140
|
+
const legacy = parseTrackerState(
|
|
141
|
+
withoutAuditCursor(renderTrackerBody(state)),
|
|
142
|
+
);
|
|
143
|
+
const migrated = validateLegacyCodexAuditCursor(legacy, [
|
|
144
|
+
"rust-v0.0.0",
|
|
145
|
+
"rust-v0.1.0",
|
|
146
|
+
"rust-v0.2.0",
|
|
147
|
+
"rust-v0.3.0",
|
|
148
|
+
]);
|
|
149
|
+
expect(migrated.last_checked_tag).toBe("rust-v0.1.0");
|
|
150
|
+
expect(getCodexAuditCursorTag(migrated)).toBe("rust-v0.3.0");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("fails closed on a disconnected future legacy replay", () => {
|
|
154
|
+
let state = upsertTrackerEntry(
|
|
155
|
+
emptyTrackerState(),
|
|
156
|
+
entry(1, "no_local_impact"),
|
|
157
|
+
);
|
|
158
|
+
state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
|
|
159
|
+
state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
|
|
160
|
+
|
|
161
|
+
expect(() =>
|
|
162
|
+
parseTrackerState(withoutAuditCursor(renderTrackerBody(state))),
|
|
163
|
+
).toThrow("Codex tracker hidden state is invalid");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("fails closed on a cumulative legacy replay", () => {
|
|
167
|
+
const cumulative = {
|
|
168
|
+
...analysis("rust-v0.5.0", "tool-surface review needed"),
|
|
169
|
+
is_adjacent_release: false,
|
|
170
|
+
};
|
|
171
|
+
const state = recordAnalysis(emptyTrackerState(), {
|
|
172
|
+
analysis: cumulative,
|
|
173
|
+
outcome: "no_local_impact",
|
|
174
|
+
notes: "legacy cumulative replay",
|
|
175
|
+
});
|
|
176
|
+
const legacy = parseTrackerState(
|
|
177
|
+
withoutAuditCursor(renderTrackerBody(state)),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
expect(() =>
|
|
181
|
+
validateLegacyCodexAuditCursor(legacy, [
|
|
182
|
+
"rust-v0.1.0",
|
|
183
|
+
"rust-v0.2.0",
|
|
184
|
+
"rust-v0.3.0",
|
|
185
|
+
"rust-v0.4.0",
|
|
186
|
+
"rust-v0.5.0",
|
|
187
|
+
]),
|
|
188
|
+
).toThrow("Legacy Codex tracker range is not adjacent");
|
|
189
|
+
});
|
|
190
|
+
|
|
75
191
|
test("records noops in hidden state without adding visible table rows", () => {
|
|
76
192
|
const state = recordAnalysis(emptyTrackerState(), {
|
|
77
193
|
analysis: analysis("rust-v0.2.0"),
|
|
@@ -81,11 +197,132 @@ describe("tracker state", () => {
|
|
|
81
197
|
});
|
|
82
198
|
|
|
83
199
|
const body = renderTrackerBody(state);
|
|
84
|
-
expect(
|
|
200
|
+
expect(
|
|
201
|
+
hasProcessedRange(
|
|
202
|
+
parseTrackerState(body),
|
|
203
|
+
"rust-v0.1.0",
|
|
204
|
+
"rust-v0.2.0",
|
|
205
|
+
),
|
|
206
|
+
).toBe(true);
|
|
85
207
|
expect(body).toContain("_No actionable releases recorded yet._");
|
|
86
208
|
expect(body).toContain("no watched changes");
|
|
87
209
|
});
|
|
88
210
|
|
|
211
|
+
test("retries an errored release from its previous tag", () => {
|
|
212
|
+
let state = upsertTrackerEntry(
|
|
213
|
+
emptyTrackerState(),
|
|
214
|
+
entry(1, "recorded_noop"),
|
|
215
|
+
);
|
|
216
|
+
state = upsertTrackerEntry(state, entry(2, "error"));
|
|
217
|
+
|
|
218
|
+
expect(
|
|
219
|
+
hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
|
|
220
|
+
).toBe(false);
|
|
221
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
|
|
222
|
+
|
|
223
|
+
state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
|
|
224
|
+
expect(
|
|
225
|
+
hasProcessedRange(state, "rust-v0.1.0", "rust-v0.2.0"),
|
|
226
|
+
).toBe(true);
|
|
227
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("uses an errored release baseline when no terminal entry exists", () => {
|
|
231
|
+
const state = upsertTrackerEntry(
|
|
232
|
+
emptyTrackerState(),
|
|
233
|
+
entry(2, "error"),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("does not regress the cursor after an older replay", () => {
|
|
240
|
+
let state = upsertTrackerEntry(
|
|
241
|
+
emptyTrackerState(),
|
|
242
|
+
entry(10, "no_local_impact"),
|
|
243
|
+
);
|
|
244
|
+
state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
|
|
245
|
+
|
|
246
|
+
expect(state.last_checked_tag).toBe("rust-v0.5.0");
|
|
247
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.10.0");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("does not let a future replay skip an errored release", () => {
|
|
251
|
+
let state = upsertTrackerEntry(
|
|
252
|
+
emptyTrackerState(),
|
|
253
|
+
entry(1, "no_local_impact"),
|
|
254
|
+
);
|
|
255
|
+
state = upsertTrackerEntry(state, entry(2, "error"));
|
|
256
|
+
state = upsertTrackerEntry(state, entry(5, "no_local_impact"));
|
|
257
|
+
|
|
258
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
|
|
259
|
+
|
|
260
|
+
state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
|
|
261
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("does not advance for an explicit non-adjacent range", () => {
|
|
265
|
+
const initial = upsertTrackerEntry(
|
|
266
|
+
emptyTrackerState(),
|
|
267
|
+
entry(1, "no_local_impact"),
|
|
268
|
+
);
|
|
269
|
+
const cumulative = {
|
|
270
|
+
...analysis("rust-v0.5.0", "tool-surface review needed"),
|
|
271
|
+
is_adjacent_release: false,
|
|
272
|
+
};
|
|
273
|
+
const state = recordAnalysis(initial, {
|
|
274
|
+
analysis: cumulative,
|
|
275
|
+
outcome: "no_local_impact",
|
|
276
|
+
notes: "explicit cumulative replay",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.1.0");
|
|
280
|
+
expect(
|
|
281
|
+
getCodexAuditCursorTag(
|
|
282
|
+
advanceCodexAuditCursor(
|
|
283
|
+
state,
|
|
284
|
+
"rust-v0.1.0",
|
|
285
|
+
"rust-v0.5.0",
|
|
286
|
+
false,
|
|
287
|
+
),
|
|
288
|
+
),
|
|
289
|
+
).toBe("rust-v0.1.0");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("advances across an already audited adjacent range", () => {
|
|
293
|
+
let state = upsertTrackerEntry(
|
|
294
|
+
emptyTrackerState(),
|
|
295
|
+
entry(1, "no_local_impact"),
|
|
296
|
+
);
|
|
297
|
+
state = upsertTrackerEntry(state, entry(3, "no_local_impact"));
|
|
298
|
+
state = upsertTrackerEntry(state, entry(2, "no_local_impact"));
|
|
299
|
+
|
|
300
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.2.0");
|
|
301
|
+
state = advanceCodexAuditCursor(
|
|
302
|
+
state,
|
|
303
|
+
"rust-v0.2.0",
|
|
304
|
+
"rust-v0.3.0",
|
|
305
|
+
true,
|
|
306
|
+
);
|
|
307
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.3.0");
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("retains the durable cursor entry when history is truncated", () => {
|
|
311
|
+
let state = upsertTrackerEntry(
|
|
312
|
+
emptyTrackerState(),
|
|
313
|
+
entry(100, "no_local_impact"),
|
|
314
|
+
);
|
|
315
|
+
for (let index = 1; index <= 60; index += 1) {
|
|
316
|
+
state = upsertTrackerEntry(state, entry(index, "no_local_impact"));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
expect(state.processed).toHaveLength(50);
|
|
320
|
+
expect(state.processed.some(({ tag }) => tag === "rust-v0.100.0")).toBe(
|
|
321
|
+
true,
|
|
322
|
+
);
|
|
323
|
+
expect(getCodexAuditCursorTag(state)).toBe("rust-v0.100.0");
|
|
324
|
+
});
|
|
325
|
+
|
|
89
326
|
test("keeps the last 50 processed releases in hidden state", () => {
|
|
90
327
|
let state = emptyTrackerState();
|
|
91
328
|
for (let i = 1; i <= 60; i++) {
|
|
@@ -26,6 +26,8 @@ export interface TrackerEntry {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface TrackerState {
|
|
29
|
+
audit_cursor_tag: string | null;
|
|
30
|
+
audit_cursor_validated: boolean;
|
|
29
31
|
last_checked_tag: string | null;
|
|
30
32
|
last_checked_at: string | null;
|
|
31
33
|
processed: TrackerEntry[];
|
|
@@ -41,6 +43,8 @@ export interface RecordAnalysisOptions {
|
|
|
41
43
|
|
|
42
44
|
export function emptyTrackerState(): TrackerState {
|
|
43
45
|
return {
|
|
46
|
+
audit_cursor_tag: null,
|
|
47
|
+
audit_cursor_validated: true,
|
|
44
48
|
last_checked_tag: null,
|
|
45
49
|
last_checked_at: null,
|
|
46
50
|
processed: [],
|
|
@@ -49,21 +53,81 @@ export function emptyTrackerState(): TrackerState {
|
|
|
49
53
|
|
|
50
54
|
export function parseTrackerState(body: string): TrackerState {
|
|
51
55
|
const start = body.indexOf(STATE_START);
|
|
52
|
-
if (start === -1)
|
|
56
|
+
if (start === -1) throw new Error("Codex tracker hidden state is missing");
|
|
53
57
|
|
|
54
58
|
const jsonStart = start + STATE_START.length;
|
|
55
59
|
const end = body.indexOf(STATE_END, jsonStart);
|
|
56
|
-
if (end === -1)
|
|
60
|
+
if (end === -1) throw new Error("Codex tracker hidden state is incomplete");
|
|
57
61
|
|
|
58
62
|
try {
|
|
59
63
|
return normalizeState(JSON.parse(body.slice(jsonStart, end).trim()));
|
|
60
|
-
} catch {
|
|
61
|
-
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error("Codex tracker hidden state is invalid", { cause: error });
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
export function
|
|
66
|
-
return
|
|
69
|
+
export function isTerminalOutcome(outcome: TrackerOutcome): boolean {
|
|
70
|
+
return outcome !== "error";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function hasProcessedRange(
|
|
74
|
+
state: TrackerState,
|
|
75
|
+
previousTag: string,
|
|
76
|
+
currentTag: string,
|
|
77
|
+
): boolean {
|
|
78
|
+
return state.processed.some(
|
|
79
|
+
(entry) =>
|
|
80
|
+
entry.previous_tag === previousTag &&
|
|
81
|
+
entry.tag === currentTag &&
|
|
82
|
+
isTerminalOutcome(entry.outcome),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getCodexAuditCursorTag(state: TrackerState): string | null {
|
|
87
|
+
return state.audit_cursor_tag;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function validateLegacyCodexAuditCursor(
|
|
91
|
+
state: TrackerState,
|
|
92
|
+
stableTags: string[],
|
|
93
|
+
): TrackerState {
|
|
94
|
+
if (state.audit_cursor_validated) return state;
|
|
95
|
+
|
|
96
|
+
let tag = state.audit_cursor_tag;
|
|
97
|
+
while (tag !== null) {
|
|
98
|
+
const entry = state.processed.find(
|
|
99
|
+
(candidate) =>
|
|
100
|
+
candidate.tag === tag && isTerminalOutcome(candidate.outcome),
|
|
101
|
+
);
|
|
102
|
+
if (!entry) break;
|
|
103
|
+
const currentIndex = stableTags.indexOf(entry.tag);
|
|
104
|
+
if (
|
|
105
|
+
currentIndex < 1 ||
|
|
106
|
+
stableTags[currentIndex - 1] !== entry.previous_tag
|
|
107
|
+
) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Legacy Codex tracker range is not adjacent: ${entry.previous_tag}...${entry.tag}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
tag = entry.previous_tag;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { ...state, audit_cursor_validated: true };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function advanceCodexAuditCursor(
|
|
119
|
+
state: TrackerState,
|
|
120
|
+
previousTag: string,
|
|
121
|
+
currentTag: string,
|
|
122
|
+
isAdjacentRelease: boolean,
|
|
123
|
+
): TrackerState {
|
|
124
|
+
if (!isAdjacentRelease || state.audit_cursor_tag !== previousTag) return state;
|
|
125
|
+
if (!hasProcessedRange(state, previousTag, currentTag)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Cannot advance Codex audit cursor without terminal ${previousTag}...${currentTag}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return { ...state, audit_cursor_tag: currentTag };
|
|
67
131
|
}
|
|
68
132
|
|
|
69
133
|
export function recordAnalysis(
|
|
@@ -81,19 +145,51 @@ export function recordAnalysis(
|
|
|
81
145
|
processed_at: processedAt,
|
|
82
146
|
compare_url: options.analysis.compare_url,
|
|
83
147
|
workflow_run_url: options.analysis.workflow_run_url,
|
|
84
|
-
});
|
|
148
|
+
}, options.analysis.is_adjacent_release);
|
|
85
149
|
}
|
|
86
150
|
|
|
87
151
|
export function upsertTrackerEntry(
|
|
88
152
|
state: TrackerState,
|
|
89
153
|
entry: TrackerEntry,
|
|
154
|
+
advanceAuditCursor = true,
|
|
90
155
|
): TrackerState {
|
|
91
|
-
|
|
156
|
+
let auditCursorTag = state.audit_cursor_tag;
|
|
157
|
+
if (auditCursorTag === null && !advanceAuditCursor) {
|
|
158
|
+
auditCursorTag = entry.previous_tag;
|
|
159
|
+
} else if (
|
|
160
|
+
advanceAuditCursor &&
|
|
161
|
+
entry.outcome === "error" &&
|
|
162
|
+
auditCursorTag === null
|
|
163
|
+
) {
|
|
164
|
+
auditCursorTag = entry.previous_tag;
|
|
165
|
+
} else if (
|
|
166
|
+
advanceAuditCursor &&
|
|
167
|
+
isTerminalOutcome(entry.outcome) &&
|
|
168
|
+
(auditCursorTag === null || entry.previous_tag === auditCursorTag)
|
|
169
|
+
) {
|
|
170
|
+
auditCursorTag = entry.tag;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const candidates = [
|
|
92
174
|
entry,
|
|
93
|
-
...state.processed.filter(
|
|
94
|
-
|
|
175
|
+
...state.processed.filter(
|
|
176
|
+
(existing) =>
|
|
177
|
+
existing.tag !== entry.tag ||
|
|
178
|
+
existing.previous_tag !== entry.previous_tag,
|
|
179
|
+
),
|
|
180
|
+
];
|
|
181
|
+
let processed = candidates.slice(0, HIDDEN_STATE_LIMIT);
|
|
182
|
+
if (auditCursorTag !== null && !isSupportedAuditCursor(processed, auditCursorTag)) {
|
|
183
|
+
const support = candidates.find((candidate) =>
|
|
184
|
+
isAuditCursorSupportEntry(candidate, auditCursorTag),
|
|
185
|
+
);
|
|
186
|
+
if (!support) throw new Error("Codex audit cursor has no supporting entry");
|
|
187
|
+
processed = [...processed.slice(0, HIDDEN_STATE_LIMIT - 1), support];
|
|
188
|
+
}
|
|
95
189
|
|
|
96
190
|
return {
|
|
191
|
+
audit_cursor_tag: auditCursorTag,
|
|
192
|
+
audit_cursor_validated: state.audit_cursor_validated,
|
|
97
193
|
last_checked_tag: entry.tag,
|
|
98
194
|
last_checked_at: entry.processed_at,
|
|
99
195
|
processed,
|
|
@@ -103,9 +199,7 @@ export function upsertTrackerEntry(
|
|
|
103
199
|
export function renderTrackerBody(state: TrackerState): string {
|
|
104
200
|
const normalized = normalizeState(state);
|
|
105
201
|
const parts: string[] = [
|
|
106
|
-
"Central tracker for
|
|
107
|
-
"",
|
|
108
|
-
"The legacy per-release `codex-release-watch.yml` issue workflow is still enabled as the baseline while this tracker bakes off the new automation path.",
|
|
202
|
+
"Central tracker for Amelia-driven Codex upstream drift monitoring.",
|
|
109
203
|
"",
|
|
110
204
|
renderLastChecked(normalized),
|
|
111
205
|
"",
|
|
@@ -181,28 +275,129 @@ function escapeTable(value: string): string {
|
|
|
181
275
|
}
|
|
182
276
|
|
|
183
277
|
function normalizeState(value: unknown): TrackerState {
|
|
184
|
-
if (!isRecord(value))
|
|
278
|
+
if (!isRecord(value)) throw new TypeError("tracker state must be an object");
|
|
279
|
+
if (!Array.isArray(value.processed) || !value.processed.every(isTrackerEntry)) {
|
|
280
|
+
throw new TypeError("tracker processed entries are invalid");
|
|
281
|
+
}
|
|
282
|
+
if (
|
|
283
|
+
value.last_checked_tag !== null &&
|
|
284
|
+
typeof value.last_checked_tag !== "string"
|
|
285
|
+
) {
|
|
286
|
+
throw new TypeError("tracker last_checked_tag is invalid");
|
|
287
|
+
}
|
|
288
|
+
if (
|
|
289
|
+
value.last_checked_at !== null &&
|
|
290
|
+
typeof value.last_checked_at !== "string"
|
|
291
|
+
) {
|
|
292
|
+
throw new TypeError("tracker last_checked_at is invalid");
|
|
293
|
+
}
|
|
185
294
|
|
|
186
|
-
const processed =
|
|
187
|
-
|
|
188
|
-
|
|
295
|
+
const processed = value.processed.slice(
|
|
296
|
+
0,
|
|
297
|
+
HIDDEN_STATE_LIMIT,
|
|
298
|
+
) as TrackerEntry[];
|
|
299
|
+
const lastCheckedTag = value.last_checked_tag as string | null;
|
|
300
|
+
const lastChecked =
|
|
301
|
+
lastCheckedTag === null
|
|
302
|
+
? null
|
|
303
|
+
: processed.find((entry) => entry.tag === lastCheckedTag);
|
|
304
|
+
if (lastCheckedTag === null ? processed.length > 0 : !lastChecked) {
|
|
305
|
+
throw new TypeError("tracker last_checked_tag is inconsistent");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let auditCursorTag: string | null;
|
|
309
|
+
let auditCursorValidated: boolean;
|
|
310
|
+
if (value.audit_cursor_tag === undefined) {
|
|
311
|
+
auditCursorTag = deriveLegacyAuditCursor(processed);
|
|
312
|
+
auditCursorValidated = false;
|
|
313
|
+
} else if (
|
|
314
|
+
(value.audit_cursor_tag === null ||
|
|
315
|
+
typeof value.audit_cursor_tag === "string") &&
|
|
316
|
+
value.audit_cursor_validated === true
|
|
317
|
+
) {
|
|
318
|
+
auditCursorTag = value.audit_cursor_tag;
|
|
319
|
+
auditCursorValidated = true;
|
|
320
|
+
} else {
|
|
321
|
+
throw new TypeError("tracker audit cursor is invalid");
|
|
322
|
+
}
|
|
323
|
+
if (auditCursorTag !== null && !isStableTag(auditCursorTag)) {
|
|
324
|
+
throw new TypeError("tracker audit_cursor_tag is invalid");
|
|
325
|
+
}
|
|
326
|
+
if (auditCursorTag === null && processed.length > 0) {
|
|
327
|
+
throw new TypeError("tracker audit_cursor_tag is missing");
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
auditCursorTag !== null &&
|
|
331
|
+
!isSupportedAuditCursor(processed, auditCursorTag)
|
|
332
|
+
) {
|
|
333
|
+
throw new TypeError("tracker audit_cursor_tag is inconsistent");
|
|
334
|
+
}
|
|
189
335
|
|
|
190
336
|
return {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
last_checked_at:
|
|
196
|
-
typeof value.last_checked_at === "string" ? value.last_checked_at : null,
|
|
337
|
+
audit_cursor_tag: auditCursorTag,
|
|
338
|
+
audit_cursor_validated: auditCursorValidated,
|
|
339
|
+
last_checked_tag: lastCheckedTag,
|
|
340
|
+
last_checked_at: value.last_checked_at as string | null,
|
|
197
341
|
processed,
|
|
198
342
|
};
|
|
199
343
|
}
|
|
200
344
|
|
|
345
|
+
function isSupportedAuditCursor(
|
|
346
|
+
processed: TrackerEntry[],
|
|
347
|
+
cursorTag: string,
|
|
348
|
+
): boolean {
|
|
349
|
+
return processed.some((entry) =>
|
|
350
|
+
isAuditCursorSupportEntry(entry, cursorTag),
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function isAuditCursorSupportEntry(
|
|
355
|
+
entry: TrackerEntry,
|
|
356
|
+
cursorTag: string,
|
|
357
|
+
): boolean {
|
|
358
|
+
return (
|
|
359
|
+
(entry.tag === cursorTag && isTerminalOutcome(entry.outcome)) ||
|
|
360
|
+
entry.previous_tag === cursorTag
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function deriveLegacyAuditCursor(processed: TrackerEntry[]): string | null {
|
|
365
|
+
const terminal = processed.filter((entry) =>
|
|
366
|
+
isTerminalOutcome(entry.outcome),
|
|
367
|
+
);
|
|
368
|
+
if (terminal.length === 0) {
|
|
369
|
+
const errorBaselines = [
|
|
370
|
+
...new Set(
|
|
371
|
+
processed
|
|
372
|
+
.filter((entry) => entry.outcome === "error")
|
|
373
|
+
.map((entry) => entry.previous_tag),
|
|
374
|
+
),
|
|
375
|
+
];
|
|
376
|
+
if (errorBaselines.length > 1) {
|
|
377
|
+
throw new TypeError("legacy tracker error baselines are ambiguous");
|
|
378
|
+
}
|
|
379
|
+
return errorBaselines[0] ?? null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const tags = terminal.map((entry) => entry.tag);
|
|
383
|
+
if (new Set(tags).size !== tags.length) {
|
|
384
|
+
throw new TypeError("legacy tracker terminal tags are duplicated");
|
|
385
|
+
}
|
|
386
|
+
const previousTags = new Set(terminal.map((entry) => entry.previous_tag));
|
|
387
|
+
const endpoints = tags.filter((tag) => !previousTags.has(tag));
|
|
388
|
+
if (endpoints.length !== 1) {
|
|
389
|
+
throw new TypeError("legacy tracker audit chain is ambiguous");
|
|
390
|
+
}
|
|
391
|
+
return endpoints[0] ?? null;
|
|
392
|
+
}
|
|
393
|
+
|
|
201
394
|
function isTrackerEntry(value: unknown): value is TrackerEntry {
|
|
202
395
|
if (!isRecord(value)) return false;
|
|
203
396
|
return (
|
|
204
397
|
typeof value.tag === "string" &&
|
|
398
|
+
isStableTag(value.tag) &&
|
|
205
399
|
typeof value.previous_tag === "string" &&
|
|
400
|
+
isStableTag(value.previous_tag) &&
|
|
206
401
|
isVerdict(value.verdict) &&
|
|
207
402
|
isOutcome(value.outcome) &&
|
|
208
403
|
(typeof value.pr_url === "string" || value.pr_url === null) &&
|
|
@@ -213,6 +408,10 @@ function isTrackerEntry(value: unknown): value is TrackerEntry {
|
|
|
213
408
|
);
|
|
214
409
|
}
|
|
215
410
|
|
|
411
|
+
function isStableTag(value: string): boolean {
|
|
412
|
+
return /^(?:rust-v|v)?\d+\.\d+\.\d+$/.test(value);
|
|
413
|
+
}
|
|
414
|
+
|
|
216
415
|
function isVerdict(value: unknown): value is Verdict {
|
|
217
416
|
return (
|
|
218
417
|
value === "no-op" ||
|