@xopcai/xopc 0.0.20 → 0.0.21

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 (195) hide show
  1. package/dist/extensions/feishu/src/adapters/cli-login.d.ts +8 -0
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +225 -0
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -0
  4. package/dist/extensions/feishu/src/adapters/onboard-cli.js +1 -105
  5. package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -1
  6. package/dist/extensions/feishu/src/auth/app-registration.d.ts +47 -0
  7. package/dist/extensions/feishu/src/auth/app-registration.js +122 -0
  8. package/dist/extensions/feishu/src/auth/app-registration.js.map +1 -0
  9. package/dist/extensions/feishu/src/plugin.d.ts +2 -0
  10. package/dist/extensions/feishu/src/plugin.js +2 -0
  11. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  12. package/dist/extensions/telegram/src/inbound-processor.js +1 -1
  13. package/dist/extensions/telegram/src/plugin.d.ts +1 -1
  14. package/dist/extensions/telegram/src/plugin.js +1 -1
  15. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  16. package/dist/extensions/telegram/xopc.extension.json +1 -1
  17. package/dist/extensions/weixin/src/plugin.js +1 -1
  18. package/dist/gateway/static/root/assets/{agents-DbLV2ldC.js → agents-MbH57-L9.js} +2 -2
  19. package/dist/gateway/static/root/assets/{agents-DbLV2ldC.js.map → agents-MbH57-L9.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{apps-page-CDRSbv3l.js → apps-page-3i3DvI7i.js} +2 -2
  21. package/dist/gateway/static/root/assets/{apps-page-CDRSbv3l.js.map → apps-page-3i3DvI7i.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/channels-settings-CcuSzoB6.js +9 -0
  23. package/dist/gateway/static/root/assets/channels-settings-CcuSzoB6.js.map +1 -0
  24. package/dist/gateway/static/root/assets/{cron-page-D-fhl446.js → cron-page-Be1h9Yub.js} +2 -2
  25. package/dist/gateway/static/root/assets/{cron-page-D-fhl446.js.map → cron-page-Be1h9Yub.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{cron-utils-DqyPqEDr.js → cron-utils-CR97EvZS.js} +2 -2
  27. package/dist/gateway/static/root/assets/{cron-utils-DqyPqEDr.js.map → cron-utils-CR97EvZS.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{dist-BTNDXpKu.js → dist-r_Gy-XJv.js} +2 -2
  29. package/dist/gateway/static/root/assets/{dist-BTNDXpKu.js.map → dist-r_Gy-XJv.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-debug-page-CiOtMG3X.js → extension-debug-page-QfYEYruq.js} +2 -2
  31. package/dist/gateway/static/root/assets/{extension-debug-page-CiOtMG3X.js.map → extension-debug-page-QfYEYruq.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-a59AFw7Q.js → extension-page-4FW-BmKG.js} +2 -2
  33. package/dist/gateway/static/root/assets/{extension-page-a59AFw7Q.js.map → extension-page-4FW-BmKG.js.map} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-settings-page-BQyLvxBY.js → extension-settings-page-E_Wq9LL8.js} +2 -2
  35. package/dist/gateway/static/root/assets/{extension-settings-page-BQyLvxBY.js.map → extension-settings-page-E_Wq9LL8.js.map} +1 -1
  36. package/dist/gateway/static/root/assets/{index-fGYWcYhm.js → index-CcQtNJKo.js} +60 -54
  37. package/dist/gateway/static/root/assets/{index-fGYWcYhm.js.map → index-CcQtNJKo.js.map} +1 -1
  38. package/dist/gateway/static/root/assets/index-D9Wmfh2f.css +1 -0
  39. package/dist/gateway/static/root/assets/{logs-page-DMSWW0-k.js → logs-page-DFhTU-kG.js} +2 -2
  40. package/dist/gateway/static/root/assets/{logs-page-DMSWW0-k.js.map → logs-page-DFhTU-kG.js.map} +1 -1
  41. package/dist/gateway/static/root/assets/{sessions-page-CL2E3nPk.js → sessions-page-wmnnIj6Z.js} +2 -2
  42. package/dist/gateway/static/root/assets/{sessions-page-CL2E3nPk.js.map → sessions-page-wmnnIj6Z.js.map} +1 -1
  43. package/dist/gateway/static/root/assets/settings-page-BTmUXY4s.js +2 -0
  44. package/dist/gateway/static/root/assets/settings-page-BTmUXY4s.js.map +1 -0
  45. package/dist/gateway/static/root/assets/{skills-page-0rmNu4AL.js → skills-page-D-fRbJG0.js} +2 -2
  46. package/dist/gateway/static/root/assets/{skills-page-0rmNu4AL.js.map → skills-page-D-fRbJG0.js.map} +1 -1
  47. package/dist/gateway/static/root/index.html +2 -2
  48. package/dist/package.js +1 -1
  49. package/dist/src/agent/agent-manager.js +6 -6
  50. package/dist/src/agent/context/workspace-seed.js +1 -1
  51. package/dist/src/agent/ipc/bus.js +1 -1
  52. package/dist/src/agent/ipc/inbox.js +1 -1
  53. package/dist/src/agent/ipc/socket.js +1 -1
  54. package/dist/src/agent/memory/builtin-memory-store.d.ts +2 -1
  55. package/dist/src/agent/memory/builtin-memory-store.js +7 -6
  56. package/dist/src/agent/memory/builtin-memory-store.js.map +1 -1
  57. package/dist/src/agent/models/manager.js +1 -1
  58. package/dist/src/agent/prompt/memory/index.d.ts +4 -2
  59. package/dist/src/agent/prompt/memory/index.js +22 -10
  60. package/dist/src/agent/prompt/memory/index.js.map +1 -1
  61. package/dist/src/agent/prompt/service-prompt-builder.js +1 -1
  62. package/dist/src/agent/service.js +5 -5
  63. package/dist/src/agent/skills/index.js +1 -1
  64. package/dist/src/agent/skills/scanner.js +1 -1
  65. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  66. package/dist/src/agent/skills/skill-manager.js +1 -1
  67. package/dist/src/agent/tools/factory.js +10 -3
  68. package/dist/src/agent/tools/factory.js.map +1 -1
  69. package/dist/src/agent/tools/index.d.ts +1 -1
  70. package/dist/src/agent/tools/memory-tool.d.ts +7 -2
  71. package/dist/src/agent/tools/memory-tool.js +11 -5
  72. package/dist/src/agent/tools/memory-tool.js.map +1 -1
  73. package/dist/src/agent/tools/send-media.js +1 -1
  74. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  75. package/dist/src/agent/tools/write.js +1 -1
  76. package/dist/src/auth/credentials.js +2 -2
  77. package/dist/src/auth/sync-provider-auth.js +1 -1
  78. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  79. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  80. package/dist/src/channels/registry.d.ts +1 -1
  81. package/dist/src/channels/registry.js +25 -1
  82. package/dist/src/channels/registry.js.map +1 -1
  83. package/dist/src/chat-commands/builtins/config.js +3 -3
  84. package/dist/src/chat-commands/builtins/session.js +1 -1
  85. package/dist/src/chat-commands/context.js +1 -1
  86. package/dist/src/chat-commands/index.js +1 -1
  87. package/dist/src/chat-commands/processor.js +1 -1
  88. package/dist/src/cli/commands/agent.js +1 -1
  89. package/dist/src/cli/commands/channels.js +20 -2
  90. package/dist/src/cli/commands/channels.js.map +1 -1
  91. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  92. package/dist/src/cli/commands/gateway/call.d.ts +2 -0
  93. package/dist/src/cli/commands/gateway/call.js +90 -0
  94. package/dist/src/cli/commands/gateway/call.js.map +1 -0
  95. package/dist/src/cli/commands/gateway/health.d.ts +2 -0
  96. package/dist/src/cli/commands/gateway/health.js +77 -0
  97. package/dist/src/cli/commands/gateway/health.js.map +1 -0
  98. package/dist/src/cli/commands/gateway/index.d.ts +3 -0
  99. package/dist/src/cli/commands/gateway/index.js +4 -1
  100. package/dist/src/cli/commands/gateway/probe.d.ts +2 -0
  101. package/dist/src/cli/commands/gateway/probe.js +102 -0
  102. package/dist/src/cli/commands/gateway/probe.js.map +1 -0
  103. package/dist/src/cli/commands/gateway/status.d.ts +0 -3
  104. package/dist/src/cli/commands/gateway/status.js +107 -24
  105. package/dist/src/cli/commands/gateway/status.js.map +1 -1
  106. package/dist/src/cli/commands/gateway.js +7 -1
  107. package/dist/src/cli/commands/gateway.js.map +1 -1
  108. package/dist/src/cli/commands/init.js +3 -3
  109. package/dist/src/cli/commands/update.js +19 -1
  110. package/dist/src/cli/commands/update.js.map +1 -1
  111. package/dist/src/cli/utils/gateway-client.d.ts +28 -0
  112. package/dist/src/cli/utils/gateway-client.js +115 -0
  113. package/dist/src/cli/utils/gateway-client.js.map +1 -0
  114. package/dist/src/config/index.js +2 -2
  115. package/dist/src/config/loader.js +1 -1
  116. package/dist/src/config/models-json.js +1 -1
  117. package/dist/src/config/paths-state.d.ts +4 -0
  118. package/dist/src/config/paths-state.js +9 -1
  119. package/dist/src/config/paths-state.js.map +1 -1
  120. package/dist/src/config/profile.js +2 -2
  121. package/dist/src/config/reload.d.ts +2 -0
  122. package/dist/src/config/reload.js +9 -1
  123. package/dist/src/config/reload.js.map +1 -1
  124. package/dist/src/config/rules.js +12 -2
  125. package/dist/src/config/rules.js.map +1 -1
  126. package/dist/src/cron/executor.js +2 -2
  127. package/dist/src/cron/persistence.js +1 -1
  128. package/dist/src/cron/run-log-store.js +1 -1
  129. package/dist/src/extensions/api.d.ts +6 -1
  130. package/dist/src/extensions/api.js +52 -1
  131. package/dist/src/extensions/api.js.map +1 -1
  132. package/dist/src/extensions/health.js +1 -1
  133. package/dist/src/extensions/loader.d.ts +6 -1
  134. package/dist/src/extensions/loader.js +21 -2
  135. package/dist/src/extensions/loader.js.map +1 -1
  136. package/dist/src/extensions/lockfile.js +1 -1
  137. package/dist/src/extensions/normalize-manifest.js +33 -0
  138. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  139. package/dist/src/extensions/sdk/index.d.ts +1 -1
  140. package/dist/src/extensions/sdk/index.js.map +1 -1
  141. package/dist/src/extensions/types/core.d.ts +35 -1
  142. package/dist/src/extensions/types/manifest.d.ts +14 -0
  143. package/dist/src/gateway/agents-admin.js +1 -1
  144. package/dist/src/gateway/hono/lib/config-payload.d.ts +3 -0
  145. package/dist/src/gateway/hono/lib/config-payload.js +1 -0
  146. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  147. package/dist/src/gateway/hono/oauth.js +1 -1
  148. package/dist/src/gateway/hono/routes/channels.js +111 -0
  149. package/dist/src/gateway/hono/routes/channels.js.map +1 -1
  150. package/dist/src/gateway/hono/routes/commands-skills.js +13 -2
  151. package/dist/src/gateway/hono/routes/commands-skills.js.map +1 -1
  152. package/dist/src/gateway/hono/routes/config.js +82 -1
  153. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  154. package/dist/src/gateway/hono/routes/public-gateway.js +17 -0
  155. package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
  156. package/dist/src/gateway/hono/routes/sessions.js +16 -0
  157. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  158. package/dist/src/gateway/hono/routes/status.js +31 -7
  159. package/dist/src/gateway/hono/routes/status.js.map +1 -1
  160. package/dist/src/gateway/hono/routes/update.js +118 -15
  161. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  162. package/dist/src/gateway/hono/routes/workspace.js +2 -2
  163. package/dist/src/gateway/hono/sse.js +2 -2
  164. package/dist/src/gateway/index.js +1 -1
  165. package/dist/src/gateway/server.js +3 -0
  166. package/dist/src/gateway/server.js.map +1 -1
  167. package/dist/src/gateway/service.d.ts +23 -0
  168. package/dist/src/gateway/service.js +111 -4
  169. package/dist/src/gateway/service.js.map +1 -1
  170. package/dist/src/gateway/workspace-heartbeat-path.js +1 -1
  171. package/dist/src/infra/update-check.js +54 -21
  172. package/dist/src/infra/update-check.js.map +1 -1
  173. package/dist/src/infra/update-lock.d.ts +13 -0
  174. package/dist/src/infra/update-lock.js +67 -0
  175. package/dist/src/infra/update-lock.js.map +1 -0
  176. package/dist/src/infra/update-runner.d.ts +6 -5
  177. package/dist/src/infra/update-runner.js +93 -13
  178. package/dist/src/infra/update-runner.js.map +1 -1
  179. package/dist/src/infra/update-startup.js +37 -11
  180. package/dist/src/infra/update-startup.js.map +1 -1
  181. package/dist/src/providers/index.js +2 -2
  182. package/dist/src/providers/model-registry.js +1 -1
  183. package/dist/src/session/config-store.js +1 -1
  184. package/dist/src/session/session-title.js +1 -1
  185. package/dist/src/session/store.js +3 -3
  186. package/dist/src/utils/logger/audit.js +1 -1
  187. package/dist/src/utils/logger/log-store.js +1 -1
  188. package/dist/src/utils/logger/rotation.js +1 -1
  189. package/dist/src/voice/tts/audio.js +1 -1
  190. package/package.json +1 -1
  191. package/dist/gateway/static/root/assets/channels-settings-DyNnMN1-.js +0 -9
  192. package/dist/gateway/static/root/assets/channels-settings-DyNnMN1-.js.map +0 -1
  193. package/dist/gateway/static/root/assets/index-BQNdJlkw.css +0 -1
  194. package/dist/gateway/static/root/assets/settings-page-CSIVMAJE.js +0 -2
  195. package/dist/gateway/static/root/assets/settings-page-CSIVMAJE.js.map +0 -1
@@ -1,14 +1,37 @@
1
+ import { createLogger } from "../utils/logger/index.js";
2
+ import { init_logger } from "../utils/logger.js";
1
3
  import { join } from "node:path";
2
- import { access } from "node:fs/promises";
4
+ import { access, readdir, unlink } from "node:fs/promises";
3
5
  import { spawn } from "node:child_process";
4
6
  //#region src/infra/update-runner.ts
7
+ init_logger();
8
+ const log = createLogger("UpdateRunner");
5
9
  const AUTO_UPDATE_TIMEOUT_MS = 2700 * 1e3;
6
- /**
7
- * Spawn a child process to execute `xopc update --yes --channel <channel> --json`.
8
- * Uses the current runtime (process.execPath) and entry point (process.argv[1]) to
9
- * ensure the correct Node.js version and binary path are used.
10
- */
11
- async function runAutoUpdateCommand(params) {
10
+ function createLineEmitter(onProgress) {
11
+ let bufOut = "";
12
+ let bufErr = "";
13
+ const flush = (buf, source) => {
14
+ const parts = buf.split("\n");
15
+ const rest = parts.pop() ?? "";
16
+ for (const line of parts) if (line.length) onProgress?.(line, source);
17
+ return rest;
18
+ };
19
+ return {
20
+ pushStdout(chunk) {
21
+ bufOut += chunk;
22
+ bufOut = flush(bufOut, "stdout");
23
+ },
24
+ pushStderr(chunk) {
25
+ bufErr += chunk;
26
+ bufErr = flush(bufErr, "stderr");
27
+ },
28
+ flushEnd() {
29
+ if (bufOut.length) onProgress?.(bufOut, "stdout");
30
+ if (bufErr.length) onProgress?.(bufErr, "stderr");
31
+ }
32
+ };
33
+ }
34
+ async function spawnUpdateCommand(params) {
12
35
  const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;
13
36
  const argv = await resolveUpdateCommandArgv([
14
37
  "update",
@@ -32,32 +55,60 @@ async function runAutoUpdateCommand(params) {
32
55
  });
33
56
  let stdout = "";
34
57
  let stderr = "";
58
+ let stdoutTruncated = false;
59
+ let stderrTruncated = false;
60
+ const lineEmitter = createLineEmitter(params.onProgress);
35
61
  const timeoutId = setTimeout(() => {
36
62
  child.kill("SIGTERM");
37
63
  }, timeoutMs);
38
64
  const finish = (result) => {
39
65
  clearTimeout(timeoutId);
66
+ lineEmitter.flushEnd();
40
67
  resolve(result);
41
68
  };
42
69
  child.stdout?.on("data", (chunk) => {
43
- stdout += chunk.toString();
44
- if (stdout.length > 64e3) stdout = stdout.slice(-32e3);
70
+ const text = chunk.toString();
71
+ stdout += text;
72
+ lineEmitter.pushStdout(text);
73
+ if (stdout.length > 64e3) {
74
+ if (!stdoutTruncated) {
75
+ log.warn("Update command stdout exceeded 64KB; truncating");
76
+ stdoutTruncated = true;
77
+ }
78
+ stdout = stdout.slice(-32e3);
79
+ }
45
80
  });
46
81
  child.stderr?.on("data", (chunk) => {
47
- stderr += chunk.toString();
48
- if (stderr.length > 64e3) stderr = stderr.slice(-32e3);
82
+ const text = chunk.toString();
83
+ stderr += text;
84
+ lineEmitter.pushStderr(text);
85
+ if (stderr.length > 64e3) {
86
+ if (!stderrTruncated) {
87
+ log.warn("Update command stderr exceeded 64KB; truncating");
88
+ stderrTruncated = true;
89
+ }
90
+ stderr = stderr.slice(-32e3);
91
+ }
49
92
  });
50
93
  child.on("error", (err) => {
94
+ log.error({ err }, `Update subprocess spawn error: ${err.message}`);
51
95
  finish({
52
96
  ok: false,
53
97
  exitCode: null,
54
- reason: String(err),
98
+ reason: err.message,
55
99
  stdout,
56
100
  stderr
57
101
  });
58
102
  });
59
103
  child.on("exit", (code, signal) => {
60
104
  if (signal === "SIGTERM" || code === 143) {
105
+ log.warn({
106
+ code,
107
+ signal
108
+ }, "Update subprocess timed out; attempting lock file cleanup");
109
+ cleanupNpmLockFiles(params.root).catch((cleanupErr) => {
110
+ log.warn({ err: cleanupErr }, "Failed to clean npm lock files after timeout");
111
+ });
61
112
  finish({
62
113
  ok: false,
63
114
  exitCode: code,
@@ -77,6 +128,24 @@ async function runAutoUpdateCommand(params) {
77
128
  });
78
129
  });
79
130
  }
131
+ async function runAutoUpdateCommand(params) {
132
+ return spawnUpdateCommand(params);
133
+ }
134
+ async function runAutoUpdateCommandWithProgress(params) {
135
+ return spawnUpdateCommand(params);
136
+ }
137
+ async function cleanupNpmLockFiles(root) {
138
+ if (!root) return;
139
+ let entries;
140
+ try {
141
+ entries = await readdir(root);
142
+ } catch {
143
+ return;
144
+ }
145
+ for (const entry of entries) if (entry.startsWith(".package-lock")) try {
146
+ await unlink(join(root, entry));
147
+ } catch {}
148
+ }
80
149
  /**
81
150
  * Resolve the argv array for spawning the update command.
82
151
  *
@@ -104,9 +173,20 @@ async function resolveUpdateCommandArgv(baseArgs, root) {
104
173
  ];
105
174
  } catch {}
106
175
  }
176
+ log.warn("Falling back to bare `xopc` command — version mismatch possible");
177
+ try {
178
+ const { execSync } = await import("node:child_process");
179
+ const whichResult = execSync(process.platform === "win32" ? "where xopc" : "which xopc", {
180
+ encoding: "utf-8",
181
+ timeout: 3e3
182
+ }).trim();
183
+ if (whichResult) log.info({ resolvedPath: whichResult.split(/\r?\n/)[0]?.trim() }, "Resolved xopc via PATH");
184
+ } catch {
185
+ log.warn("Could not resolve `xopc` in PATH; update command may fail");
186
+ }
107
187
  return ["xopc", ...baseArgs];
108
188
  }
109
189
  //#endregion
110
- export { runAutoUpdateCommand };
190
+ export { runAutoUpdateCommand, runAutoUpdateCommandWithProgress };
111
191
 
112
192
  //# sourceMappingURL=update-runner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts\n\nimport { spawn } from 'node:child_process';\nimport { access } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport type { UpdateChannel } from './update-channels.js';\n\nconst AUTO_UPDATE_TIMEOUT_MS = 45 * 60 * 1000; // 45 minutes\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n};\n\n/**\n * Spawn a child process to execute `xopc update --yes --channel <channel> --json`.\n * Uses the current runtime (process.execPath) and entry point (process.argv[1]) to\n * ensure the correct Node.js version and binary path are used.\n */\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n}): Promise<AutoUpdateResult> {\n const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;\n const baseArgs = ['update', '--yes', '--channel', params.channel, '--json'];\n\n const argv = await resolveUpdateCommandArgv(baseArgs, params.root ?? null);\n\n return new Promise<AutoUpdateResult>((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n env: {\n ...process.env,\n XOPC_AUTO_UPDATE: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n });\n\n let stdout = '';\n let stderr = '';\n\n const timeoutId = setTimeout(() => {\n child.kill('SIGTERM');\n }, timeoutMs);\n\n const finish = (result: AutoUpdateResult) => {\n clearTimeout(timeoutId);\n resolve(result);\n };\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n if (stdout.length > 64_000) stdout = stdout.slice(-32_000);\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n if (stderr.length > 64_000) stderr = stderr.slice(-32_000);\n });\n\n child.on('error', (err) => {\n finish({ ok: false, exitCode: null, reason: String(err), stdout, stderr });\n });\n\n child.on('exit', (code, signal) => {\n if (signal === 'SIGTERM' || code === 143) {\n finish({ ok: false, exitCode: code, reason: 'timeout', stdout, stderr });\n return;\n }\n finish({\n ok: code === 0,\n exitCode: code,\n reason: code === 0 ? undefined : 'non-zero-exit',\n stdout,\n stderr,\n });\n });\n });\n}\n\n/**\n * Resolve the argv array for spawning the update command.\n *\n * Priority:\n * 1. process.execPath + process.argv[1] (current runtime + entry point)\n * 2. process.execPath + known dist entry points in root\n * 3. Fallback to bare `xopc` (assumes global install)\n */\nasync function resolveUpdateCommandArgv(\n baseArgs: string[],\n root: string | null,\n): Promise<string[]> {\n const execPath = process.execPath?.trim();\n const argv1 = process.argv[1]?.trim();\n\n // Best case: we know both the runtime and the entry point\n if (execPath && argv1) {\n return [execPath, argv1, ...baseArgs];\n }\n\n // Try known entry points in the package root\n if (execPath && root) {\n const candidates = [join(root, 'dist/src/cli/index.js'), join(root, 'dist/index.js')];\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return [execPath, candidate, ...baseArgs];\n } catch {\n // try next\n }\n }\n }\n\n // Fallback: rely on global PATH\n return ['xopc', ...baseArgs];\n}\n"],"mappings":";;;;AAQA,MAAM,yBAAyB,OAAU;;;;;;AAezC,eAAsB,qBAAqB,QAIb;CAC5B,MAAM,YAAY,OAAO,aAAa;CAGtC,MAAM,OAAO,MAAM,yBAAyB;EAF1B;EAAU;EAAS;EAAa,OAAO;EAAS;EAEd,EAAE,OAAO,QAAQ,KAAK;AAE1E,QAAO,IAAI,SAA2B,YAAY;EAChD,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,MAAM,EAAE,EAAE;GAC1C,KAAK;IACH,GAAG,QAAQ;IACX,kBAAkB;IACnB;GACD,OAAO;IAAC;IAAU;IAAQ;IAAO;GACjC,UAAU;GACX,CAAC;EAEF,IAAI,SAAS;EACb,IAAI,SAAS;EAEb,MAAM,YAAY,iBAAiB;AACjC,SAAM,KAAK,UAAU;KACpB,UAAU;EAEb,MAAM,UAAU,WAA6B;AAC3C,gBAAa,UAAU;AACvB,WAAQ,OAAO;;AAGjB,QAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,aAAU,MAAM,UAAU;AAC1B,OAAI,OAAO,SAAS,KAAQ,UAAS,OAAO,MAAM,MAAQ;IAC1D;AACF,QAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,aAAU,MAAM,UAAU;AAC1B,OAAI,OAAO,SAAS,KAAQ,UAAS,OAAO,MAAM,MAAQ;IAC1D;AAEF,QAAM,GAAG,UAAU,QAAQ;AACzB,UAAO;IAAE,IAAI;IAAO,UAAU;IAAM,QAAQ,OAAO,IAAI;IAAE;IAAQ;IAAQ,CAAC;IAC1E;AAEF,QAAM,GAAG,SAAS,MAAM,WAAW;AACjC,OAAI,WAAW,aAAa,SAAS,KAAK;AACxC,WAAO;KAAE,IAAI;KAAO,UAAU;KAAM,QAAQ;KAAW;KAAQ;KAAQ,CAAC;AACxE;;AAEF,UAAO;IACL,IAAI,SAAS;IACb,UAAU;IACV,QAAQ,SAAS,IAAI,KAAA,IAAY;IACjC;IACA;IACD,CAAC;IACF;GACF;;;;;;;;;;AAWJ,eAAe,yBACb,UACA,MACmB;CACnB,MAAM,WAAW,QAAQ,UAAU,MAAM;CACzC,MAAM,QAAQ,QAAQ,KAAK,IAAI,MAAM;AAGrC,KAAI,YAAY,MACd,QAAO;EAAC;EAAU;EAAO,GAAG;EAAS;AAIvC,KAAI,YAAY,MAAM;EACpB,MAAM,aAAa,CAAC,KAAK,MAAM,wBAAwB,EAAE,KAAK,MAAM,gBAAgB,CAAC;AACrF,OAAK,MAAM,aAAa,WACtB,KAAI;AACF,SAAM,OAAO,UAAU;AACvB,UAAO;IAAC;IAAU;IAAW,GAAG;IAAS;UACnC;;AAOZ,QAAO,CAAC,QAAQ,GAAG,SAAS"}
1
+ {"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts\n\nimport { spawn } from 'node:child_process';\nimport { access, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport type { UpdateChannel } from './update-channels.js';\n\nconst log = createLogger('UpdateRunner');\n\nconst AUTO_UPDATE_TIMEOUT_MS = 45 * 60 * 1000; // 45 minutes\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n};\n\ntype SpawnUpdateParams = {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n};\n\nfunction createLineEmitter(\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>,\n) {\n let bufOut = '';\n let bufErr = '';\n const flush = (buf: string, source: 'stdout' | 'stderr'): string => {\n const parts = buf.split('\\n');\n const rest = parts.pop() ?? '';\n for (const line of parts) {\n if (line.length) void onProgress?.(line, source);\n }\n return rest;\n };\n return {\n pushStdout(chunk: string) {\n bufOut += chunk;\n bufOut = flush(bufOut, 'stdout');\n },\n pushStderr(chunk: string) {\n bufErr += chunk;\n bufErr = flush(bufErr, 'stderr');\n },\n flushEnd() {\n if (bufOut.length) void onProgress?.(bufOut, 'stdout');\n if (bufErr.length) void onProgress?.(bufErr, 'stderr');\n },\n };\n}\n\nasync function spawnUpdateCommand(params: SpawnUpdateParams): Promise<AutoUpdateResult> {\n const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;\n const baseArgs = ['update', '--yes', '--channel', params.channel, '--json'];\n const argv = await resolveUpdateCommandArgv(baseArgs, params.root ?? null);\n\n return new Promise<AutoUpdateResult>((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n env: {\n ...process.env,\n XOPC_AUTO_UPDATE: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n });\n\n let stdout = '';\n let stderr = '';\n let stdoutTruncated = false;\n let stderrTruncated = false;\n\n const lineEmitter = createLineEmitter(params.onProgress);\n\n const timeoutId = setTimeout(() => {\n child.kill('SIGTERM');\n }, timeoutMs);\n\n const finish = (result: AutoUpdateResult) => {\n clearTimeout(timeoutId);\n lineEmitter.flushEnd();\n resolve(result);\n };\n\n child.stdout?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stdout += text;\n lineEmitter.pushStdout(text);\n if (stdout.length > 64_000) {\n if (!stdoutTruncated) {\n log.warn('Update command stdout exceeded 64KB; truncating');\n stdoutTruncated = true;\n }\n stdout = stdout.slice(-32_000);\n }\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stderr += text;\n lineEmitter.pushStderr(text);\n if (stderr.length > 64_000) {\n if (!stderrTruncated) {\n log.warn('Update command stderr exceeded 64KB; truncating');\n stderrTruncated = true;\n }\n stderr = stderr.slice(-32_000);\n }\n });\n\n child.on('error', (err) => {\n log.error({ err }, `Update subprocess spawn error: ${err.message}`);\n finish({ ok: false, exitCode: null, reason: err.message, stdout, stderr });\n });\n\n child.on('exit', (code, signal) => {\n if (signal === 'SIGTERM' || code === 143) {\n log.warn({ code, signal }, 'Update subprocess timed out; attempting lock file cleanup');\n void cleanupNpmLockFiles(params.root).catch((cleanupErr) => {\n log.warn({ err: cleanupErr }, 'Failed to clean npm lock files after timeout');\n });\n finish({ ok: false, exitCode: code, reason: 'timeout', stdout, stderr });\n return;\n }\n finish({\n ok: code === 0,\n exitCode: code,\n reason: code === 0 ? undefined : 'non-zero-exit',\n stdout,\n stderr,\n });\n });\n });\n}\n\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nexport async function runAutoUpdateCommandWithProgress(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nasync function cleanupNpmLockFiles(root: string | null | undefined): Promise<void> {\n if (!root) return;\n let entries: string[];\n try {\n entries = await readdir(root);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.startsWith('.package-lock')) {\n try {\n await unlink(join(root, entry));\n } catch {\n // best-effort\n }\n }\n }\n}\n\n/**\n * Resolve the argv array for spawning the update command.\n *\n * Priority:\n * 1. process.execPath + process.argv[1] (current runtime + entry point)\n * 2. process.execPath + known dist entry points in root\n * 3. Fallback to bare `xopc` (assumes global install)\n */\nasync function resolveUpdateCommandArgv(\n baseArgs: string[],\n root: string | null,\n): Promise<string[]> {\n const execPath = process.execPath?.trim();\n const argv1 = process.argv[1]?.trim();\n\n if (execPath && argv1) {\n return [execPath, argv1, ...baseArgs];\n }\n\n if (execPath && root) {\n const candidates = [join(root, 'dist/src/cli/index.js'), join(root, 'dist/index.js')];\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return [execPath, candidate, ...baseArgs];\n } catch {\n // try next\n }\n }\n }\n\n log.warn('Falling back to bare `xopc` command — version mismatch possible');\n try {\n const { execSync } = await import('node:child_process');\n const cmd = process.platform === 'win32' ? 'where xopc' : 'which xopc';\n const whichResult = execSync(cmd, { encoding: 'utf-8', timeout: 3000 }).trim();\n if (whichResult) {\n log.info({ resolvedPath: whichResult.split(/\\r?\\n/)[0]?.trim() }, 'Resolved xopc via PATH');\n }\n } catch {\n log.warn('Could not resolve `xopc` in PATH; update command may fail');\n }\n\n return ['xopc', ...baseArgs];\n}\n"],"mappings":"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"}
@@ -1,19 +1,18 @@
1
1
  import { PACKAGE_VERSION, init_package_version } from "../package-version.js";
2
+ import { init_paths_state, resolveUpdateCheckStatePath } from "../config/paths-state.js";
2
3
  import { createLogger } from "../utils/logger/index.js";
3
4
  import { init_logger } from "../utils/logger.js";
4
- import { resolveStateDir } from "../config/paths-state.js";
5
- import { init_paths } from "../config/paths.js";
5
+ import { acquireUpdateLock } from "./update-lock.js";
6
6
  import { normalizeUpdateChannel } from "./update-channels.js";
7
7
  import { compareSemver, detectInstallKind, resolveNpmChannelTag, resolvePackageRoot } from "./update-check.js";
8
- import { dirname, join } from "node:path";
9
- import { mkdir, readFile, writeFile } from "node:fs/promises";
10
- import { createHash, randomUUID } from "node:crypto";
8
+ import { dirname } from "node:path";
9
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
10
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
11
11
  //#region src/infra/update-startup.ts
12
- init_paths();
12
+ init_paths_state();
13
13
  init_package_version();
14
14
  init_logger();
15
15
  const log = createLogger("UpdateCheck");
16
- const UPDATE_CHECK_FILENAME = "update-check.json";
17
16
  const CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
18
17
  const ONE_HOUR_MS = 3600 * 1e3;
19
18
  let updateAvailableCache = null;
@@ -25,14 +24,31 @@ async function readState(statePath) {
25
24
  try {
26
25
  const raw = await readFile(statePath, "utf-8");
27
26
  const parsed = JSON.parse(raw);
28
- return parsed && typeof parsed === "object" ? parsed : {};
29
- } catch {
27
+ if (!parsed || typeof parsed !== "object") {
28
+ log.warn({ statePath }, "Update check state file contains non-object; resetting");
29
+ return {};
30
+ }
31
+ return parsed;
32
+ } catch (err) {
33
+ if (err.code !== "ENOENT") log.warn({
34
+ err,
35
+ statePath
36
+ }, "Failed to read update check state; resetting");
30
37
  return {};
31
38
  }
32
39
  }
33
40
  async function writeState(statePath, state) {
34
41
  await mkdir(dirname(statePath), { recursive: true });
35
- await writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
42
+ const tmpPath = `${statePath}.${randomBytes(4).toString("hex")}.tmp`;
43
+ try {
44
+ await writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8");
45
+ await rename(tmpPath, statePath);
46
+ } catch (err) {
47
+ try {
48
+ await unlink(tmpPath);
49
+ } catch {}
50
+ throw err;
51
+ }
36
52
  }
37
53
  function resolveCheckIntervalMs(config) {
38
54
  const auto = config.update?.auto;
@@ -59,7 +75,7 @@ async function runGatewayUpdateCheck(params) {
59
75
  const autoEnabled = config.update?.auto?.enabled ?? false;
60
76
  const shouldCheckHints = config.update?.checkOnStart !== false;
61
77
  if (!force && !shouldCheckHints && !autoEnabled) return;
62
- const statePath = join(resolveStateDir(), UPDATE_CHECK_FILENAME);
78
+ const statePath = resolveUpdateCheckStatePath();
63
79
  const state = await readState(statePath);
64
80
  const now = Date.now();
65
81
  const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;
@@ -184,6 +200,14 @@ async function handleAutoUpdate(params) {
184
200
  version,
185
201
  tag
186
202
  }, "Starting auto-update");
203
+ const lock = await acquireUpdateLock("auto");
204
+ if (!lock) {
205
+ log.info({
206
+ version,
207
+ tag
208
+ }, "Auto-update skipped: another update is in progress");
209
+ return;
210
+ }
187
211
  try {
188
212
  const { runAutoUpdateCommand } = await import("./update-runner.js");
189
213
  const result = await runAutoUpdateCommand({
@@ -211,6 +235,8 @@ async function handleAutoUpdate(params) {
211
235
  channel,
212
236
  version
213
237
  }, "Auto-update command threw");
238
+ } finally {
239
+ await lock.release();
214
240
  }
215
241
  }
216
242
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\n\nimport type { Config } from '../config/schema.js';\nimport { resolveStateDir } from '../config/paths.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst UPDATE_CHECK_FILENAME = 'update-check.json';\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n return parsed && typeof parsed === 'object' ? parsed : {};\n } catch {\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await mkdir(dirname(statePath), { recursive: true });\n await writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8');\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const stateDir = resolveStateDir();\n const statePath = join(stateDir, UPDATE_CHECK_FILENAME);\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({ channel, root });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n log.info({ channel, version, tag }, 'Auto-update applied successfully');\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomBytes, randomUUID } from 'node:crypto';\nimport { readFile, writeFile, mkdir, rename, unlink } from 'node:fs/promises';\nimport { dirname } from 'node:path';\n\nimport type { Config } from '../config/schema.js';\nimport { resolveUpdateCheckStatePath } from '../config/paths-state.js';\nimport { acquireUpdateLock } from './update-lock.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n if (!parsed || typeof parsed !== 'object') {\n log.warn({ statePath }, 'Update check state file contains non-object; resetting');\n return {};\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn({ err, statePath }, 'Failed to read update check state; resetting');\n }\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await mkdir(dirname(statePath), { recursive: true });\n const tmpPath = `${statePath}.${randomBytes(4).toString('hex')}.tmp`;\n try {\n await writeFile(tmpPath, JSON.stringify(state, null, 2), 'utf-8');\n await rename(tmpPath, statePath);\n } catch (err) {\n try {\n await unlink(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const statePath = resolveUpdateCheckStatePath();\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n const lock = await acquireUpdateLock('auto');\n if (!lock) {\n log.info({ version, tag }, 'Auto-update skipped: another update is in progress');\n return;\n }\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({ channel, root });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n log.info({ channel, version, tag }, 'Auto-update applied successfully');\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { __esmMin, __exportAll } from "../../_virtual/_rolldown/runtime.js";
2
- import { PROVIDER_ENV_MAP, getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
3
- import { CredentialResolver, hasCredentials, init_credentials, resolveApiKey } from "../auth/credentials.js";
4
2
  import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
3
+ import { PROVIDER_ENV_MAP, getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
5
4
  import { ModelRegistry, getModelRegistry, init_model_registry, resetModelRegistry } from "./model-registry.js";
5
+ import { CredentialResolver, hasCredentials, init_credentials, resolveApiKey } from "../auth/credentials.js";
6
6
  import { getProviderRegistry, init_plugin_registry } from "./plugin-registry.js";
7
7
  import { getModel, getModels, getProviders } from "@mariozechner/pi-ai";
8
8
  //#region src/providers/index.ts
@@ -1,10 +1,10 @@
1
1
  import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
2
2
  import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
- import { getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
5
4
  import { resolveModelsJsonPath } from "../config/paths.js";
6
5
  import { init_resolve_config_value, resolveConfigValue, resolveHeaders } from "../config/resolve-config-value.js";
7
6
  import { getDefaultModelValues, init_models_json, validateModelsConfig } from "../config/models-json.js";
7
+ import { getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
8
8
  import { existsSync, readFileSync } from "fs";
9
9
  import { getModels, getProviders } from "@mariozechner/pi-ai";
10
10
  //#region src/providers/model-registry.ts
@@ -1,8 +1,8 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { mkdir, readFile, writeFile } from "fs/promises";
4
3
  import { join } from "path";
5
4
  import { existsSync } from "fs";
5
+ import { mkdir, readFile, writeFile } from "fs/promises";
6
6
  //#region src/session/config-store.ts
7
7
  /**
8
8
  * Session Config Store
@@ -1,6 +1,6 @@
1
+ import { init_session_key, isCronSessionKey, parseSessionKey } from "../routing/session-key.js";
1
2
  import { createLogger } from "../utils/logger/index.js";
2
3
  import { init_logger } from "../utils/logger.js";
3
- import { init_session_key, isCronSessionKey, parseSessionKey } from "../routing/session-key.js";
4
4
  import { init_providers, resolveModel } from "../providers/index.js";
5
5
  import { stripInboundFileMetadataFromText } from "../channels/attachments/inbound-persist.js";
6
6
  import { stripEnvelopeTimestampPrefix } from "../channels/envelope-timestamp.js";
@@ -1,17 +1,17 @@
1
+ import { init_agent_scope, resolveDefaultAgentId } from "../agent/agent-scope.js";
2
+ import { init_session_key, parseSessionKey } from "../routing/session-key.js";
1
3
  import { createLogger } from "../utils/logger/index.js";
2
4
  import { init_logger } from "../utils/logger.js";
3
- import { init_agent_scope, resolveDefaultAgentId } from "../agent/agent-scope.js";
4
5
  import { FILENAMES, init_paths, resolveSessionsDir } from "../config/paths.js";
5
- import { init_session_key, parseSessionKey } from "../routing/session-key.js";
6
6
  import { invalidateSessionSearchIndexCache } from "./search-index-cache.js";
7
7
  import { resolveSessionShardRelativePath } from "./shard-path.js";
8
8
  import "./types.js";
9
9
  import { SessionCompactor } from "../agent/memory/compaction.js";
10
10
  import { SlidingWindow } from "../agent/memory/window.js";
11
11
  import { cleanTrailingErrors, hasProblematicMessages } from "../agent/memory/message-sanitizer.js";
12
- import { mkdir, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
13
12
  import { basename, join } from "path";
14
13
  import { existsSync } from "fs";
14
+ import { mkdir, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
15
15
  //#region src/session/store.ts
16
16
  init_paths();
17
17
  init_agent_scope();
@@ -1,8 +1,8 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { getLogDir, init_config } from "./config.js";
3
- import { appendFile } from "fs/promises";
4
3
  import path from "path";
5
4
  import { existsSync, mkdirSync } from "fs";
5
+ import { appendFile } from "fs/promises";
6
6
  //#region src/utils/logger/audit.ts
7
7
  /**
8
8
  * Audit Log
@@ -1,6 +1,6 @@
1
- import { readFile } from "fs/promises";
2
1
  import { basename, join } from "path";
3
2
  import { createReadStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "fs";
3
+ import { readFile } from "fs/promises";
4
4
  import { gunzip } from "zlib";
5
5
  import { promisify } from "util";
6
6
  import { createInterface } from "readline";
@@ -1,8 +1,8 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { config, getLogDir, init_config } from "./config.js";
3
- import { writeFile } from "fs/promises";
4
3
  import { join } from "path";
5
4
  import { readFileSync, readdirSync, statSync, unlinkSync } from "fs";
5
+ import { writeFile } from "fs/promises";
6
6
  import { gzip } from "zlib";
7
7
  import { promisify } from "util";
8
8
  //#region src/utils/logger/rotation.ts
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
- import { unlink, writeFile } from "fs/promises";
4
3
  import { join } from "path";
4
+ import { unlink, writeFile } from "fs/promises";
5
5
  import { spawn } from "child_process";
6
6
  import { tmpdir } from "os";
7
7
  //#region src/voice/tts/audio.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Personal AI assistant: CLI, gateway (HTTP/WebSocket + React console), Telegram and WeChat (Weixin) channels — TypeScript, 20+ LLM providers via pi-ai, extensions and skills.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",