@lazyingart/agintiflow 0.20.128 → 0.20.129

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 CHANGED
@@ -103,7 +103,7 @@ Provider signup and key pages:
103
103
  | Qwen / DashScope | [https://bailian.console.aliyun.com/](https://bailian.console.aliyun.com/) | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` |
104
104
  | GRS AI image tools | [https://grsai.ai/dashboard/api-keys](https://grsai.ai/dashboard/api-keys) | Configure with `/auxiliary grsai` or `aginti login grsai` |
105
105
 
106
- The CLI quietly auto-starts or reuses the local web UI from the same project. It tries `http://127.0.0.1:3210` first, then `3211`, `3212`, and so on if the port is already occupied by another project. The active URL is shown in the CLI launch header. If startup is blocked, stale, or unavailable, the same header row shows the recovery hint; run `/webapp [port]` inside the CLI to retry, or `/webapp restart [port]` to stop and relaunch the local webapp with the current project and canonical `~/.agintiflow` session home.
106
+ The CLI quietly auto-starts or reuses the local web UI from the same project. It tries `http://127.0.0.1:3210` first, then `3211`, `3212`, and so on if the port is already occupied by another project. The active URL is shown in the CLI launch header. If startup is blocked, stale, or unavailable, the same header row shows the recovery hint; run `/webapp [port]` inside the CLI to retry, or `/webapp restart [port]` to stop and relaunch the local webapp with the current project and canonical `~/.agintiflow` session home. After a successful `aginti update` or accepted startup auto-update, AgInTiFlow also restarts the compatible local webapp so artifact serving uses the updated package.
107
107
 
108
108
  Package installation also makes a best-effort, non-blocking webapp initialization. Install never fails because the optional local webapp could not start.
109
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.128",
3
+ "version": "0.20.129",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -14,7 +14,7 @@ AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-a
14
14
 
15
15
  Project-oriented programming for agentic workflows.
16
16
 
17
- AAPS is a project oriented and prompt-native programming language and visual studio for turning prompts into structured, verifiable pipelines. It connects wet and dry experiments, hardware and software, and human intent with executable agent work through tasks, typed inputs, declared outputs, validation gates, recovery steps, and durable artifacts.
17
+ AAPS is a prompt-native programming language and visual studio for turning prompts into structured, verifiable pipelines. It connects wet and dry experiments, hardware and software, and human intent with executable agent work through tasks, typed inputs, declared outputs, validation gates, recovery steps, and durable artifacts.
18
18
 
19
19
  ## Shorter Version
20
20
 
@@ -101,4 +101,52 @@ else process.env.AGINTIFLOW_AUTO_UPDATE_STARTUP_INTERVAL_MS = previousStartupInt
101
101
  if (previousCi === undefined) delete process.env.CI;
102
102
  else process.env.CI = previousCi;
103
103
 
104
+ const updateHome = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-auto-update-install-"));
105
+ const previousInstallHome = process.env.AGINTIFLOW_HOME;
106
+ process.env.AGINTIFLOW_HOME = updateHome;
107
+ await fs.writeFile(
108
+ path.join(updateHome, "update-check.json"),
109
+ `${JSON.stringify({ checkedAt: Date.now(), latest: "0.20.99" })}\n`,
110
+ "utf8"
111
+ );
112
+ const hookEvents = [];
113
+ const fakeInstallWrites = [];
114
+ const fakeInstallStdout = {
115
+ isTTY: true,
116
+ write(value) {
117
+ fakeInstallWrites.push(String(value));
118
+ },
119
+ };
120
+ const fakeInstallStderr = {
121
+ write(value) {
122
+ fakeInstallWrites.push(String(value));
123
+ },
124
+ };
125
+ const installed = await maybeAutoUpdate({
126
+ argv: ["update"],
127
+ manual: true,
128
+ packageDir: scopedGlobalPath,
129
+ packageName: "@lazyingart/agintiflow",
130
+ packageVersion: "0.20.38",
131
+ restart: false,
132
+ stdout: fakeInstallStdout,
133
+ stderr: fakeInstallStderr,
134
+ installPackage: async (packageName) => {
135
+ hookEvents.push(["install", packageName]);
136
+ return { ok: true, code: 0 };
137
+ },
138
+ afterUpdate: async (context) => {
139
+ hookEvents.push(["afterUpdate", context.latest]);
140
+ return { ok: true, restarted: true, url: "http://127.0.0.1:3210" };
141
+ },
142
+ });
143
+ assert(installed.updated === true, "fake install did not report update success");
144
+ assert(installed.webappRestart?.ok === true && installed.webappRestart.restarted === true, "after-update webapp restart result missing");
145
+ assert(hookEvents.some((event) => event[0] === "install"), "install hook was not called");
146
+ assert(hookEvents.some((event) => event[0] === "afterUpdate"), "after-update hook was not called");
147
+ assert(fakeInstallWrites.join("").includes("webapp restarted after update"), "after-update webapp restart was not reported");
148
+ if (previousInstallHome === undefined) delete process.env.AGINTIFLOW_HOME;
149
+ else process.env.AGINTIFLOW_HOME = previousInstallHome;
150
+ await fs.rm(updateHome, { recursive: true, force: true });
151
+
104
152
  console.log("auto-update smoke ok");
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs/promises";
3
+ import net from "node:net";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
@@ -13,6 +14,14 @@ const preferredPort = 44500 + Math.floor(Math.random() * 1000);
13
14
  let childPid = 0;
14
15
  const originalHome = process.env.AGINTIFLOW_HOME;
15
16
 
17
+ function listenOccupier(port) {
18
+ return new Promise((resolve, reject) => {
19
+ const server = net.createServer();
20
+ server.once("error", reject);
21
+ server.listen(port, "127.0.0.1", () => resolve(server));
22
+ });
23
+ }
24
+
16
25
  try {
17
26
  process.env.AGINTIFLOW_HOME = inheritedHome;
18
27
  const first = await ensureAgintiWebApp({
@@ -55,6 +64,42 @@ try {
55
64
  throw new Error(`expected restart on same URL with a new pid, got ${JSON.stringify(restarted)}`);
56
65
  }
57
66
  childPid = Number(restarted.pid) || childPid;
67
+ try {
68
+ process.kill(childPid, "SIGTERM");
69
+ } catch {
70
+ // The primary restart target may have already exited.
71
+ }
72
+ childPid = 0;
73
+
74
+ const occupiedPort = preferredPort + 103;
75
+ const occupier = await listenOccupier(occupiedPort);
76
+ try {
77
+ const fallback = await ensureAgintiWebApp({
78
+ packageDir: repoRoot,
79
+ cwd: runtimeDir,
80
+ home: homeDir,
81
+ preferredPort: occupiedPort,
82
+ host: "127.0.0.1",
83
+ });
84
+ if (!fallback.ok || !fallback.started || Number(fallback.port) <= occupiedPort) {
85
+ throw new Error(`expected fallback webapp above occupied port ${occupiedPort}, got ${JSON.stringify(fallback)}`);
86
+ }
87
+ const fallbackPid = Number(fallback.pid) || 0;
88
+ const fallbackRestarted = await ensureAgintiWebApp({
89
+ packageDir: repoRoot,
90
+ cwd: runtimeDir,
91
+ home: homeDir,
92
+ preferredPort: occupiedPort,
93
+ host: "127.0.0.1",
94
+ restart: true,
95
+ });
96
+ if (!fallbackRestarted.ok || !fallbackRestarted.restarted || fallbackRestarted.url !== fallback.url || Number(fallbackRestarted.pid) === fallbackPid) {
97
+ throw new Error(`expected restart of compatible fallback-port webapp, got ${JSON.stringify(fallbackRestarted)}`);
98
+ }
99
+ childPid = Number(fallbackRestarted.pid) || 0;
100
+ } finally {
101
+ occupier.close();
102
+ }
58
103
  console.log(`web auto-start smoke passed: ${first.url}`);
59
104
  } finally {
60
105
  if (originalHome === undefined) delete process.env.AGINTIFLOW_HOME;
@@ -189,6 +189,24 @@ async function restartCurrentProcess() {
189
189
  });
190
190
  }
191
191
 
192
+ async function runAfterUpdateHook(afterUpdate, context, stdout, stderr) {
193
+ if (typeof afterUpdate !== "function") return null;
194
+ try {
195
+ const result = (await afterUpdate(context)) || { ok: true };
196
+ if (result.ok && result.url) {
197
+ const state = result.restarted ? "restarted" : result.reused ? "reused" : result.started ? "started" : "ready";
198
+ stdout.write(`AgInTiFlow webapp ${state} after update: ${result.url}\n`);
199
+ } else if (result.ok === false) {
200
+ stderr.write(`AgInTiFlow webapp restart after update failed: ${result.error || "unknown error"}\n`);
201
+ }
202
+ return result;
203
+ } catch (error) {
204
+ const message = error instanceof Error ? error.message : String(error);
205
+ stderr.write(`AgInTiFlow webapp restart after update failed: ${message}\n`);
206
+ return { ok: false, error: message };
207
+ }
208
+ }
209
+
192
210
  function shouldSkipFailedInstall(cache, currentMs, force) {
193
211
  if (force) return false;
194
212
  const failedAt = Number(cache.lastInstallFailedAt || 0);
@@ -289,6 +307,9 @@ export async function maybeAutoUpdate({
289
307
  packageVersion = "",
290
308
  restart = false,
291
309
  selectUpdateAction = promptUpdateChoice,
310
+ installPackage = installLatest,
311
+ restartProcess = restartCurrentProcess,
312
+ afterUpdate = null,
292
313
  stdout = process.stdout,
293
314
  stderr = process.stderr,
294
315
  } = {}) {
@@ -369,7 +390,7 @@ export async function maybeAutoUpdate({
369
390
 
370
391
  stdout.write(`AgInTiFlow update available: ${packageVersion} -> ${latest}\n`);
371
392
  stdout.write(`Running: npm install -g ${packageName}@latest\n`);
372
- const install = await installLatest(packageName);
393
+ const install = await installPackage(packageName);
373
394
  if (!install.ok) {
374
395
  await writeCache({
375
396
  ...cache,
@@ -394,12 +415,27 @@ export async function maybeAutoUpdate({
394
415
  packageName,
395
416
  });
396
417
  stdout.write(`AgInTiFlow updated to ${latest}.\n`);
418
+ const webappRestart = await runAfterUpdateHook(
419
+ afterUpdate,
420
+ { current: packageVersion, latest, packageName },
421
+ stdout,
422
+ stderr
423
+ );
397
424
 
398
425
  if (restart) {
399
426
  stdout.write("Restarting AgInTiFlow with the updated package...\n");
400
- const restarted = await restartCurrentProcess();
401
- return { checked: true, latest, current: packageVersion, updated: true, restarted: true, exitCode: restarted.exitCode, error: restarted.error };
427
+ const restarted = await restartProcess();
428
+ return {
429
+ checked: true,
430
+ latest,
431
+ current: packageVersion,
432
+ updated: true,
433
+ restarted: true,
434
+ exitCode: restarted.exitCode,
435
+ error: restarted.error,
436
+ webappRestart,
437
+ };
402
438
  }
403
439
 
404
- return { checked: true, latest, current: packageVersion, updated: true };
440
+ return { checked: true, latest, current: packageVersion, updated: true, webappRestart };
405
441
  }
package/src/cli.js CHANGED
@@ -869,6 +869,43 @@ function stripLeadingGlobalOptions(argv = []) {
869
869
  };
870
870
  }
871
871
 
872
+ function commandCwdFromArgv(argv = []) {
873
+ for (let index = 0; index < argv.length; index += 1) {
874
+ if (argv[index] === "--cwd") {
875
+ const cwd = readOption(argv, index);
876
+ if (cwd) return path.resolve(cwd);
877
+ }
878
+ }
879
+ return process.cwd();
880
+ }
881
+
882
+ function languageFromArgv(argv = []) {
883
+ for (let index = 0; index < argv.length; index += 1) {
884
+ const arg = argv[index];
885
+ if (arg === "--language" || arg === "--lang" || arg === "-L") {
886
+ const first = readOption(argv, index);
887
+ const second = argv[index + 2] && !String(argv[index + 2]).startsWith("--") ? argv[index + 2] : "";
888
+ if (["cn", "zh"].includes(String(first || "").toLowerCase()) && ["s", "t"].includes(String(second || "").toLowerCase())) {
889
+ return resolveLanguage(`${first}-${second}`);
890
+ }
891
+ return resolveLanguage(first);
892
+ }
893
+ }
894
+ return "";
895
+ }
896
+
897
+ async function restartWebAppAfterUpdate({ commandCwd = process.cwd(), language = "" } = {}) {
898
+ return await ensureAgintiWebApp({
899
+ packageDir,
900
+ cwd: commandCwd,
901
+ host: process.env.AGINTI_WEB_HOST || process.env.HOST || "127.0.0.1",
902
+ preferredPort: process.env.AGINTI_WEB_PORT || process.env.PORT || 3210,
903
+ language,
904
+ restart: true,
905
+ respectAutoStartDisable: false,
906
+ }).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), url: "" }));
907
+ }
908
+
872
909
  function providerLabel(provider) {
873
910
  const normalized = String(provider || "").toLowerCase();
874
911
  if (normalized === "openai") return "OpenAI";
@@ -1687,6 +1724,8 @@ export async function main(argv = process.argv.slice(2)) {
1687
1724
  }
1688
1725
 
1689
1726
  if (argv[0] === "update" || argv[0] === "upgrade") {
1727
+ const updateCommandCwd = commandCwdFromArgv(argv);
1728
+ const updateLanguage = languageFromArgv(argv);
1690
1729
  const updateResult = await maybeAutoUpdate({
1691
1730
  argv,
1692
1731
  force: true,
@@ -1695,11 +1734,14 @@ export async function main(argv = process.argv.slice(2)) {
1695
1734
  packageName: packageJson.name,
1696
1735
  packageVersion: packageJson.version,
1697
1736
  restart: false,
1737
+ afterUpdate: () => restartWebAppAfterUpdate({ commandCwd: updateCommandCwd, language: updateLanguage }),
1698
1738
  });
1699
1739
  if (updateResult.error) process.exit(1);
1700
1740
  return;
1701
1741
  }
1702
1742
 
1743
+ const updateCommandCwd = commandCwdFromArgv(argv);
1744
+ const updateLanguage = languageFromArgv(argv);
1703
1745
  const autoUpdateResult = await maybeAutoUpdate({
1704
1746
  argv,
1705
1747
  force: argv.includes("--auto-update"),
@@ -1707,6 +1749,7 @@ export async function main(argv = process.argv.slice(2)) {
1707
1749
  packageName: packageJson.name,
1708
1750
  packageVersion: packageJson.version,
1709
1751
  restart: true,
1752
+ afterUpdate: () => restartWebAppAfterUpdate({ commandCwd: updateCommandCwd, language: updateLanguage }),
1710
1753
  });
1711
1754
  if (autoUpdateResult.restarted) process.exit(autoUpdateResult.exitCode ?? 0);
1712
1755
 
@@ -187,7 +187,8 @@ export async function findReusableOrFreeWebPort({
187
187
  if (port >= 65536) break;
188
188
  const health = await fetchHealthDetails(normalizedHost, port);
189
189
  if (health.ok) {
190
- if (restart && Number(port) === startPort) {
190
+ const compatible = compatibleHealth(health, { cwd: runtimeDir, home: homeDir, packageDir });
191
+ if (restart && compatible) {
191
192
  const stopped = await stopWebAppOnPort({ host: normalizedHost, port, health });
192
193
  if (!stopped.ok) return { port, host: normalizedHost, url: "", reused: false, available: false, stopped, error: stopped.error };
193
194
  return {
@@ -200,7 +201,7 @@ export async function findReusableOrFreeWebPort({
200
201
  stopped,
201
202
  };
202
203
  }
203
- if (compatibleHealth(health, { cwd: runtimeDir, home: homeDir, packageDir })) {
204
+ if (compatible) {
204
205
  return { port, host: normalizedHost, url: webUrl(normalizedHost, port), reused: true, available: false, health };
205
206
  }
206
207
  continue;