@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.
- package/README.md +9 -4
- package/docs/agent-runtime-pipe.md +5 -4
- package/docs/cli-i18n.md +1 -1
- package/docs/project-agent-lessons.md +1 -1
- package/docs/real-deepseek-capabilities.md +1 -1
- package/docs/self-development-supervision.md +1 -2
- package/docs/skills-and-tools.md +1 -1
- package/docs/supervised-capability-curriculum.md +111 -0
- package/package.json +9 -3
- package/public/app.js +4 -0
- package/references/backend-frontend-artifact-tunnel.md +3 -3
- package/references/general-task-capability-taxonomy.md +109 -0
- package/references/supervision-student-recursive-training.md +116 -0
- package/scripts/smoke-auto-update.js +96 -0
- package/scripts/smoke-auxiliary-tools.js +1 -0
- package/scripts/smoke-canvas-artifacts.js +67 -0
- package/scripts/smoke-capabilities.js +51 -2
- package/scripts/smoke-cli-chat.js +2 -0
- package/scripts/smoke-coding-tools.js +41 -0
- package/scripts/smoke-inbox.js +40 -1
- package/scripts/smoke-skills.js +53 -1
- package/scripts/smoke-toolchain-docker.js +1 -0
- package/scripts/smoke-web-api.js +6 -1
- package/skills/android/SKILL.md +2 -1
- package/skills/code-review/SKILL.md +24 -0
- package/skills/data-analysis/SKILL.md +29 -0
- package/skills/database/SKILL.md +27 -0
- package/skills/devops-deployment/SKILL.md +29 -0
- package/skills/docs-knowledge/SKILL.md +28 -0
- package/skills/dotnet-csharp/SKILL.md +24 -0
- package/skills/education-tutorial/SKILL.md +26 -0
- package/skills/go/SKILL.md +23 -0
- package/skills/ios-swift/SKILL.md +26 -0
- package/skills/java-jvm/SKILL.md +25 -0
- package/skills/php/SKILL.md +24 -0
- package/skills/presentation-slides/SKILL.md +26 -0
- package/skills/qa-testing/SKILL.md +27 -0
- package/skills/ruby/SKILL.md +24 -0
- package/skills/rust/SKILL.md +23 -0
- package/skills/security-review/SKILL.md +28 -0
- package/skills/supervision-student/SKILL.md +67 -0
- package/skills/writing-editing/SKILL.md +28 -0
- package/src/agent-runner.js +49 -7
- package/src/artifact-tunnel.js +75 -4
- package/src/auto-update.js +389 -0
- package/src/capabilities.js +6 -1
- package/src/cli.js +129 -14
- package/src/config.js +6 -2
- package/src/engineering-guidance.js +12 -2
- package/src/interactive-cli.js +42 -5
- package/src/model-client.js +2 -2
- package/src/model-routing.js +3 -1
- package/src/parallel-scouts.js +17 -1
- package/src/project.js +200 -20
- package/src/session-index.js +135 -0
- package/src/session-store.js +67 -3
- package/src/skill-library.js +16 -2
- package/src/sqlite.js +19 -0
- package/src/static-preview-server.js +8 -1
- package/src/task-profiles.js +318 -5
- package/src/web-db.js +58 -6
- package/src/workspace-tools.js +1 -0
- package/web.js +37 -11
package/src/artifact-tunnel.js
CHANGED
|
@@ -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
|
-
:
|
|
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
|
+
}
|
package/src/capabilities.js
CHANGED
|
@@ -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
|
-
|
|
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(
|