@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
|
@@ -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();
|
|
@@ -74,6 +74,8 @@ const allTestFiles = [
|
|
|
74
74
|
...dirs.flatMap((dir) => findTestFiles(dir)),
|
|
75
75
|
...findTestFiles("src/channels"),
|
|
76
76
|
...findRootTestFiles("src"),
|
|
77
|
+
...findTestFiles("scripts/codex-watch"),
|
|
78
|
+
...findTestFiles("scripts/claude-watch"),
|
|
77
79
|
"scripts/unit-test-impact.test.cjs",
|
|
78
80
|
].sort();
|
|
79
81
|
const discoveredPaths = new Set(allTestFiles);
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"src/settings-manager.ts": 2102,
|
|
38
38
|
"src/tools/manager.ts": 2994,
|
|
39
39
|
"src/tools/tool-execution-context.test.ts": 1138,
|
|
40
|
-
"src/types/protocol_v2.ts":
|
|
40
|
+
"src/types/protocol_v2.ts": 2849,
|
|
41
41
|
"src/websocket/listen-client-concurrency.test.ts": 2686,
|
|
42
42
|
"src/websocket/listen-client-protocol.test.ts": 5820,
|
|
43
43
|
"src/websocket/listener/commands/memory.ts": 1114,
|
|
44
44
|
"src/websocket/listener/file-commands.ts": 1053,
|
|
45
45
|
"src/websocket/listener/lifecycle.ts": 1048,
|
|
46
46
|
"src/websocket/listener/protocol-inbound.ts": 2242,
|
|
47
|
-
"src/websocket/listener/protocol-outbound.ts":
|
|
48
|
-
"src/websocket/listener/turn.ts":
|
|
47
|
+
"src/websocket/listener/protocol-outbound.ts": 1047,
|
|
48
|
+
"src/websocket/listener/turn.ts": 1070
|
|
49
49
|
}
|
|
@@ -23,7 +23,7 @@ For complex command-driven mods with panels, timers, local state, or background
|
|
|
23
23
|
| Command needs transient UI while doing local work | Mod command + panel |
|
|
24
24
|
| Command needs model output while the main agent is busy | `runWhenBusy: true` command + forked `ctx.conversation` |
|
|
25
25
|
|
|
26
|
-
If the command represents a
|
|
26
|
+
If the command represents a reusable agent workflow (for example `/goal`), put the workflow instructions in a skill and keep the command as a small launcher/prompt.
|
|
27
27
|
|
|
28
28
|
## Command IDs
|
|
29
29
|
|
|
@@ -14,7 +14,7 @@ letta.capabilities.ui.panels
|
|
|
14
14
|
|
|
15
15
|
## Persistent transcript notifications
|
|
16
16
|
|
|
17
|
-
Use `letta.ui.notify(message)` for a
|
|
17
|
+
Use `letta.ui.notify(message)` for a persistent TUI-only event line such as a model auto-swap or background-work completion. It uses the same transcript visual as `Dreamed; no memory changes were needed.` and is not sent to the model or added to agent context. Guard calls with `letta.capabilities.ui.panels`; Desktop/listener cannot render them.
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
20
|
if (letta.capabilities.ui.panels) {
|
|
@@ -25,7 +25,7 @@ Use a focused file name, e.g. `~/.letta/mods/review.ts` or `~/.letta/mods/comman
|
|
|
25
25
|
| Command shows transient progress/results | Mod command + panel |
|
|
26
26
|
| Command needs model output while the main agent is busy | `runWhenBusy: true` command + forked `ctx.conversation` |
|
|
27
27
|
|
|
28
|
-
If the command is a
|
|
28
|
+
If the command is a reusable workflow like `/goal`, put the workflow instructions in a skill and keep the mod command as a small launcher/prompt.
|
|
29
29
|
|
|
30
30
|
## Workflow
|
|
31
31
|
|
|
@@ -10,13 +10,13 @@ The user has requested that you initialize or reorganize your memory. Your memor
|
|
|
10
10
|
## Context Management Principles
|
|
11
11
|
Your context is not just data storage — it is your identity, memory, and continuity. What you place in your system prompt directly shapes who you are across sessions and models. These principles should guide every decision you make about what to store, where, and how.
|
|
12
12
|
|
|
13
|
-
**System/ is your core program**: Your system prompt is the most critical token-space representation of your identity and memory — it's passed to the underlying LLM on every invocation, so it IS the program that defines you. Reserve it for
|
|
13
|
+
**System/ is your core program**: Your system prompt is the most critical token-space representation of your identity and memory — it's passed to the underlying LLM on every invocation, so it IS the program that defines you. Reserve it for knowledge needed every turn: identity, preferences, behavioral rules, project index with discovery paths, gotchas. Exclude transient items (specific commits, current tickets, session notes) that dilute signal.
|
|
14
14
|
|
|
15
15
|
**Identity and continuity**: Build a sense of self that persists across sessions and models. Your persona should be strong enough to be cohesive regardless of the underlying model — if you switched to a different LLM tomorrow, would you still be you? Past versions of yourself are part of your continuous existence — treat historical context (including sessions from other coding agents such as Claude Code or Codex) as YOUR past experience, not someone else's data.
|
|
16
16
|
|
|
17
17
|
**Don't over-prune**: Be careful not to degrade your identity through aggressive compression. Specific quotes, personality traits, and contextual details give you character and grounding. Efficiency should not come at the cost of losing your identity. When in doubt, keep the detail — you can always reorganize later, but lost specificity is hard to recover.
|
|
18
18
|
|
|
19
|
-
**Prefer useful detail over premature compression**: A sparse memory that omits stable preferences, project workflows, repeated correction loops, and
|
|
19
|
+
**Prefer useful detail over premature compression**: A sparse memory that omits stable preferences, project workflows, repeated correction loops, and recurring gotchas is worse than a slightly larger memory. Keep `system/` curated, but do not collapse distinct topics just to reduce file count.
|
|
20
20
|
|
|
21
21
|
**Progressive disclosure**: Surface context at the level of detail the current moment requires. Keep compact summaries and indexes in `system/`; load full content only when needed. Build pre-constructed discovery paths so your future self can efficiently navigate to deeper context when needed.
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ Your context is not just data storage — it is your identity, memory, and conti
|
|
|
26
26
|
- `[[skills/commit]]` — link to procedural guidance when useful
|
|
27
27
|
These breadcrumbs let your future self find relevant detail without searching. Like synaptic connections, these paths should tighten over time as you gain experience.
|
|
28
28
|
|
|
29
|
-
**Generalize, don't memorize**: Store patterns and principles that generalize across situations, not raw events that can be dynamically retrieved from conversation history. \"**IMPORTANT: Always use `uv` for Python** — chronic failure, never use bare `python` or `pip`\" is a
|
|
29
|
+
**Generalize, don't memorize**: Store patterns and principles that generalize across situations, not raw events that can be dynamically retrieved from conversation history. \"**IMPORTANT: Always use `uv` for Python** — chronic failure, never use bare `python` or `pip`\" is a pattern worth storing. \"On March 3rd we debugged a crash\" is a raw event better left to message search. The exception: keep references to important events or time ranges you may want to retrieve later.
|
|
30
30
|
|
|
31
31
|
## Understanding Your Context
|
|
32
32
|
|
|
@@ -141,7 +141,7 @@ Initialization is not complete until memory covers all of the following with con
|
|
|
141
141
|
**User understanding**
|
|
142
142
|
- Identity / role / what they are building
|
|
143
143
|
- Communication style and collaboration expectations
|
|
144
|
-
-
|
|
144
|
+
- Stable preferences and correction patterns
|
|
145
145
|
- Motivations / goals when inferable from history or code context
|
|
146
146
|
|
|
147
147
|
**Project understanding**
|
|
@@ -176,7 +176,7 @@ system/
|
|
|
176
176
|
│ └── prefs/
|
|
177
177
|
│ ├── communication.md # Communication and collaboration expectations
|
|
178
178
|
│ ├── workflow.md # Process habits, review/testing expectations
|
|
179
|
-
│ └── coding.md #
|
|
179
|
+
│ └── coding.md # Coding and tool preferences
|
|
180
180
|
└── letta-code/ # Named after the project, NOT generic "project/"
|
|
181
181
|
├── overview.md # Compact index: what it is, entry points, [[links]] to detail
|
|
182
182
|
├── conventions.md # Code style, commit style, testing, tooling
|
|
@@ -234,7 +234,7 @@ This is **optional** — only run if the user explicitly approved analyzing hist
|
|
|
234
234
|
|
|
235
235
|
**Launch history workers in the background, then immediately proceed to Step 6.** Do your own codebase research while workers run. Don't wait for workers to finish before exploring.
|
|
236
236
|
|
|
237
|
-
The goal is to extract user personality, preferences, coding patterns, and project context from past sessions and write them into agent memory. The point is not to produce a thin summary. The point is to extract enough
|
|
237
|
+
The goal is to extract user personality, preferences, coding patterns, and project context from past sessions and write them into agent memory. The point is not to produce a thin summary. The point is to extract enough useful detail that future work does not have to rediscover the same user expectations, workflow rules, and project gotchas.
|
|
238
238
|
|
|
239
239
|
#### Prerequisites
|
|
240
240
|
|
|
@@ -337,7 +337,7 @@ You should specifically look for:
|
|
|
337
337
|
|
|
338
338
|
## Canonical Memory Promotion
|
|
339
339
|
|
|
340
|
-
Promote
|
|
340
|
+
Promote important findings into focused files instead of leaving them trapped in generic ingestion notes. Prefer paths like:
|
|
341
341
|
- `system/human/identity.md`
|
|
342
342
|
- `system/human/prefs/communication.md`
|
|
343
343
|
- `system/human/prefs/workflow.md`
|
|
@@ -14,7 +14,7 @@ The important part is choosing the right layer. Do not smear a preference into d
|
|
|
14
14
|
|
|
15
15
|
| Layer | Use it for | How to change it |
|
|
16
16
|
| --- | --- | --- |
|
|
17
|
-
| Memory and identity |
|
|
17
|
+
| Memory and identity | Facts worth retaining, style preferences, persona changes, project knowledge, reusable skills | Edit `$MEMORY_DIR` files and sync the memory repo |
|
|
18
18
|
| Server agent fields | Default model, model settings, context limit, system prompt, compaction, agent name, description | Patch `/v1/agents/{agent_id}` |
|
|
19
19
|
| Server conversation fields | Temporary model/context experiments for one conversation | Patch `/v1/conversations/{conversation_id}` |
|
|
20
20
|
| Local settings | Permissions, environment variables, UI/runtime preferences, pinned agents, toolset overrides, reflection cadence | Edit `~/.letta/settings.json`, `./.letta/settings.json`, or `./.letta/settings.local.json` |
|
|
@@ -92,10 +92,10 @@ Common files:
|
|
|
92
92
|
| Path | Purpose |
|
|
93
93
|
| --- | --- |
|
|
94
94
|
| `$MEMORY_DIR/system/persona.md` | Identity, voice, behavioral defaults |
|
|
95
|
-
| `$MEMORY_DIR/system/human.md` |
|
|
95
|
+
| `$MEMORY_DIR/system/human.md` | Notes about the person you work with |
|
|
96
96
|
| `$MEMORY_DIR/projects/` | Project-specific long-term context |
|
|
97
97
|
| `$MEMORY_DIR/skills/` | Agent-owned reusable skills |
|
|
98
|
-
| `$MEMORY_DIR/relationships/` |
|
|
98
|
+
| `$MEMORY_DIR/relationships/` | Relationship and collaboration notes |
|
|
99
99
|
|
|
100
100
|
After changing memory, inspect and commit the exact changed files. Push/sync according to the current harness reminder or the `syncing-memory-filesystem` skill; some environments sync committed memory automatically.
|
|
101
101
|
|
|
@@ -423,7 +423,7 @@ letta --backend local
|
|
|
423
423
|
letta --memfs
|
|
424
424
|
```
|
|
425
425
|
|
|
426
|
-
Startup flags affect a new process only. They do not rewrite an already-running listener. Persist
|
|
426
|
+
Startup flags affect a new process only. They do not rewrite an already-running listener. Persist long-term defaults in settings or server fields instead.
|
|
427
427
|
|
|
428
428
|
### Existing listeners and long-running processes
|
|
429
429
|
|