@lazyingart/agintiflow 0.20.25 → 0.20.39

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 (63) hide show
  1. package/README.md +9 -4
  2. package/docs/agent-runtime-pipe.md +5 -4
  3. package/docs/cli-i18n.md +1 -1
  4. package/docs/project-agent-lessons.md +1 -1
  5. package/docs/real-deepseek-capabilities.md +1 -1
  6. package/docs/self-development-supervision.md +1 -2
  7. package/docs/skills-and-tools.md +1 -1
  8. package/docs/supervised-capability-curriculum.md +111 -0
  9. package/package.json +9 -3
  10. package/public/app.js +4 -0
  11. package/references/backend-frontend-artifact-tunnel.md +3 -3
  12. package/references/general-task-capability-taxonomy.md +109 -0
  13. package/references/supervision-student-recursive-training.md +116 -0
  14. package/scripts/smoke-auto-update.js +96 -0
  15. package/scripts/smoke-auxiliary-tools.js +1 -0
  16. package/scripts/smoke-canvas-artifacts.js +67 -0
  17. package/scripts/smoke-capabilities.js +51 -2
  18. package/scripts/smoke-cli-chat.js +2 -0
  19. package/scripts/smoke-coding-tools.js +41 -0
  20. package/scripts/smoke-inbox.js +40 -1
  21. package/scripts/smoke-skills.js +53 -1
  22. package/scripts/smoke-toolchain-docker.js +1 -0
  23. package/scripts/smoke-web-api.js +6 -1
  24. package/skills/android/SKILL.md +2 -1
  25. package/skills/code-review/SKILL.md +24 -0
  26. package/skills/data-analysis/SKILL.md +29 -0
  27. package/skills/database/SKILL.md +27 -0
  28. package/skills/devops-deployment/SKILL.md +29 -0
  29. package/skills/docs-knowledge/SKILL.md +28 -0
  30. package/skills/dotnet-csharp/SKILL.md +24 -0
  31. package/skills/education-tutorial/SKILL.md +26 -0
  32. package/skills/go/SKILL.md +23 -0
  33. package/skills/ios-swift/SKILL.md +26 -0
  34. package/skills/java-jvm/SKILL.md +25 -0
  35. package/skills/php/SKILL.md +24 -0
  36. package/skills/presentation-slides/SKILL.md +26 -0
  37. package/skills/qa-testing/SKILL.md +27 -0
  38. package/skills/ruby/SKILL.md +24 -0
  39. package/skills/rust/SKILL.md +23 -0
  40. package/skills/security-review/SKILL.md +28 -0
  41. package/skills/supervision-student/SKILL.md +67 -0
  42. package/skills/writing-editing/SKILL.md +28 -0
  43. package/src/agent-runner.js +49 -7
  44. package/src/artifact-tunnel.js +75 -4
  45. package/src/auto-update.js +389 -0
  46. package/src/capabilities.js +6 -1
  47. package/src/cli.js +129 -14
  48. package/src/config.js +6 -2
  49. package/src/engineering-guidance.js +12 -2
  50. package/src/interactive-cli.js +42 -5
  51. package/src/model-client.js +2 -2
  52. package/src/model-routing.js +3 -1
  53. package/src/parallel-scouts.js +17 -1
  54. package/src/project.js +200 -20
  55. package/src/session-index.js +135 -0
  56. package/src/session-store.js +67 -3
  57. package/src/skill-library.js +16 -2
  58. package/src/sqlite.js +19 -0
  59. package/src/static-preview-server.js +8 -1
  60. package/src/task-profiles.js +318 -5
  61. package/src/web-db.js +58 -6
  62. package/src/workspace-tools.js +1 -0
  63. package/web.js +37 -11
@@ -7,6 +7,7 @@ import { checkWorkspaceToolUse, resolveWorkspacePath } from "./workspace-tools.j
7
7
  const MAX_INLINE_TEXT_BYTES = 120_000;
8
8
  const MAX_ARTIFACT_TEXT_BYTES = 520_000;
9
9
  const MAX_ARTIFACT_IMAGE_BYTES = 4_000_000;
10
+ const MAX_PERSISTED_CANVAS_FILE_BYTES = 50_000_000;
10
11
  const IMAGE_MIME_BY_EXT = new Map([
11
12
  [".png", "image/png"],
12
13
  [".jpg", "image/jpeg"],
@@ -59,6 +60,16 @@ function mimeForPath(filePath) {
59
60
  return IMAGE_MIME_BY_EXT.get(ext) || BINARY_RENDER_MIME_BY_EXT.get(ext) || "text/plain; charset=utf-8";
60
61
  }
61
62
 
63
+ function safeCanvasFilename(artifactId, filePath) {
64
+ const rawBase = path.basename(String(filePath || "artifact"));
65
+ const safeBase = rawBase
66
+ .replace(/^\.+/, "")
67
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
68
+ .replace(/-+/g, "-")
69
+ .slice(0, 90);
70
+ return `${String(artifactId || "canvas").replace(/[^A-Za-z0-9._-]+/g, "-")}-${safeBase || "artifact"}`;
71
+ }
72
+
62
73
  function publicArtifact(item) {
63
74
  return {
64
75
  id: item.id,
@@ -170,10 +181,18 @@ function addWorkspaceArtifacts(items, event, sessionId) {
170
181
  });
171
182
  }
172
183
 
173
- function addCanvasArtifact(items, event, sessionId) {
184
+ function addCanvasArtifact(items, event, sessionId, store) {
174
185
  const data = event.data || {};
175
186
  const artifactId = data.artifactId || stableId(sessionId, event.timestamp, "canvas", data.title, data.path);
176
187
  const artifactPath = normalizeDisplayPath(data.path || "");
188
+ const sessionFilePath = typeof data.sessionFilePath === "string" ? path.resolve(data.sessionFilePath) : "";
189
+ const sessionRef =
190
+ sessionFilePath && store?.sessionDir && isInside(store.sessionDir, sessionFilePath)
191
+ ? {
192
+ type: "session-file",
193
+ path: sessionFilePath,
194
+ }
195
+ : null;
177
196
  const text = typeof data.content === "string" ? redactSensitiveText(data.content) : "";
178
197
  items.push({
179
198
  id: artifactId,
@@ -185,13 +204,15 @@ function addCanvasArtifact(items, event, sessionId) {
185
204
  source: "agent-canvas",
186
205
  tab: "canvas",
187
206
  createdAt: event.timestamp,
188
- mime: mimeForPath(artifactPath),
207
+ mime: mimeForPath(sessionRef?.path || artifactPath),
189
208
  ref: text
190
209
  ? {
191
210
  type: "inline",
192
211
  text,
193
212
  }
194
- : artifactPath
213
+ : sessionRef
214
+ ? sessionRef
215
+ : artifactPath
195
216
  ? {
196
217
  type: "workspace-file",
197
218
  path: artifactPath,
@@ -234,7 +255,7 @@ export function buildArtifacts({ sessionId, events, store }) {
234
255
  const selectedId = selectedArtifactId(events);
235
256
 
236
257
  for (const event of events) {
237
- if (event.type === "canvas.item") addCanvasArtifact(items, event, sessionId);
258
+ if (event.type === "canvas.item") addCanvasArtifact(items, event, sessionId, store);
238
259
  if (event.type === "snapshot.captured") addSnapshotArtifacts(items, event, store, sessionId);
239
260
  if (event.type === "file.changed") addWorkspaceArtifacts(items, event, sessionId);
240
261
  if (event.type === "session.finished") addFinalAnswerArtifact(items, event, sessionId);
@@ -399,3 +420,53 @@ export function normalizeCanvasPayload(args, config) {
399
420
  },
400
421
  };
401
422
  }
423
+
424
+ export async function persistCanvasPayloadFile(payload, { config, store }) {
425
+ if (!payload?.path) return { ok: true, payload };
426
+ if (!store?.artifactsDir || !store?.sessionDir) {
427
+ return { ok: false, reason: "Canvas session artifact store is unavailable." };
428
+ }
429
+
430
+ let target;
431
+ try {
432
+ target = resolveWorkspacePath(config, payload.path);
433
+ } catch (error) {
434
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
435
+ }
436
+
437
+ const stat = await fs.stat(target.absolutePath).catch(() => null);
438
+ if (!stat?.isFile()) {
439
+ return { ok: false, reason: `Canvas path does not exist or is not a file: ${payload.path}` };
440
+ }
441
+
442
+ if (stat.size > MAX_PERSISTED_CANVAS_FILE_BYTES) {
443
+ return {
444
+ ok: true,
445
+ payload: {
446
+ ...payload,
447
+ originalPath: payload.path,
448
+ artifactPersisted: false,
449
+ artifactBytes: stat.size,
450
+ artifactPersistenceWarning: `Canvas file is larger than ${MAX_PERSISTED_CANVAS_FILE_BYTES} bytes; keep the workspace file for preview.`,
451
+ },
452
+ };
453
+ }
454
+
455
+ await store.ensure();
456
+ const canvasDir = path.join(store.artifactsDir, "canvas");
457
+ await fs.mkdir(canvasDir, { recursive: true });
458
+ const sessionFilePath = path.join(canvasDir, safeCanvasFilename(payload.artifactId, target.relativePath));
459
+ await fs.copyFile(target.absolutePath, sessionFilePath);
460
+
461
+ return {
462
+ ok: true,
463
+ payload: {
464
+ ...payload,
465
+ originalPath: payload.path,
466
+ sessionPath: normalizeDisplayPath(path.relative(store.sessionDir, sessionFilePath)),
467
+ sessionFilePath,
468
+ artifactPersisted: true,
469
+ artifactBytes: stat.size,
470
+ },
471
+ };
472
+ }
@@ -0,0 +1,389 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import readline from "node:readline";
6
+ import { promisify } from "node:util";
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ const DEFAULT_PACKAGE_NAME = "@lazyingart/agintiflow";
10
+ const DEFAULT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
11
+ const DEFAULT_FAILURE_RETRY_MS = 6 * 60 * 60 * 1000;
12
+ const UPDATE_CHOICES = [
13
+ {
14
+ id: "update",
15
+ label: "Update now",
16
+ description: "Install latest globally, then restart AgInTiFlow.",
17
+ },
18
+ {
19
+ id: "skip-once",
20
+ label: "Skip this time",
21
+ description: "Continue with the current version for this run.",
22
+ },
23
+ {
24
+ id: "skip-version",
25
+ label: "Skip this version",
26
+ description: "Do not ask again until a newer release appears.",
27
+ },
28
+ ];
29
+ const START_COMMANDS = new Set(["", "chat", "interactive", "resume", "web", "--web", "--chat", "--interactive"]);
30
+ const SKIP_COMMANDS = new Set([
31
+ "auth",
32
+ "capabilities",
33
+ "doctor",
34
+ "help",
35
+ "init",
36
+ "keys",
37
+ "keys/status",
38
+ "login",
39
+ "model",
40
+ "models",
41
+ "queue",
42
+ "sessions",
43
+ "skill",
44
+ "skills",
45
+ "storage",
46
+ "update",
47
+ "upgrade",
48
+ "version",
49
+ ]);
50
+
51
+ function npmCommand() {
52
+ return process.platform === "win32" ? "npm.cmd" : "npm";
53
+ }
54
+
55
+ function agintiHome() {
56
+ return path.resolve(process.env.AGINTIFLOW_HOME || path.join(os.homedir(), ".agintiflow"));
57
+ }
58
+
59
+ function updateCachePath() {
60
+ return path.join(agintiHome(), "update-check.json");
61
+ }
62
+
63
+ function parseVersion(value) {
64
+ const match = String(value || "").trim().match(/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
65
+ if (!match) return null;
66
+ return [Number(match[1] || 0), Number(match[2] || 0), Number(match[3] || 0)];
67
+ }
68
+
69
+ export function compareSemver(left, right) {
70
+ const a = parseVersion(left);
71
+ const b = parseVersion(right);
72
+ if (!a || !b) return 0;
73
+ for (let index = 0; index < 3; index += 1) {
74
+ if (a[index] > b[index]) return 1;
75
+ if (a[index] < b[index]) return -1;
76
+ }
77
+ return 0;
78
+ }
79
+
80
+ export function isNewerVersion(latest, current) {
81
+ return compareSemver(latest, current) > 0;
82
+ }
83
+
84
+ export function isGlobalNpmInstall(packageDir, packageName = DEFAULT_PACKAGE_NAME) {
85
+ const normalized = path.resolve(packageDir || "").replaceAll("\\", "/").toLowerCase();
86
+ const packagePath = `/node_modules/${packageName.toLowerCase()}`;
87
+ return normalized.includes(packagePath);
88
+ }
89
+
90
+ export function shouldAutoUpdateCommand(argv = []) {
91
+ if (argv.includes("--no-auto-update")) return false;
92
+ if (argv.includes("--help") || argv.includes("-h") || argv.includes("--version") || argv.includes("-v")) return false;
93
+ const command = String(argv[0] || "").trim();
94
+ if (SKIP_COMMANDS.has(command)) return false;
95
+ if (command.startsWith("--") && command !== "--web" && command !== "--chat" && command !== "--interactive") return true;
96
+ if (START_COMMANDS.has(command)) return true;
97
+ return true;
98
+ }
99
+
100
+ function envDisablesAutoUpdate() {
101
+ const noValues = [process.env.AGINTIFLOW_NO_AUTO_UPDATE, process.env.AGINTI_NO_AUTO_UPDATE].map((value) => String(value || "").toLowerCase());
102
+ if (noValues.some((value) => ["1", "true", "yes", "on"].includes(value))) return true;
103
+ const explicit = process.env.AGINTIFLOW_AUTO_UPDATE;
104
+ if (explicit === undefined) return false;
105
+ return ["0", "false", "no", "off"].includes(String(explicit).toLowerCase());
106
+ }
107
+
108
+ async function readCache() {
109
+ try {
110
+ return JSON.parse(await fs.readFile(updateCachePath(), "utf8"));
111
+ } catch {
112
+ return {};
113
+ }
114
+ }
115
+
116
+ async function writeCache(nextCache) {
117
+ const filePath = updateCachePath();
118
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
119
+ await fs.writeFile(filePath, `${JSON.stringify(nextCache, null, 2)}\n`, "utf8");
120
+ }
121
+
122
+ async function npmLatestVersion(packageName, timeoutMs) {
123
+ const { stdout } = await execFileAsync(npmCommand(), ["view", packageName, "version", "--json"], {
124
+ timeout: timeoutMs,
125
+ maxBuffer: 100 * 1024,
126
+ env: { ...process.env, npm_config_fund: "false", npm_config_audit: "false" },
127
+ });
128
+ return JSON.parse(stdout.trim());
129
+ }
130
+
131
+ function nowMs() {
132
+ return Date.now();
133
+ }
134
+
135
+ function checkIntervalMs() {
136
+ const raw = process.env.AGINTIFLOW_AUTO_UPDATE_INTERVAL_MS;
137
+ if (raw === undefined || raw === "") return DEFAULT_CHECK_INTERVAL_MS;
138
+ const value = Number(raw);
139
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_CHECK_INTERVAL_MS;
140
+ }
141
+
142
+ function failureRetryMs() {
143
+ const raw = process.env.AGINTIFLOW_AUTO_UPDATE_FAILURE_RETRY_MS;
144
+ if (raw === undefined || raw === "") return DEFAULT_FAILURE_RETRY_MS;
145
+ const value = Number(raw);
146
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_FAILURE_RETRY_MS;
147
+ }
148
+
149
+ async function installLatest(packageName) {
150
+ return await new Promise((resolve) => {
151
+ const child = spawn(npmCommand(), ["install", "-g", `${packageName}@latest`, "--no-audit", "--no-fund"], {
152
+ stdio: "inherit",
153
+ env: process.env,
154
+ });
155
+ child.on("error", (error) => resolve({ ok: false, error: error.message }));
156
+ child.on("exit", (code, signal) => {
157
+ resolve({
158
+ ok: code === 0,
159
+ code,
160
+ error: code === 0 ? "" : `npm install exited with ${signal || code}`,
161
+ });
162
+ });
163
+ });
164
+ }
165
+
166
+ async function restartCurrentProcess() {
167
+ return await new Promise((resolve) => {
168
+ const child = spawn(process.execPath, process.argv.slice(1), {
169
+ stdio: "inherit",
170
+ env: { ...process.env, AGINTIFLOW_AUTO_UPDATE_RESTARTED: "1" },
171
+ });
172
+ child.on("error", (error) => resolve({ ok: false, exitCode: 1, error: error.message }));
173
+ child.on("exit", (code) => resolve({ ok: true, exitCode: code ?? 0 }));
174
+ });
175
+ }
176
+
177
+ function shouldSkipFailedInstall(cache, currentMs, force) {
178
+ if (force) return false;
179
+ const failedAt = Number(cache.lastInstallFailedAt || 0);
180
+ return failedAt > 0 && currentMs - failedAt < failureRetryMs();
181
+ }
182
+
183
+ function renderUpdateSelector(output, { current, latest, packageName, selectedIndex, renderedLines }) {
184
+ const rows = [
185
+ `AgInTiFlow update available: ${current} -> ${latest}`,
186
+ `Package: ${packageName}`,
187
+ "Use Up/Down to choose, Enter to confirm, Esc to skip.",
188
+ "",
189
+ ...UPDATE_CHOICES.map((choice, index) => {
190
+ const cursor = index === selectedIndex ? ">" : " ";
191
+ return `${cursor} ${choice.label.padEnd(17)} ${choice.description}`;
192
+ }),
193
+ ];
194
+ if (renderedLines > 0) output.write(`\x1b[${renderedLines}A\x1b[J`);
195
+ output.write(`${rows.join("\n")}\n`);
196
+ return rows.length;
197
+ }
198
+
199
+ export async function promptUpdateChoice({
200
+ current = "",
201
+ latest = "",
202
+ packageName = DEFAULT_PACKAGE_NAME,
203
+ input = process.stdin,
204
+ output = process.stdout,
205
+ } = {}) {
206
+ if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return "skip-once";
207
+
208
+ readline.emitKeypressEvents(input);
209
+ const wasRaw = input.isRaw;
210
+ let renderedLines = 0;
211
+ let selectedIndex = 0;
212
+ let resolved = false;
213
+
214
+ return await new Promise((resolve) => {
215
+ function finish(choice) {
216
+ if (resolved) return;
217
+ resolved = true;
218
+ input.off("keypress", onKeypress);
219
+ input.setRawMode(Boolean(wasRaw));
220
+ if (!wasRaw) input.pause();
221
+ output.write("\n");
222
+ resolve(choice);
223
+ }
224
+
225
+ function render() {
226
+ renderedLines = renderUpdateSelector(output, {
227
+ current,
228
+ latest,
229
+ packageName,
230
+ selectedIndex,
231
+ renderedLines,
232
+ });
233
+ }
234
+
235
+ function onKeypress(_chunk, key = {}) {
236
+ if (key.ctrl && key.name === "c") {
237
+ input.setRawMode(Boolean(wasRaw));
238
+ if (!wasRaw) input.pause();
239
+ output.write("\n");
240
+ process.exit(130);
241
+ }
242
+ if (key.name === "up" || key.name === "left") {
243
+ selectedIndex = (selectedIndex - 1 + UPDATE_CHOICES.length) % UPDATE_CHOICES.length;
244
+ render();
245
+ return;
246
+ }
247
+ if (key.name === "down" || key.name === "right" || key.name === "tab") {
248
+ selectedIndex = (selectedIndex + 1) % UPDATE_CHOICES.length;
249
+ render();
250
+ return;
251
+ }
252
+ if (key.name === "return" || key.name === "enter") {
253
+ finish(UPDATE_CHOICES[selectedIndex].id);
254
+ return;
255
+ }
256
+ if (key.name === "escape") {
257
+ finish("skip-once");
258
+ }
259
+ }
260
+
261
+ input.setRawMode(true);
262
+ input.resume();
263
+ render();
264
+ input.on("keypress", onKeypress);
265
+ });
266
+ }
267
+
268
+ export async function maybeAutoUpdate({
269
+ argv = [],
270
+ force = false,
271
+ manual = false,
272
+ packageDir = "",
273
+ packageName = DEFAULT_PACKAGE_NAME,
274
+ packageVersion = "",
275
+ restart = false,
276
+ selectUpdateAction = promptUpdateChoice,
277
+ stdout = process.stdout,
278
+ stderr = process.stderr,
279
+ } = {}) {
280
+ if (!manual && !force && !shouldAutoUpdateCommand(argv)) return { checked: false, skipped: "command" };
281
+ if (!manual && !force && envDisablesAutoUpdate()) return { checked: false, skipped: "disabled" };
282
+ if (!manual && !force && process.env.CI) return { checked: false, skipped: "ci" };
283
+ if (!manual && !force && !stdout.isTTY) return { checked: false, skipped: "non-tty" };
284
+ if (!manual && !force && process.env.AGINTIFLOW_AUTO_UPDATE_RESTARTED === "1") return { checked: false, skipped: "restarted" };
285
+
286
+ if (!isGlobalNpmInstall(packageDir, packageName)) {
287
+ if (manual) {
288
+ stdout.write("Auto-update skipped: this looks like a source checkout, not a global npm install.\n");
289
+ stdout.write(`To update a published install, run: npm install -g ${packageName}@latest\n`);
290
+ }
291
+ return { checked: false, skipped: "source-checkout" };
292
+ }
293
+
294
+ const currentMs = nowMs();
295
+ const cache = await readCache();
296
+ let latest = String(cache.latest || "");
297
+ const checkedAt = Number(cache.checkedAt || 0);
298
+ const stale = force || currentMs - checkedAt >= checkIntervalMs();
299
+
300
+ if (stale || !latest) {
301
+ try {
302
+ latest = String(await npmLatestVersion(packageName, Number(process.env.AGINTIFLOW_AUTO_UPDATE_TIMEOUT_MS || 5000)) || "");
303
+ await writeCache({
304
+ ...cache,
305
+ checkedAt: currentMs,
306
+ latest,
307
+ latestCheckedBy: "npm-view",
308
+ packageName,
309
+ });
310
+ } catch (error) {
311
+ if (manual) stderr.write(`Could not check npm latest version: ${error instanceof Error ? error.message : String(error)}\n`);
312
+ return { checked: false, skipped: "latest-check-failed", error };
313
+ }
314
+ }
315
+
316
+ if (!isNewerVersion(latest, packageVersion)) {
317
+ if (manual) stdout.write(`AgInTiFlow is up to date (${packageVersion}).\n`);
318
+ return { checked: true, latest, current: packageVersion, updated: false };
319
+ }
320
+
321
+ if (shouldSkipFailedInstall(cache, currentMs, force)) {
322
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "recent-install-failure" };
323
+ }
324
+
325
+ if (!manual && !force) {
326
+ if (cache.skippedVersion === latest) {
327
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skipped-version" };
328
+ }
329
+ const choice = await selectUpdateAction({
330
+ current: packageVersion,
331
+ latest,
332
+ packageName,
333
+ input: process.stdin,
334
+ output: stdout,
335
+ });
336
+ if (choice === "skip-version") {
337
+ await writeCache({
338
+ ...cache,
339
+ checkedAt: currentMs,
340
+ latest,
341
+ skippedVersion: latest,
342
+ skippedAt: currentMs,
343
+ packageName,
344
+ });
345
+ stdout.write(`Skipped AgInTiFlow ${latest}. Run \`aginti update\` to install it later.\n`);
346
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skip-version" };
347
+ }
348
+ if (choice !== "update") {
349
+ stdout.write(`Skipped update for this run. Run \`aginti update\` to install ${latest} later.\n`);
350
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skip-once" };
351
+ }
352
+ }
353
+
354
+ stdout.write(`AgInTiFlow update available: ${packageVersion} -> ${latest}\n`);
355
+ stdout.write(`Running: npm install -g ${packageName}@latest\n`);
356
+ const install = await installLatest(packageName);
357
+ if (!install.ok) {
358
+ await writeCache({
359
+ ...cache,
360
+ checkedAt: currentMs,
361
+ latest,
362
+ lastInstallFailedAt: currentMs,
363
+ lastInstallError: install.error,
364
+ packageName,
365
+ });
366
+ stderr.write(`AgInTiFlow auto-update failed: ${install.error}\n`);
367
+ stderr.write(`You can update manually with: npm install -g ${packageName}@latest\n`);
368
+ return { checked: true, latest, current: packageVersion, updated: false, error: install.error };
369
+ }
370
+
371
+ await writeCache({
372
+ ...cache,
373
+ checkedAt: currentMs,
374
+ latest,
375
+ lastInstallSucceededAt: currentMs,
376
+ lastInstallFailedAt: 0,
377
+ lastInstallError: "",
378
+ packageName,
379
+ });
380
+ stdout.write(`AgInTiFlow updated to ${latest}.\n`);
381
+
382
+ if (restart) {
383
+ stdout.write("Restarting AgInTiFlow with the updated package...\n");
384
+ const restarted = await restartCurrentProcess();
385
+ return { checked: true, latest, current: packageVersion, updated: true, restarted: true, exitCode: restarted.exitCode, error: restarted.error };
386
+ }
387
+
388
+ return { checked: true, latest, current: packageVersion, updated: true };
389
+ }
@@ -198,7 +198,11 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
198
198
  instructionsPresent: instructions.exists,
199
199
  sessionsDir: paths.sessionsDir,
200
200
  sessionDbPath: paths.sessionDbPath,
201
- sharedSessionFolder: path.resolve(config.sessionsDir) === path.resolve(paths.sessionsDir),
201
+ globalSessionsDir: paths.globalSessionsDir,
202
+ globalSessionIndexPath: paths.globalSessionIndexPath,
203
+ sharedSessionFolder:
204
+ path.resolve(config.sessionsDir) === path.resolve(paths.globalSessionsDir) &&
205
+ path.resolve(config.projectSessionsDir || paths.sessionsDir) === path.resolve(paths.sessionsDir),
202
206
  codebaseMap: {
203
207
  present: Boolean(codebaseMap.ok),
204
208
  path: paths.codebaseMapPath,
@@ -285,6 +289,7 @@ export function printCapabilityReport(report) {
285
289
  console.log(`cwd=${report.project.commandCwd}`);
286
290
  console.log(`instructions=${report.project.instructionsPath} present=${report.project.instructionsPresent}`);
287
291
  console.log(`sessions=${report.project.sessionsDir}`);
292
+ console.log(`globalSessions=${report.project.globalSessionsDir}`);
288
293
  console.log(`sessionDb=${report.project.sessionDbPath}`);
289
294
  console.log(`sharedSessions=${report.project.sharedSessionFolder}`);
290
295
  console.log(