@code-yeongyu/senpi 2026.9.28-5 → 2026.9.28-7

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 (156) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/bundle/chunks/{anthropic-messages-GG4ZKS6A.js → anthropic-messages-QOQJCY3F.js} +1 -1
  3. package/dist/bundle/chunks/{app-server-command-3AJWPHKJ.js → app-server-command-OUTRKHMK.js} +8 -8
  4. package/dist/bundle/chunks/{chunk-XYGXUFMG.js → chunk-3HCYHGK2.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-EXUI5D5V.js → chunk-56COAYOU.js} +9 -9
  6. package/dist/bundle/chunks/{chunk-MMHQLGF2.js → chunk-AIM2YESB.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-4BDFNRBE.js → chunk-B32PXL7U.js} +1 -1
  8. package/dist/bundle/chunks/chunk-CEMOA3TF.js +2 -0
  9. package/dist/bundle/chunks/{chunk-XNIX26QC.js → chunk-DB2X7SOZ.js} +2 -2
  10. package/dist/bundle/chunks/chunk-E5F5SVZF.js +5 -0
  11. package/dist/bundle/chunks/{chunk-OHJLQ7BY.js → chunk-F2YU5BZG.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-BQGVC47Y.js → chunk-FILEUW2D.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-JGRPPPHS.js → chunk-JASECXGC.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-YLIS5N3W.js → chunk-JK5LVAOU.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-KDZAVHA7.js → chunk-L4OOEWAT.js} +2 -2
  16. package/dist/bundle/chunks/{chunk-Z5NVK72W.js → chunk-LM2UMEX6.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-C5I2LUOI.js → chunk-SQUUL72H.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-LEAJXPT2.js → chunk-TOCWAEQB.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-YEMM3LPR.js → chunk-VW3QZSX3.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-7SFG425F.js → chunk-Y4CH7LYI.js} +2 -2
  21. package/dist/bundle/chunks/chunk-YJUO37UX.js +2 -0
  22. package/dist/bundle/chunks/{cli-main-NUSBF3HE.js → cli-main-26P4V2BK.js} +1 -1
  23. package/dist/bundle/chunks/github-copilot.js +1 -1
  24. package/dist/bundle/chunks/{help-fast-path-A4S4KOQ6.js → help-fast-path-BOMUI7U3.js} +1 -1
  25. package/dist/bundle/chunks/{host-command-D2CHSATG.js → host-command-ZVXNTXMN.js} +1 -1
  26. package/dist/bundle/chunks/host-lifecycle-MPLSJ5NB.js +3 -0
  27. package/dist/bundle/chunks/host-lifecycle.js +3 -3
  28. package/dist/bundle/chunks/{interactive-host-runtime-AAK47HIV.js → interactive-host-runtime-Q5EORM6B.js} +1 -1
  29. package/dist/bundle/chunks/{interactive-mode-LSTD6EUQ.js → interactive-mode-OVSKUTVC.js} +1 -1
  30. package/dist/bundle/chunks/{list-tips-S4U7TKER.js → list-tips-ARO4HRVK.js} +1 -1
  31. package/dist/bundle/chunks/{multi-session-host-5WNZ7XRV.js → multi-session-host-LJNXZRFA.js} +7 -7
  32. package/dist/bundle/chunks/{openai-completions-DVEOAQK4.js → openai-completions-BHE6TMUP.js} +1 -1
  33. package/dist/bundle/chunks/{openai-responses-7H2ADNW4.js → openai-responses-OGQ5QORF.js} +1 -1
  34. package/dist/bundle/chunks/{package-manager-cli-3HZYQYVX.js → package-manager-cli-MJCRY2JU.js} +1 -1
  35. package/dist/bundle/chunks/{rotation-stream-F36RJ6VP.js → rotation-stream-Q2EVDMUU.js} +1 -1
  36. package/dist/bundle/chunks/{rpc-mode-GMPBEVSL.js → rpc-mode-LY4HAZ5G.js} +1 -1
  37. package/dist/bundle/chunks/{schedule-command-BYWZGYOJ.js → schedule-command-3ANAEUY6.js} +1 -1
  38. package/dist/bundle/chunks/{session-picker-6VU2F5VF.js → session-picker-DRM7AGPY.js} +1 -1
  39. package/dist/bundle/chunks/session-worker.js +37 -37
  40. package/dist/bundle/cli.js +1 -1
  41. package/dist/bundle/index.js +1 -1
  42. package/dist/bundle/rpc-entry.js +1 -1
  43. package/dist/cli/args.js +2 -2
  44. package/dist/cli/args.js.map +1 -1
  45. package/dist/core/extensions/builtin/btw/index.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/btw/index.js +3 -1
  47. package/dist/core/extensions/builtin/btw/index.js.map +1 -1
  48. package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
  49. package/dist/core/extensions/builtin/prompt-preset/presets.js +2 -1
  50. package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
  51. package/dist/core/extensions/builtin/websearch/websearch/native.d.ts +1 -0
  52. package/dist/core/extensions/builtin/websearch/websearch/native.d.ts.map +1 -1
  53. package/dist/core/extensions/builtin/websearch/websearch/native.js +8 -1
  54. package/dist/core/extensions/builtin/websearch/websearch/native.js.map +1 -1
  55. package/dist/main.d.ts +2 -2
  56. package/dist/main.d.ts.map +1 -1
  57. package/dist/main.js +25 -6
  58. package/dist/main.js.map +1 -1
  59. package/dist/modes/app-server/cli-args.d.ts +3 -0
  60. package/dist/modes/app-server/cli-args.d.ts.map +1 -1
  61. package/dist/modes/app-server/cli-args.js +25 -4
  62. package/dist/modes/app-server/cli-args.js.map +1 -1
  63. package/dist/modes/app-server/daemon/probe.d.ts +1 -0
  64. package/dist/modes/app-server/daemon/probe.d.ts.map +1 -1
  65. package/dist/modes/app-server/daemon/probe.js +4 -1
  66. package/dist/modes/app-server/daemon/probe.js.map +1 -1
  67. package/dist/modes/app-server/daemon/spawn.d.ts +26 -0
  68. package/dist/modes/app-server/daemon/spawn.d.ts.map +1 -0
  69. package/dist/modes/app-server/daemon/spawn.js +112 -0
  70. package/dist/modes/app-server/daemon/spawn.js.map +1 -0
  71. package/dist/modes/app-server/daemon.d.ts.map +1 -1
  72. package/dist/modes/app-server/daemon.js +14 -112
  73. package/dist/modes/app-server/daemon.js.map +1 -1
  74. package/dist/modes/app-server/extension-paths.d.ts +7 -0
  75. package/dist/modes/app-server/extension-paths.d.ts.map +1 -0
  76. package/dist/modes/app-server/extension-paths.js +10 -0
  77. package/dist/modes/app-server/extension-paths.js.map +1 -0
  78. package/dist/modes/app-server/index.d.ts.map +1 -1
  79. package/dist/modes/app-server/index.js +4 -1
  80. package/dist/modes/app-server/index.js.map +1 -1
  81. package/dist/modes/app-server/runtime.d.ts +5 -1
  82. package/dist/modes/app-server/runtime.d.ts.map +1 -1
  83. package/dist/modes/app-server/runtime.js +19 -3
  84. package/dist/modes/app-server/runtime.js.map +1 -1
  85. package/dist/modes/interactive/tips/catalog/computer-tips.js +1 -1
  86. package/dist/modes/interactive/tips/catalog/computer-tips.js.map +1 -1
  87. package/dist/modes/rpc/custom-capability.d.ts +5 -0
  88. package/dist/modes/rpc/custom-capability.d.ts.map +1 -1
  89. package/dist/modes/rpc/custom-capability.js +5 -0
  90. package/dist/modes/rpc/custom-capability.js.map +1 -1
  91. package/dist/modes/rpc/host-identity-env.d.ts +20 -0
  92. package/dist/modes/rpc/host-identity-env.d.ts.map +1 -0
  93. package/dist/modes/rpc/host-identity-env.js +20 -0
  94. package/dist/modes/rpc/host-identity-env.js.map +1 -0
  95. package/dist/modes/rpc/host-lifecycle.js +1 -1
  96. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  97. package/dist/modes/rpc/host-observe-request.d.ts +2 -0
  98. package/dist/modes/rpc/host-observe-request.d.ts.map +1 -1
  99. package/dist/modes/rpc/host-observe-request.js +4 -0
  100. package/dist/modes/rpc/host-observe-request.js.map +1 -1
  101. package/dist/modes/rpc/host-spawn-environment.js +1 -1
  102. package/dist/modes/rpc/host-spawn-environment.js.map +1 -1
  103. package/dist/modes/rpc/host-warm.d.ts +57 -0
  104. package/dist/modes/rpc/host-warm.d.ts.map +1 -0
  105. package/dist/modes/rpc/host-warm.js +125 -0
  106. package/dist/modes/rpc/host-warm.js.map +1 -0
  107. package/dist/modes/rpc/protocol-identity.d.ts +2 -11
  108. package/dist/modes/rpc/protocol-identity.d.ts.map +1 -1
  109. package/dist/modes/rpc/protocol-identity.js +2 -11
  110. package/dist/modes/rpc/protocol-identity.js.map +1 -1
  111. package/dist/modes/rpc/rpc-types.d.ts +24 -1
  112. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  113. package/dist/modes/rpc/rpc-types.js +2 -0
  114. package/dist/modes/rpc/rpc-types.js.map +1 -1
  115. package/dist/modes/rpc/session-command-router.d.ts +1 -1
  116. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  117. package/dist/modes/rpc/session-command-router.js +12 -1
  118. package/dist/modes/rpc/session-command-router.js.map +1 -1
  119. package/dist/modes/rpc/session-registry.d.ts +6 -2
  120. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  121. package/dist/modes/rpc/session-registry.js +6 -0
  122. package/dist/modes/rpc/session-registry.js.map +1 -1
  123. package/docs/app-server-daemon.md +1 -1
  124. package/docs/app-server.md +3 -0
  125. package/docs/rpc.md +48 -2
  126. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  127. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  128. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  129. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.d.ts +19 -0
  130. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.d.ts.map +1 -0
  131. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.js +48 -0
  132. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.js.map +1 -0
  133. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.js +3 -0
  135. package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js +18 -36
  138. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.d.ts +5 -3
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.js +6 -5
  144. package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js +15 -1
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  149. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  150. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  151. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  152. package/package.json +7 -7
  153. package/dist/bundle/chunks/chunk-A4UMPXQF.js +0 -2
  154. package/dist/bundle/chunks/chunk-HH3KJDWA.js +0 -2
  155. package/dist/bundle/chunks/chunk-XPJLBOWV.js +0 -5
  156. package/dist/bundle/chunks/host-lifecycle-S7O5K62Z.js +0 -3
@@ -4,8 +4,8 @@ export const APP_SERVER_LISTEN_USAGE = "Invalid --listen value. Use stdio://, un
4
4
  export function formatAppServerUsage() {
5
5
  const listenForms = "stdio://|unix://|unix:///abs/path|ws://IP:PORT";
6
6
  return [
7
- `Usage: ${APP_NAME} app-server [--listen <${listenForms}>] [--ws-auth <token-file|off>] [--json-logs]`,
8
- ` ${APP_NAME} app-server daemon <start|stop|status|restart> [--listen <${listenForms}>]`,
7
+ `Usage: ${APP_NAME} app-server [--listen <${listenForms}>] [--ws-auth <token-file|off>] [--json-logs] [--extension <path>]...`,
8
+ ` ${APP_NAME} app-server daemon <start|stop|status|restart> [--listen <${listenForms}>] [--extension <path>]...`,
9
9
  ].join("\n");
10
10
  }
11
11
  function parseListen(value) {
@@ -65,12 +65,23 @@ function parseDaemonVerb(value) {
65
65
  function parseWsAuth(value) {
66
66
  return value === "off" ? { kind: "off" } : { kind: "token-file", path: value };
67
67
  }
68
+ const EXTENSION_USAGE = "--extension requires <path>.";
68
69
  function parseServerArgs(args) {
69
70
  let listen = { kind: "stdio", url: "stdio://" };
70
71
  let wsAuth;
71
72
  let jsonLogs = false;
73
+ const extensions = [];
72
74
  for (let index = 0; index < args.length; index++) {
73
75
  const arg = args[index];
76
+ if (arg === "--extension") {
77
+ const value = args[index + 1];
78
+ if (value === undefined) {
79
+ return { kind: "usage-error", message: EXTENSION_USAGE };
80
+ }
81
+ extensions.push(value);
82
+ index++;
83
+ continue;
84
+ }
74
85
  if (arg === "--listen") {
75
86
  const value = args[index + 1];
76
87
  if (value === undefined) {
@@ -99,7 +110,7 @@ function parseServerArgs(args) {
99
110
  }
100
111
  return { kind: "usage-error", message: `Unexpected app-server argument: ${arg}` };
101
112
  }
102
- return { kind: "server", listen, wsAuth, jsonLogs };
113
+ return { kind: "server", listen, wsAuth, jsonLogs, extensions };
103
114
  }
104
115
  function parseDaemonArgs(args) {
105
116
  const verb = parseDaemonVerb(args[0]);
@@ -107,8 +118,18 @@ function parseDaemonArgs(args) {
107
118
  return { kind: "usage-error", message: "Usage: app-server daemon <start|stop|status|restart>." };
108
119
  }
109
120
  let listen = { kind: "ws", url: "ws://127.0.0.1:18800", host: "127.0.0.1", port: 18800 };
121
+ const extensions = [];
110
122
  for (let index = 1; index < args.length; index++) {
111
123
  const arg = args[index];
124
+ if (arg === "--extension") {
125
+ const value = args[index + 1];
126
+ if (value === undefined) {
127
+ return { kind: "usage-error", message: EXTENSION_USAGE };
128
+ }
129
+ extensions.push(value);
130
+ index++;
131
+ continue;
132
+ }
112
133
  if (arg === "--listen") {
113
134
  const value = args[index + 1];
114
135
  if (value === undefined) {
@@ -124,7 +145,7 @@ function parseDaemonArgs(args) {
124
145
  }
125
146
  return { kind: "usage-error", message: `Unexpected app-server daemon argument: ${arg}` };
126
147
  }
127
- return { kind: "daemon", verb, listen };
148
+ return { kind: "daemon", verb, listen, extensions };
128
149
  }
129
150
  export function parseAppServerCliArgs(args) {
130
151
  if (args[0] === "daemon") {
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../../../src/modes/app-server/cli-args.ts"],"names":[],"mappings":"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","sourcesContent":["import { isIP } from \"node:net\";\nimport { APP_NAME } from \"../../config.ts\";\n\nexport type AppServerDaemonVerb = \"start\" | \"stop\" | \"status\" | \"restart\";\n\nexport type AppServerListen =\n\t| { readonly kind: \"stdio\"; readonly url: \"stdio://\" }\n\t| { readonly kind: \"unix\"; readonly url: string; readonly path?: string }\n\t| { readonly kind: \"ws\"; readonly url: string; readonly host: string; readonly port: number };\n\nexport type AppServerWsAuth = { readonly kind: \"off\" } | { readonly kind: \"token-file\"; readonly path: string };\n\nexport interface AppServerModeOptions {\n\treadonly kind: \"server\";\n\treadonly listen: AppServerListen;\n\treadonly wsAuth?: AppServerWsAuth;\n\treadonly jsonLogs: boolean;\n}\n\nexport interface AppServerDaemonCommandOptions {\n\treadonly kind: \"daemon\";\n\treadonly verb: AppServerDaemonVerb;\n\treadonly listen: AppServerListen;\n}\n\nexport interface AppServerUsageError {\n\treadonly kind: \"usage-error\";\n\treadonly message: string;\n}\n\nexport type AppServerCliArgs = AppServerModeOptions | AppServerDaemonCommandOptions | AppServerUsageError;\n\nexport const APP_SERVER_LISTEN_USAGE =\n\t\"Invalid --listen value. Use stdio://, unix://, unix:///abs/path, or ws://IP:PORT.\";\n\nexport function formatAppServerUsage(): string {\n\tconst listenForms = \"stdio://|unix://|unix:///abs/path|ws://IP:PORT\";\n\treturn [\n\t\t`Usage: ${APP_NAME} app-server [--listen <${listenForms}>] [--ws-auth <token-file|off>] [--json-logs]`,\n\t\t` ${APP_NAME} app-server daemon <start|stop|status|restart> [--listen <${listenForms}>]`,\n\t].join(\"\\n\");\n}\n\nfunction parseListen(value: string): AppServerListen | undefined {\n\tif (value === \"stdio://\") {\n\t\treturn { kind: \"stdio\", url: \"stdio://\" };\n\t}\n\n\tif (value === \"unix://\") {\n\t\treturn { kind: \"unix\", url: \"unix://\" };\n\t}\n\n\tif (value.startsWith(\"unix:///\")) {\n\t\tconst path = value.slice(\"unix://\".length);\n\t\tif (path.startsWith(\"/\")) {\n\t\t\treturn { kind: \"unix\", url: value, path };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!value.startsWith(\"ws://\")) {\n\t\treturn undefined;\n\t}\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(value);\n\t} catch (error: unknown) {\n\t\tif (error instanceof TypeError) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t}\n\n\tconst port = Number(parsed.port);\n\tif (\n\t\tparsed.protocol !== \"ws:\" ||\n\t\tparsed.username !== \"\" ||\n\t\tparsed.password !== \"\" ||\n\t\tparsed.pathname !== \"/\" ||\n\t\tparsed.search !== \"\" ||\n\t\tparsed.hash !== \"\" ||\n\t\tparsed.port === \"\" ||\n\t\t!Number.isInteger(port) ||\n\t\tport < 1 ||\n\t\tport > 65535 ||\n\t\tisIP(parsed.hostname) === 0\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn { kind: \"ws\", url: value, host: parsed.hostname, port };\n}\n\nfunction parseDaemonVerb(value: string | undefined): AppServerDaemonVerb | undefined {\n\tswitch (value) {\n\t\tcase \"start\":\n\t\tcase \"stop\":\n\t\tcase \"status\":\n\t\tcase \"restart\":\n\t\t\treturn value;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseWsAuth(value: string): AppServerWsAuth {\n\treturn value === \"off\" ? { kind: \"off\" } : { kind: \"token-file\", path: value };\n}\n\nfunction parseServerArgs(args: readonly string[]): AppServerModeOptions | AppServerUsageError {\n\tlet listen: AppServerListen = { kind: \"stdio\", url: \"stdio://\" };\n\tlet wsAuth: AppServerWsAuth | undefined;\n\tlet jsonLogs = false;\n\n\tfor (let index = 0; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--listen\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tconst parsed = parseListen(value);\n\t\t\tif (parsed === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tlisten = parsed;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--ws-auth\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: \"--ws-auth requires <token-file|off>.\" };\n\t\t\t}\n\t\t\twsAuth = parseWsAuth(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--json-logs\") {\n\t\t\tjsonLogs = true;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"usage-error\", message: `Unexpected app-server argument: ${arg}` };\n\t}\n\n\treturn { kind: \"server\", listen, wsAuth, jsonLogs };\n}\n\nfunction parseDaemonArgs(args: readonly string[]): AppServerDaemonCommandOptions | AppServerUsageError {\n\tconst verb = parseDaemonVerb(args[0]);\n\tif (verb === undefined) {\n\t\treturn { kind: \"usage-error\", message: \"Usage: app-server daemon <start|stop|status|restart>.\" };\n\t}\n\n\tlet listen: AppServerListen = { kind: \"ws\", url: \"ws://127.0.0.1:18800\", host: \"127.0.0.1\", port: 18800 };\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--listen\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tconst parsed = parseListen(value);\n\t\t\tif (parsed === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tlisten = parsed;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"usage-error\", message: `Unexpected app-server daemon argument: ${arg}` };\n\t}\n\n\treturn { kind: \"daemon\", verb, listen };\n}\n\nexport function parseAppServerCliArgs(args: readonly string[]): AppServerCliArgs {\n\tif (args[0] === \"daemon\") {\n\t\treturn parseDaemonArgs(args.slice(1));\n\t}\n\treturn parseServerArgs(args);\n}\n"]}
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../../../src/modes/app-server/cli-args.ts"],"names":[],"mappings":"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","sourcesContent":["import { isIP } from \"node:net\";\nimport { APP_NAME } from \"../../config.ts\";\n\nexport type AppServerDaemonVerb = \"start\" | \"stop\" | \"status\" | \"restart\";\n\nexport type AppServerListen =\n\t| { readonly kind: \"stdio\"; readonly url: \"stdio://\" }\n\t| { readonly kind: \"unix\"; readonly url: string; readonly path?: string }\n\t| { readonly kind: \"ws\"; readonly url: string; readonly host: string; readonly port: number };\n\nexport type AppServerWsAuth = { readonly kind: \"off\" } | { readonly kind: \"token-file\"; readonly path: string };\n\nexport interface AppServerModeOptions {\n\treadonly kind: \"server\";\n\treadonly listen: AppServerListen;\n\treadonly wsAuth?: AppServerWsAuth;\n\treadonly jsonLogs: boolean;\n\t/** Extension sources loaded into every thread session, like the global `--extension` flag. */\n\treadonly extensions: readonly string[];\n}\n\nexport interface AppServerDaemonCommandOptions {\n\treadonly kind: \"daemon\";\n\treadonly verb: AppServerDaemonVerb;\n\treadonly listen: AppServerListen;\n\treadonly extensions: readonly string[];\n}\n\nexport interface AppServerUsageError {\n\treadonly kind: \"usage-error\";\n\treadonly message: string;\n}\n\nexport type AppServerCliArgs = AppServerModeOptions | AppServerDaemonCommandOptions | AppServerUsageError;\n\nexport const APP_SERVER_LISTEN_USAGE =\n\t\"Invalid --listen value. Use stdio://, unix://, unix:///abs/path, or ws://IP:PORT.\";\n\nexport function formatAppServerUsage(): string {\n\tconst listenForms = \"stdio://|unix://|unix:///abs/path|ws://IP:PORT\";\n\treturn [\n\t\t`Usage: ${APP_NAME} app-server [--listen <${listenForms}>] [--ws-auth <token-file|off>] [--json-logs] [--extension <path>]...`,\n\t\t` ${APP_NAME} app-server daemon <start|stop|status|restart> [--listen <${listenForms}>] [--extension <path>]...`,\n\t].join(\"\\n\");\n}\n\nfunction parseListen(value: string): AppServerListen | undefined {\n\tif (value === \"stdio://\") {\n\t\treturn { kind: \"stdio\", url: \"stdio://\" };\n\t}\n\n\tif (value === \"unix://\") {\n\t\treturn { kind: \"unix\", url: \"unix://\" };\n\t}\n\n\tif (value.startsWith(\"unix:///\")) {\n\t\tconst path = value.slice(\"unix://\".length);\n\t\tif (path.startsWith(\"/\")) {\n\t\t\treturn { kind: \"unix\", url: value, path };\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tif (!value.startsWith(\"ws://\")) {\n\t\treturn undefined;\n\t}\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(value);\n\t} catch (error: unknown) {\n\t\tif (error instanceof TypeError) {\n\t\t\treturn undefined;\n\t\t}\n\t\tthrow error;\n\t}\n\n\tconst port = Number(parsed.port);\n\tif (\n\t\tparsed.protocol !== \"ws:\" ||\n\t\tparsed.username !== \"\" ||\n\t\tparsed.password !== \"\" ||\n\t\tparsed.pathname !== \"/\" ||\n\t\tparsed.search !== \"\" ||\n\t\tparsed.hash !== \"\" ||\n\t\tparsed.port === \"\" ||\n\t\t!Number.isInteger(port) ||\n\t\tport < 1 ||\n\t\tport > 65535 ||\n\t\tisIP(parsed.hostname) === 0\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn { kind: \"ws\", url: value, host: parsed.hostname, port };\n}\n\nfunction parseDaemonVerb(value: string | undefined): AppServerDaemonVerb | undefined {\n\tswitch (value) {\n\t\tcase \"start\":\n\t\tcase \"stop\":\n\t\tcase \"status\":\n\t\tcase \"restart\":\n\t\t\treturn value;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseWsAuth(value: string): AppServerWsAuth {\n\treturn value === \"off\" ? { kind: \"off\" } : { kind: \"token-file\", path: value };\n}\n\nconst EXTENSION_USAGE = \"--extension requires <path>.\";\n\nfunction parseServerArgs(args: readonly string[]): AppServerModeOptions | AppServerUsageError {\n\tlet listen: AppServerListen = { kind: \"stdio\", url: \"stdio://\" };\n\tlet wsAuth: AppServerWsAuth | undefined;\n\tlet jsonLogs = false;\n\tconst extensions: string[] = [];\n\n\tfor (let index = 0; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--extension\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: EXTENSION_USAGE };\n\t\t\t}\n\t\t\textensions.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--listen\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tconst parsed = parseListen(value);\n\t\t\tif (parsed === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tlisten = parsed;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--ws-auth\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: \"--ws-auth requires <token-file|off>.\" };\n\t\t\t}\n\t\t\twsAuth = parseWsAuth(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--json-logs\") {\n\t\t\tjsonLogs = true;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"usage-error\", message: `Unexpected app-server argument: ${arg}` };\n\t}\n\n\treturn { kind: \"server\", listen, wsAuth, jsonLogs, extensions };\n}\n\nfunction parseDaemonArgs(args: readonly string[]): AppServerDaemonCommandOptions | AppServerUsageError {\n\tconst verb = parseDaemonVerb(args[0]);\n\tif (verb === undefined) {\n\t\treturn { kind: \"usage-error\", message: \"Usage: app-server daemon <start|stop|status|restart>.\" };\n\t}\n\n\tlet listen: AppServerListen = { kind: \"ws\", url: \"ws://127.0.0.1:18800\", host: \"127.0.0.1\", port: 18800 };\n\tconst extensions: string[] = [];\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--extension\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: EXTENSION_USAGE };\n\t\t\t}\n\t\t\textensions.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--listen\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tconst parsed = parseListen(value);\n\t\t\tif (parsed === undefined) {\n\t\t\t\treturn { kind: \"usage-error\", message: APP_SERVER_LISTEN_USAGE };\n\t\t\t}\n\t\t\tlisten = parsed;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"usage-error\", message: `Unexpected app-server daemon argument: ${arg}` };\n\t}\n\n\treturn { kind: \"daemon\", verb, listen, extensions };\n}\n\nexport function parseAppServerCliArgs(args: readonly string[]): AppServerCliArgs {\n\tif (args[0] === \"daemon\") {\n\t\treturn parseDaemonArgs(args.slice(1));\n\t}\n\treturn parseServerArgs(args);\n}\n"]}
@@ -16,6 +16,7 @@ export declare function pollProbe(paths: TokenPaths, listen: AppServerListen, ti
16
16
  export declare function readInitializeProbe(text: string): string | undefined;
17
17
  export declare function readSettings(paths: SettingsPaths): Promise<{
18
18
  readonly listen: AppServerListen;
19
+ readonly extensions: readonly string[];
19
20
  } | undefined>;
20
21
  export declare function parseListen(value: unknown): AppServerListen | undefined;
21
22
  export declare function cleanupState(paths: CleanupPaths, listen: AppServerListen): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/probe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,UAAU,GAAG;IACjB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,KAAK,YAAY,GAAG;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,KAAK,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEzE,wBAAsB,WAAW,CAChC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAe7B;AAED,wBAAgB,cAAc,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwC7B;AAED,wBAAsB,SAAS,CAC9B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWpE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAAG,SAAS,CAAC,CAUlH;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAYvE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,wBAAgB,aAAa,CAC5B,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,GACb,WAAW,CAGb;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAE5F"}
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/probe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,UAAU,GAAG;IACjB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,aAAa,GAAG;IACpB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,KAAK,YAAY,GAAG;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,KAAK,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAEzE,wBAAsB,WAAW,CAChC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAe7B;AAED,wBAAgB,cAAc,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwC7B;AAED,wBAAsB,SAAS,CAC9B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWpE;AAED,wBAAsB,YAAY,CACjC,KAAK,EAAE,aAAa,GAClB,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAAC,CAanG;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAYvE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,wBAAgB,aAAa,CAC5B,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,GACb,WAAW,CAGb;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAE5F"}
@@ -96,7 +96,10 @@ export async function readSettings(paths) {
96
96
  if (!isRecord(parsed))
97
97
  return undefined;
98
98
  const listen = parseListen(parsed.listen);
99
- return listen ? { listen } : undefined;
99
+ const extensions = Array.isArray(parsed.extensions)
100
+ ? parsed.extensions.filter((value) => typeof value === "string")
101
+ : [];
102
+ return listen ? { listen, extensions } : undefined;
100
103
  }
101
104
  catch (error) {
102
105
  if (isNodeErrorCode(error, "ENOENT") || error instanceof SyntaxError)
@@ -1 +1 @@
1
- {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/probe.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport WebSocket from \"ws\";\nimport { VERSION } from \"../../../config.ts\";\nimport type { AppServerListen } from \"../index.ts\";\n\ntype TokenPaths = {\n\treadonly tokenFile: string;\n};\n\ntype SettingsPaths = {\n\treadonly settingsFile: string;\n};\n\ntype CleanupPaths = {\n\treadonly pidFile: string;\n\treadonly settingsFile: string;\n};\n\ntype ProbeOutput = Readonly<Record<string, string | number | undefined>>;\n\nexport async function probeListen(\n\tpaths: TokenPaths,\n\tlisten: AppServerListen,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\tif (listen.kind === \"stdio\" || signal?.aborted) return undefined;\n\tlet token: string | undefined;\n\ttry {\n\t\ttoken = (await readFile(paths.tokenFile, \"utf8\")).trim();\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\") && listen.kind === \"ws\") return undefined;\n\t\tif (!isNodeErrorCode(error, \"ENOENT\")) throw error;\n\t\ttoken = undefined;\n\t}\n\tconst probeTarget =\n\t\tlisten.kind === \"ws\"\n\t\t\t? listen.url\n\t\t\t: `ws+unix://${listen.path ?? join(dirname(paths.tokenFile), \"app-server.sock\")}:/`;\n\treturn probeWebSocket(probeTarget, token && token.length > 0 ? token : undefined, timeoutMs, signal);\n}\n\nexport function probeWebSocket(\n\turl: string,\n\ttoken: string | undefined,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\treturn new Promise((resolveProbe) => {\n\t\tif (signal?.aborted) {\n\t\t\tresolveProbe(undefined);\n\t\t\treturn;\n\t\t}\n\t\tlet settled = false;\n\t\tconst socket = new WebSocket(url, token ? { headers: { Authorization: `Bearer ${token}` } } : undefined);\n\t\tconst finish = (result: string | undefined): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tsocket.terminate();\n\t\t\tresolveProbe(result);\n\t\t};\n\t\tconst onAbort = (): void => finish(undefined);\n\t\tconst timeout = setTimeout(() => finish(undefined), timeoutMs);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tsocket.once(\"open\", () => {\n\t\t\tsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: 1,\n\t\t\t\t\tmethod: \"initialize\",\n\t\t\t\t\tparams: {\n\t\t\t\t\t\tclientInfo: { name: \"senpi_app_server_daemon\", title: \"senpi app-server daemon\", version: VERSION },\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.once(\"message\", (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tfinish(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(readInitializeProbe(data.toString(\"utf8\")));\n\t\t});\n\t\tsocket.on(\"error\", () => finish(undefined));\n\t\tsocket.once(\"close\", () => finish(undefined));\n\t});\n}\n\nexport async function pollProbe(\n\tpaths: TokenPaths,\n\tlisten: AppServerListen,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif (signal?.aborted) return undefined;\n\t\tconst probe = await probeListen(paths, listen, 2_000, signal);\n\t\tif (probe) return probe;\n\t\tif (signal?.aborted) return undefined;\n\t\tawait delay(100, signal);\n\t}\n\treturn undefined;\n}\n\nexport function readInitializeProbe(text: string): string | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch (error: unknown) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n\tif (!isRecord(parsed) || parsed.id !== 1 || !isRecord(parsed.result)) return undefined;\n\tconst userAgent = parsed.result.userAgent;\n\treturn typeof userAgent === \"string\" ? userAgent : undefined;\n}\n\nexport async function readSettings(paths: SettingsPaths): Promise<{ readonly listen: AppServerListen } | undefined> {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(await readFile(paths.settingsFile, \"utf8\"));\n\t\tif (!isRecord(parsed)) return undefined;\n\t\tconst listen = parseListen(parsed.listen);\n\t\treturn listen ? { listen } : undefined;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\") || error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n}\n\nexport function parseListen(value: unknown): AppServerListen | undefined {\n\tif (!isRecord(value) || typeof value.kind !== \"string\" || typeof value.url !== \"string\") return undefined;\n\tif (value.kind === \"stdio\" && value.url === \"stdio://\") return { kind: \"stdio\", url: \"stdio://\" };\n\tif (value.kind === \"unix\") {\n\t\treturn typeof value.path === \"string\"\n\t\t\t? { kind: \"unix\", url: value.url, path: value.path }\n\t\t\t: { kind: \"unix\", url: value.url };\n\t}\n\tif (value.kind === \"ws\" && typeof value.host === \"string\" && typeof value.port === \"number\") {\n\t\treturn { kind: \"ws\", url: value.url, host: value.host, port: value.port };\n\t}\n\treturn undefined;\n}\n\nexport async function cleanupState(paths: CleanupPaths, listen: AppServerListen): Promise<void> {\n\tawait rm(paths.pidFile, { force: true });\n\tawait rm(paths.settingsFile, { force: true });\n\tif (listen.kind === \"unix\" && listen.path) await rm(listen.path, { force: true });\n}\n\nexport function runningOutput(\n\tstatus: \"already-running\" | \"running\",\n\tpid: number | undefined,\n\tlisten: AppServerListen,\n\tversion: string,\n): ProbeOutput {\n\tif (pid === undefined) return runningUnmanagedOutput(listen, version);\n\treturn { status, pid, listen: listen.url, version };\n}\n\nexport function runningUnmanagedOutput(listen: AppServerListen, version: string): ProbeOutput {\n\treturn { status: \"running-unmanaged\", listen: listen.url, version };\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolveDelay) => {\n\t\tif (signal?.aborted) {\n\t\t\tresolveDelay();\n\t\t\treturn;\n\t\t}\n\t\tconst finish = (): void => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", finish);\n\t\t\tresolveDelay();\n\t\t};\n\t\tconst timeout = setTimeout(finish, ms);\n\t\tsignal?.addEventListener(\"abort\", finish, { once: true });\n\t});\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/probe.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport WebSocket from \"ws\";\nimport { VERSION } from \"../../../config.ts\";\nimport type { AppServerListen } from \"../index.ts\";\n\ntype TokenPaths = {\n\treadonly tokenFile: string;\n};\n\ntype SettingsPaths = {\n\treadonly settingsFile: string;\n};\n\ntype CleanupPaths = {\n\treadonly pidFile: string;\n\treadonly settingsFile: string;\n};\n\ntype ProbeOutput = Readonly<Record<string, string | number | undefined>>;\n\nexport async function probeListen(\n\tpaths: TokenPaths,\n\tlisten: AppServerListen,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\tif (listen.kind === \"stdio\" || signal?.aborted) return undefined;\n\tlet token: string | undefined;\n\ttry {\n\t\ttoken = (await readFile(paths.tokenFile, \"utf8\")).trim();\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\") && listen.kind === \"ws\") return undefined;\n\t\tif (!isNodeErrorCode(error, \"ENOENT\")) throw error;\n\t\ttoken = undefined;\n\t}\n\tconst probeTarget =\n\t\tlisten.kind === \"ws\"\n\t\t\t? listen.url\n\t\t\t: `ws+unix://${listen.path ?? join(dirname(paths.tokenFile), \"app-server.sock\")}:/`;\n\treturn probeWebSocket(probeTarget, token && token.length > 0 ? token : undefined, timeoutMs, signal);\n}\n\nexport function probeWebSocket(\n\turl: string,\n\ttoken: string | undefined,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\treturn new Promise((resolveProbe) => {\n\t\tif (signal?.aborted) {\n\t\t\tresolveProbe(undefined);\n\t\t\treturn;\n\t\t}\n\t\tlet settled = false;\n\t\tconst socket = new WebSocket(url, token ? { headers: { Authorization: `Bearer ${token}` } } : undefined);\n\t\tconst finish = (result: string | undefined): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tsocket.terminate();\n\t\t\tresolveProbe(result);\n\t\t};\n\t\tconst onAbort = (): void => finish(undefined);\n\t\tconst timeout = setTimeout(() => finish(undefined), timeoutMs);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tsocket.once(\"open\", () => {\n\t\t\tsocket.send(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: 1,\n\t\t\t\t\tmethod: \"initialize\",\n\t\t\t\t\tparams: {\n\t\t\t\t\t\tclientInfo: { name: \"senpi_app_server_daemon\", title: \"senpi app-server daemon\", version: VERSION },\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t\tsocket.once(\"message\", (data, isBinary) => {\n\t\t\tif (isBinary) {\n\t\t\t\tfinish(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfinish(readInitializeProbe(data.toString(\"utf8\")));\n\t\t});\n\t\tsocket.on(\"error\", () => finish(undefined));\n\t\tsocket.once(\"close\", () => finish(undefined));\n\t});\n}\n\nexport async function pollProbe(\n\tpaths: TokenPaths,\n\tlisten: AppServerListen,\n\ttimeoutMs: number,\n\tsignal?: AbortSignal,\n): Promise<string | undefined> {\n\tconst deadline = Date.now() + timeoutMs;\n\twhile (Date.now() <= deadline) {\n\t\tif (signal?.aborted) return undefined;\n\t\tconst probe = await probeListen(paths, listen, 2_000, signal);\n\t\tif (probe) return probe;\n\t\tif (signal?.aborted) return undefined;\n\t\tawait delay(100, signal);\n\t}\n\treturn undefined;\n}\n\nexport function readInitializeProbe(text: string): string | undefined {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch (error: unknown) {\n\t\tif (error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n\tif (!isRecord(parsed) || parsed.id !== 1 || !isRecord(parsed.result)) return undefined;\n\tconst userAgent = parsed.result.userAgent;\n\treturn typeof userAgent === \"string\" ? userAgent : undefined;\n}\n\nexport async function readSettings(\n\tpaths: SettingsPaths,\n): Promise<{ readonly listen: AppServerListen; readonly extensions: readonly string[] } | undefined> {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(await readFile(paths.settingsFile, \"utf8\"));\n\t\tif (!isRecord(parsed)) return undefined;\n\t\tconst listen = parseListen(parsed.listen);\n\t\tconst extensions = Array.isArray(parsed.extensions)\n\t\t\t? parsed.extensions.filter((value): value is string => typeof value === \"string\")\n\t\t\t: [];\n\t\treturn listen ? { listen, extensions } : undefined;\n\t} catch (error: unknown) {\n\t\tif (isNodeErrorCode(error, \"ENOENT\") || error instanceof SyntaxError) return undefined;\n\t\tthrow error;\n\t}\n}\n\nexport function parseListen(value: unknown): AppServerListen | undefined {\n\tif (!isRecord(value) || typeof value.kind !== \"string\" || typeof value.url !== \"string\") return undefined;\n\tif (value.kind === \"stdio\" && value.url === \"stdio://\") return { kind: \"stdio\", url: \"stdio://\" };\n\tif (value.kind === \"unix\") {\n\t\treturn typeof value.path === \"string\"\n\t\t\t? { kind: \"unix\", url: value.url, path: value.path }\n\t\t\t: { kind: \"unix\", url: value.url };\n\t}\n\tif (value.kind === \"ws\" && typeof value.host === \"string\" && typeof value.port === \"number\") {\n\t\treturn { kind: \"ws\", url: value.url, host: value.host, port: value.port };\n\t}\n\treturn undefined;\n}\n\nexport async function cleanupState(paths: CleanupPaths, listen: AppServerListen): Promise<void> {\n\tawait rm(paths.pidFile, { force: true });\n\tawait rm(paths.settingsFile, { force: true });\n\tif (listen.kind === \"unix\" && listen.path) await rm(listen.path, { force: true });\n}\n\nexport function runningOutput(\n\tstatus: \"already-running\" | \"running\",\n\tpid: number | undefined,\n\tlisten: AppServerListen,\n\tversion: string,\n): ProbeOutput {\n\tif (pid === undefined) return runningUnmanagedOutput(listen, version);\n\treturn { status, pid, listen: listen.url, version };\n}\n\nexport function runningUnmanagedOutput(listen: AppServerListen, version: string): ProbeOutput {\n\treturn { status: \"running-unmanaged\", listen: listen.url, version };\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolveDelay) => {\n\t\tif (signal?.aborted) {\n\t\t\tresolveDelay();\n\t\t\treturn;\n\t\t}\n\t\tconst finish = (): void => {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", finish);\n\t\t\tresolveDelay();\n\t\t};\n\t\tconst timeout = setTimeout(finish, ms);\n\t\tsignal?.addEventListener(\"abort\", finish, { once: true });\n\t});\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isNodeErrorCode(error: unknown, code: string): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === code;\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import type { AppServerListen } from "../cli-args.ts";
2
+ export type DaemonExit = {
3
+ readonly kind: "error";
4
+ readonly error: Error;
5
+ } | {
6
+ readonly kind: "exit";
7
+ readonly code: number | null;
8
+ readonly signal: NodeJS.Signals | null;
9
+ };
10
+ export type SpawnedDaemon = {
11
+ readonly pid: number;
12
+ readonly exited: Promise<DaemonExit>;
13
+ };
14
+ /** What a daemon is launched with; recorded in settings.json so `restart` relaunches the same daemon. */
15
+ export type DaemonLaunchIntent = {
16
+ readonly listen: AppServerListen;
17
+ readonly extensions: readonly string[];
18
+ };
19
+ type SpawnPaths = {
20
+ readonly pidFile: string;
21
+ readonly settingsFile: string;
22
+ readonly stderrLog: string;
23
+ };
24
+ export declare function spawnDaemon(paths: SpawnPaths, intent: DaemonLaunchIntent, cliMainPath: string): Promise<SpawnedDaemon>;
25
+ export {};
26
+ //# sourceMappingURL=spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/spawn.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAItD,MAAM,MAAM,UAAU,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEnG,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CACrC,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,kBAAkB,GAAG;IAChC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,CAAC;AAEF,KAAK,UAAU,GAAG;IACjB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAsB,WAAW,CAChC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAgGxB"}
@@ -0,0 +1,112 @@
1
+ import { spawn } from "node:child_process";
2
+ import { open, writeFile } from "node:fs/promises";
3
+ import { cleanupState } from "./probe.js";
4
+ import { readProcessStartTime, waitForStartTime } from "./process.js";
5
+ export async function spawnDaemon(paths, intent, cliMainPath) {
6
+ const { listen, extensions } = intent;
7
+ const stderr = await open(paths.stderrLog, "w");
8
+ try {
9
+ const daemonExec = process.versions.bun
10
+ ? process.env.npm_node_execpath && !/[/\\]bun(?:$|[/\\])/.test(process.env.npm_node_execpath)
11
+ ? process.env.npm_node_execpath
12
+ : "/opt/homebrew/bin/node"
13
+ : process.execPath;
14
+ const child = spawn(daemonExec, [
15
+ ...(process.versions.bun ? [] : process.execArgv),
16
+ cliMainPath,
17
+ "app-server",
18
+ "--listen",
19
+ listen.url,
20
+ ...extensions.flatMap((extension) => ["--extension", extension]),
21
+ ], {
22
+ detached: true,
23
+ windowsHide: true,
24
+ env: { ...process.env, SENPI_RUNTIME: "node" },
25
+ stdio: ["ignore", "ignore", stderr.fd],
26
+ });
27
+ const exited = observeDaemonExit(child);
28
+ const pid = child.pid;
29
+ if (pid === undefined)
30
+ throw new Error("failed to spawn daemon process");
31
+ let startTime;
32
+ try {
33
+ const observed = await Promise.race([
34
+ waitForStartTime(pid, 10_000),
35
+ exited.then(() => {
36
+ throw new Error(`spawned daemon ${pid} exited before its start time could be read`);
37
+ }),
38
+ ]);
39
+ // UNKNOWN identity on a live daemon means the probe was starved, not that startup failed.
40
+ // The per-attempt win32 probe budget is 1s, which a loaded runner exceeds every time, so
41
+ // take one unhurried read before treating an unreadable identity as a startup error.
42
+ const resolved = observed ?? (await readProcessStartTime(pid, process.platform, 15_000).catch(() => undefined));
43
+ if (resolved === undefined) {
44
+ throw new Error(`spawned daemon ${pid} started but its process identity stayed unreadable`);
45
+ }
46
+ startTime = resolved;
47
+ }
48
+ catch (error) {
49
+ // Keep the handle owned until registration succeeds. This terminates the
50
+ // exact child even when start-time acquisition fails, without a raw PID.
51
+ if (child.exitCode === null && child.signalCode === null) {
52
+ try {
53
+ child.kill("SIGTERM");
54
+ }
55
+ catch { }
56
+ await Promise.race([exited, delay(2_000)]);
57
+ if (child.exitCode === null && child.signalCode === null) {
58
+ try {
59
+ child.kill("SIGKILL");
60
+ }
61
+ catch { }
62
+ }
63
+ }
64
+ await cleanupState(paths, listen);
65
+ throw error;
66
+ }
67
+ try {
68
+ await writeFile(paths.pidFile, `${JSON.stringify({ pid, processStartTime: startTime })}\n`, { mode: 0o600 });
69
+ await writeFile(paths.settingsFile, `${JSON.stringify({ listen, extensions })}\n`, { mode: 0o600 });
70
+ }
71
+ catch (error) {
72
+ // Registration is the ownership hand-off point. Until both files exist,
73
+ // retain the exact ChildProcess handle and terminate it on any write
74
+ // failure so a partial registration can never leave an unmanaged daemon.
75
+ if (child.exitCode === null && child.signalCode === null) {
76
+ try {
77
+ child.kill("SIGTERM");
78
+ }
79
+ catch { }
80
+ await Promise.race([exited, delay(2_000)]);
81
+ if (child.exitCode === null && child.signalCode === null) {
82
+ try {
83
+ child.kill("SIGKILL");
84
+ }
85
+ catch (killError) {
86
+ throw new Error(`failed to terminate daemon after registration failure: ${killError instanceof Error ? killError.message : String(killError)}`);
87
+ }
88
+ if (!(await Promise.race([exited.then(() => true), delay(2_000).then(() => false)]))) {
89
+ throw new Error(`daemon ${pid} remained alive after SIGKILL during registration failure`);
90
+ }
91
+ }
92
+ }
93
+ await cleanupState(paths, { ...listen, ...(listen.kind === "unix" ? { path: undefined } : {}) });
94
+ throw error;
95
+ }
96
+ child.unref();
97
+ return { pid, exited };
98
+ }
99
+ finally {
100
+ await stderr.close();
101
+ }
102
+ }
103
+ function delay(ms) {
104
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
105
+ }
106
+ function observeDaemonExit(child) {
107
+ return new Promise((resolveExit) => {
108
+ child.once("error", (error) => resolveExit({ kind: "error", error }));
109
+ child.once("exit", (code, signal) => resolveExit({ kind: "exit", code, signal }));
110
+ });
111
+ }
112
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../../../src/modes/app-server/daemon/spawn.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { open, writeFile } from \"node:fs/promises\";\nimport type { AppServerListen } from \"../cli-args.ts\";\nimport { cleanupState } from \"./probe.ts\";\nimport { readProcessStartTime, waitForStartTime } from \"./process.ts\";\n\nexport type DaemonExit =\n\t| { readonly kind: \"error\"; readonly error: Error }\n\t| { readonly kind: \"exit\"; readonly code: number | null; readonly signal: NodeJS.Signals | null };\n\nexport type SpawnedDaemon = {\n\treadonly pid: number;\n\treadonly exited: Promise<DaemonExit>;\n};\n\n/** What a daemon is launched with; recorded in settings.json so `restart` relaunches the same daemon. */\nexport type DaemonLaunchIntent = {\n\treadonly listen: AppServerListen;\n\treadonly extensions: readonly string[];\n};\n\ntype SpawnPaths = {\n\treadonly pidFile: string;\n\treadonly settingsFile: string;\n\treadonly stderrLog: string;\n};\n\nexport async function spawnDaemon(\n\tpaths: SpawnPaths,\n\tintent: DaemonLaunchIntent,\n\tcliMainPath: string,\n): Promise<SpawnedDaemon> {\n\tconst { listen, extensions } = intent;\n\tconst stderr = await open(paths.stderrLog, \"w\");\n\ttry {\n\t\tconst daemonExec = process.versions.bun\n\t\t\t? process.env.npm_node_execpath && !/[/\\\\]bun(?:$|[/\\\\])/.test(process.env.npm_node_execpath)\n\t\t\t\t? process.env.npm_node_execpath\n\t\t\t\t: \"/opt/homebrew/bin/node\"\n\t\t\t: process.execPath;\n\t\tconst child = spawn(\n\t\t\tdaemonExec,\n\t\t\t[\n\t\t\t\t...(process.versions.bun ? [] : process.execArgv),\n\t\t\t\tcliMainPath,\n\t\t\t\t\"app-server\",\n\t\t\t\t\"--listen\",\n\t\t\t\tlisten.url,\n\t\t\t\t...extensions.flatMap((extension) => [\"--extension\", extension]),\n\t\t\t],\n\t\t\t{\n\t\t\t\tdetached: true,\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv: { ...process.env, SENPI_RUNTIME: \"node\" },\n\t\t\t\tstdio: [\"ignore\", \"ignore\", stderr.fd],\n\t\t\t},\n\t\t);\n\t\tconst exited = observeDaemonExit(child);\n\t\tconst pid = child.pid;\n\t\tif (pid === undefined) throw new Error(\"failed to spawn daemon process\");\n\t\tlet startTime: string;\n\t\ttry {\n\t\t\tconst observed = await Promise.race([\n\t\t\t\twaitForStartTime(pid, 10_000),\n\t\t\t\texited.then(() => {\n\t\t\t\t\tthrow new Error(`spawned daemon ${pid} exited before its start time could be read`);\n\t\t\t\t}),\n\t\t\t]);\n\t\t\t// UNKNOWN identity on a live daemon means the probe was starved, not that startup failed.\n\t\t\t// The per-attempt win32 probe budget is 1s, which a loaded runner exceeds every time, so\n\t\t\t// take one unhurried read before treating an unreadable identity as a startup error.\n\t\t\tconst resolved =\n\t\t\t\tobserved ?? (await readProcessStartTime(pid, process.platform, 15_000).catch(() => undefined));\n\t\t\tif (resolved === undefined) {\n\t\t\t\tthrow new Error(`spawned daemon ${pid} started but its process identity stayed unreadable`);\n\t\t\t}\n\t\t\tstartTime = resolved;\n\t\t} catch (error: unknown) {\n\t\t\t// Keep the handle owned until registration succeeds. This terminates the\n\t\t\t// exact child even when start-time acquisition fails, without a raw PID.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, listen);\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait writeFile(paths.pidFile, `${JSON.stringify({ pid, processStartTime: startTime })}\\n`, { mode: 0o600 });\n\t\t\tawait writeFile(paths.settingsFile, `${JSON.stringify({ listen, extensions })}\\n`, { mode: 0o600 });\n\t\t} catch (error: unknown) {\n\t\t\t// Registration is the ownership hand-off point. Until both files exist,\n\t\t\t// retain the exact ChildProcess handle and terminate it on any write\n\t\t\t// failure so a partial registration can never leave an unmanaged daemon.\n\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\ttry {\n\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t} catch {}\n\t\t\t\tawait Promise.race([exited, delay(2_000)]);\n\t\t\t\tif (child.exitCode === null && child.signalCode === null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t} catch (killError: unknown) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`failed to terminate daemon after registration failure: ${killError instanceof Error ? killError.message : String(killError)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!(await Promise.race([exited.then(() => true), delay(2_000).then(() => false)]))) {\n\t\t\t\t\t\tthrow new Error(`daemon ${pid} remained alive after SIGKILL during registration failure`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait cleanupState(paths, { ...listen, ...(listen.kind === \"unix\" ? { path: undefined } : {}) });\n\t\t\tthrow error;\n\t\t}\n\t\tchild.unref();\n\t\treturn { pid, exited };\n\t} finally {\n\t\tawait stderr.close();\n\t}\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolveDelay) => setTimeout(resolveDelay, ms));\n}\n\nfunction observeDaemonExit(child: ChildProcess): Promise<DaemonExit> {\n\treturn new Promise((resolveExit) => {\n\t\tchild.once(\"error\", (error) => resolveExit({ kind: \"error\", error }));\n\t\tchild.once(\"exit\", (code, signal) => resolveExit({ kind: \"exit\", code, signal }));\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../src/modes/app-server/daemon.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,6BAA6B,EAAmB,MAAM,YAAY,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAoBD,wBAAgB,iBAAiB,CAAC,QAAQ,SAAgB,GAAG,WAAW,CAUvE;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQnG;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrG"}
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../src/modes/app-server/daemon.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,6BAA6B,EAAmB,MAAM,YAAY,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAWD,wBAAgB,iBAAiB,CAAC,QAAQ,SAAgB,GAAG,WAAW,CAUvE;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQnG;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrG"}
@@ -1,12 +1,13 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdir, open, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile } from "node:fs/promises";
3
2
  import { dirname, join, resolve } from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
4
  import { getAgentDir } from "../../config.js";
6
5
  import { acquireOwnershipSafeLock } from "../rpc/ownership-safe-lock.js";
7
6
  import { inspectAppServerListenOccupancy } from "./daemon/occupancy.js";
8
7
  import { cleanupState, pollProbe, probeListen, readSettings, runningOutput, runningUnmanagedOutput, } from "./daemon/probe.js";
9
- import { parseDaemonPidFile, processMatchesPidFile, readProcessStartTime, stopValidatedPid, waitForGone, waitForStartTime, } from "./daemon/process.js";
8
+ import { parseDaemonPidFile, processMatchesPidFile, readProcessStartTime, stopValidatedPid, waitForGone, } from "./daemon/process.js";
9
+ import { spawnDaemon } from "./daemon/spawn.js";
10
+ import { resolveAppServerExtensionPaths } from "./extension-paths.js";
10
11
  const lockOptions = { retries: { retries: 100, minTimeout: 20, maxTimeout: 100 } };
11
12
  export function createDaemonPaths(agentDir = getAgentDir()) {
12
13
  const dir = join(agentDir, "app-server-daemon");
@@ -45,21 +46,27 @@ export async function runAppServerDaemonCommand(options) {
45
46
  async function runLockedDaemonCommand(options, paths) {
46
47
  const settings = await readSettings(paths);
47
48
  const listen = options.verb === "start" ? options.listen : (settings?.listen ?? options.listen);
49
+ const extensions = resolveAppServerExtensionPaths(options.extensions);
48
50
  switch (options.verb) {
49
51
  case "start":
50
- return startDaemon(paths, listen);
52
+ return startDaemon(paths, { listen, extensions });
51
53
  case "stop":
52
54
  return stopDaemon(paths, listen);
53
55
  case "status":
54
56
  return statusDaemon(paths, listen);
55
57
  case "restart": {
58
+ // Like the listener, a restart relaunches what was recorded; explicit extensions replace it.
56
59
  const restartListen = settings?.listen ?? options.listen;
57
60
  await stopDaemon(paths, restartListen);
58
- return startDaemon(paths, restartListen);
61
+ return startDaemon(paths, {
62
+ listen: restartListen,
63
+ extensions: extensions.length > 0 ? extensions : (settings?.extensions ?? []),
64
+ });
59
65
  }
60
66
  }
61
67
  }
62
- async function startDaemon(paths, listen) {
68
+ async function startDaemon(paths, intent) {
69
+ const { listen } = intent;
63
70
  const occupancy = await inspectAppServerListenOccupancy(paths, listen);
64
71
  if (occupancy.kind === "app-server") {
65
72
  const pidFile = await readPidFile(paths);
@@ -75,7 +82,7 @@ async function startDaemon(paths, listen) {
75
82
  return runningOutput("already-running", pidFile.pid, listen, lateProbe);
76
83
  throw new Error(`managed daemon pid ${pidFile.pid} did not answer initialize`);
77
84
  }
78
- const spawned = await spawnDaemon(paths, listen);
85
+ const spawned = await spawnDaemon(paths, intent, resolveCliMainPath());
79
86
  const readiness = await waitForDaemonReady(paths, listen, spawned.exited);
80
87
  if (readiness.kind === "ready") {
81
88
  return { status: "started", pid: spawned.pid, listen: listen.url };
@@ -119,102 +126,6 @@ async function statusDaemon(paths, listen) {
119
126
  await cleanupState(paths, listen);
120
127
  return { status: "not-running" };
121
128
  }
122
- async function spawnDaemon(paths, listen) {
123
- const stderr = await open(paths.stderrLog, "w");
124
- try {
125
- const daemonExec = process.versions.bun
126
- ? process.env.npm_node_execpath && !/[/\\]bun(?:$|[/\\])/.test(process.env.npm_node_execpath)
127
- ? process.env.npm_node_execpath
128
- : "/opt/homebrew/bin/node"
129
- : process.execPath;
130
- const child = spawn(daemonExec, [
131
- ...(process.versions.bun ? [] : process.execArgv),
132
- resolveCliMainPath(),
133
- "app-server",
134
- "--listen",
135
- listen.url,
136
- ], {
137
- detached: true,
138
- windowsHide: true,
139
- env: { ...process.env, SENPI_RUNTIME: "node" },
140
- stdio: ["ignore", "ignore", stderr.fd],
141
- });
142
- const exited = observeDaemonExit(child);
143
- const pid = child.pid;
144
- if (pid === undefined)
145
- throw new Error("failed to spawn daemon process");
146
- let startTime;
147
- try {
148
- const observed = await Promise.race([
149
- waitForStartTime(pid, 10_000),
150
- exited.then(() => {
151
- throw new Error(`spawned daemon ${pid} exited before its start time could be read`);
152
- }),
153
- ]);
154
- // UNKNOWN identity on a live daemon means the probe was starved, not that startup failed.
155
- // The per-attempt win32 probe budget is 1s, which a loaded runner exceeds every time, so
156
- // take one unhurried read before treating an unreadable identity as a startup error.
157
- const resolved = observed ?? (await readProcessStartTime(pid, process.platform, 15_000).catch(() => undefined));
158
- if (resolved === undefined) {
159
- throw new Error(`spawned daemon ${pid} started but its process identity stayed unreadable`);
160
- }
161
- startTime = resolved;
162
- }
163
- catch (error) {
164
- // Keep the handle owned until registration succeeds. This terminates the
165
- // exact child even when start-time acquisition fails, without a raw PID.
166
- if (child.exitCode === null && child.signalCode === null) {
167
- try {
168
- child.kill("SIGTERM");
169
- }
170
- catch { }
171
- await Promise.race([exited, delay(2_000)]);
172
- if (child.exitCode === null && child.signalCode === null) {
173
- try {
174
- child.kill("SIGKILL");
175
- }
176
- catch { }
177
- }
178
- }
179
- await cleanupState(paths, listen);
180
- throw error;
181
- }
182
- try {
183
- await writeFile(paths.pidFile, `${JSON.stringify({ pid, processStartTime: startTime })}\n`, { mode: 0o600 });
184
- await writeFile(paths.settingsFile, `${JSON.stringify({ listen })}\n`, { mode: 0o600 });
185
- }
186
- catch (error) {
187
- // Registration is the ownership hand-off point. Until both files exist,
188
- // retain the exact ChildProcess handle and terminate it on any write
189
- // failure so a partial registration can never leave an unmanaged daemon.
190
- if (child.exitCode === null && child.signalCode === null) {
191
- try {
192
- child.kill("SIGTERM");
193
- }
194
- catch { }
195
- await Promise.race([exited, delay(2_000)]);
196
- if (child.exitCode === null && child.signalCode === null) {
197
- try {
198
- child.kill("SIGKILL");
199
- }
200
- catch (killError) {
201
- throw new Error(`failed to terminate daemon after registration failure: ${killError instanceof Error ? killError.message : String(killError)}`);
202
- }
203
- if (!(await Promise.race([exited.then(() => true), delay(2_000).then(() => false)]))) {
204
- throw new Error(`daemon ${pid} remained alive after SIGKILL during registration failure`);
205
- }
206
- }
207
- }
208
- await cleanupState(paths, { ...listen, ...(listen.kind === "unix" ? { path: undefined } : {}) });
209
- throw error;
210
- }
211
- child.unref();
212
- return { pid, exited };
213
- }
214
- finally {
215
- await stderr.close();
216
- }
217
- }
218
129
  async function waitForDaemonReady(paths, listen, exited) {
219
130
  const controller = new AbortController();
220
131
  try {
@@ -226,15 +137,6 @@ async function waitForDaemonReady(paths, listen, exited) {
226
137
  controller.abort();
227
138
  }
228
139
  }
229
- function delay(ms) {
230
- return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
231
- }
232
- function observeDaemonExit(child) {
233
- return new Promise((resolveExit) => {
234
- child.once("error", (error) => resolveExit({ kind: "error", error }));
235
- child.once("exit", (code, signal) => resolveExit({ kind: "exit", code, signal }));
236
- });
237
- }
238
140
  function describeDaemonExit(exit) {
239
141
  if (exit.kind === "error")
240
142
  return `failed to launch (${exit.error.message})`;