@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.
- package/CHANGELOG.md +35 -0
- package/dist/bundle/chunks/{anthropic-messages-GG4ZKS6A.js → anthropic-messages-QOQJCY3F.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-3AJWPHKJ.js → app-server-command-OUTRKHMK.js} +8 -8
- package/dist/bundle/chunks/{chunk-XYGXUFMG.js → chunk-3HCYHGK2.js} +1 -1
- package/dist/bundle/chunks/{chunk-EXUI5D5V.js → chunk-56COAYOU.js} +9 -9
- package/dist/bundle/chunks/{chunk-MMHQLGF2.js → chunk-AIM2YESB.js} +1 -1
- package/dist/bundle/chunks/{chunk-4BDFNRBE.js → chunk-B32PXL7U.js} +1 -1
- package/dist/bundle/chunks/chunk-CEMOA3TF.js +2 -0
- package/dist/bundle/chunks/{chunk-XNIX26QC.js → chunk-DB2X7SOZ.js} +2 -2
- package/dist/bundle/chunks/chunk-E5F5SVZF.js +5 -0
- package/dist/bundle/chunks/{chunk-OHJLQ7BY.js → chunk-F2YU5BZG.js} +1 -1
- package/dist/bundle/chunks/{chunk-BQGVC47Y.js → chunk-FILEUW2D.js} +1 -1
- package/dist/bundle/chunks/{chunk-JGRPPPHS.js → chunk-JASECXGC.js} +1 -1
- package/dist/bundle/chunks/{chunk-YLIS5N3W.js → chunk-JK5LVAOU.js} +1 -1
- package/dist/bundle/chunks/{chunk-KDZAVHA7.js → chunk-L4OOEWAT.js} +2 -2
- package/dist/bundle/chunks/{chunk-Z5NVK72W.js → chunk-LM2UMEX6.js} +1 -1
- package/dist/bundle/chunks/{chunk-C5I2LUOI.js → chunk-SQUUL72H.js} +1 -1
- package/dist/bundle/chunks/{chunk-LEAJXPT2.js → chunk-TOCWAEQB.js} +1 -1
- package/dist/bundle/chunks/{chunk-YEMM3LPR.js → chunk-VW3QZSX3.js} +1 -1
- package/dist/bundle/chunks/{chunk-7SFG425F.js → chunk-Y4CH7LYI.js} +2 -2
- package/dist/bundle/chunks/chunk-YJUO37UX.js +2 -0
- package/dist/bundle/chunks/{cli-main-NUSBF3HE.js → cli-main-26P4V2BK.js} +1 -1
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{help-fast-path-A4S4KOQ6.js → help-fast-path-BOMUI7U3.js} +1 -1
- package/dist/bundle/chunks/{host-command-D2CHSATG.js → host-command-ZVXNTXMN.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle-MPLSJ5NB.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +3 -3
- package/dist/bundle/chunks/{interactive-host-runtime-AAK47HIV.js → interactive-host-runtime-Q5EORM6B.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-LSTD6EUQ.js → interactive-mode-OVSKUTVC.js} +1 -1
- package/dist/bundle/chunks/{list-tips-S4U7TKER.js → list-tips-ARO4HRVK.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-5WNZ7XRV.js → multi-session-host-LJNXZRFA.js} +7 -7
- package/dist/bundle/chunks/{openai-completions-DVEOAQK4.js → openai-completions-BHE6TMUP.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-7H2ADNW4.js → openai-responses-OGQ5QORF.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-3HZYQYVX.js → package-manager-cli-MJCRY2JU.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-F36RJ6VP.js → rotation-stream-Q2EVDMUU.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-GMPBEVSL.js → rpc-mode-LY4HAZ5G.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-BYWZGYOJ.js → schedule-command-3ANAEUY6.js} +1 -1
- package/dist/bundle/chunks/{session-picker-6VU2F5VF.js → session-picker-DRM7AGPY.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +37 -37
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.js +2 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/extensions/builtin/btw/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/btw/index.js +3 -1
- package/dist/core/extensions/builtin/btw/index.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js +2 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/native.d.ts +1 -0
- package/dist/core/extensions/builtin/websearch/websearch/native.d.ts.map +1 -1
- package/dist/core/extensions/builtin/websearch/websearch/native.js +8 -1
- package/dist/core/extensions/builtin/websearch/websearch/native.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +25 -6
- package/dist/main.js.map +1 -1
- package/dist/modes/app-server/cli-args.d.ts +3 -0
- package/dist/modes/app-server/cli-args.d.ts.map +1 -1
- package/dist/modes/app-server/cli-args.js +25 -4
- package/dist/modes/app-server/cli-args.js.map +1 -1
- package/dist/modes/app-server/daemon/probe.d.ts +1 -0
- package/dist/modes/app-server/daemon/probe.d.ts.map +1 -1
- package/dist/modes/app-server/daemon/probe.js +4 -1
- package/dist/modes/app-server/daemon/probe.js.map +1 -1
- package/dist/modes/app-server/daemon/spawn.d.ts +26 -0
- package/dist/modes/app-server/daemon/spawn.d.ts.map +1 -0
- package/dist/modes/app-server/daemon/spawn.js +112 -0
- package/dist/modes/app-server/daemon/spawn.js.map +1 -0
- package/dist/modes/app-server/daemon.d.ts.map +1 -1
- package/dist/modes/app-server/daemon.js +14 -112
- package/dist/modes/app-server/daemon.js.map +1 -1
- package/dist/modes/app-server/extension-paths.d.ts +7 -0
- package/dist/modes/app-server/extension-paths.d.ts.map +1 -0
- package/dist/modes/app-server/extension-paths.js +10 -0
- package/dist/modes/app-server/extension-paths.js.map +1 -0
- package/dist/modes/app-server/index.d.ts.map +1 -1
- package/dist/modes/app-server/index.js +4 -1
- package/dist/modes/app-server/index.js.map +1 -1
- package/dist/modes/app-server/runtime.d.ts +5 -1
- package/dist/modes/app-server/runtime.d.ts.map +1 -1
- package/dist/modes/app-server/runtime.js +19 -3
- package/dist/modes/app-server/runtime.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/computer-tips.js +1 -1
- package/dist/modes/interactive/tips/catalog/computer-tips.js.map +1 -1
- package/dist/modes/rpc/custom-capability.d.ts +5 -0
- package/dist/modes/rpc/custom-capability.d.ts.map +1 -1
- package/dist/modes/rpc/custom-capability.js +5 -0
- package/dist/modes/rpc/custom-capability.js.map +1 -1
- package/dist/modes/rpc/host-identity-env.d.ts +20 -0
- package/dist/modes/rpc/host-identity-env.d.ts.map +1 -0
- package/dist/modes/rpc/host-identity-env.js +20 -0
- package/dist/modes/rpc/host-identity-env.js.map +1 -0
- package/dist/modes/rpc/host-lifecycle.js +1 -1
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-observe-request.d.ts +2 -0
- package/dist/modes/rpc/host-observe-request.d.ts.map +1 -1
- package/dist/modes/rpc/host-observe-request.js +4 -0
- package/dist/modes/rpc/host-observe-request.js.map +1 -1
- package/dist/modes/rpc/host-spawn-environment.js +1 -1
- package/dist/modes/rpc/host-spawn-environment.js.map +1 -1
- package/dist/modes/rpc/host-warm.d.ts +57 -0
- package/dist/modes/rpc/host-warm.d.ts.map +1 -0
- package/dist/modes/rpc/host-warm.js +125 -0
- package/dist/modes/rpc/host-warm.js.map +1 -0
- package/dist/modes/rpc/protocol-identity.d.ts +2 -11
- package/dist/modes/rpc/protocol-identity.d.ts.map +1 -1
- package/dist/modes/rpc/protocol-identity.js +2 -11
- package/dist/modes/rpc/protocol-identity.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +24 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +2 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +1 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +12 -1
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +6 -2
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +6 -0
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/docs/app-server-daemon.md +1 -1
- package/docs/app-server.md +3 -0
- package/docs/rpc.md +48 -2
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.d.ts +19 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.js +48 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-endpoint.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/github-copilot-errors.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js +18 -36
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.d.ts +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.js +6 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/devin.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js +15 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/chunk-A4UMPXQF.js +0 -2
- package/dist/bundle/chunks/chunk-HH3KJDWA.js +0 -2
- package/dist/bundle/chunks/chunk-XPJLBOWV.js +0 -5
- 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,
|
|
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
|
-
|
|
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;
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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})`;
|