@letta-ai/letta-code 0.30.28 → 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.
Files changed (29) hide show
  1. package/dist/mcp-client.js +2 -2
  2. package/dist/mcp-client.js.map +1 -1
  3. package/dist/types/agent/client-skills.d.ts +3 -0
  4. package/dist/types/agent/client-skills.d.ts.map +1 -1
  5. package/dist/types/agent/memory-git.d.ts +2 -0
  6. package/dist/types/agent/memory-git.d.ts.map +1 -1
  7. package/dist/types/agent/shared-memory-skills.d.ts +18 -0
  8. package/dist/types/agent/shared-memory-skills.d.ts.map +1 -0
  9. package/dist/types/backend/dev/pi-model-factory.d.ts +6 -0
  10. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  11. package/dist/types/backend/dev/pi-provider-registry.d.ts.map +1 -1
  12. package/dist/types/backend/local/local-provider-auth-store.d.ts.map +1 -1
  13. package/dist/types/tools/impl/skill.d.ts +10 -5
  14. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  15. package/image-resize-worker.js +42 -20
  16. package/letta.js +87528 -141365
  17. package/package.json +4 -3
  18. package/scripts/codex-watch/agent-watch.ts +63 -8
  19. package/scripts/codex-watch/release-analysis.test.ts +57 -0
  20. package/scripts/codex-watch/release-analysis.ts +31 -1
  21. package/scripts/codex-watch/tracker.test.ts +243 -6
  22. package/scripts/codex-watch/tracker.ts +221 -20
  23. package/scripts/pi-ai-watch/agent-watch.ts +253 -0
  24. package/scripts/pi-ai-watch/github.ts +145 -0
  25. package/scripts/pi-ai-watch/release-analysis.test.ts +137 -0
  26. package/scripts/pi-ai-watch/release-analysis.ts +371 -0
  27. package/scripts/pi-ai-watch/tracker.test.ts +138 -0
  28. package/scripts/pi-ai-watch/tracker.ts +303 -0
  29. package/scripts/pi-ai-watch/update-tracker.ts +215 -0
@@ -0,0 +1,303 @@
1
+ import type { PiAiWatchAnalysis } from "./release-analysis.ts";
2
+
3
+ const STATE_START = "<!-- pi-ai-watch-state";
4
+ const STATE_END = "-->";
5
+ const HISTORY_LIMIT = 50;
6
+ const VISIBLE_LIMIT = 20;
7
+
8
+ export type TrackerOutcome =
9
+ | "no_upgrade"
10
+ | "pr_created"
11
+ | "needs_human_review"
12
+ | "error";
13
+
14
+ export interface TrackerEntry {
15
+ version: string;
16
+ previous_version: string;
17
+ installed_version: string;
18
+ outcome: TrackerOutcome;
19
+ pr_url: string | null;
20
+ notes: string;
21
+ processed_at: string;
22
+ compare_url: string;
23
+ workflow_run_url: string;
24
+ }
25
+
26
+ export interface TrackerState {
27
+ audit_cursor_version: string;
28
+ last_checked_version: string | null;
29
+ last_checked_at: string | null;
30
+ processed: TrackerEntry[];
31
+ }
32
+
33
+ export interface RecordAnalysisOptions {
34
+ analysis: PiAiWatchAnalysis;
35
+ outcome: TrackerOutcome;
36
+ notes: string;
37
+ prUrl?: string | null;
38
+ processedAt?: string;
39
+ }
40
+
41
+ export function initialTrackerState(installedVersion: string): TrackerState {
42
+ assertStableVersion(installedVersion);
43
+ return {
44
+ audit_cursor_version: installedVersion,
45
+ last_checked_version: null,
46
+ last_checked_at: null,
47
+ processed: [],
48
+ };
49
+ }
50
+
51
+ export function parseTrackerState(body: string): TrackerState {
52
+ const start = body.indexOf(STATE_START);
53
+ if (start === -1) throw new Error("pi-ai tracker hidden state is missing");
54
+ const jsonStart = start + STATE_START.length;
55
+ const end = body.indexOf(STATE_END, jsonStart);
56
+ if (end === -1) throw new Error("pi-ai tracker hidden state is incomplete");
57
+
58
+ try {
59
+ return normalizeState(JSON.parse(body.slice(jsonStart, end).trim()));
60
+ } catch (error) {
61
+ throw new Error("pi-ai tracker hidden state is invalid", { cause: error });
62
+ }
63
+ }
64
+
65
+ export function recordAnalysis(
66
+ state: TrackerState,
67
+ options: RecordAnalysisOptions,
68
+ ): TrackerState {
69
+ const entry: TrackerEntry = {
70
+ version: options.analysis.current_version,
71
+ previous_version: options.analysis.previous_version,
72
+ installed_version: options.analysis.installed_version,
73
+ outcome: options.outcome,
74
+ pr_url: options.prUrl ?? null,
75
+ notes: options.notes,
76
+ processed_at: options.processedAt ?? new Date().toISOString(),
77
+ compare_url: options.analysis.compare_url,
78
+ workflow_run_url: options.analysis.workflow_run_url,
79
+ };
80
+ const processed = [
81
+ entry,
82
+ ...state.processed.filter(
83
+ (existing) =>
84
+ existing.version !== entry.version ||
85
+ existing.previous_version !== entry.previous_version,
86
+ ),
87
+ ].slice(0, HISTORY_LIMIT);
88
+
89
+ const advancesCursor =
90
+ options.analysis.is_adjacent_release &&
91
+ entry.previous_version === state.audit_cursor_version &&
92
+ (entry.outcome === "no_upgrade" || entry.outcome === "needs_human_review");
93
+
94
+ return {
95
+ audit_cursor_version: advancesCursor
96
+ ? entry.version
97
+ : state.audit_cursor_version,
98
+ last_checked_version: entry.version,
99
+ last_checked_at: entry.processed_at,
100
+ processed,
101
+ };
102
+ }
103
+
104
+ export function hasRecordedOutcome(
105
+ state: TrackerState,
106
+ previousVersion: string,
107
+ currentVersion: string,
108
+ ): boolean {
109
+ return state.processed.some(
110
+ (entry) =>
111
+ entry.previous_version === previousVersion &&
112
+ entry.version === currentVersion &&
113
+ entry.outcome !== "error",
114
+ );
115
+ }
116
+
117
+ export function hasCompletedRange(
118
+ state: TrackerState,
119
+ previousVersion: string,
120
+ currentVersion: string,
121
+ ): boolean {
122
+ return state.processed.some(
123
+ (entry) =>
124
+ entry.previous_version === previousVersion &&
125
+ entry.version === currentVersion &&
126
+ (entry.outcome === "no_upgrade" ||
127
+ entry.outcome === "needs_human_review"),
128
+ );
129
+ }
130
+
131
+ export function getPendingPrForCursor(
132
+ state: TrackerState,
133
+ ): TrackerEntry | null {
134
+ return (
135
+ state.processed.find(
136
+ (entry) =>
137
+ entry.previous_version === state.audit_cursor_version &&
138
+ entry.outcome === "pr_created" &&
139
+ entry.pr_url,
140
+ ) ?? null
141
+ );
142
+ }
143
+
144
+ export function advanceMergedPr(
145
+ state: TrackerState,
146
+ currentVersion: string,
147
+ ): TrackerState {
148
+ const pending = getPendingPrForCursor(state);
149
+ if (!pending || pending.version !== currentVersion) {
150
+ throw new Error(`No pending pi-ai PR for ${currentVersion}`);
151
+ }
152
+ return { ...state, audit_cursor_version: currentVersion };
153
+ }
154
+
155
+ export function renderTrackerBody(state: TrackerState): string {
156
+ const normalized = normalizeState(state);
157
+ return `${[
158
+ "Central tracker for Amelia-driven pi-ai dependency upgrade reviews.",
159
+ "",
160
+ `_Audit cursor: ${normalized.audit_cursor_version}._`,
161
+ renderLastChecked(normalized),
162
+ "",
163
+ "## Recent reviews",
164
+ "",
165
+ renderTable(normalized),
166
+ "",
167
+ "## Hidden state",
168
+ "",
169
+ "The workflow uses the hidden JSON block below for ordered release processing and dedupe.",
170
+ "",
171
+ serializeTrackerState(normalized),
172
+ ].join("\n")}\n`;
173
+ }
174
+
175
+ export function serializeTrackerState(state: TrackerState): string {
176
+ const normalized = normalizeState(state);
177
+ return `${STATE_START}\n${JSON.stringify(normalized, null, 2)}\n${STATE_END}`;
178
+ }
179
+
180
+ function renderLastChecked(state: TrackerState): string {
181
+ if (!state.last_checked_version || !state.last_checked_at) {
182
+ return "_Last checked: never._";
183
+ }
184
+ const latest = state.processed.find(
185
+ (entry) => entry.version === state.last_checked_version,
186
+ );
187
+ const suffix = latest ? `, ${statusSummary(latest)}.` : ".";
188
+ return `_Last checked: ${state.last_checked_version} at ${state.last_checked_at}${suffix}_`;
189
+ }
190
+
191
+ function renderTable(state: TrackerState): string {
192
+ const entries = state.processed.slice(0, VISIBLE_LIMIT);
193
+ if (entries.length === 0) return "_No pi-ai releases reviewed yet._";
194
+
195
+ const rows = [
196
+ "| Release | Installed | Outcome | PR | Notes |",
197
+ "|---|---|---|---|---|",
198
+ ];
199
+ for (const entry of entries) {
200
+ rows.push(
201
+ `| [${entry.version}](${entry.compare_url}) | ${entry.installed_version} | ${entry.outcome} | ${renderPr(entry.pr_url)} | ${escapeTable(entry.notes)} |`,
202
+ );
203
+ }
204
+ return rows.join("\n");
205
+ }
206
+
207
+ function statusSummary(entry: TrackerEntry): string {
208
+ if (entry.outcome === "pr_created" && entry.pr_url) {
209
+ return `PR created: ${entry.pr_url}`;
210
+ }
211
+ return entry.notes || entry.outcome;
212
+ }
213
+
214
+ function renderPr(prUrl: string | null): string {
215
+ return prUrl ? `[PR](${prUrl})` : "-";
216
+ }
217
+
218
+ function escapeTable(value: string): string {
219
+ return value.replaceAll("|", "\\|").replaceAll("\n", " ");
220
+ }
221
+
222
+ function normalizeState(value: unknown): TrackerState {
223
+ if (!isRecord(value)) throw new TypeError("tracker state must be an object");
224
+ if (typeof value.audit_cursor_version !== "string") {
225
+ throw new TypeError("tracker audit cursor is invalid");
226
+ }
227
+ assertStableVersion(value.audit_cursor_version);
228
+ if (
229
+ !Array.isArray(value.processed) ||
230
+ !value.processed.every(isTrackerEntry)
231
+ ) {
232
+ throw new TypeError("tracker processed entries are invalid");
233
+ }
234
+ if (
235
+ value.last_checked_version !== null &&
236
+ typeof value.last_checked_version !== "string"
237
+ ) {
238
+ throw new TypeError("tracker last checked version is invalid");
239
+ }
240
+ if (
241
+ value.last_checked_at !== null &&
242
+ typeof value.last_checked_at !== "string"
243
+ ) {
244
+ throw new TypeError("tracker last checked time is invalid");
245
+ }
246
+
247
+ const processed = value.processed.slice(0, HISTORY_LIMIT) as TrackerEntry[];
248
+ const lastCheckedVersion = value.last_checked_version as string | null;
249
+ if (
250
+ lastCheckedVersion !== null &&
251
+ !processed.some((entry) => entry.version === lastCheckedVersion)
252
+ ) {
253
+ throw new TypeError("tracker last checked version is inconsistent");
254
+ }
255
+
256
+ return {
257
+ audit_cursor_version: value.audit_cursor_version,
258
+ last_checked_version: lastCheckedVersion,
259
+ last_checked_at: value.last_checked_at as string | null,
260
+ processed,
261
+ };
262
+ }
263
+
264
+ function isTrackerEntry(value: unknown): value is TrackerEntry {
265
+ if (!isRecord(value)) return false;
266
+ return (
267
+ typeof value.version === "string" &&
268
+ isStableVersion(value.version) &&
269
+ typeof value.previous_version === "string" &&
270
+ isStableVersion(value.previous_version) &&
271
+ typeof value.installed_version === "string" &&
272
+ isStableVersion(value.installed_version) &&
273
+ isOutcome(value.outcome) &&
274
+ (typeof value.pr_url === "string" || value.pr_url === null) &&
275
+ typeof value.notes === "string" &&
276
+ typeof value.processed_at === "string" &&
277
+ typeof value.compare_url === "string" &&
278
+ typeof value.workflow_run_url === "string"
279
+ );
280
+ }
281
+
282
+ function isOutcome(value: unknown): value is TrackerOutcome {
283
+ return (
284
+ value === "no_upgrade" ||
285
+ value === "pr_created" ||
286
+ value === "needs_human_review" ||
287
+ value === "error"
288
+ );
289
+ }
290
+
291
+ function assertStableVersion(version: string): void {
292
+ if (!isStableVersion(version)) {
293
+ throw new TypeError(`Invalid stable pi-ai version ${version}`);
294
+ }
295
+ }
296
+
297
+ function isStableVersion(version: string): boolean {
298
+ return /^\d+\.\d+\.\d+$/.test(version);
299
+ }
300
+
301
+ function isRecord(value: unknown): value is Record<string, unknown> {
302
+ return typeof value === "object" && value !== null;
303
+ }
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { editIssueBody, getIssueBody, ghJson } from "./github.ts";
5
+ import {
6
+ DEFAULT_TARGET_REPO,
7
+ type PiAiWatchAnalysis,
8
+ } from "./release-analysis.ts";
9
+ import {
10
+ hasRecordedOutcome,
11
+ parseTrackerState,
12
+ recordAnalysis,
13
+ renderTrackerBody,
14
+ type TrackerOutcome,
15
+ } from "./tracker.ts";
16
+
17
+ interface Args {
18
+ repo: string;
19
+ trackerIssue: number | null;
20
+ analysisFile: string | null;
21
+ outcome: TrackerOutcome | null;
22
+ notes: string;
23
+ prUrl: string | null;
24
+ expectedGithubLogin: string | null;
25
+ previousVersion: string | null;
26
+ currentVersion: string | null;
27
+ assertRecorded: boolean;
28
+ dryRun: boolean;
29
+ }
30
+
31
+ function parseArgs(argv: string[]): Args {
32
+ const args: Args = {
33
+ repo: DEFAULT_TARGET_REPO,
34
+ trackerIssue: null,
35
+ analysisFile: null,
36
+ outcome: null,
37
+ notes: "",
38
+ prUrl: null,
39
+ expectedGithubLogin: null,
40
+ previousVersion: null,
41
+ currentVersion: null,
42
+ assertRecorded: false,
43
+ dryRun: false,
44
+ };
45
+ for (let index = 0; index < argv.length; index += 1) {
46
+ const argument = argv[index];
47
+ if (argument === "--repo") args.repo = argv[++index] ?? args.repo;
48
+ else if (argument === "--tracker-issue") {
49
+ args.trackerIssue = Number(argv[++index]);
50
+ } else if (argument === "--analysis-file") {
51
+ args.analysisFile = argv[++index] ?? null;
52
+ } else if (argument === "--outcome") {
53
+ args.outcome = parseOutcome(argv[++index]);
54
+ } else if (argument === "--notes") args.notes = argv[++index] ?? "";
55
+ else if (argument === "--pr-url") args.prUrl = argv[++index] ?? null;
56
+ else if (argument === "--expected-github-login") {
57
+ args.expectedGithubLogin = argv[++index] ?? null;
58
+ } else if (argument === "--previous-version") {
59
+ args.previousVersion = argv[++index] ?? null;
60
+ } else if (argument === "--current-version") {
61
+ args.currentVersion = argv[++index] ?? null;
62
+ } else if (argument === "--assert-recorded") args.assertRecorded = true;
63
+ else if (argument === "--dry-run") args.dryRun = true;
64
+ else if (argument === "--help" || argument === "-h") {
65
+ console.log(
66
+ "Usage: bun scripts/pi-ai-watch/update-tracker.ts --tracker-issue ISSUE [--analysis-file FILE --outcome OUTCOME --notes TEXT --pr-url URL --expected-github-login LOGIN] [--assert-recorded --previous-version VERSION --current-version VERSION] [--repo OWNER/REPO] [--dry-run]",
67
+ );
68
+ process.exit(0);
69
+ } else {
70
+ throw new Error(`Unknown argument: ${argument}`);
71
+ }
72
+ }
73
+
74
+ if (!args.trackerIssue || Number.isNaN(args.trackerIssue)) {
75
+ throw new Error("--tracker-issue is required");
76
+ }
77
+ if (args.assertRecorded) {
78
+ if (!args.previousVersion || !args.currentVersion) {
79
+ throw new Error(
80
+ "--previous-version and --current-version are required with --assert-recorded",
81
+ );
82
+ }
83
+ return args;
84
+ }
85
+ if (!args.analysisFile) throw new Error("--analysis-file is required");
86
+ if (!args.outcome) throw new Error("--outcome is required");
87
+ if (args.outcome === "pr_created") {
88
+ if (!args.prUrl) throw new Error("--pr-url is required for pr_created");
89
+ if (!args.expectedGithubLogin) {
90
+ throw new Error("--expected-github-login is required for pr_created");
91
+ }
92
+ }
93
+ return args;
94
+ }
95
+
96
+ function main(): void {
97
+ const args = parseArgs(process.argv.slice(2));
98
+ const issueNumber = args.trackerIssue as number;
99
+ const body = getIssueBody(args.repo, issueNumber);
100
+ const state = parseTrackerState(body);
101
+
102
+ if (args.assertRecorded) {
103
+ if (
104
+ !hasRecordedOutcome(
105
+ state,
106
+ args.previousVersion as string,
107
+ args.currentVersion as string,
108
+ )
109
+ ) {
110
+ throw new Error(
111
+ `No recorded pi-ai outcome for ${args.previousVersion}...${args.currentVersion}`,
112
+ );
113
+ }
114
+ console.log(
115
+ `Verified pi-ai outcome ${args.previousVersion}...${args.currentVersion}`,
116
+ );
117
+ return;
118
+ }
119
+
120
+ const analysis = JSON.parse(
121
+ readFileSync(args.analysisFile as string, "utf8"),
122
+ ) as PiAiWatchAnalysis;
123
+ if (args.outcome === "pr_created") {
124
+ verifyUpgradePr(
125
+ args.prUrl as string,
126
+ args.repo,
127
+ args.expectedGithubLogin as string,
128
+ analysis,
129
+ );
130
+ }
131
+
132
+ const next = recordAnalysis(state, {
133
+ analysis,
134
+ outcome: args.outcome as TrackerOutcome,
135
+ notes: args.notes || defaultNotes(args.outcome as TrackerOutcome),
136
+ prUrl: args.prUrl,
137
+ });
138
+ const nextBody = renderTrackerBody(next);
139
+ if (args.dryRun) {
140
+ console.log(nextBody);
141
+ return;
142
+ }
143
+ editIssueBody(args.repo, issueNumber, nextBody);
144
+ console.log(
145
+ `Recorded pi-ai ${analysis.current_version} as ${args.outcome} in #${issueNumber}`,
146
+ );
147
+ }
148
+
149
+ function verifyUpgradePr(
150
+ prUrl: string,
151
+ expectedRepo: string,
152
+ expectedGithubLogin: string,
153
+ analysis: PiAiWatchAnalysis,
154
+ ): void {
155
+ const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)$/.exec(
156
+ prUrl,
157
+ );
158
+ if (!match) throw new Error(`Invalid GitHub pull request URL ${prUrl}`);
159
+ const [, owner, repo, number] = match;
160
+ if (`${owner}/${repo}` !== expectedRepo) {
161
+ throw new Error(`pi-ai upgrade PR must belong to ${expectedRepo}`);
162
+ }
163
+ const pull = ghJson<{
164
+ author: { login: string };
165
+ body: string;
166
+ isDraft: boolean;
167
+ state: string;
168
+ }>([
169
+ "pr",
170
+ "view",
171
+ number as string,
172
+ "--repo",
173
+ `${owner}/${repo}`,
174
+ "--json",
175
+ "author,body,isDraft,state",
176
+ ]);
177
+ const marker = `Pi-ai-watch: ${analysis.previous_version}...${analysis.current_version}`;
178
+ if (pull.author.login !== expectedGithubLogin) {
179
+ throw new Error(
180
+ `pi-ai PR author ${pull.author.login} does not match ${expectedGithubLogin}`,
181
+ );
182
+ }
183
+ if (!pull.isDraft) throw new Error("pi-ai upgrade PR must be a draft");
184
+ if (pull.state !== "OPEN") throw new Error("pi-ai upgrade PR must be open");
185
+ if (!pull.body.includes(marker)) {
186
+ throw new Error(`pi-ai upgrade PR is missing marker: ${marker}`);
187
+ }
188
+ }
189
+
190
+ function parseOutcome(value: string | undefined): TrackerOutcome {
191
+ if (
192
+ value === "no_upgrade" ||
193
+ value === "pr_created" ||
194
+ value === "needs_human_review" ||
195
+ value === "error"
196
+ ) {
197
+ return value;
198
+ }
199
+ throw new Error(`Unknown outcome: ${value}`);
200
+ }
201
+
202
+ function defaultNotes(outcome: TrackerOutcome): string {
203
+ switch (outcome) {
204
+ case "no_upgrade":
205
+ return "reviewed; no upgrade needed for this release";
206
+ case "pr_created":
207
+ return "opened pi-ai dependency upgrade PR";
208
+ case "needs_human_review":
209
+ return "needs human review";
210
+ case "error":
211
+ return "automation hit an error; retry this release";
212
+ }
213
+ }
214
+
215
+ main();