@senpiper/sp-agent 0.1.10 → 0.1.11

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 (86) hide show
  1. package/INSTALL.md +36 -24
  2. package/README.md +13 -13
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +4 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/distribution.d.ts +2 -2
  7. package/dist/distribution.d.ts.map +1 -1
  8. package/dist/distribution.js +1 -1
  9. package/dist/distribution.js.map +1 -1
  10. package/dist/extension-identity.generated.d.ts +3 -3
  11. package/dist/extension-identity.generated.d.ts.map +1 -1
  12. package/dist/extension-identity.generated.js +3 -3
  13. package/dist/extension-identity.generated.js.map +1 -1
  14. package/dist/install-sp.sh +9 -9
  15. package/dist/release-manager.d.ts.map +1 -1
  16. package/dist/release-manager.js +2 -2
  17. package/dist/release-manager.js.map +1 -1
  18. package/dist/runtime-cli.d.ts.map +1 -1
  19. package/dist/runtime-cli.js +4 -4
  20. package/dist/runtime-cli.js.map +1 -1
  21. package/dist/sdk.d.ts +1 -1
  22. package/dist/sdk.d.ts.map +1 -1
  23. package/dist/sdk.js +5 -5
  24. package/dist/sdk.js.map +1 -1
  25. package/dist/session-manager.d.ts +1 -1
  26. package/dist/session-manager.d.ts.map +1 -1
  27. package/dist/session-manager.js +15 -15
  28. package/dist/session-manager.js.map +1 -1
  29. package/dist/sp-web.d.ts.map +1 -1
  30. package/dist/sp-web.js +2 -2
  31. package/dist/sp-web.js.map +1 -1
  32. package/dist/version.d.ts +1 -1
  33. package/dist/version.d.ts.map +1 -1
  34. package/dist/version.js +1 -1
  35. package/dist/version.js.map +1 -1
  36. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/chunks/{chunk-LUSUF4ZH.js → chunk-VVKE4QZJ.js} +1 -1
  37. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/cli.js +1 -1
  38. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/index.js +1 -1
  39. package/node_modules/@senpiper/pi-coding-agent/dist/bundle/rpc-entry.js +1 -1
  40. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  41. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  42. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  43. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  44. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  45. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  46. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  47. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  48. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  49. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  50. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  51. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  52. package/node_modules/@senpiper/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  53. package/node_modules/@senpiper/pi-coding-agent/package.json +1 -1
  54. package/node_modules/@senpiper/sp-agent-extensions/CHANGELOG.md +17 -0
  55. package/node_modules/@senpiper/sp-agent-extensions/CONCURRENT_SESSIONS.md +9 -9
  56. package/node_modules/@senpiper/sp-agent-extensions/TERMINAL_WEB_BRIDGE.md +28 -9
  57. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.d.ts.map +1 -1
  58. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js +5 -5
  59. package/node_modules/@senpiper/sp-agent-extensions/dist/cli-commands.js.map +1 -1
  60. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts +1 -1
  61. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.d.ts.map +1 -1
  62. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js +18 -18
  63. package/node_modules/@senpiper/sp-agent-extensions/dist/gateway.js.map +1 -1
  64. package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts +1 -1
  65. package/node_modules/@senpiper/sp-agent-extensions/dist/index.d.ts.map +1 -1
  66. package/node_modules/@senpiper/sp-agent-extensions/dist/index.js +1 -1
  67. package/node_modules/@senpiper/sp-agent-extensions/dist/index.js.map +1 -1
  68. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.d.ts.map +1 -1
  69. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js +9 -9
  70. package/node_modules/@senpiper/sp-agent-extensions/dist/login-command.js.map +1 -1
  71. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts +27 -0
  72. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.d.ts.map +1 -1
  73. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js +270 -28
  74. package/node_modules/@senpiper/sp-agent-extensions/dist/sp-web-launcher.js.map +1 -1
  75. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.d.ts.map +1 -1
  76. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js +12 -19
  77. package/node_modules/@senpiper/sp-agent-extensions/dist/startup-header.js.map +1 -1
  78. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts +1 -1
  79. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.d.ts.map +1 -1
  80. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js +11 -9
  81. package/node_modules/@senpiper/sp-agent-extensions/dist/terminal-web-bridge.js.map +1 -1
  82. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.d.ts.map +1 -1
  83. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js +36 -10
  84. package/node_modules/@senpiper/sp-agent-extensions/dist/web-command.js.map +1 -1
  85. package/node_modules/@senpiper/sp-agent-extensions/package.json +3 -2
  86. package/package.json +7 -6
@@ -1,12 +1,20 @@
1
- import { spawn } from "node:child_process";
2
- import { chmodSync, closeSync, constants, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { chmodSync, closeSync, constants, existsSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
4
  import { createRequire } from "node:module";
4
5
  import { join } from "node:path";
6
+ import { setTimeout as sleep } from "node:timers/promises";
5
7
  const SIDECAR_VERSION = 1;
6
8
  const SIDECAR_STATE_FILE = "sp-web-sidecar.json";
7
9
  const LEGACY_STATE_FILE = "pi-web-sidecar.json";
8
- // Keep one lock namespace while older SP launchers are still running.
10
+ // Keep one lock namespace while older Senpiper Workbench launchers are still running.
9
11
  const SIDECAR_LOCK_FILE = "pi-web-sidecar.lock";
12
+ // The sp-web wrapper forwards SIGTERM to Next and force-kills Next after 5s.
13
+ const SIDECAR_STOP_TIMEOUT_MS = 8_000;
14
+ const SIDECAR_STOP_POLL_MS = 100;
15
+ // Older records lack a launch token. Their timestamp must match the process
16
+ // table's second-resolution start time as well as the exact command line.
17
+ const SIDECAR_IDENTITY_TOLERANCE_MS = 5_000;
10
18
  function isRecord(value) {
11
19
  return typeof value === "object" && value !== null && !Array.isArray(value);
12
20
  }
@@ -21,7 +29,9 @@ function readState(path) {
21
29
  typeof value.executable !== "string" ||
22
30
  !Array.isArray(value.args) ||
23
31
  !value.args.every((entry) => typeof entry === "string") ||
24
- typeof value.startedAt !== "string") {
32
+ typeof value.startedAt !== "string" ||
33
+ (value.launchToken !== undefined &&
34
+ (typeof value.launchToken !== "string" || !/^[a-f0-9]{64}$/u.test(value.launchToken)))) {
25
35
  return undefined;
26
36
  }
27
37
  return {
@@ -30,6 +40,7 @@ function readState(path) {
30
40
  executable: value.executable,
31
41
  args: value.args,
32
42
  startedAt: value.startedAt,
43
+ ...(typeof value.launchToken === "string" ? { launchToken: value.launchToken } : {}),
33
44
  };
34
45
  }
35
46
  catch {
@@ -105,6 +116,143 @@ function lockIsStale(path, stat = statSync) {
105
116
  return false;
106
117
  }
107
118
  }
119
+ /** Cooperative lock shared by stop and start. One operation owns it at a time. */
120
+ function acquireSidecarLock(lockPath) {
121
+ for (let attempt = 0; attempt < 2; attempt++) {
122
+ let fd;
123
+ try {
124
+ fd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
125
+ }
126
+ catch (error) {
127
+ if (!isRecord(error) || error.code !== "EEXIST")
128
+ throw error;
129
+ if (!lockIsStale(lockPath))
130
+ return "pending";
131
+ removeFile(lockPath);
132
+ continue;
133
+ }
134
+ try {
135
+ writeFileSync(fd, `${process.pid}\n`, "utf8");
136
+ return fd;
137
+ }
138
+ catch (error) {
139
+ closeSync(fd);
140
+ removeFile(lockPath);
141
+ throw error;
142
+ }
143
+ }
144
+ return "pending";
145
+ }
146
+ function readLinuxProcessArgv(pid) {
147
+ try {
148
+ const argv = readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0");
149
+ while (argv.length > 0 && argv[argv.length - 1] === "")
150
+ argv.pop();
151
+ return argv.length > 0 ? argv : undefined;
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ }
157
+ function readLinuxProcessEnvironment(pid) {
158
+ try {
159
+ const env = {};
160
+ for (const entry of readFileSync(`/proc/${pid}/environ`, "utf8").split("\0")) {
161
+ const separator = entry.indexOf("=");
162
+ if (separator <= 0)
163
+ continue;
164
+ env[entry.slice(0, separator)] = entry.slice(separator + 1);
165
+ }
166
+ return env;
167
+ }
168
+ catch {
169
+ return undefined;
170
+ }
171
+ }
172
+ function readProcessTableField(pid, field, withEnvironment = false) {
173
+ if (process.platform === "win32")
174
+ return undefined;
175
+ try {
176
+ return execFileSync("ps", [withEnvironment ? "eww" : "-ww", "-o", `${field}=`, "-p", String(pid)], {
177
+ encoding: "utf8",
178
+ stdio: ["ignore", "pipe", "ignore"],
179
+ timeout: 2_000,
180
+ env: { ...process.env, LC_ALL: "C" },
181
+ }).trim();
182
+ }
183
+ catch {
184
+ return undefined;
185
+ }
186
+ }
187
+ /**
188
+ * Reads enough of the live process to prove it is still the recorded launch.
189
+ * Linux exposes full argv plus the launch environment; other Unix systems fall
190
+ * back to the process table command and start time.
191
+ */
192
+ function readLiveProcessIdentity(pid) {
193
+ const startedAt = readProcessTableField(pid, "lstart");
194
+ const startedAtMs = startedAt === undefined ? Number.NaN : Date.parse(startedAt);
195
+ if (process.platform === "linux") {
196
+ const argv = readLinuxProcessArgv(pid);
197
+ if (argv)
198
+ return { argv, env: readLinuxProcessEnvironment(pid), startedAtMs };
199
+ }
200
+ const command = readProcessTableField(pid, "command");
201
+ if (command === undefined || command === "")
202
+ return undefined;
203
+ const environment = readProcessTableField(pid, "command", true);
204
+ const launchToken = environment?.match(/(?:^|\s)SP_WEB_SIDECAR_TOKEN=([a-f0-9]{64})(?:\s|$)/u)?.[1];
205
+ return { command, startedAtMs, launchToken };
206
+ }
207
+ function verifySidecarIdentity(record, discoveryDir, identity) {
208
+ if (!identity)
209
+ return { ok: false, reason: "its command line could not be read" };
210
+ const owner = identity.env?.SP_TERMINAL_BRIDGE_DISCOVERY_DIR ?? identity.env?.PI_TERMINAL_BRIDGE_DISCOVERY_DIR;
211
+ if (owner !== undefined && owner !== discoveryDir) {
212
+ return { ok: false, reason: "it belongs to a different Senpiper Workbench profile" };
213
+ }
214
+ if (record.launchToken !== undefined) {
215
+ // Executable overrides can be shebang scripts or PATH aliases, so their
216
+ // live argv need not match spawn's argv. A per-launch nonce survives that
217
+ // transformation without trusting a PID or command name alone.
218
+ return (identity.launchToken ?? identity.env?.SP_WEB_SIDECAR_TOKEN) === record.launchToken
219
+ ? { ok: true }
220
+ : { ok: false, reason: "its launch identity does not match the recorded server" };
221
+ }
222
+ const expected = [record.executable, ...record.args];
223
+ if (identity.argv) {
224
+ if (identity.argv.length !== expected.length || identity.argv.some((value, index) => value !== expected[index])) {
225
+ return { ok: false, reason: "its command line does not match the recorded launch" };
226
+ }
227
+ }
228
+ else if (identity.command !== undefined) {
229
+ if (identity.command !== expected.join(" ")) {
230
+ return { ok: false, reason: "its command line does not match the recorded launch" };
231
+ }
232
+ }
233
+ else {
234
+ return { ok: false, reason: "its command line could not be read" };
235
+ }
236
+ const recordedAt = Date.parse(record.startedAt);
237
+ if (identity.startedAtMs === undefined ||
238
+ !Number.isFinite(identity.startedAtMs) ||
239
+ !Number.isFinite(recordedAt) ||
240
+ Math.abs(identity.startedAtMs - recordedAt) > SIDECAR_IDENTITY_TOLERANCE_MS) {
241
+ return { ok: false, reason: "its start time does not match the recorded launch" };
242
+ }
243
+ return { ok: true };
244
+ }
245
+ async function waitForProcessExit(pid, timeoutMs, isRunning) {
246
+ const deadline = Date.now() + timeoutMs;
247
+ for (;;) {
248
+ if (!isRunning(pid))
249
+ return true;
250
+ const remaining = deadline - Date.now();
251
+ if (remaining <= 0)
252
+ return false;
253
+ await sleep(Math.min(SIDECAR_STOP_POLL_MS, remaining));
254
+ }
255
+ }
108
256
  export function ensureSpWebSidecar(discoveryDir, options = {}) {
109
257
  const env = options.env ?? process.env;
110
258
  if (disabled(env))
@@ -114,40 +262,24 @@ export function ensureSpWebSidecar(discoveryDir, options = {}) {
114
262
  const lockPath = join(discoveryDir, SIDECAR_LOCK_FILE);
115
263
  const isRunning = options.isProcessRunning ?? processIsRunning;
116
264
  const liveState = () => [readState(statePath), readState(legacyPath)].find((state) => state && isRunning(state.pid));
117
- const current = liveState();
118
- if (current)
119
- return { status: "reused", pid: current.pid };
120
265
  let lockFd;
121
- let ownsLock = false;
122
266
  try {
123
- try {
124
- lockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
125
- ownsLock = true;
126
- writeFileSync(lockFd, `${process.pid}\n`, "utf8");
127
- }
128
- catch (error) {
129
- if (!isRecord(error) || error.code !== "EEXIST")
130
- throw error;
131
- const racedState = liveState();
132
- if (racedState)
133
- return { status: "reused", pid: racedState.pid };
134
- if (!lockIsStale(lockPath))
135
- return { status: "pending" };
136
- removeFile(lockPath);
137
- lockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
138
- ownsLock = true;
139
- writeFileSync(lockFd, `${process.pid}\n`, "utf8");
140
- }
267
+ const acquired = acquireSidecarLock(lockPath);
268
+ if (acquired === "pending")
269
+ return { status: "pending" };
270
+ lockFd = acquired;
141
271
  const racedState = liveState();
142
272
  if (racedState)
143
273
  return { status: "reused", pid: racedState.pid };
144
274
  const { executable, args } = resolveSpWebLaunch(env);
275
+ const launchToken = randomBytes(32).toString("hex");
145
276
  const childEnv = {
146
277
  ...env,
147
278
  SP_WEB_NO_OPEN: "1",
148
279
  SP_TERMINAL_BRIDGE_ONLY: "1",
149
280
  SP_TERMINAL_BRIDGE_DISCOVERY_DIR: discoveryDir,
150
281
  SP_TERMINAL_BRIDGE_PROTOCOL: "sp-terminal-bridge-v1",
282
+ SP_WEB_SIDECAR_TOKEN: launchToken,
151
283
  };
152
284
  const spawnProcess = options.spawnProcess ?? ((command, commandArgs, spawnOptions) => spawn(command, commandArgs, spawnOptions));
153
285
  const child = spawnProcess(executable, args, {
@@ -160,7 +292,7 @@ export function ensureSpWebSidecar(discoveryDir, options = {}) {
160
292
  if (child.pid === undefined) {
161
293
  return {
162
294
  status: "failed",
163
- error: `Could not start SP Web with ${executable}. Install Node and the bundled @senpiper/sp-web package, or set SP_WEB_EXECUTABLE to an installed sp-web executable.`,
295
+ error: `Could not start Senpiper Workbench with ${executable}. Install Node and the bundled @senpiper/sp-web package, or set SP_WEB_EXECUTABLE to an installed sp-web executable.`,
164
296
  };
165
297
  }
166
298
  const state = {
@@ -169,6 +301,7 @@ export function ensureSpWebSidecar(discoveryDir, options = {}) {
169
301
  executable,
170
302
  args,
171
303
  startedAt: new Date().toISOString(),
304
+ launchToken,
172
305
  };
173
306
  writePrivateState(statePath, state);
174
307
  // Older cooperating launchers must see the new process too.
@@ -182,9 +315,118 @@ export function ensureSpWebSidecar(discoveryDir, options = {}) {
182
315
  finally {
183
316
  if (lockFd !== undefined)
184
317
  closeSync(lockFd);
185
- if (ownsLock)
318
+ if (lockFd !== undefined)
319
+ removeFile(lockPath);
320
+ }
321
+ }
322
+ export async function stopSpWebSidecar(discoveryDir, options = {}) {
323
+ if (!existsSync(discoveryDir))
324
+ return { status: "not-running" };
325
+ const statePath = join(discoveryDir, SIDECAR_STATE_FILE);
326
+ const legacyPath = join(discoveryDir, LEGACY_STATE_FILE);
327
+ const lockPath = join(discoveryDir, SIDECAR_LOCK_FILE);
328
+ const isRunning = options.isProcessRunning ?? processIsRunning;
329
+ const signalProcess = options.signalProcess ??
330
+ ((pid, signal) => {
331
+ process.kill(pid, signal);
332
+ });
333
+ const readIdentity = options.readProcessIdentity ?? readLiveProcessIdentity;
334
+ const timeoutMs = options.stopTimeoutMs ?? SIDECAR_STOP_TIMEOUT_MS;
335
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 20_000) {
336
+ return { status: "failed", error: "The stop timeout must be between 0 and 20000 ms." };
337
+ }
338
+ const waitForExit = options.waitForProcessExit ?? ((pid, bound) => waitForProcessExit(pid, bound, isRunning));
339
+ let lockFd;
340
+ try {
341
+ const acquired = acquireSidecarLock(lockPath);
342
+ if (acquired === "pending")
343
+ return { status: "pending" };
344
+ lockFd = acquired;
345
+ }
346
+ catch (error) {
347
+ return { status: "failed", error: error instanceof Error ? error.message : String(error) };
348
+ }
349
+ let result = { status: "failed", error: "The stop did not complete." };
350
+ try {
351
+ const records = [readState(statePath), readState(legacyPath)].filter((record) => record !== undefined && isRunning(record.pid));
352
+ const distinctPids = new Set(records.map((record) => record.pid));
353
+ if (distinctPids.size > 1) {
354
+ result = {
355
+ status: "failed",
356
+ error: `Conflicting live Senpiper Workbench records point to PIDs ${[...distinctPids].join(" and ")}. Remove the stale record after checking both processes.`,
357
+ };
358
+ }
359
+ else if (records.length === 0) {
360
+ removeFile(statePath);
361
+ removeFile(legacyPath);
362
+ result = { status: "not-running" };
363
+ }
364
+ else {
365
+ const record = records[0];
366
+ if (record.pid === process.pid || record.pid === 1) {
367
+ throw new Error(`Refusing to stop PID ${record.pid}: it is not a managed web server.`);
368
+ }
369
+ const verification = verifySidecarIdentity(record, discoveryDir, readIdentity(record.pid));
370
+ if (!verification.ok) {
371
+ result = {
372
+ status: "failed",
373
+ pid: record.pid,
374
+ error: `Refusing to stop PID ${record.pid}: ${verification.reason}.`,
375
+ };
376
+ }
377
+ else {
378
+ let signaled = false;
379
+ try {
380
+ signalProcess(record.pid, "SIGTERM");
381
+ signaled = true;
382
+ }
383
+ catch (error) {
384
+ result = {
385
+ status: "failed",
386
+ pid: record.pid,
387
+ error: `Could not stop PID ${record.pid}: ${error instanceof Error ? error.message : String(error)}`,
388
+ };
389
+ }
390
+ if (signaled) {
391
+ const exited = (await waitForExit(record.pid, timeoutMs)) && !isRunning(record.pid);
392
+ if (!exited) {
393
+ result = {
394
+ status: "failed",
395
+ pid: record.pid,
396
+ error: `PID ${record.pid} did not exit within ${timeoutMs} ms.`,
397
+ };
398
+ }
399
+ else {
400
+ // Cleanup only after the process exit was confirmed.
401
+ removeFile(statePath);
402
+ removeFile(legacyPath);
403
+ result = { status: "stopped", pid: record.pid };
404
+ }
405
+ }
406
+ }
407
+ }
408
+ }
409
+ catch (error) {
410
+ result = { status: "failed", error: error instanceof Error ? error.message : String(error) };
411
+ }
412
+ try {
413
+ if (lockFd !== undefined) {
414
+ closeSync(lockFd);
186
415
  removeFile(lockPath);
416
+ }
417
+ }
418
+ catch (error) {
419
+ const message = error instanceof Error ? error.message : String(error);
420
+ result =
421
+ result.status === "failed"
422
+ ? { ...result, error: `${result.error ?? "The stop failed."} Could not release ${lockPath}: ${message}` }
423
+ : {
424
+ status: "failed",
425
+ pid: result.pid,
426
+ error: `Stopped the managed web server but could not release ${lockPath}: ${message}`,
427
+ };
187
428
  }
429
+ return result;
188
430
  }
189
431
  export function getSpWebSidecarStatePath(discoveryDir) {
190
432
  return join(discoveryDir, SIDECAR_STATE_FILE);
@@ -1 +1 @@
1
- {"version":3,"file":"sp-web-launcher.js","sourceRoot":"","sources":["../src/sp-web-launcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, type SpawnOptions, spawn } from \"node:child_process\";\nimport {\n\tchmodSync,\n\tcloseSync,\n\tconstants,\n\topenSync,\n\treadFileSync,\n\ttype Stats,\n\tstatSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\n\nconst SIDECAR_VERSION = 1;\nconst SIDECAR_STATE_FILE = \"sp-web-sidecar.json\";\nconst LEGACY_STATE_FILE = \"pi-web-sidecar.json\";\n// Keep one lock namespace while older SP launchers are still running.\nconst SIDECAR_LOCK_FILE = \"pi-web-sidecar.lock\";\n\nexport interface SpWebSidecarState {\n\tversion: 1;\n\tpid: number;\n\texecutable: string;\n\targs: string[];\n\tstartedAt: string;\n}\n\nexport interface SpWebLaunchResult {\n\tstatus: \"disabled\" | \"reused\" | \"launched\" | \"pending\" | \"failed\";\n\tpid?: number;\n\terror?: string;\n}\n\nexport interface SpWebLauncherOptions {\n\tenv?: NodeJS.ProcessEnv;\n\tspawnProcess?: (executable: string, args: readonly string[], options: SpawnOptions) => ChildProcess;\n\tisProcessRunning?: (pid: number) => boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readState(path: string): SpWebSidecarState | undefined {\n\ttry {\n\t\tconst value = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n\t\tif (\n\t\t\t!isRecord(value) ||\n\t\t\tvalue.version !== SIDECAR_VERSION ||\n\t\t\ttypeof value.pid !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.pid) ||\n\t\t\tvalue.pid <= 0 ||\n\t\t\ttypeof value.executable !== \"string\" ||\n\t\t\t!Array.isArray(value.args) ||\n\t\t\t!value.args.every((entry) => typeof entry === \"string\") ||\n\t\t\ttypeof value.startedAt !== \"string\"\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tversion: SIDECAR_VERSION,\n\t\t\tpid: value.pid,\n\t\t\texecutable: value.executable,\n\t\t\targs: value.args,\n\t\t\tstartedAt: value.startedAt,\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction processIsRunning(pid: number) {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn isRecord(error) && error.code === \"EPERM\";\n\t}\n}\n\nfunction removeFile(path: string) {\n\ttry {\n\t\tunlinkSync(path);\n\t} catch (error) {\n\t\tif (!isRecord(error) || error.code !== \"ENOENT\") throw error;\n\t}\n}\n\nfunction writePrivateState(path: string, state: SpWebSidecarState) {\n\twriteFileSync(path, `${JSON.stringify(state, null, \"\\t\")}\\n`, {\n\t\tencoding: \"utf8\",\n\t\tmode: 0o600,\n\t});\n\tif (process.platform !== \"win32\") chmodSync(path, 0o600);\n}\n\nfunction parseArgs(env: NodeJS.ProcessEnv): string[] {\n\tconst raw = env.SP_WEB_ARGS_JSON ?? env.SP_PI_WEB_ARGS_JSON;\n\tif (!raw) return [];\n\tconst value = JSON.parse(raw) as unknown;\n\tif (!Array.isArray(value) || !value.every((entry) => typeof entry === \"string\")) {\n\t\tthrow new Error(\"SP_WEB_ARGS_JSON must be a JSON array of strings.\");\n\t}\n\treturn value;\n}\n\nfunction disabled(env: NodeJS.ProcessEnv) {\n\treturn /^(?:1|true|yes)$/iu.test(env.SP_WEB_DISABLE ?? env.SP_PI_WEB_DISABLE ?? \"\");\n}\n\nexport function resolveSpWebLaunch(\n\tenv: NodeJS.ProcessEnv,\n\truntime = { moduleUrl: import.meta.url, execPath: process.execPath, bun: !!process.versions.bun },\n): { executable: string; args: string[] } {\n\tconst args = parseArgs(env);\n\tif (!args.includes(\"--no-open\")) args.push(\"--no-open\");\n\tconst explicit = (env.SP_WEB_EXECUTABLE ?? env.SP_PI_WEB_EXECUTABLE)?.trim();\n\tif (explicit) return { executable: explicit, args };\n\n\t// The module anchor handles Node installs. The executable anchor also handles\n\t// compiled Bun binaries inside the npm package, whose module URL is virtual.\n\tfor (const anchor of [runtime.moduleUrl, runtime.execPath]) {\n\t\ttry {\n\t\t\tconst entry = createRequire(anchor).resolve(\"@senpiper/sp-web/bin/sp-web.js\");\n\t\t\treturn { executable: runtime.bun ? \"node\" : runtime.execPath, args: [entry, ...args] };\n\t\t} catch (error) {\n\t\t\tif (!isRecord(error) || error.code !== \"MODULE_NOT_FOUND\") throw error;\n\t\t}\n\t}\n\t// Standalone archives do not necessarily contain an npm dependency tree.\n\treturn { executable: \"sp-web\", args };\n}\n\nfunction lockIsStale(path: string, stat: (path: string) => Stats = statSync) {\n\ttry {\n\t\treturn Date.now() - stat(path).mtimeMs > 30_000;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function ensureSpWebSidecar(discoveryDir: string, options: SpWebLauncherOptions = {}): SpWebLaunchResult {\n\tconst env = options.env ?? process.env;\n\tif (disabled(env)) return { status: \"disabled\" };\n\n\tconst statePath = join(discoveryDir, SIDECAR_STATE_FILE);\n\tconst legacyPath = join(discoveryDir, LEGACY_STATE_FILE);\n\tconst lockPath = join(discoveryDir, SIDECAR_LOCK_FILE);\n\tconst isRunning = options.isProcessRunning ?? processIsRunning;\n\tconst liveState = () => [readState(statePath), readState(legacyPath)].find((state) => state && isRunning(state.pid));\n\tconst current = liveState();\n\tif (current) return { status: \"reused\", pid: current.pid };\n\n\tlet lockFd: number | undefined;\n\tlet ownsLock = false;\n\ttry {\n\t\ttry {\n\t\t\tlockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);\n\t\t\townsLock = true;\n\t\t\twriteFileSync(lockFd, `${process.pid}\\n`, \"utf8\");\n\t\t} catch (error) {\n\t\t\tif (!isRecord(error) || error.code !== \"EEXIST\") throw error;\n\t\t\tconst racedState = liveState();\n\t\t\tif (racedState) return { status: \"reused\", pid: racedState.pid };\n\t\t\tif (!lockIsStale(lockPath)) return { status: \"pending\" };\n\t\t\tremoveFile(lockPath);\n\t\t\tlockFd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);\n\t\t\townsLock = true;\n\t\t\twriteFileSync(lockFd, `${process.pid}\\n`, \"utf8\");\n\t\t}\n\n\t\tconst racedState = liveState();\n\t\tif (racedState) return { status: \"reused\", pid: racedState.pid };\n\t\tconst { executable, args } = resolveSpWebLaunch(env);\n\t\tconst childEnv: NodeJS.ProcessEnv = {\n\t\t\t...env,\n\t\t\tSP_WEB_NO_OPEN: \"1\",\n\t\t\tSP_TERMINAL_BRIDGE_ONLY: \"1\",\n\t\t\tSP_TERMINAL_BRIDGE_DISCOVERY_DIR: discoveryDir,\n\t\t\tSP_TERMINAL_BRIDGE_PROTOCOL: \"sp-terminal-bridge-v1\",\n\t\t};\n\t\tconst spawnProcess =\n\t\t\toptions.spawnProcess ?? ((command, commandArgs, spawnOptions) => spawn(command, commandArgs, spawnOptions));\n\t\tconst child = spawnProcess(executable, args, {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tshell: false,\n\t\t\tenv: childEnv,\n\t\t});\n\t\tchild.once(\"error\", () => {});\n\t\tif (child.pid === undefined) {\n\t\t\treturn {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: `Could not start SP Web with ${executable}. Install Node and the bundled @senpiper/sp-web package, or set SP_WEB_EXECUTABLE to an installed sp-web executable.`,\n\t\t\t};\n\t\t}\n\t\tconst state: SpWebSidecarState = {\n\t\t\tversion: SIDECAR_VERSION,\n\t\t\tpid: child.pid,\n\t\t\texecutable,\n\t\t\targs,\n\t\t\tstartedAt: new Date().toISOString(),\n\t\t};\n\t\twritePrivateState(statePath, state);\n\t\t// Older cooperating launchers must see the new process too.\n\t\twritePrivateState(legacyPath, state);\n\t\tchild.unref();\n\t\treturn { status: \"launched\", pid: child.pid };\n\t} catch (error) {\n\t\treturn { status: \"failed\", error: error instanceof Error ? error.message : String(error) };\n\t} finally {\n\t\tif (lockFd !== undefined) closeSync(lockFd);\n\t\tif (ownsLock) removeFile(lockPath);\n\t}\n}\n\nexport function getSpWebSidecarStatePath(discoveryDir: string) {\n\treturn join(discoveryDir, SIDECAR_STATE_FILE);\n}\n"]}
1
+ {"version":3,"file":"sp-web-launcher.js","sourceRoot":"","sources":["../src/sp-web-launcher.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, execFileSync, type SpawnOptions, spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n\tchmodSync,\n\tcloseSync,\n\tconstants,\n\texistsSync,\n\topenSync,\n\treadFileSync,\n\ttype Stats,\n\tstatSync,\n\tunlinkSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nconst SIDECAR_VERSION = 1;\nconst SIDECAR_STATE_FILE = \"sp-web-sidecar.json\";\nconst LEGACY_STATE_FILE = \"pi-web-sidecar.json\";\n// Keep one lock namespace while older Senpiper Workbench launchers are still running.\nconst SIDECAR_LOCK_FILE = \"pi-web-sidecar.lock\";\n// The sp-web wrapper forwards SIGTERM to Next and force-kills Next after 5s.\nconst SIDECAR_STOP_TIMEOUT_MS = 8_000;\nconst SIDECAR_STOP_POLL_MS = 100;\n// Older records lack a launch token. Their timestamp must match the process\n// table's second-resolution start time as well as the exact command line.\nconst SIDECAR_IDENTITY_TOLERANCE_MS = 5_000;\n\nexport interface SpWebSidecarState {\n\tversion: 1;\n\tpid: number;\n\texecutable: string;\n\targs: string[];\n\tstartedAt: string;\n\t/** Random identity inherited only by this launched process tree. */\n\tlaunchToken?: string;\n}\n\nexport interface SpWebLaunchResult {\n\tstatus: \"disabled\" | \"reused\" | \"launched\" | \"pending\" | \"failed\";\n\tpid?: number;\n\terror?: string;\n}\n\nexport interface SpWebStopResult {\n\tstatus: \"stopped\" | \"not-running\" | \"pending\" | \"failed\";\n\tpid?: number;\n\terror?: string;\n}\n\nexport interface SpWebProcessIdentity {\n\tlaunchToken?: string;\n\t/** Full argv of the live process, when the platform exposes argument boundaries. */\n\targv?: string[];\n\t/** Joined command line, used when argv boundaries are unavailable. */\n\tcommand?: string;\n\t/** Live process environment, when readable. */\n\tenv?: NodeJS.ProcessEnv;\n\t/** Wall-clock start time of the live process, when readable. */\n\tstartedAtMs?: number;\n}\n\nexport interface SpWebLauncherOptions {\n\tenv?: NodeJS.ProcessEnv;\n\tspawnProcess?: (executable: string, args: readonly string[], options: SpawnOptions) => ChildProcess;\n\tisProcessRunning?: (pid: number) => boolean;\n\t/** Verifies that a live PID is still the process recorded at launch. */\n\treadProcessIdentity?: (pid: number) => SpWebProcessIdentity | undefined;\n\t/** Sends the graceful stop signal. Defaults to process.kill. */\n\tsignalProcess?: (pid: number, signal: NodeJS.Signals) => void;\n\t/** Waits for a signaled PID to exit. Defaults to polling isProcessRunning. */\n\twaitForProcessExit?: (pid: number, timeoutMs: number) => Promise<boolean>;\n\t/** Grace period for /web off before reporting a timeout. Defaults to 8000 ms. */\n\tstopTimeoutMs?: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction readState(path: string): SpWebSidecarState | undefined {\n\ttry {\n\t\tconst value = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n\t\tif (\n\t\t\t!isRecord(value) ||\n\t\t\tvalue.version !== SIDECAR_VERSION ||\n\t\t\ttypeof value.pid !== \"number\" ||\n\t\t\t!Number.isSafeInteger(value.pid) ||\n\t\t\tvalue.pid <= 0 ||\n\t\t\ttypeof value.executable !== \"string\" ||\n\t\t\t!Array.isArray(value.args) ||\n\t\t\t!value.args.every((entry) => typeof entry === \"string\") ||\n\t\t\ttypeof value.startedAt !== \"string\" ||\n\t\t\t(value.launchToken !== undefined &&\n\t\t\t\t(typeof value.launchToken !== \"string\" || !/^[a-f0-9]{64}$/u.test(value.launchToken)))\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tversion: SIDECAR_VERSION,\n\t\t\tpid: value.pid,\n\t\t\texecutable: value.executable,\n\t\t\targs: value.args,\n\t\t\tstartedAt: value.startedAt,\n\t\t\t...(typeof value.launchToken === \"string\" ? { launchToken: value.launchToken } : {}),\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction processIsRunning(pid: number) {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn isRecord(error) && error.code === \"EPERM\";\n\t}\n}\n\nfunction removeFile(path: string) {\n\ttry {\n\t\tunlinkSync(path);\n\t} catch (error) {\n\t\tif (!isRecord(error) || error.code !== \"ENOENT\") throw error;\n\t}\n}\n\nfunction writePrivateState(path: string, state: SpWebSidecarState) {\n\twriteFileSync(path, `${JSON.stringify(state, null, \"\\t\")}\\n`, {\n\t\tencoding: \"utf8\",\n\t\tmode: 0o600,\n\t});\n\tif (process.platform !== \"win32\") chmodSync(path, 0o600);\n}\n\nfunction parseArgs(env: NodeJS.ProcessEnv): string[] {\n\tconst raw = env.SP_WEB_ARGS_JSON ?? env.SP_PI_WEB_ARGS_JSON;\n\tif (!raw) return [];\n\tconst value = JSON.parse(raw) as unknown;\n\tif (!Array.isArray(value) || !value.every((entry) => typeof entry === \"string\")) {\n\t\tthrow new Error(\"SP_WEB_ARGS_JSON must be a JSON array of strings.\");\n\t}\n\treturn value;\n}\n\nfunction disabled(env: NodeJS.ProcessEnv) {\n\treturn /^(?:1|true|yes)$/iu.test(env.SP_WEB_DISABLE ?? env.SP_PI_WEB_DISABLE ?? \"\");\n}\n\nexport function resolveSpWebLaunch(\n\tenv: NodeJS.ProcessEnv,\n\truntime = { moduleUrl: import.meta.url, execPath: process.execPath, bun: !!process.versions.bun },\n): { executable: string; args: string[] } {\n\tconst args = parseArgs(env);\n\tif (!args.includes(\"--no-open\")) args.push(\"--no-open\");\n\tconst explicit = (env.SP_WEB_EXECUTABLE ?? env.SP_PI_WEB_EXECUTABLE)?.trim();\n\tif (explicit) return { executable: explicit, args };\n\n\t// The module anchor handles Node installs. The executable anchor also handles\n\t// compiled Bun binaries inside the npm package, whose module URL is virtual.\n\tfor (const anchor of [runtime.moduleUrl, runtime.execPath]) {\n\t\ttry {\n\t\t\tconst entry = createRequire(anchor).resolve(\"@senpiper/sp-web/bin/sp-web.js\");\n\t\t\treturn { executable: runtime.bun ? \"node\" : runtime.execPath, args: [entry, ...args] };\n\t\t} catch (error) {\n\t\t\tif (!isRecord(error) || error.code !== \"MODULE_NOT_FOUND\") throw error;\n\t\t}\n\t}\n\t// Standalone archives do not necessarily contain an npm dependency tree.\n\treturn { executable: \"sp-web\", args };\n}\n\nfunction lockIsStale(path: string, stat: (path: string) => Stats = statSync) {\n\ttry {\n\t\treturn Date.now() - stat(path).mtimeMs > 30_000;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Cooperative lock shared by stop and start. One operation owns it at a time. */\nfunction acquireSidecarLock(lockPath: string): number | \"pending\" {\n\tfor (let attempt = 0; attempt < 2; attempt++) {\n\t\tlet fd: number;\n\t\ttry {\n\t\t\tfd = openSync(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);\n\t\t} catch (error) {\n\t\t\tif (!isRecord(error) || error.code !== \"EEXIST\") throw error;\n\t\t\tif (!lockIsStale(lockPath)) return \"pending\";\n\t\t\tremoveFile(lockPath);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\twriteFileSync(fd, `${process.pid}\\n`, \"utf8\");\n\t\t\treturn fd;\n\t\t} catch (error) {\n\t\t\tcloseSync(fd);\n\t\t\tremoveFile(lockPath);\n\t\t\tthrow error;\n\t\t}\n\t}\n\treturn \"pending\";\n}\n\nfunction readLinuxProcessArgv(pid: number): string[] | undefined {\n\ttry {\n\t\tconst argv = readFileSync(`/proc/${pid}/cmdline`, \"utf8\").split(\"\\0\");\n\t\twhile (argv.length > 0 && argv[argv.length - 1] === \"\") argv.pop();\n\t\treturn argv.length > 0 ? argv : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction readLinuxProcessEnvironment(pid: number): NodeJS.ProcessEnv | undefined {\n\ttry {\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const entry of readFileSync(`/proc/${pid}/environ`, \"utf8\").split(\"\\0\")) {\n\t\t\tconst separator = entry.indexOf(\"=\");\n\t\t\tif (separator <= 0) continue;\n\t\t\tenv[entry.slice(0, separator)] = entry.slice(separator + 1);\n\t\t}\n\t\treturn env;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction readProcessTableField(pid: number, field: \"command\" | \"lstart\", withEnvironment = false): string | undefined {\n\tif (process.platform === \"win32\") return undefined;\n\ttry {\n\t\treturn execFileSync(\"ps\", [withEnvironment ? \"eww\" : \"-ww\", \"-o\", `${field}=`, \"-p\", String(pid)], {\n\t\t\tencoding: \"utf8\",\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\ttimeout: 2_000,\n\t\t\tenv: { ...process.env, LC_ALL: \"C\" },\n\t\t}).trim();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Reads enough of the live process to prove it is still the recorded launch.\n * Linux exposes full argv plus the launch environment; other Unix systems fall\n * back to the process table command and start time.\n */\nfunction readLiveProcessIdentity(pid: number): SpWebProcessIdentity | undefined {\n\tconst startedAt = readProcessTableField(pid, \"lstart\");\n\tconst startedAtMs = startedAt === undefined ? Number.NaN : Date.parse(startedAt);\n\tif (process.platform === \"linux\") {\n\t\tconst argv = readLinuxProcessArgv(pid);\n\t\tif (argv) return { argv, env: readLinuxProcessEnvironment(pid), startedAtMs };\n\t}\n\tconst command = readProcessTableField(pid, \"command\");\n\tif (command === undefined || command === \"\") return undefined;\n\tconst environment = readProcessTableField(pid, \"command\", true);\n\tconst launchToken = environment?.match(/(?:^|\\s)SP_WEB_SIDECAR_TOKEN=([a-f0-9]{64})(?:\\s|$)/u)?.[1];\n\treturn { command, startedAtMs, launchToken };\n}\n\nfunction verifySidecarIdentity(\n\trecord: SpWebSidecarState,\n\tdiscoveryDir: string,\n\tidentity: SpWebProcessIdentity | undefined,\n): { ok: true } | { ok: false; reason: string } {\n\tif (!identity) return { ok: false, reason: \"its command line could not be read\" };\n\tconst owner = identity.env?.SP_TERMINAL_BRIDGE_DISCOVERY_DIR ?? identity.env?.PI_TERMINAL_BRIDGE_DISCOVERY_DIR;\n\tif (owner !== undefined && owner !== discoveryDir) {\n\t\treturn { ok: false, reason: \"it belongs to a different Senpiper Workbench profile\" };\n\t}\n\tif (record.launchToken !== undefined) {\n\t\t// Executable overrides can be shebang scripts or PATH aliases, so their\n\t\t// live argv need not match spawn's argv. A per-launch nonce survives that\n\t\t// transformation without trusting a PID or command name alone.\n\t\treturn (identity.launchToken ?? identity.env?.SP_WEB_SIDECAR_TOKEN) === record.launchToken\n\t\t\t? { ok: true }\n\t\t\t: { ok: false, reason: \"its launch identity does not match the recorded server\" };\n\t}\n\tconst expected = [record.executable, ...record.args];\n\tif (identity.argv) {\n\t\tif (identity.argv.length !== expected.length || identity.argv.some((value, index) => value !== expected[index])) {\n\t\t\treturn { ok: false, reason: \"its command line does not match the recorded launch\" };\n\t\t}\n\t} else if (identity.command !== undefined) {\n\t\tif (identity.command !== expected.join(\" \")) {\n\t\t\treturn { ok: false, reason: \"its command line does not match the recorded launch\" };\n\t\t}\n\t} else {\n\t\treturn { ok: false, reason: \"its command line could not be read\" };\n\t}\n\tconst recordedAt = Date.parse(record.startedAt);\n\tif (\n\t\tidentity.startedAtMs === undefined ||\n\t\t!Number.isFinite(identity.startedAtMs) ||\n\t\t!Number.isFinite(recordedAt) ||\n\t\tMath.abs(identity.startedAtMs - recordedAt) > SIDECAR_IDENTITY_TOLERANCE_MS\n\t) {\n\t\treturn { ok: false, reason: \"its start time does not match the recorded launch\" };\n\t}\n\treturn { ok: true };\n}\n\nasync function waitForProcessExit(\n\tpid: number,\n\ttimeoutMs: number,\n\tisRunning: (pid: number) => boolean,\n): Promise<boolean> {\n\tconst deadline = Date.now() + timeoutMs;\n\tfor (;;) {\n\t\tif (!isRunning(pid)) return true;\n\t\tconst remaining = deadline - Date.now();\n\t\tif (remaining <= 0) return false;\n\t\tawait sleep(Math.min(SIDECAR_STOP_POLL_MS, remaining));\n\t}\n}\n\nexport function ensureSpWebSidecar(discoveryDir: string, options: SpWebLauncherOptions = {}): SpWebLaunchResult {\n\tconst env = options.env ?? process.env;\n\tif (disabled(env)) return { status: \"disabled\" };\n\n\tconst statePath = join(discoveryDir, SIDECAR_STATE_FILE);\n\tconst legacyPath = join(discoveryDir, LEGACY_STATE_FILE);\n\tconst lockPath = join(discoveryDir, SIDECAR_LOCK_FILE);\n\tconst isRunning = options.isProcessRunning ?? processIsRunning;\n\tconst liveState = () => [readState(statePath), readState(legacyPath)].find((state) => state && isRunning(state.pid));\n\tlet lockFd: number | undefined;\n\ttry {\n\t\tconst acquired = acquireSidecarLock(lockPath);\n\t\tif (acquired === \"pending\") return { status: \"pending\" };\n\t\tlockFd = acquired;\n\n\t\tconst racedState = liveState();\n\t\tif (racedState) return { status: \"reused\", pid: racedState.pid };\n\t\tconst { executable, args } = resolveSpWebLaunch(env);\n\t\tconst launchToken = randomBytes(32).toString(\"hex\");\n\t\tconst childEnv: NodeJS.ProcessEnv = {\n\t\t\t...env,\n\t\t\tSP_WEB_NO_OPEN: \"1\",\n\t\t\tSP_TERMINAL_BRIDGE_ONLY: \"1\",\n\t\t\tSP_TERMINAL_BRIDGE_DISCOVERY_DIR: discoveryDir,\n\t\t\tSP_TERMINAL_BRIDGE_PROTOCOL: \"sp-terminal-bridge-v1\",\n\t\t\tSP_WEB_SIDECAR_TOKEN: launchToken,\n\t\t};\n\t\tconst spawnProcess =\n\t\t\toptions.spawnProcess ?? ((command, commandArgs, spawnOptions) => spawn(command, commandArgs, spawnOptions));\n\t\tconst child = spawnProcess(executable, args, {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tshell: false,\n\t\t\tenv: childEnv,\n\t\t});\n\t\tchild.once(\"error\", () => {});\n\t\tif (child.pid === undefined) {\n\t\t\treturn {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: `Could not start Senpiper Workbench with ${executable}. Install Node and the bundled @senpiper/sp-web package, or set SP_WEB_EXECUTABLE to an installed sp-web executable.`,\n\t\t\t};\n\t\t}\n\t\tconst state: SpWebSidecarState = {\n\t\t\tversion: SIDECAR_VERSION,\n\t\t\tpid: child.pid,\n\t\t\texecutable,\n\t\t\targs,\n\t\t\tstartedAt: new Date().toISOString(),\n\t\t\tlaunchToken,\n\t\t};\n\t\twritePrivateState(statePath, state);\n\t\t// Older cooperating launchers must see the new process too.\n\t\twritePrivateState(legacyPath, state);\n\t\tchild.unref();\n\t\treturn { status: \"launched\", pid: child.pid };\n\t} catch (error) {\n\t\treturn { status: \"failed\", error: error instanceof Error ? error.message : String(error) };\n\t} finally {\n\t\tif (lockFd !== undefined) closeSync(lockFd);\n\t\tif (lockFd !== undefined) removeFile(lockPath);\n\t}\n}\n\nexport async function stopSpWebSidecar(\n\tdiscoveryDir: string,\n\toptions: SpWebLauncherOptions = {},\n): Promise<SpWebStopResult> {\n\tif (!existsSync(discoveryDir)) return { status: \"not-running\" };\n\n\tconst statePath = join(discoveryDir, SIDECAR_STATE_FILE);\n\tconst legacyPath = join(discoveryDir, LEGACY_STATE_FILE);\n\tconst lockPath = join(discoveryDir, SIDECAR_LOCK_FILE);\n\tconst isRunning = options.isProcessRunning ?? processIsRunning;\n\tconst signalProcess =\n\t\toptions.signalProcess ??\n\t\t((pid: number, signal: NodeJS.Signals) => {\n\t\t\tprocess.kill(pid, signal);\n\t\t});\n\tconst readIdentity = options.readProcessIdentity ?? readLiveProcessIdentity;\n\tconst timeoutMs = options.stopTimeoutMs ?? SIDECAR_STOP_TIMEOUT_MS;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 20_000) {\n\t\treturn { status: \"failed\", error: \"The stop timeout must be between 0 and 20000 ms.\" };\n\t}\n\tconst waitForExit =\n\t\toptions.waitForProcessExit ?? ((pid: number, bound: number) => waitForProcessExit(pid, bound, isRunning));\n\n\tlet lockFd: number | undefined;\n\ttry {\n\t\tconst acquired = acquireSidecarLock(lockPath);\n\t\tif (acquired === \"pending\") return { status: \"pending\" };\n\t\tlockFd = acquired;\n\t} catch (error) {\n\t\treturn { status: \"failed\", error: error instanceof Error ? error.message : String(error) };\n\t}\n\n\tlet result: SpWebStopResult = { status: \"failed\", error: \"The stop did not complete.\" };\n\ttry {\n\t\tconst records = [readState(statePath), readState(legacyPath)].filter(\n\t\t\t(record): record is SpWebSidecarState => record !== undefined && isRunning(record.pid),\n\t\t);\n\t\tconst distinctPids = new Set(records.map((record) => record.pid));\n\t\tif (distinctPids.size > 1) {\n\t\t\tresult = {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\terror: `Conflicting live Senpiper Workbench records point to PIDs ${[...distinctPids].join(\" and \")}. Remove the stale record after checking both processes.`,\n\t\t\t};\n\t\t} else if (records.length === 0) {\n\t\t\tremoveFile(statePath);\n\t\t\tremoveFile(legacyPath);\n\t\t\tresult = { status: \"not-running\" };\n\t\t} else {\n\t\t\tconst record = records[0]!;\n\t\t\tif (record.pid === process.pid || record.pid === 1) {\n\t\t\t\tthrow new Error(`Refusing to stop PID ${record.pid}: it is not a managed web server.`);\n\t\t\t}\n\t\t\tconst verification = verifySidecarIdentity(record, discoveryDir, readIdentity(record.pid));\n\t\t\tif (!verification.ok) {\n\t\t\t\tresult = {\n\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\tpid: record.pid,\n\t\t\t\t\terror: `Refusing to stop PID ${record.pid}: ${verification.reason}.`,\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tlet signaled = false;\n\t\t\t\ttry {\n\t\t\t\t\tsignalProcess(record.pid, \"SIGTERM\");\n\t\t\t\t\tsignaled = true;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tresult = {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tpid: record.pid,\n\t\t\t\t\t\terror: `Could not stop PID ${record.pid}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (signaled) {\n\t\t\t\t\tconst exited = (await waitForExit(record.pid, timeoutMs)) && !isRunning(record.pid);\n\t\t\t\t\tif (!exited) {\n\t\t\t\t\t\tresult = {\n\t\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\t\tpid: record.pid,\n\t\t\t\t\t\t\terror: `PID ${record.pid} did not exit within ${timeoutMs} ms.`,\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Cleanup only after the process exit was confirmed.\n\t\t\t\t\t\tremoveFile(statePath);\n\t\t\t\t\t\tremoveFile(legacyPath);\n\t\t\t\t\t\tresult = { status: \"stopped\", pid: record.pid };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tresult = { status: \"failed\", error: error instanceof Error ? error.message : String(error) };\n\t}\n\n\ttry {\n\t\tif (lockFd !== undefined) {\n\t\t\tcloseSync(lockFd);\n\t\t\tremoveFile(lockPath);\n\t\t}\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tresult =\n\t\t\tresult.status === \"failed\"\n\t\t\t\t? { ...result, error: `${result.error ?? \"The stop failed.\"} Could not release ${lockPath}: ${message}` }\n\t\t\t\t: {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tpid: result.pid,\n\t\t\t\t\t\terror: `Stopped the managed web server but could not release ${lockPath}: ${message}`,\n\t\t\t\t\t};\n\t}\n\treturn result;\n}\n\nexport function getSpWebSidecarStatePath(discoveryDir: string) {\n\treturn join(discoveryDir, SIDECAR_STATE_FILE);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"startup-header.d.ts","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAarF,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,MAAM,YAmCf;AAED,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,cAAc,sBAEpE;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,QAWvD","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst LLM_IN_A_BOX_BANNER = [\n\t\" _ _ __ __ ___ _ _ _ ____ ___ __ __\",\n\t\"| | | | | \\\\/ | |_ _| \\\\ | | / \\\\ | __ ) / _ \\\\ \\\\ \\\\/ /\",\n\t\"| | | | | |\\\\/| | | || \\\\| | / _ \\\\ | _ \\\\| | | | \\\\ /\",\n\t\"| |___| |___| | | | | || |\\\\ | / ___ \\\\ | |_) | |_| | / \\\\ \",\n\t\"|_____|_____|_| |_| |___|_| \\\\_| /_/ \\\\_\\\\ |____/ \\\\___/ /_/\\\\_\\\\\",\n];\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `sp-gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\n\tif (availableWidth < 68) {\n\t\treturn [\n\t\t\ttruncateLine(theme.bold(theme.fg(\"accent\", \"LLM IN A BOX\")) + theme.fg(\"dim\", \" · powered by Senpiper\")),\n\t\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t\t...loginPrompt,\n\t\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t\t];\n\t}\n\n\treturn [\n\t\t...LLM_IN_A_BOX_BANNER.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"powered by Senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
1
+ {"version":3,"file":"startup-header.d.ts","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAcrF,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,MAAM,YA0Bf;AAED,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,cAAc,sBAEpE;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,QAWvD","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst WORKBENCH_BANNER = [\n\t\"__ __ ___ ____ _ __ ____ _____ _ _ ____ _ _\",\n\tString.raw`\\ \\ / / / _ \\ | _ \\ | |/ / | __ ) | ____| | \\ | | / ___| | | | |`,\n\tString.raw` \\ \\ /\\ / / | | | | | |_) | | ' / | _ \\ | _| | \\| | | | | |_| |`,\n\tString.raw` \\ V V / | |_| | | _ < | . \\ | |_) | | |___ | |\\ | | |___ | _ |`,\n\tString.raw` \\_/\\_/ \\___/ |_| \\_\\ |_|\\_\\ |____/ |_____| |_| \\_| \\____| |_| |_|`,\n];\nconst WORKBENCH_BANNER_WIDTH = Math.max(...WORKBENCH_BANNER.map((line) => line.length));\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `Senpiper Gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\tconst wordmark = availableWidth >= WORKBENCH_BANNER_WIDTH ? WORKBENCH_BANNER : [\"WORKBENCH\"];\n\n\treturn [\n\t\t...wordmark.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"by senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
@@ -1,13 +1,14 @@
1
1
  import { truncateToWidth } from "@senpiper/pi-coding-agent/runtime-deps";
2
2
  import { readSpGatewayState } from "./gateway.js";
3
3
  import { getSpWebUrl } from "./web-command.js";
4
- const LLM_IN_A_BOX_BANNER = [
5
- " _ _ __ __ ___ _ _ _ ____ ___ __ __",
6
- "| | | | | \\/ | |_ _| \\ | | / \\ | __ ) / _ \\ \\ \\/ /",
7
- "| | | | | |\\/| | | || \\| | / _ \\ | _ \\| | | | \\ /",
8
- "| |___| |___| | | | | || |\\ | / ___ \\ | |_) | |_| | / \\ ",
9
- "|_____|_____|_| |_| |___|_| \\_| /_/ \\_\\ |____/ \\___/ /_/\\_\\",
4
+ const WORKBENCH_BANNER = [
5
+ "__ __ ___ ____ _ __ ____ _____ _ _ ____ _ _",
6
+ String.raw `\ \ / / / _ \ | _ \ | |/ / | __ ) | ____| | \ | | / ___| | | | |`,
7
+ String.raw ` \ \ /\ / / | | | | | |_) | | ' / | _ \ | _| | \| | | | | |_| |`,
8
+ String.raw ` \ V V / | |_| | | _ < | . \ | |_) | | |___ | |\ | | |___ | _ |`,
9
+ String.raw ` \_/\_/ \___/ |_| \_\ |_|\_\ |____/ |_____| |_| \_| \____| |_| |_|`,
10
10
  ];
11
+ const WORKBENCH_BANNER_WIDTH = Math.max(...WORKBENCH_BANNER.map((line) => line.length));
11
12
  export function renderSpStartupHeader(theme, width, modelId, gatewayConnected = false, webUrl) {
12
13
  if (webUrl === undefined) {
13
14
  try {
@@ -19,24 +20,16 @@ export function renderSpStartupHeader(theme, width, modelId, gatewayConnected =
19
20
  }
20
21
  const availableWidth = Math.max(1, width);
21
22
  const truncateLine = (line) => truncateToWidth(line, availableWidth);
22
- const status = `sp-gateway: ${gatewayConnected ? "connected" : "not configured"} · model: ${modelId ?? "not selected"}`;
23
+ const status = `Senpiper Gateway: ${gatewayConnected ? "connected" : "not configured"} · model: ${modelId ?? "not selected"}`;
23
24
  const loginPrompt = gatewayConnected
24
25
  ? []
25
26
  : [truncateLine(theme.bold(theme.fg("warning", "Not logged in. Run /login to continue.")))];
26
27
  const commands = "/web · /login · /model";
27
- if (availableWidth < 68) {
28
- return [
29
- truncateLine(theme.bold(theme.fg("accent", "LLM IN A BOX")) + theme.fg("dim", " · powered by Senpiper")),
30
- truncateLine(theme.fg("dim", `SP Web: ${webUrl}`)),
31
- truncateLine(theme.fg("muted", status)),
32
- ...loginPrompt,
33
- truncateLine(theme.fg("dim", commands)),
34
- ];
35
- }
28
+ const wordmark = availableWidth >= WORKBENCH_BANNER_WIDTH ? WORKBENCH_BANNER : ["WORKBENCH"];
36
29
  return [
37
- ...LLM_IN_A_BOX_BANNER.map((line) => truncateLine(theme.bold(theme.fg("accent", line)))),
38
- truncateLine(theme.fg("dim", "powered by Senpiper")),
39
- truncateLine(theme.fg("dim", `SP Web: ${webUrl}`)),
30
+ ...wordmark.map((line) => truncateLine(theme.bold(theme.fg("accent", line)))),
31
+ truncateLine(theme.fg("dim", "by senpiper")),
32
+ truncateLine(theme.fg("dim", `Web: ${webUrl}`)),
40
33
  truncateLine(theme.fg("muted", status)),
41
34
  ...loginPrompt,
42
35
  truncateLine(theme.fg("dim", commands)),
@@ -1 +1 @@
1
- {"version":3,"file":"startup-header.js","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst LLM_IN_A_BOX_BANNER = [\n\t\" _ _ __ __ ___ _ _ _ ____ ___ __ __\",\n\t\"| | | | | \\\\/ | |_ _| \\\\ | | / \\\\ | __ ) / _ \\\\ \\\\ \\\\/ /\",\n\t\"| | | | | |\\\\/| | | || \\\\| | / _ \\\\ | _ \\\\| | | | \\\\ /\",\n\t\"| |___| |___| | | | | || |\\\\ | / ___ \\\\ | |_) | |_| | / \\\\ \",\n\t\"|_____|_____|_| |_| |___|_| \\\\_| /_/ \\\\_\\\\ |____/ \\\\___/ /_/\\\\_\\\\\",\n];\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `sp-gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\n\tif (availableWidth < 68) {\n\t\treturn [\n\t\t\ttruncateLine(theme.bold(theme.fg(\"accent\", \"LLM IN A BOX\")) + theme.fg(\"dim\", \" · powered by Senpiper\")),\n\t\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t\t...loginPrompt,\n\t\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t\t];\n\t}\n\n\treturn [\n\t\t...LLM_IN_A_BOX_BANNER.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"powered by Senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `SP Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
1
+ {"version":3,"file":"startup-header.js","sourceRoot":"","sources":["../src/startup-header.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,gBAAgB,GAAG;IACxB,4EAA4E;IAC5E,MAAM,CAAC,GAAG,CAAA,6EAA6E;IACvF,MAAM,CAAC,GAAG,CAAA,6EAA6E;IACvF,MAAM,CAAC,GAAG,CAAA,6EAA6E;IACvF,MAAM,CAAC,GAAG,CAAA,6EAA6E;CACvF,CAAC;AACF,MAAM,sBAAsB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAExF,MAAM,UAAU,qBAAqB,CACpC,KAAY,EACZ,KAAa,EACb,OAAgB,EAChB,gBAAgB,GAAG,KAAK,EACxB,MAAe,EACd;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC;YACJ,MAAM,GAAG,WAAW,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,GAAG,4BAA4B,CAAC;QACvC,CAAC;IACF,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC7E,MAAM,MAAM,GAAG,qBAAqB,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,cAAa,OAAO,IAAI,cAAc,EAAE,CAAC;IAC9H,MAAM,WAAW,GAAG,gBAAgB;QACnC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,0BAAwB,CAAC;IAC1C,MAAM,QAAQ,GAAG,cAAc,IAAI,sBAAsB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAE7F,OAAO;QACN,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7E,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAC5C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,MAAM,EAAE,CAAC,CAAC;QAC/C,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvC,GAAG,WAAW;QACd,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;KACvC,CAAC;AAAA,CACF;AAED,MAAM,UAAU,sBAAsB,CAAC,cAA8B,EAAE;IACtE,OAAO,cAAc,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC;AAAA,CAC/E;AAED,MAAM,UAAU,uBAAuB,CAAC,EAAgB,EAAE;IACzD,EAAE,CAAC,4BAA4B,CAAC,sBAAsB,CAAC,CAAC;IACxD,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;QACjD,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO;QACnC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACtC,MAAM,CAAC,KAAK,EAAE;gBACb,OAAO,qBAAqB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,KAAK,SAAS,CAAC,CAAC;YAAA,CAClG;YACD,UAAU,GAAG,EAAC,CAAC;SACf,CAAC,CAAC,CAAC;IAAA,CACJ,CAAC,CAAC;AAAA,CACH","sourcesContent":["import type { ExtensionAPI, StartupMessage, Theme } from \"@senpiper/pi-coding-agent\";\nimport { truncateToWidth } from \"@senpiper/pi-coding-agent/runtime-deps\";\nimport { readSpGatewayState } from \"./gateway.ts\";\nimport { getSpWebUrl } from \"./web-command.ts\";\n\nconst WORKBENCH_BANNER = [\n\t\"__ __ ___ ____ _ __ ____ _____ _ _ ____ _ _\",\n\tString.raw`\\ \\ / / / _ \\ | _ \\ | |/ / | __ ) | ____| | \\ | | / ___| | | | |`,\n\tString.raw` \\ \\ /\\ / / | | | | | |_) | | ' / | _ \\ | _| | \\| | | | | |_| |`,\n\tString.raw` \\ V V / | |_| | | _ < | . \\ | |_) | | |___ | |\\ | | |___ | _ |`,\n\tString.raw` \\_/\\_/ \\___/ |_| \\_\\ |_|\\_\\ |____/ |_____| |_| \\_| \\____| |_| |_|`,\n];\nconst WORKBENCH_BANNER_WIDTH = Math.max(...WORKBENCH_BANNER.map((line) => line.length));\n\nexport function renderSpStartupHeader(\n\ttheme: Theme,\n\twidth: number,\n\tmodelId?: string,\n\tgatewayConnected = false,\n\twebUrl?: string,\n) {\n\tif (webUrl === undefined) {\n\t\ttry {\n\t\t\twebUrl = getSpWebUrl();\n\t\t} catch {\n\t\t\twebUrl = \"Check SP_WEB configuration\";\n\t\t}\n\t}\n\tconst availableWidth = Math.max(1, width);\n\tconst truncateLine = (line: string) => truncateToWidth(line, availableWidth);\n\tconst status = `Senpiper Gateway: ${gatewayConnected ? \"connected\" : \"not configured\"} · model: ${modelId ?? \"not selected\"}`;\n\tconst loginPrompt = gatewayConnected\n\t\t? []\n\t\t: [truncateLine(theme.bold(theme.fg(\"warning\", \"Not logged in. Run /login to continue.\")))];\n\tconst commands = \"/web · /login · /model\";\n\tconst wordmark = availableWidth >= WORKBENCH_BANNER_WIDTH ? WORKBENCH_BANNER : [\"WORKBENCH\"];\n\n\treturn [\n\t\t...wordmark.map((line) => truncateLine(theme.bold(theme.fg(\"accent\", line)))),\n\t\ttruncateLine(theme.fg(\"dim\", \"by senpiper\")),\n\t\ttruncateLine(theme.fg(\"dim\", `Web: ${webUrl}`)),\n\t\ttruncateLine(theme.fg(\"muted\", status)),\n\t\t...loginPrompt,\n\t\ttruncateLine(theme.fg(\"dim\", commands)),\n\t];\n}\n\nexport function filterSpStartupMessage(startupMessage: StartupMessage) {\n\treturn startupMessage.type === \"no-model\" ? undefined : startupMessage.message;\n}\n\nexport function registerSpStartupHeader(pi: ExtensionAPI) {\n\tpi.registerStartupMessageFilter(filterSpStartupMessage);\n\tpi.on(\"session_start\", async (_event, context) => {\n\t\tif (context.mode !== \"tui\") return;\n\t\tcontext.ui.setHeader((_tui, theme) => ({\n\t\t\trender(width) {\n\t\t\t\treturn renderSpStartupHeader(theme, width, context.model?.id, readSpGatewayState() !== undefined);\n\t\t\t},\n\t\t\tinvalidate() {},\n\t\t}));\n\t});\n}\n"]}
@@ -32,7 +32,7 @@ export interface TerminalBridgeDescriptor {
32
32
  export interface TerminalWebBridgeOptions {
33
33
  /** Set only by a distribution that installed the lifetime writer provider before file opening. */
34
34
  writerLeaseVersion?: 1;
35
- /** Supplied by the verified SP distribution, shared across terminal replacements. */
35
+ /** Supplied by the verified Senpiper Workbench distribution, shared across terminal replacements. */
36
36
  sessions?: {
37
37
  create(cwd: string): Promise<{
38
38
  sessionId: string;