@p4code/cli 0.0.34 → 0.0.36

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/dist/bin.mjs CHANGED
@@ -1,16 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { a as HostProcessEnvironment, c as HostProcessPlatform, i as HostProcessArguments, l as HostProcessUserId, n as PtySpawnError, o as HostProcessExecutablePath, r as HostProcessArchitecture, s as HostProcessHostname, t as PtyAdapter } from "./PtyAdapter-DO4FyE0R.mjs";
3
+ import { a as HostProcessEnvironment, c as HostProcessPlatform, i as HostProcessArguments, l as HostProcessUserId, n as PtySpawnError, o as HostProcessExecutablePath, r as HostProcessArchitecture, s as HostProcessHostname, t as PtyAdapter } from "./PtyAdapter-DLKyoorI.mjs";
4
4
  import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
5
5
  import * as NodeServices from "@effect/platform-node/NodeServices";
6
+ import * as Console from "effect/Console";
6
7
  import * as Effect from "effect/Effect";
7
8
  import * as Layer from "effect/Layer";
8
9
  import { Argument, Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";
10
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServer, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
11
+ import * as Context from "effect/Context";
12
+ import * as NodeOS from "node:os";
9
13
  import * as NodeNet from "node:net";
10
14
  import * as Data from "effect/Data";
11
- import * as Context from "effect/Context";
12
15
  import * as Predicate from "effect/Predicate";
16
+ import * as FileSystem from "effect/FileSystem";
17
+ import "effect/LogLevel";
18
+ import * as Path from "effect/Path";
13
19
  import * as Schema$1 from "effect/Schema";
20
+ import * as NodePath from "node:path";
21
+ import * as NodeChildProcess from "node:child_process";
22
+ import * as NodeFS from "node:fs";
23
+ import * as DateTime from "effect/DateTime";
24
+ import * as Duration from "effect/Duration";
25
+ import * as Option from "effect/Option";
14
26
  import * as SchemaTransformation from "effect/SchemaTransformation";
15
27
  import * as HttpApiSchema from "effect/unstable/httpapi/HttpApiSchema";
16
28
  import * as HttpApi from "effect/unstable/httpapi/HttpApi";
@@ -19,20 +31,13 @@ import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
19
31
  import * as HttpApiMiddleware from "effect/unstable/httpapi/HttpApiMiddleware";
20
32
  import * as HttpServerRespondable$1 from "effect/unstable/http/HttpServerRespondable";
21
33
  import * as HttpServerResponse$1 from "effect/unstable/http/HttpServerResponse";
22
- import * as Option from "effect/Option";
23
34
  import * as SchemaIssue from "effect/SchemaIssue";
24
35
  import * as Struct from "effect/Struct";
25
36
  import { Schema } from "effect";
26
- import * as Duration from "effect/Duration";
27
37
  import * as Rpc from "effect/unstable/rpc/Rpc";
28
38
  import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
29
- import * as Console from "effect/Console";
30
39
  import * as References from "effect/References";
31
- import * as DateTime from "effect/DateTime";
32
40
  import * as HttpServerRequest$1 from "effect/unstable/http/HttpServerRequest";
33
- import * as FileSystem from "effect/FileSystem";
34
- import "effect/LogLevel";
35
- import * as Path from "effect/Path";
36
41
  import * as NodeCrypto from "node:crypto";
37
42
  import * as Encoding from "effect/Encoding";
38
43
  import * as Result from "effect/Result";
@@ -48,12 +53,7 @@ import * as Cause from "effect/Cause";
48
53
  import * as Exit from "effect/Exit";
49
54
  import * as SchemaGetter from "effect/SchemaGetter";
50
55
  import * as Config from "effect/Config";
51
- import * as NodeFS from "node:fs";
52
56
  import * as NodeReadline from "node:readline";
53
- import * as NodeOS from "node:os";
54
- import * as NodePath from "node:path";
55
- import * as NodeChildProcess from "node:child_process";
56
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServer, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
57
57
  import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
58
58
  import * as Clock from "effect/Clock";
59
59
  import * as Deferred from "effect/Deferred";
@@ -232,7 +232,708 @@ const make$67 = () => {
232
232
  const layer$62 = Layer.sync(NetService, make$67);
233
233
  //#endregion
234
234
  //#region package.json
235
- var version = "0.0.34";
235
+ var version = "0.0.36";
236
+ //#endregion
237
+ //#region src/config.ts
238
+ /**
239
+ * ServerConfig - Runtime configuration services.
240
+ *
241
+ * Defines process-level server configuration and networking helpers used by
242
+ * startup and runtime layers.
243
+ *
244
+ * @module ServerConfig
245
+ */
246
+ const DEFAULT_PORT = 3773;
247
+ const RuntimeMode$1 = Schema$1.Literals(["web", "desktop"]);
248
+ Schema$1.Literals(["browser", "headless"]);
249
+ /**
250
+ * ServerConfig - Service tag for server runtime configuration.
251
+ */
252
+ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerConfig") {
253
+ /** @deprecated Import and use `layerTest` from this module. */
254
+ static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
255
+ };
256
+ const make$66 = (config) => ServerConfig$1.of(config);
257
+ const layer$61 = (config) => Layer.succeed(ServerConfig$1, make$66(config));
258
+ const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
259
+ const { join } = yield* Path.Path;
260
+ const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
261
+ const dbPath = join(stateDir, "state.sqlite");
262
+ const attachmentsDir = join(stateDir, "attachments");
263
+ const logsDir = join(stateDir, "logs");
264
+ const providerLogsDir = join(logsDir, "provider");
265
+ const providerStatusCacheDir = join(baseDir, "caches");
266
+ return {
267
+ stateDir,
268
+ dbPath,
269
+ keybindingsConfigPath: join(stateDir, "keybindings.json"),
270
+ settingsPath: join(stateDir, "settings.json"),
271
+ providerStatusCacheDir,
272
+ worktreesDir: join(baseDir, "worktrees"),
273
+ attachmentsDir,
274
+ logsDir,
275
+ serverLogPath: join(logsDir, "server.log"),
276
+ serverTracePath: join(logsDir, "server.trace.ndjson"),
277
+ providerLogsDir,
278
+ providerEventLogPath: join(providerLogsDir, "events.log"),
279
+ terminalLogsDir: join(logsDir, "terminals"),
280
+ anonymousIdPath: join(stateDir, "anonymous-id"),
281
+ environmentIdPath: join(stateDir, "environment-id"),
282
+ serverRuntimeStatePath: join(stateDir, "server-runtime.json"),
283
+ secretsDir: join(stateDir, "secrets")
284
+ };
285
+ });
286
+ const ensureServerDirectories = Effect.fn(function* (derivedPaths) {
287
+ const fs = yield* FileSystem.FileSystem;
288
+ const path = yield* Path.Path;
289
+ yield* Effect.all([
290
+ fs.makeDirectory(derivedPaths.stateDir, { recursive: true }),
291
+ fs.makeDirectory(derivedPaths.logsDir, { recursive: true }),
292
+ fs.makeDirectory(derivedPaths.providerLogsDir, { recursive: true }),
293
+ fs.makeDirectory(derivedPaths.terminalLogsDir, { recursive: true }),
294
+ fs.makeDirectory(derivedPaths.attachmentsDir, { recursive: true }),
295
+ fs.makeDirectory(derivedPaths.worktreesDir, { recursive: true }),
296
+ fs.makeDirectory(path.dirname(derivedPaths.keybindingsConfigPath), { recursive: true }),
297
+ fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }),
298
+ fs.makeDirectory(derivedPaths.providerStatusCacheDir, { recursive: true }),
299
+ fs.makeDirectory(path.dirname(derivedPaths.anonymousIdPath), { recursive: true }),
300
+ fs.makeDirectory(path.dirname(derivedPaths.serverRuntimeStatePath), { recursive: true })
301
+ ], { concurrency: "unbounded" });
302
+ });
303
+ const makeTest$1 = Effect.fn("ServerConfig.makeTest")(function* (cwd, baseDirOrPrefix) {
304
+ const devUrl = void 0;
305
+ const fs = yield* FileSystem.FileSystem;
306
+ const baseDir = typeof baseDirOrPrefix === "string" ? baseDirOrPrefix : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
307
+ const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
308
+ yield* ensureServerDirectories(derivedPaths);
309
+ return ServerConfig$1.of({
310
+ logLevel: "Error",
311
+ traceMinLevel: "Info",
312
+ traceTimingEnabled: true,
313
+ traceBatchWindowMs: 200,
314
+ traceMaxBytes: 10 * 1024 * 1024,
315
+ traceMaxFiles: 10,
316
+ otlpTracesUrl: void 0,
317
+ otlpMetricsUrl: void 0,
318
+ otlpExportIntervalMs: 1e4,
319
+ otlpServiceName: "p4-server",
320
+ cwd,
321
+ baseDir,
322
+ ...derivedPaths,
323
+ mode: "web",
324
+ autoBootstrapProjectFromCwd: false,
325
+ logWebSocketEvents: false,
326
+ tailscaleServeEnabled: false,
327
+ tailscaleServePort: 443,
328
+ port: 0,
329
+ host: void 0,
330
+ desktopBootstrapToken: void 0,
331
+ staticDir: void 0,
332
+ devUrl,
333
+ devAllowedOrigins: [],
334
+ noBrowser: false,
335
+ startupPresentation: "browser"
336
+ });
337
+ });
338
+ const layerTest$2 = (cwd, baseDirOrPrefix) => Layer.effect(ServerConfig$1, makeTest$1(cwd, baseDirOrPrefix));
339
+ const resolveStaticDir = Effect.fn(function* () {
340
+ const { join, resolve } = yield* Path.Path;
341
+ const { exists } = yield* FileSystem.FileSystem;
342
+ const bundledClient = resolve(join(import.meta.dirname, "client"));
343
+ if (yield* exists(join(bundledClient, "index.html")).pipe(Effect.orElseSucceed(() => false))) return bundledClient;
344
+ const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist"));
345
+ if (yield* exists(join(monorepoClient, "index.html")).pipe(Effect.orElseSucceed(() => false))) return monorepoClient;
346
+ });
347
+ //#endregion
348
+ //#region ../../packages/shared/src/shell.ts
349
+ const SHELL_ENV_NAME_PATTERN = /^[A-Z0-9_]+$/;
350
+ const WINDOWS_PATH_DELIMITER = ";";
351
+ const POSIX_PATH_DELIMITER = ":";
352
+ const WINDOWS_SHELL_CANDIDATES = ["pwsh.exe", "powershell.exe"];
353
+ function canExecuteFile(filePath) {
354
+ try {
355
+ NodeFS.accessSync(filePath, NodeFS.constants.X_OK);
356
+ return true;
357
+ } catch {
358
+ return false;
359
+ }
360
+ }
361
+ var CommandResolutionError = class extends Data.TaggedError("CommandResolutionError") {};
362
+ const WINDOWS_SHELL_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
363
+ /**
364
+ * Escapes a single argument for `cmd.exe` shell mode (`spawn(..., { shell: true })`
365
+ * on Windows). Node joins the command and arguments with spaces and hands the
366
+ * resulting string to `cmd.exe` without any quoting, so every dynamic argument
367
+ * must be escaped to survive both cmd.exe parsing and the target program's
368
+ * `CommandLineToArgvW` parsing. Mirrors cross-spawn's argument escaping.
369
+ */
370
+ function escapeWindowsShellArg(arg) {
371
+ let escaped = arg.replace(/(\\*)"/g, "$1$1\\\"");
372
+ escaped = escaped.replace(/(\\*)$/, "$1$1");
373
+ escaped = `"${escaped}"`;
374
+ return escaped.replace(WINDOWS_SHELL_META_CHARS, "^$1");
375
+ }
376
+ /**
377
+ * Escapes arguments for shell-mode spawns: applies {@link escapeWindowsShellArg}
378
+ * when the platform is `win32` (where `shell: true` routes through `cmd.exe`)
379
+ * and returns the arguments untouched everywhere else.
380
+ */
381
+ function sanitizeShellModeArgsForPlatform(args, platform) {
382
+ return platform === "win32" ? args.map(escapeWindowsShellArg) : [...args];
383
+ }
384
+ function resolveSpawnExecutableWithNode(command, platform, env) {
385
+ const path = platform === "win32" ? NodePath.win32 : NodePath.posix;
386
+ const windowsPathExtensions = platform === "win32" ? resolveWindowsPathExtensions(env) : [];
387
+ const candidates = resolveCommandCandidates(command, platform, windowsPathExtensions, path.extname);
388
+ const isExecutable = (candidate) => {
389
+ try {
390
+ if (!NodeFS.statSync(candidate).isFile()) return false;
391
+ if (platform === "win32") return windowsPathExtensions.includes(path.extname(candidate).toUpperCase());
392
+ return canExecuteFile(candidate);
393
+ } catch {
394
+ return false;
395
+ }
396
+ };
397
+ if (command.includes("/") || command.includes("\\")) return candidates.find(isExecutable);
398
+ for (const pathEntry of (readEnvPath(env) ?? "").split(pathDelimiterForPlatform(platform))) {
399
+ const normalizedPathEntry = stripWrappingQuotes(pathEntry.trim());
400
+ if (normalizedPathEntry.length === 0) continue;
401
+ for (const candidate of candidates) {
402
+ const candidatePath = path.join(normalizedPathEntry, candidate);
403
+ if (isExecutable(candidatePath)) return candidatePath;
404
+ }
405
+ }
406
+ }
407
+ const SpawnExecutableResolution = Context.Reference("@p4code/shared/shell/SpawnExecutableResolution", { defaultValue: () => resolveSpawnExecutableWithNode });
408
+ function trimNonEmpty(value) {
409
+ const trimmed = value?.trim();
410
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
411
+ }
412
+ function readUserLoginShell() {
413
+ try {
414
+ return trimNonEmpty(NodeOS.userInfo().shell);
415
+ } catch {
416
+ return;
417
+ }
418
+ }
419
+ function listLoginShellCandidates(platform, shell, userShell = readUserLoginShell()) {
420
+ const fallbackShell = platform === "darwin" ? "/bin/zsh" : platform === "linux" ? "/bin/bash" : void 0;
421
+ const seen = /* @__PURE__ */ new Set();
422
+ const candidates = [];
423
+ for (const candidate of [
424
+ trimNonEmpty(shell),
425
+ trimNonEmpty(userShell),
426
+ fallbackShell
427
+ ]) {
428
+ if (!candidate || seen.has(candidate)) continue;
429
+ seen.add(candidate);
430
+ candidates.push(candidate);
431
+ }
432
+ return candidates;
433
+ }
434
+ function readPathFromLoginShell(shell, execFile = NodeChildProcess.execFileSync) {
435
+ return readEnvironmentFromLoginShell(shell, ["PATH"], execFile).PATH;
436
+ }
437
+ function readPathFromLaunchctl(execFile = NodeChildProcess.execFileSync) {
438
+ try {
439
+ return trimNonEmpty(execFile("/bin/launchctl", ["getenv", "PATH"], {
440
+ encoding: "utf8",
441
+ timeout: 2e3
442
+ }));
443
+ } catch {
444
+ return;
445
+ }
446
+ }
447
+ function mergePathEntries(preferredPath, inheritedPath, platform) {
448
+ const delimiter = platform === "win32" ? ";" : ":";
449
+ const merged = [];
450
+ const seen = /* @__PURE__ */ new Set();
451
+ for (const pathValue of [preferredPath, inheritedPath]) {
452
+ if (!pathValue) continue;
453
+ for (const entry of pathValue.split(delimiter)) {
454
+ const trimmedEntry = entry.trim();
455
+ if (!trimmedEntry || seen.has(trimmedEntry)) continue;
456
+ seen.add(trimmedEntry);
457
+ merged.push(trimmedEntry);
458
+ }
459
+ }
460
+ return merged.length > 0 ? merged.join(delimiter) : void 0;
461
+ }
462
+ function envCaptureStart(name) {
463
+ return `__P4CODE_ENV_${name}_START__`;
464
+ }
465
+ function envCaptureEnd(name) {
466
+ return `__P4CODE_ENV_${name}_END__`;
467
+ }
468
+ function buildEnvironmentCaptureCommand(names) {
469
+ return names.map((name) => {
470
+ if (!SHELL_ENV_NAME_PATTERN.test(name)) throw new Error(`Unsupported environment variable name: ${name}`);
471
+ return [
472
+ `printf '%s\\n' '${envCaptureStart(name)}'`,
473
+ `printenv ${name} || true`,
474
+ `printf '%s\\n' '${envCaptureEnd(name)}'`
475
+ ].join("; ");
476
+ }).join("; ");
477
+ }
478
+ function buildWindowsEnvironmentCaptureCommand(names) {
479
+ return ["$ErrorActionPreference = 'Stop'", ...names.flatMap((name) => {
480
+ if (!SHELL_ENV_NAME_PATTERN.test(name)) throw new Error(`Unsupported environment variable name: ${name}`);
481
+ return [
482
+ `Write-Output '${envCaptureStart(name)}'`,
483
+ `$value = [Environment]::GetEnvironmentVariable('${name}')`,
484
+ "if ($null -ne $value -and $value.Length -gt 0) { Write-Output $value }",
485
+ `Write-Output '${envCaptureEnd(name)}'`
486
+ ];
487
+ })].join("; ");
488
+ }
489
+ function extractEnvironmentValue(output, name) {
490
+ const startMarker = envCaptureStart(name);
491
+ const endMarker = envCaptureEnd(name);
492
+ const startIndex = output.indexOf(startMarker);
493
+ if (startIndex === -1) return void 0;
494
+ const valueStartIndex = startIndex + startMarker.length;
495
+ const endIndex = output.indexOf(endMarker, valueStartIndex);
496
+ if (endIndex === -1) return void 0;
497
+ const value = output.slice(valueStartIndex, endIndex).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
498
+ return value.length > 0 ? value : void 0;
499
+ }
500
+ const readEnvironmentFromLoginShell = (shell, names, execFile = NodeChildProcess.execFileSync) => {
501
+ if (names.length === 0) return {};
502
+ const output = execFile(shell, ["-ilc", buildEnvironmentCaptureCommand(names)], {
503
+ encoding: "utf8",
504
+ timeout: 5e3
505
+ });
506
+ const environment = {};
507
+ for (const name of names) {
508
+ const value = extractEnvironmentValue(output, name);
509
+ if (value !== void 0) environment[name] = value;
510
+ }
511
+ return environment;
512
+ };
513
+ const WindowsShellEnvironment = Context.Reference("@p4code/shared/shell/WindowsShellEnvironment", { defaultValue: () => readEnvironmentFromWindowsShell });
514
+ const CommandAvailability = Context.Reference("@p4code/shared/shell/CommandAvailability", { defaultValue: () => isCommandAvailable });
515
+ function readEnvironmentFromWindowsShell(names, optionsOrExecFile, maybeExecFile) {
516
+ if (names.length === 0) return {};
517
+ const options = typeof optionsOrExecFile === "function" ? {} : optionsOrExecFile ?? {};
518
+ const execFile = typeof optionsOrExecFile === "function" ? optionsOrExecFile : maybeExecFile ?? NodeChildProcess.execFileSync;
519
+ const command = buildWindowsEnvironmentCaptureCommand(names);
520
+ const args = [
521
+ "-NoLogo",
522
+ ...options.loadProfile ? [] : ["-NoProfile"],
523
+ "-NonInteractive",
524
+ "-Command",
525
+ command
526
+ ];
527
+ for (const shell of WINDOWS_SHELL_CANDIDATES) try {
528
+ const output = execFile(shell, args, {
529
+ encoding: "utf8",
530
+ timeout: 5e3
531
+ });
532
+ const environment = {};
533
+ for (const name of names) {
534
+ const value = extractEnvironmentValue(output, name);
535
+ if (value !== void 0) environment[name] = value;
536
+ }
537
+ return environment;
538
+ } catch {
539
+ continue;
540
+ }
541
+ return {};
542
+ }
543
+ function stripWrappingQuotes(value) {
544
+ return value.replace(/^"+|"+$/g, "");
545
+ }
546
+ function pathDelimiterForPlatform(platform) {
547
+ return platform === "win32" ? WINDOWS_PATH_DELIMITER : POSIX_PATH_DELIMITER;
548
+ }
549
+ function normalizePathEntryForComparison(entry, platform) {
550
+ const normalized = stripWrappingQuotes(entry.trim());
551
+ return platform === "win32" ? normalized.toLowerCase() : normalized;
552
+ }
553
+ function mergePathValues(preferredPath, inheritedPath, platform) {
554
+ const delimiter = pathDelimiterForPlatform(platform);
555
+ const merged = [];
556
+ const seen = /* @__PURE__ */ new Set();
557
+ for (const rawValue of [preferredPath, inheritedPath]) {
558
+ if (!rawValue) continue;
559
+ for (const entry of rawValue.split(delimiter)) {
560
+ const trimmed = entry.trim();
561
+ if (trimmed.length === 0) continue;
562
+ const normalized = normalizePathEntryForComparison(trimmed, platform);
563
+ if (normalized.length === 0 || seen.has(normalized)) continue;
564
+ seen.add(normalized);
565
+ merged.push(trimmed);
566
+ }
567
+ }
568
+ return merged.length > 0 ? merged.join(delimiter) : void 0;
569
+ }
570
+ function readEnvPath(env) {
571
+ return env.PATH ?? env.Path ?? env.path;
572
+ }
573
+ function resolvePathEnvironmentVariable(env) {
574
+ return readEnvPath(env) ?? "";
575
+ }
576
+ function resolveWindowsPathExtensions(env) {
577
+ const rawValue = env.PATHEXT;
578
+ const fallback = [
579
+ ".COM",
580
+ ".EXE",
581
+ ".BAT",
582
+ ".CMD"
583
+ ];
584
+ if (!rawValue) return fallback;
585
+ const parsed = [];
586
+ for (const entry of rawValue.split(";")) {
587
+ const trimmed = entry.trim();
588
+ if (trimmed.length === 0) continue;
589
+ parsed.push(trimmed.startsWith(".") ? trimmed.toUpperCase() : `.${trimmed.toUpperCase()}`);
590
+ }
591
+ return parsed.length > 0 ? Array.from(new Set(parsed)) : fallback;
592
+ }
593
+ function resolveCommandCandidates(command, platform, windowsPathExtensions, extname) {
594
+ if (platform !== "win32") return [command];
595
+ const extension = extname(command);
596
+ const normalizedExtension = extension.toUpperCase();
597
+ if (extension.length > 0 && windowsPathExtensions.includes(normalizedExtension)) {
598
+ const commandWithoutExtension = command.slice(0, -extension.length);
599
+ return Array.from(/* @__PURE__ */ new Set([
600
+ command,
601
+ `${commandWithoutExtension}${normalizedExtension}`,
602
+ `${commandWithoutExtension}${normalizedExtension.toLowerCase()}`
603
+ ]));
604
+ }
605
+ const candidates = [];
606
+ for (const candidateExtension of windowsPathExtensions) {
607
+ candidates.push(`${command}${candidateExtension}`);
608
+ candidates.push(`${command}${candidateExtension.toLowerCase()}`);
609
+ }
610
+ return Array.from(new Set(candidates));
611
+ }
612
+ const isExecutableFile = Effect.fn("shell.isExecutableFile")(function* (filePath, platform, windowsPathExtensions) {
613
+ const fileSystem = yield* FileSystem.FileSystem;
614
+ const path = yield* Path.Path;
615
+ const stat = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null));
616
+ if (stat === null || stat.type !== "File") return false;
617
+ if (platform === "win32") {
618
+ const extension = path.extname(filePath);
619
+ if (extension.length === 0) return false;
620
+ return windowsPathExtensions.includes(extension.toUpperCase());
621
+ }
622
+ return canExecuteFile(filePath);
623
+ });
624
+ const resolveCommandPathForPlatform = Effect.fn("shell.resolveCommandPathForPlatform")(function* (command, options) {
625
+ const path = yield* Path.Path;
626
+ const platform = options.platform;
627
+ const env = options.env ?? process.env;
628
+ const windowsPathExtensions = platform === "win32" ? resolveWindowsPathExtensions(env) : [];
629
+ const commandCandidates = resolveCommandCandidates(command, platform, windowsPathExtensions, path.extname);
630
+ if (command.includes("/") || command.includes("\\")) {
631
+ for (const candidate of commandCandidates) if (yield* isExecutableFile(candidate, platform, windowsPathExtensions)) return candidate;
632
+ return yield* new CommandResolutionError({
633
+ command,
634
+ reason: "not-found"
635
+ });
636
+ }
637
+ const pathValue = resolvePathEnvironmentVariable(env);
638
+ if (pathValue.length === 0) return yield* new CommandResolutionError({
639
+ command,
640
+ reason: "not-found"
641
+ });
642
+ const pathEntries = [];
643
+ for (const entry of pathValue.split(pathDelimiterForPlatform(platform))) {
644
+ const pathEntry = stripWrappingQuotes(entry.trim());
645
+ if (pathEntry.length > 0) pathEntries.push(pathEntry);
646
+ }
647
+ for (const pathEntry of pathEntries) for (const candidate of commandCandidates) {
648
+ const candidatePath = path.join(pathEntry, candidate);
649
+ if (yield* isExecutableFile(candidatePath, platform, windowsPathExtensions)) return candidatePath;
650
+ }
651
+ return yield* new CommandResolutionError({
652
+ command,
653
+ reason: "not-found"
654
+ });
655
+ });
656
+ const resolveCommandPath = Effect.fn("shell.resolveCommandPath")(function* (command, options = {}) {
657
+ return yield* resolveCommandPathForPlatform(command, {
658
+ env: options.env ?? (yield* HostProcessEnvironment),
659
+ platform: yield* HostProcessPlatform
660
+ });
661
+ });
662
+ const resolveSpawnCommand = Effect.fn("shell.resolveSpawnCommand")(function* (command, args, options = {}) {
663
+ const platform = yield* HostProcessPlatform;
664
+ if (platform !== "win32") return {
665
+ command,
666
+ args: [...args],
667
+ shell: false
668
+ };
669
+ const hostEnvironment = yield* HostProcessEnvironment;
670
+ const env = options.env === void 0 ? hostEnvironment : options.extendEnv ? {
671
+ ...hostEnvironment,
672
+ ...options.env
673
+ } : options.env;
674
+ const resolvedCommand = (yield* SpawnExecutableResolution)(command, platform, env) ?? command;
675
+ const extension = NodePath.win32.extname(resolvedCommand).toLowerCase();
676
+ if (extension !== ".cmd" && extension !== ".bat") return {
677
+ command: resolvedCommand,
678
+ args: [...args],
679
+ shell: false
680
+ };
681
+ return {
682
+ command: escapeWindowsShellArg(resolvedCommand),
683
+ args: sanitizeShellModeArgsForPlatform(args, platform),
684
+ shell: true
685
+ };
686
+ });
687
+ const isCommandAvailable = Effect.fn("shell.isCommandAvailable")(function* (command, options = {}) {
688
+ return yield* resolveCommandPath(command, options).pipe(Effect.as(true), Effect.catchTag("CommandResolutionError", () => Effect.succeed(false)));
689
+ });
690
+ function resolveKnownWindowsCliDirs(env) {
691
+ const appData = env.APPDATA?.trim();
692
+ const localAppData = env.LOCALAPPDATA?.trim();
693
+ const userProfile = env.USERPROFILE?.trim();
694
+ return [
695
+ ...appData ? [`${appData}\\npm`] : [],
696
+ ...localAppData ? [`${localAppData}\\Programs\\nodejs`, `${localAppData}\\Volta\\bin`] : [],
697
+ ...localAppData ? [`${localAppData}\\pnpm`] : [],
698
+ ...userProfile ? [`${userProfile}\\.bun\\bin`, `${userProfile}\\scoop\\shims`] : []
699
+ ];
700
+ }
701
+ function readWindowsEnvironmentSafely(readEnvironment, names, options) {
702
+ try {
703
+ return readEnvironment(names, options);
704
+ } catch {
705
+ return {};
706
+ }
707
+ }
708
+ function mergeWindowsEnv(currentEnv, patch) {
709
+ const nextEnv = { ...currentEnv };
710
+ for (const [key, value] of Object.entries(patch)) if (value !== void 0) nextEnv[key] = value;
711
+ return nextEnv;
712
+ }
713
+ const resolveWindowsEnvironment = Effect.fn("shell.resolveWindowsEnvironment")(function* (env) {
714
+ const readEnvironment = yield* WindowsShellEnvironment;
715
+ const commandAvailable = yield* CommandAvailability;
716
+ const inheritedPath = readEnvPath(env);
717
+ const shellPath = readWindowsEnvironmentSafely(readEnvironment, ["PATH"], { loadProfile: false }).PATH;
718
+ const mergedPath = mergePathValues(shellPath, inheritedPath, "win32");
719
+ const baselinePath = mergePathValues(resolveKnownWindowsCliDirs(env).join(WINDOWS_PATH_DELIMITER), mergedPath, "win32");
720
+ const baselinePatch = baselinePath ? { PATH: baselinePath } : {};
721
+ if (yield* commandAvailable("node", { env: mergeWindowsEnv(env, baselinePatch) })) return baselinePatch;
722
+ const profiledEnvironment = readWindowsEnvironmentSafely(readEnvironment, [
723
+ "PATH",
724
+ "FNM_DIR",
725
+ "FNM_MULTISHELL_PATH"
726
+ ], { loadProfile: true });
727
+ const profiledPath = mergePathValues(profiledEnvironment.PATH, baselinePath, "win32");
728
+ const profiledPatch = {
729
+ ...profiledPath ? { PATH: profiledPath } : {},
730
+ ...profiledEnvironment.FNM_DIR ? { FNM_DIR: profiledEnvironment.FNM_DIR } : {},
731
+ ...profiledEnvironment.FNM_MULTISHELL_PATH ? { FNM_MULTISHELL_PATH: profiledEnvironment.FNM_MULTISHELL_PATH } : {}
732
+ };
733
+ return Object.keys(profiledPatch).length > 0 ? {
734
+ ...baselinePatch,
735
+ ...profiledPatch
736
+ } : baselinePatch;
737
+ });
738
+ //#endregion
739
+ //#region src/os-jank.ts
740
+ function logPathHydrationWarning(message, error) {
741
+ process.stderr.write(`[server] ${message} ${error instanceof Error ? error.message : error ?? ""}\n`);
742
+ }
743
+ function hydratePosixPath(env, platform) {
744
+ let shellPath;
745
+ for (const shell of listLoginShellCandidates(platform, env.SHELL)) {
746
+ try {
747
+ shellPath = readPathFromLoginShell(shell);
748
+ } catch (error) {
749
+ logPathHydrationWarning(`Failed to read PATH from login shell ${shell}.`, error);
750
+ }
751
+ if (shellPath) break;
752
+ }
753
+ const launchctlPath = platform === "darwin" && !shellPath ? readPathFromLaunchctl() : void 0;
754
+ const mergedPath = mergePathEntries(shellPath ?? launchctlPath, env.PATH, platform);
755
+ if (mergedPath) env.PATH = mergedPath;
756
+ }
757
+ const fixPath = Effect.fn("fixPath")(function* () {
758
+ const platform = yield* HostProcessPlatform;
759
+ const env = yield* HostProcessEnvironment;
760
+ if (platform === "win32") {
761
+ const repairedEnvironment = yield* resolveWindowsEnvironment(env).pipe(Effect.catchDefect((defect) => Effect.sync(() => {
762
+ logPathHydrationWarning("Failed to hydrate PATH from the user environment.", defect);
763
+ return {};
764
+ })));
765
+ for (const [key, value] of Object.entries(repairedEnvironment)) if (value !== void 0) env[key] = value;
766
+ return;
767
+ }
768
+ if (platform !== "darwin" && platform !== "linux") return;
769
+ yield* Effect.sync(() => hydratePosixPath(env, platform)).pipe(Effect.catchDefect((defect) => Effect.sync(() => {
770
+ logPathHydrationWarning("Failed to hydrate PATH from the user environment.", defect);
771
+ })));
772
+ });
773
+ const expandHomePath$4 = Effect.fn(function* (input) {
774
+ const { join } = yield* Path.Path;
775
+ if (input === "~") return NodeOS.homedir();
776
+ if (input.startsWith("~/") || input.startsWith("~\\")) return join(NodeOS.homedir(), input.slice(2));
777
+ return input;
778
+ });
779
+ /** The data directory used when neither `--base-dir` nor `P4CODE_HOME` is set. */
780
+ const defaultBaseDir = Effect.fn(function* () {
781
+ const { join } = yield* Path.Path;
782
+ return join(NodeOS.homedir(), ".p4");
783
+ });
784
+ const resolveBaseDir = Effect.fn(function* (raw) {
785
+ const { resolve } = yield* Path.Path;
786
+ if (!raw || raw.trim().length === 0) return yield* defaultBaseDir();
787
+ return resolve(yield* expandHomePath$4(raw.trim()));
788
+ });
789
+ //#endregion
790
+ //#region ../../packages/shared/src/semver.ts
791
+ const SEMVER_NUMBER_SEGMENT = /^\d+$/;
792
+ function normalizeSemverVersion(version) {
793
+ const [main, prerelease] = version.trim().split("-", 2);
794
+ const segments = [];
795
+ for (const segment of (main ?? "").split(".")) {
796
+ const trimmed = segment.trim();
797
+ if (trimmed.length > 0) segments.push(trimmed);
798
+ }
799
+ if (segments.length === 2) segments.push("0");
800
+ return prerelease ? `${segments.join(".")}-${prerelease}` : segments.join(".");
801
+ }
802
+ function parseSemver(value) {
803
+ const [main = "", prerelease] = normalizeSemverVersion(value).replace(/^v/, "").split("-", 2);
804
+ const segments = main.split(".");
805
+ if (segments.length !== 3) return null;
806
+ const [majorSegment, minorSegment, patchSegment] = segments;
807
+ if (majorSegment === void 0 || minorSegment === void 0 || patchSegment === void 0) return null;
808
+ if (!SEMVER_NUMBER_SEGMENT.test(majorSegment) || !SEMVER_NUMBER_SEGMENT.test(minorSegment) || !SEMVER_NUMBER_SEGMENT.test(patchSegment)) return null;
809
+ const major = Number.parseInt(majorSegment, 10);
810
+ const minor = Number.parseInt(minorSegment, 10);
811
+ const patch = Number.parseInt(patchSegment, 10);
812
+ if (![
813
+ major,
814
+ minor,
815
+ patch
816
+ ].every(Number.isInteger)) return null;
817
+ return {
818
+ major,
819
+ minor,
820
+ patch,
821
+ prerelease: parsePrereleaseSegments(prerelease)
822
+ };
823
+ }
824
+ function parsePrereleaseSegments(prerelease) {
825
+ if (prerelease === void 0) return [];
826
+ const segments = [];
827
+ for (const segment of prerelease.split(".")) {
828
+ const trimmed = segment.trim();
829
+ if (trimmed.length > 0) segments.push(trimmed);
830
+ }
831
+ return segments;
832
+ }
833
+ function comparePrereleaseIdentifier(left, right) {
834
+ const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
835
+ const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
836
+ if (leftNumeric && rightNumeric) return Number.parseInt(left, 10) - Number.parseInt(right, 10);
837
+ if (leftNumeric) return -1;
838
+ if (rightNumeric) return 1;
839
+ return left.localeCompare(right);
840
+ }
841
+ function compareSemverVersions(left, right) {
842
+ const parsedLeft = parseSemver(left);
843
+ const parsedRight = parseSemver(right);
844
+ if (!parsedLeft || !parsedRight) return left.localeCompare(right);
845
+ if (parsedLeft.major !== parsedRight.major) return parsedLeft.major - parsedRight.major;
846
+ if (parsedLeft.minor !== parsedRight.minor) return parsedLeft.minor - parsedRight.minor;
847
+ if (parsedLeft.patch !== parsedRight.patch) return parsedLeft.patch - parsedRight.patch;
848
+ if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) return 0;
849
+ if (parsedLeft.prerelease.length === 0) return 1;
850
+ if (parsedRight.prerelease.length === 0) return -1;
851
+ const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
852
+ for (let index = 0; index < length; index += 1) {
853
+ const leftIdentifier = parsedLeft.prerelease[index];
854
+ const rightIdentifier = parsedRight.prerelease[index];
855
+ if (leftIdentifier === void 0) return -1;
856
+ if (rightIdentifier === void 0) return 1;
857
+ const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
858
+ if (comparison !== 0) return comparison;
859
+ }
860
+ return 0;
861
+ }
862
+ //#endregion
863
+ //#region src/cli/updateCheck.ts
864
+ /**
865
+ * Tells the user when a newer `@p4code/cli` has been published.
866
+ *
867
+ * Deliberately advisory and cheap: at most one registry request a day, a short
868
+ * timeout, every failure swallowed, and never anything that changes an exit
869
+ * code. A version check that can break a command is worse than no check.
870
+ */
871
+ const PACKAGE_NAME = "@p4code/cli";
872
+ const CHECK_INTERVAL = Duration.hours(24);
873
+ const REQUEST_TIMEOUT = Duration.seconds(3);
874
+ /**
875
+ * A failed lookup is retried far sooner than a successful one. Caching "no
876
+ * answer" for a full day would let one cold-start timeout hide a real update
877
+ * until tomorrow, which is the opposite of the point.
878
+ */
879
+ const FAILURE_RETRY_INTERVAL = Duration.hours(1);
880
+ const CACHE_FILE = "cli-update-check.json";
881
+ const CacheEntry = Schema$1.Struct({
882
+ checkedAtMs: Schema$1.Number,
883
+ latestVersion: Schema$1.NullOr(Schema$1.String)
884
+ });
885
+ const CacheEntryJson = Schema$1.fromJsonString(CacheEntry);
886
+ const decodeCacheEntry = Schema$1.decodeUnknownEffect(CacheEntryJson);
887
+ const encodeCacheEntry = Schema$1.encodeEffect(CacheEntryJson);
888
+ const NpmLatest = Schema$1.Struct({ version: Schema$1.optional(Schema$1.String) });
889
+ const fetchLatestVersion = Effect.fn("cli.updateCheck.fetch")(function* () {
890
+ const client = yield* HttpClient.HttpClient;
891
+ const request = HttpClientRequest.get(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`).pipe(HttpClientRequest.setHeader("accept", "application/json"));
892
+ const response = yield* client.execute(request).pipe(Effect.timeout(REQUEST_TIMEOUT));
893
+ if (response.status < 200 || response.status >= 300) return null;
894
+ const version = (yield* response.json.pipe(Effect.flatMap(Schema$1.decodeUnknownEffect(NpmLatest)))).version?.trim();
895
+ return version === void 0 || version.length === 0 ? null : version;
896
+ });
897
+ /**
898
+ * Reads the cached answer, refreshing it at most once per CHECK_INTERVAL.
899
+ * Returns the newest published version, or null when it is unknown.
900
+ */
901
+ const resolveLatestVersion = Effect.fn("cli.updateCheck.resolve")(function* (stateDir) {
902
+ const fs = yield* FileSystem.FileSystem;
903
+ const cachePath = (yield* Path.Path).join(stateDir, CACHE_FILE);
904
+ const nowMs = DateTime.toEpochMillis(yield* DateTime.now);
905
+ const cached = yield* fs.readFileString(cachePath).pipe(Effect.flatMap(decodeCacheEntry), Effect.asSome, Effect.orElseSucceed(() => Option.none()));
906
+ if (Option.isSome(cached)) {
907
+ const age = nowMs - cached.value.checkedAtMs;
908
+ const interval = cached.value.latestVersion === null ? FAILURE_RETRY_INTERVAL : CHECK_INTERVAL;
909
+ if (age >= 0 && age < Duration.toMillis(interval)) return cached.value.latestVersion;
910
+ }
911
+ const latestVersion = yield* fetchLatestVersion().pipe(Effect.orElseSucceed(() => null));
912
+ yield* fs.makeDirectory(stateDir, { recursive: true }).pipe(Effect.ignore);
913
+ yield* encodeCacheEntry({
914
+ checkedAtMs: nowMs,
915
+ latestVersion
916
+ }).pipe(Effect.flatMap((encoded) => fs.writeFileString(cachePath, encoded)), Effect.ignore);
917
+ return latestVersion;
918
+ });
919
+ function formatUpdateReminder(input) {
920
+ return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest && p4 service update`].join("\n");
921
+ }
922
+ /**
923
+ * The reminder line, or null when there is nothing to say. Callers print it;
924
+ * keeping the decision pure makes it testable without a console.
925
+ */
926
+ const resolveUpdateReminder = Effect.fn("cli.updateCheck.reminder")(function* (input) {
927
+ if (((yield* HostProcessEnvironment)["P4CODE_NO_UPDATE_CHECK"] ?? "") !== "") return null;
928
+ if (input.currentVersion.includes("-")) return null;
929
+ const latestVersion = yield* resolveLatestVersion(input.stateDir);
930
+ if (latestVersion === null) return null;
931
+ if (compareSemverVersions(input.currentVersion, latestVersion) >= 0) return null;
932
+ return formatUpdateReminder({
933
+ currentVersion: input.currentVersion,
934
+ latestVersion
935
+ });
936
+ });
236
937
  //#endregion
237
938
  //#region ../../packages/contracts/src/baseSchemas.ts
238
939
  const TrimmedString = Schema$1.String.pipe(Schema$1.decodeTo(Schema$1.String, SchemaTransformation.transformOrFail({
@@ -928,7 +1629,7 @@ const ModelSelection = Schema$1.Struct({
928
1629
  return Effect.succeed(base);
929
1630
  }
930
1631
  })));
931
- const RuntimeMode$1 = Schema$1.Literals([
1632
+ const RuntimeMode = Schema$1.Literals([
932
1633
  "approval-required",
933
1634
  "auto-accept-edits",
934
1635
  "auto",
@@ -1050,7 +1751,7 @@ const OrchestrationSession = Schema$1.Struct({
1050
1751
  status: OrchestrationSessionStatus,
1051
1752
  providerName: Schema$1.NullOr(TrimmedNonEmptyString),
1052
1753
  providerInstanceId: Schema$1.optional(ProviderInstanceId),
1053
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1754
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1054
1755
  activeTurnId: Schema$1.NullOr(TurnId),
1055
1756
  lastError: Schema$1.NullOr(TrimmedNonEmptyString),
1056
1757
  updatedAt: IsoDateTime
@@ -1111,7 +1812,7 @@ const OrchestrationThread = Schema$1.Struct({
1111
1812
  projectId: ProjectId,
1112
1813
  title: TrimmedNonEmptyString,
1113
1814
  modelSelection: ModelSelection,
1114
- runtimeMode: RuntimeMode$1,
1815
+ runtimeMode: RuntimeMode,
1115
1816
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1116
1817
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1117
1818
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1151,7 +1852,7 @@ const OrchestrationThreadShell = Schema$1.Struct({
1151
1852
  projectId: ProjectId,
1152
1853
  title: TrimmedNonEmptyString,
1153
1854
  modelSelection: ModelSelection,
1154
- runtimeMode: RuntimeMode$1,
1855
+ runtimeMode: RuntimeMode,
1155
1856
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1156
1857
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1157
1858
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1273,7 +1974,7 @@ const ThreadCreateCommand = Schema$1.Struct({
1273
1974
  projectId: ProjectId,
1274
1975
  title: TrimmedNonEmptyString,
1275
1976
  modelSelection: ModelSelection,
1276
- runtimeMode: RuntimeMode$1,
1977
+ runtimeMode: RuntimeMode,
1277
1978
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1278
1979
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1279
1980
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1331,7 +2032,7 @@ const ThreadRuntimeModeSetCommand = Schema$1.Struct({
1331
2032
  type: Schema$1.Literal("thread.runtime-mode.set"),
1332
2033
  commandId: CommandId,
1333
2034
  threadId: ThreadId,
1334
- runtimeMode: RuntimeMode$1,
2035
+ runtimeMode: RuntimeMode,
1335
2036
  createdAt: IsoDateTime
1336
2037
  });
1337
2038
  const ThreadInteractionModeSetCommand = Schema$1.Struct({
@@ -1345,7 +2046,7 @@ const ThreadTurnStartBootstrapCreateThread = Schema$1.Struct({
1345
2046
  projectId: ProjectId,
1346
2047
  title: TrimmedNonEmptyString,
1347
2048
  modelSelection: ModelSelection,
1348
- runtimeMode: RuntimeMode$1,
2049
+ runtimeMode: RuntimeMode,
1349
2050
  interactionMode: ProviderInteractionMode,
1350
2051
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1351
2052
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1374,7 +2075,7 @@ const ThreadTurnStartCommand = Schema$1.Struct({
1374
2075
  }),
1375
2076
  modelSelection: Schema$1.optional(ModelSelection),
1376
2077
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1377
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2078
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1378
2079
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1379
2080
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
1380
2081
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
@@ -1392,7 +2093,7 @@ const ClientThreadTurnStartCommand = Schema$1.Struct({
1392
2093
  }),
1393
2094
  modelSelection: Schema$1.optional(ModelSelection),
1394
2095
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1395
- runtimeMode: RuntimeMode$1,
2096
+ runtimeMode: RuntimeMode,
1396
2097
  interactionMode: ProviderInteractionMode,
1397
2098
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
1398
2099
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
@@ -1608,7 +2309,7 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
1608
2309
  projectId: ProjectId,
1609
2310
  title: TrimmedNonEmptyString,
1610
2311
  modelSelection: ModelSelection,
1611
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2312
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1612
2313
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1613
2314
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1614
2315
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1659,7 +2360,7 @@ const ThreadMetaUpdatedPayload$1 = Schema$1.Struct({
1659
2360
  });
1660
2361
  const ThreadRuntimeModeSetPayload$1 = Schema$1.Struct({
1661
2362
  threadId: ThreadId,
1662
- runtimeMode: RuntimeMode$1,
2363
+ runtimeMode: RuntimeMode,
1663
2364
  updatedAt: IsoDateTime
1664
2365
  });
1665
2366
  const ThreadInteractionModeSetPayload$1 = Schema$1.Struct({
@@ -1683,7 +2384,7 @@ const ThreadTurnStartRequestedPayload = Schema$1.Struct({
1683
2384
  messageId: MessageId,
1684
2385
  modelSelection: Schema$1.optional(ModelSelection),
1685
2386
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1686
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2387
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1687
2388
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1688
2389
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
1689
2390
  createdAt: IsoDateTime
@@ -3801,7 +4502,7 @@ Schema$1.Struct({
3801
4502
  provider: ProviderDriverKind,
3802
4503
  providerInstanceId: Schema$1.optional(ProviderInstanceId),
3803
4504
  status: ProviderSessionStatus,
3804
- runtimeMode: RuntimeMode$1,
4505
+ runtimeMode: RuntimeMode,
3805
4506
  cwd: Schema$1.optional(TrimmedNonEmptyString),
3806
4507
  model: Schema$1.optional(TrimmedNonEmptyString),
3807
4508
  threadId: ThreadId,
@@ -3820,7 +4521,7 @@ const ProviderSessionStartInput = Schema$1.Struct({
3820
4521
  resumeCursor: Schema$1.optional(Schema$1.Unknown),
3821
4522
  approvalPolicy: Schema$1.optional(ProviderApprovalPolicy),
3822
4523
  sandboxMode: Schema$1.optional(ProviderSandboxMode),
3823
- runtimeMode: RuntimeMode$1
4524
+ runtimeMode: RuntimeMode
3824
4525
  });
3825
4526
  const ProviderSendTurnInput = Schema$1.Struct({
3826
4527
  threadId: ThreadId,
@@ -5371,7 +6072,7 @@ const ServerSignalProcessResult = Schema$1.Struct({
5371
6072
  signaled: Schema$1.Boolean,
5372
6073
  message: Schema$1.Option(TrimmedNonEmptyString)
5373
6074
  });
5374
- const ServerConfig$1 = Schema$1.Struct({
6075
+ const ServerConfig = Schema$1.Struct({
5375
6076
  environment: ExecutionEnvironmentDescriptor,
5376
6077
  auth: ServerAuthDescriptor,
5377
6078
  cwd: TrimmedNonEmptyString,
@@ -5418,7 +6119,7 @@ const ServerConfigSettingsUpdatedPayload = Schema$1.Struct({ settings: ServerSet
5418
6119
  const ServerConfigStreamSnapshotEvent = Schema$1.Struct({
5419
6120
  version: Schema$1.Literal(1),
5420
6121
  type: Schema$1.Literal("snapshot"),
5421
- config: ServerConfig$1
6122
+ config: ServerConfig
5422
6123
  });
5423
6124
  const ServerConfigStreamKeybindingsUpdatedEvent = Schema$1.Struct({
5424
6125
  version: Schema$1.Literal(1),
@@ -6623,7 +7324,7 @@ const WsServerProbeRpc = Rpc.make(WS_METHODS.serverProbe, {
6623
7324
  });
6624
7325
  const WsServerGetConfigRpc = Rpc.make(WS_METHODS.serverGetConfig, {
6625
7326
  payload: Schema$1.Struct({}),
6626
- success: ServerConfig$1,
7327
+ success: ServerConfig,
6627
7328
  error: Schema$1.Union([
6628
7329
  KeybindingsConfigError,
6629
7330
  ServerSettingsError,
@@ -7006,117 +7707,6 @@ function parseAllowedOAuthScope(input) {
7006
7707
  return scopes;
7007
7708
  }
7008
7709
  //#endregion
7009
- //#region src/config.ts
7010
- /**
7011
- * ServerConfig - Runtime configuration services.
7012
- *
7013
- * Defines process-level server configuration and networking helpers used by
7014
- * startup and runtime layers.
7015
- *
7016
- * @module ServerConfig
7017
- */
7018
- const DEFAULT_PORT = 3773;
7019
- const RuntimeMode = Schema$1.Literals(["web", "desktop"]);
7020
- Schema$1.Literals(["browser", "headless"]);
7021
- /**
7022
- * ServerConfig - Service tag for server runtime configuration.
7023
- */
7024
- var ServerConfig = class extends Context.Service()("@p4code/cli/config/ServerConfig") {
7025
- /** @deprecated Import and use `layerTest` from this module. */
7026
- static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
7027
- };
7028
- const make$66 = (config) => ServerConfig.of(config);
7029
- const layer$61 = (config) => Layer.succeed(ServerConfig, make$66(config));
7030
- const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
7031
- const { join } = yield* Path.Path;
7032
- const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
7033
- const dbPath = join(stateDir, "state.sqlite");
7034
- const attachmentsDir = join(stateDir, "attachments");
7035
- const logsDir = join(stateDir, "logs");
7036
- const providerLogsDir = join(logsDir, "provider");
7037
- const providerStatusCacheDir = join(baseDir, "caches");
7038
- return {
7039
- stateDir,
7040
- dbPath,
7041
- keybindingsConfigPath: join(stateDir, "keybindings.json"),
7042
- settingsPath: join(stateDir, "settings.json"),
7043
- providerStatusCacheDir,
7044
- worktreesDir: join(baseDir, "worktrees"),
7045
- attachmentsDir,
7046
- logsDir,
7047
- serverLogPath: join(logsDir, "server.log"),
7048
- serverTracePath: join(logsDir, "server.trace.ndjson"),
7049
- providerLogsDir,
7050
- providerEventLogPath: join(providerLogsDir, "events.log"),
7051
- terminalLogsDir: join(logsDir, "terminals"),
7052
- anonymousIdPath: join(stateDir, "anonymous-id"),
7053
- environmentIdPath: join(stateDir, "environment-id"),
7054
- serverRuntimeStatePath: join(stateDir, "server-runtime.json"),
7055
- secretsDir: join(stateDir, "secrets")
7056
- };
7057
- });
7058
- const ensureServerDirectories = Effect.fn(function* (derivedPaths) {
7059
- const fs = yield* FileSystem.FileSystem;
7060
- const path = yield* Path.Path;
7061
- yield* Effect.all([
7062
- fs.makeDirectory(derivedPaths.stateDir, { recursive: true }),
7063
- fs.makeDirectory(derivedPaths.logsDir, { recursive: true }),
7064
- fs.makeDirectory(derivedPaths.providerLogsDir, { recursive: true }),
7065
- fs.makeDirectory(derivedPaths.terminalLogsDir, { recursive: true }),
7066
- fs.makeDirectory(derivedPaths.attachmentsDir, { recursive: true }),
7067
- fs.makeDirectory(derivedPaths.worktreesDir, { recursive: true }),
7068
- fs.makeDirectory(path.dirname(derivedPaths.keybindingsConfigPath), { recursive: true }),
7069
- fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }),
7070
- fs.makeDirectory(derivedPaths.providerStatusCacheDir, { recursive: true }),
7071
- fs.makeDirectory(path.dirname(derivedPaths.anonymousIdPath), { recursive: true }),
7072
- fs.makeDirectory(path.dirname(derivedPaths.serverRuntimeStatePath), { recursive: true })
7073
- ], { concurrency: "unbounded" });
7074
- });
7075
- const makeTest$1 = Effect.fn("ServerConfig.makeTest")(function* (cwd, baseDirOrPrefix) {
7076
- const devUrl = void 0;
7077
- const fs = yield* FileSystem.FileSystem;
7078
- const baseDir = typeof baseDirOrPrefix === "string" ? baseDirOrPrefix : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
7079
- const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
7080
- yield* ensureServerDirectories(derivedPaths);
7081
- return ServerConfig.of({
7082
- logLevel: "Error",
7083
- traceMinLevel: "Info",
7084
- traceTimingEnabled: true,
7085
- traceBatchWindowMs: 200,
7086
- traceMaxBytes: 10 * 1024 * 1024,
7087
- traceMaxFiles: 10,
7088
- otlpTracesUrl: void 0,
7089
- otlpMetricsUrl: void 0,
7090
- otlpExportIntervalMs: 1e4,
7091
- otlpServiceName: "p4-server",
7092
- cwd,
7093
- baseDir,
7094
- ...derivedPaths,
7095
- mode: "web",
7096
- autoBootstrapProjectFromCwd: false,
7097
- logWebSocketEvents: false,
7098
- tailscaleServeEnabled: false,
7099
- tailscaleServePort: 443,
7100
- port: 0,
7101
- host: void 0,
7102
- desktopBootstrapToken: void 0,
7103
- staticDir: void 0,
7104
- devUrl,
7105
- devAllowedOrigins: [],
7106
- noBrowser: false,
7107
- startupPresentation: "browser"
7108
- });
7109
- });
7110
- const layerTest$2 = (cwd, baseDirOrPrefix) => Layer.effect(ServerConfig, makeTest$1(cwd, baseDirOrPrefix));
7111
- const resolveStaticDir = Effect.fn(function* () {
7112
- const { join, resolve } = yield* Path.Path;
7113
- const { exists } = yield* FileSystem.FileSystem;
7114
- const bundledClient = resolve(join(import.meta.dirname, "client"));
7115
- if (yield* exists(join(bundledClient, "index.html")).pipe(Effect.orElseSucceed(() => false))) return bundledClient;
7116
- const monorepoClient = resolve(join(import.meta.dirname, "../../web/dist"));
7117
- if (yield* exists(join(monorepoClient, "index.html")).pipe(Effect.orElseSucceed(() => false))) return monorepoClient;
7118
- });
7119
- //#endregion
7120
7710
  //#region src/auth/utils.ts
7121
7711
  const SESSION_COOKIE_NAME = "p4_session";
7122
7712
  /**
@@ -7220,7 +7810,7 @@ function deriveAuthClientMetadata(input) {
7220
7810
  //#region src/auth/EnvironmentAuthPolicy.ts
7221
7811
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
7222
7812
  const make$65 = Effect.gen(function* () {
7223
- const config = yield* ServerConfig;
7813
+ const config = yield* ServerConfig$1;
7224
7814
  const isRemoteReachable = isRemoteReachableHost(config.host);
7225
7815
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
7226
7816
  const descriptor = {
@@ -7599,7 +8189,7 @@ const PAIRING_TOKEN_LENGTH = 12;
7599
8189
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
7600
8190
  const make$63 = Effect.gen(function* () {
7601
8191
  const crypto = yield* Crypto.Crypto;
7602
- const config = yield* ServerConfig;
8192
+ const config = yield* ServerConfig$1;
7603
8193
  const pairingLinks = yield* AuthPairingLinkRepository;
7604
8194
  const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
7605
8195
  const changesPubSub = yield* PubSub.unbounded();
@@ -7866,7 +8456,7 @@ const make$62 = Effect.gen(function* () {
7866
8456
  const crypto = yield* Crypto.Crypto;
7867
8457
  const fileSystem = yield* FileSystem.FileSystem;
7868
8458
  const path = yield* Path.Path;
7869
- const serverConfig = yield* ServerConfig;
8459
+ const serverConfig = yield* ServerConfig$1;
7870
8460
  yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
7871
8461
  yield* fileSystem.chmod(serverConfig.secretsDir, 448).pipe(Effect.mapError((cause) => new SecretStoreSecureError({
7872
8462
  resource: `secrets directory ${serverConfig.secretsDir}`,
@@ -8410,7 +9000,7 @@ function toAuthClientSession(input) {
8410
9000
  }
8411
9001
  const make$60 = Effect.gen(function* () {
8412
9002
  const crypto = yield* Crypto.Crypto;
8413
- const serverConfig = yield* ServerConfig;
9003
+ const serverConfig = yield* ServerConfig$1;
8414
9004
  const secretStore = yield* ServerSecretStore;
8415
9005
  const authSessions = yield* AuthSessionRepository;
8416
9006
  const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
@@ -10271,7 +10861,7 @@ const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")(functio
10271
10861
  }));
10272
10862
  }, Layer.unwrap);
10273
10863
  Layer.provideMerge(setup, makeRuntimeSqliteLayer({ filename: ":memory:" }));
10274
- const layerConfig = Layer.unwrap(Effect.map(Effect.service(ServerConfig), ({ dbPath }) => makeSqlitePersistenceLive(dbPath)));
10864
+ const layerConfig = Layer.unwrap(Effect.map(Effect.service(ServerConfig$1), ({ dbPath }) => makeSqlitePersistenceLive(dbPath)));
10275
10865
  const INTERNAL_ADMINISTRATIVE_BOOTSTRAP_SUBJECT = "administrative-bootstrap";
10276
10866
  const serverAuthInternalErrorContext = { cause: Schema$1.Defect() };
10277
10867
  var ServerAuthBootstrapCredentialValidationError = class extends Schema$1.TaggedErrorClass()("ServerAuthBootstrapCredentialValidationError", { ...serverAuthInternalErrorContext }) {
@@ -10934,652 +11524,210 @@ function resolveSourceControlWriterModelSelection(settings, providers) {
10934
11524
  if (!selection || !isModelSelectionProviderEnabled(settings, selection)) return settings.textGenerationModelSelection;
10935
11525
  if (providers === void 0) return selection;
10936
11526
  const provider = providers.find((candidate) => candidate.instanceId === selection.instanceId);
10937
- return provider?.enabled === true && isProviderAvailable(provider) ? selection : settings.textGenerationModelSelection;
10938
- }
10939
- function normalizePersistedServerSettingString(value) {
10940
- const trimmed = value?.trim();
10941
- return trimmed && trimmed.length > 0 ? trimmed : void 0;
10942
- }
10943
- function extractPersistedServerObservabilitySettings(input) {
10944
- return {
10945
- otlpTracesUrl: normalizePersistedServerSettingString(input.observability?.otlpTracesUrl),
10946
- otlpMetricsUrl: normalizePersistedServerSettingString(input.observability?.otlpMetricsUrl)
10947
- };
10948
- }
10949
- function parsePersistedServerObservabilitySettings(raw) {
10950
- const decoded = decodeServerSettingsJson(raw);
10951
- if (Option.isSome(decoded)) return extractPersistedServerObservabilitySettings(decoded.value);
10952
- return {
10953
- otlpTracesUrl: void 0,
10954
- otlpMetricsUrl: void 0
10955
- };
10956
- }
10957
- function shouldReplaceTextGenerationModelSelection(patch) {
10958
- return Boolean(patch && (patch.instanceId !== void 0 || patch.model !== void 0));
10959
- }
10960
- function mergeModelSelectionOptionsById(input) {
10961
- if (input.patch === void 0) return input.current ? [...input.current] : void 0;
10962
- if (input.patch.length === 0) return;
10963
- const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value]));
10964
- for (const selection of input.patch) merged.set(selection.id, selection.value);
10965
- return [...merged.entries()].map(([id, value]) => ({
10966
- id,
10967
- value
10968
- }));
10969
- }
10970
- function applyServerSettingsPatch(current, patch) {
10971
- const selectionPatch = patch.textGenerationModelSelection;
10972
- const { automaticGitFetchInterval, ...patchForMerge } = patch;
10973
- const nextWithReplacements = {
10974
- ...deepMerge(current, patchForMerge),
10975
- ...patch.providerInstances !== void 0 ? { providerInstances: patch.providerInstances } : {},
10976
- ...patch.sourceControlWriterModelSelection !== void 0 ? { sourceControlWriterModelSelection: patch.sourceControlWriterModelSelection } : {},
10977
- ...automaticGitFetchInterval !== void 0 ? { automaticGitFetchInterval } : {}
10978
- };
10979
- if (!selectionPatch) return nextWithReplacements;
10980
- const instanceId = selectionPatch.instanceId ?? current.textGenerationModelSelection.instanceId;
10981
- const model = selectionPatch.model ?? current.textGenerationModelSelection.model;
10982
- const options = shouldReplaceTextGenerationModelSelection(selectionPatch) ? selectionPatch.options : mergeModelSelectionOptionsById({
10983
- current: current.textGenerationModelSelection.options,
10984
- patch: selectionPatch.options
10985
- });
10986
- return {
10987
- ...nextWithReplacements,
10988
- textGenerationModelSelection: createModelSelection(instanceId, model, options)
10989
- };
10990
- }
10991
- //#endregion
10992
- //#region src/bootstrap.ts
10993
- var BootstrapFdStatError = class extends Schema$1.TaggedErrorClass()("BootstrapFdStatError", {
10994
- fd: Schema$1.Number,
10995
- cause: Schema$1.Defect()
10996
- }) {
10997
- get message() {
10998
- return `Failed to stat bootstrap file descriptor ${this.fd}.`;
10999
- }
11000
- };
11001
- var BootstrapInputStreamOpenError = class extends Schema$1.TaggedErrorClass()("BootstrapInputStreamOpenError", {
11002
- fd: Schema$1.Number,
11003
- platform: Schema$1.String,
11004
- fdPath: Schema$1.optional(Schema$1.String),
11005
- cause: Schema$1.Defect()
11006
- }) {
11007
- get message() {
11008
- const path = this.fdPath === void 0 ? "" : ` via '${this.fdPath}'`;
11009
- return `Failed to open bootstrap input stream for file descriptor ${this.fd}${path} on '${this.platform}'.`;
11010
- }
11011
- };
11012
- var BootstrapEnvelopeReadError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeReadError", {
11013
- fd: Schema$1.Number,
11014
- cause: Schema$1.Defect()
11015
- }) {
11016
- get message() {
11017
- return `Failed to read bootstrap envelope from file descriptor ${this.fd}.`;
11018
- }
11019
- };
11020
- var BootstrapEnvelopeDecodeError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeDecodeError", {
11021
- fd: Schema$1.Number,
11022
- cause: Schema$1.Defect()
11023
- }) {
11024
- get message() {
11025
- return `Failed to decode bootstrap envelope from file descriptor ${this.fd}.`;
11026
- }
11027
- };
11028
- Schema$1.Union([
11029
- BootstrapFdStatError,
11030
- BootstrapInputStreamOpenError,
11031
- BootstrapEnvelopeReadError,
11032
- BootstrapEnvelopeDecodeError
11033
- ]);
11034
- const readBootstrapEnvelope = Effect.fn("readBootstrapEnvelope")(function* (schema, fd, options) {
11035
- if (!(yield* isFdReady(fd))) return Option.none();
11036
- const stream = yield* makeBootstrapInputStream(fd);
11037
- const timeoutMs = options?.timeoutMs ?? 1e3;
11038
- return yield* Effect.callback((resume) => {
11039
- const input = NodeReadline.createInterface({
11040
- input: stream,
11041
- crlfDelay: Infinity
11042
- });
11043
- const cleanup = () => {
11044
- stream.removeListener("error", handleError);
11045
- input.removeListener("line", handleLine);
11046
- input.removeListener("close", handleClose);
11047
- input.close();
11048
- stream.destroy();
11049
- };
11050
- const handleError = (error) => {
11051
- if (isUnavailableBootstrapFdError(error)) {
11052
- resume(Effect.succeedNone);
11053
- return;
11054
- }
11055
- resume(Effect.fail(new BootstrapEnvelopeReadError({
11056
- fd,
11057
- cause: error
11058
- })));
11059
- };
11060
- const handleLine = (line) => {
11061
- const parsed = decodeJsonResult(schema)(line);
11062
- if (Result.isSuccess(parsed)) resume(Effect.succeedSome(parsed.success));
11063
- else resume(Effect.fail(new BootstrapEnvelopeDecodeError({
11064
- fd,
11065
- cause: parsed.failure
11066
- })));
11067
- };
11068
- const handleClose = () => {
11069
- resume(Effect.succeedNone);
11070
- };
11071
- stream.once("error", handleError);
11072
- input.once("line", handleLine);
11073
- input.once("close", handleClose);
11074
- return Effect.sync(cleanup);
11075
- }).pipe(Effect.timeoutOption(timeoutMs), Effect.map(Option.flatten));
11076
- });
11077
- const isUnavailableBootstrapFdError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "EBADF" || _.code === "ENOENT");
11078
- const isFdReady = (fd) => Effect.try({
11079
- try: () => NodeFS.fstatSync(fd),
11080
- catch: (error) => new BootstrapFdStatError({
11081
- fd,
11082
- cause: error
11083
- })
11084
- }).pipe(Effect.as(true), Effect.catchTags({ BootstrapFdStatError: (error) => isUnavailableBootstrapFdError(error.cause) ? Effect.succeed(false) : Effect.fail(error) }));
11085
- const makeBootstrapInputStream = (fd) => Effect.gen(function* () {
11086
- const platform = yield* HostProcessPlatform;
11087
- const fdPath = resolveFdPath(fd, platform);
11088
- return yield* Effect.try({
11089
- try: () => {
11090
- if (fdPath === void 0) return makeDirectBootstrapStream(fd);
11091
- let streamFd;
11092
- try {
11093
- streamFd = NodeFS.openSync(fdPath, "r");
11094
- return NodeFS.createReadStream("", {
11095
- fd: streamFd,
11096
- encoding: "utf8",
11097
- autoClose: true
11098
- });
11099
- } catch (error) {
11100
- if (isBootstrapFdPathDuplicationError(error)) {
11101
- if (streamFd !== void 0) NodeFS.closeSync(streamFd);
11102
- return makeDirectBootstrapStream(fd);
11103
- }
11104
- throw error;
11105
- }
11106
- },
11107
- catch: (error) => new BootstrapInputStreamOpenError({
11108
- fd,
11109
- platform,
11110
- ...fdPath === void 0 ? {} : { fdPath },
11111
- cause: error
11112
- })
11113
- });
11114
- });
11115
- const makeDirectBootstrapStream = (fd) => {
11116
- try {
11117
- return NodeFS.createReadStream("", {
11118
- fd,
11119
- encoding: "utf8",
11120
- autoClose: true
11121
- });
11122
- } catch {
11123
- const stream = new NodeNet.Socket({
11124
- fd,
11125
- readable: true,
11126
- writable: false
11127
- });
11128
- stream.setEncoding("utf8");
11129
- return stream;
11130
- }
11131
- };
11132
- const isBootstrapFdPathDuplicationError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "ENXIO" || _.code === "EINVAL" || _.code === "EPERM" || _.code === "EACCES");
11133
- function resolveFdPath(fd, platform) {
11134
- if (platform === "linux") return `/proc/self/fd/${fd}`;
11135
- if (platform === "win32") return;
11136
- return `/dev/fd/${fd}`;
11137
- }
11138
- //#endregion
11139
- //#region ../../packages/shared/src/shell.ts
11140
- const SHELL_ENV_NAME_PATTERN = /^[A-Z0-9_]+$/;
11141
- const WINDOWS_PATH_DELIMITER = ";";
11142
- const POSIX_PATH_DELIMITER = ":";
11143
- const WINDOWS_SHELL_CANDIDATES = ["pwsh.exe", "powershell.exe"];
11144
- function canExecuteFile(filePath) {
11145
- try {
11146
- NodeFS.accessSync(filePath, NodeFS.constants.X_OK);
11147
- return true;
11148
- } catch {
11149
- return false;
11150
- }
11151
- }
11152
- var CommandResolutionError = class extends Data.TaggedError("CommandResolutionError") {};
11153
- const WINDOWS_SHELL_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
11154
- /**
11155
- * Escapes a single argument for `cmd.exe` shell mode (`spawn(..., { shell: true })`
11156
- * on Windows). Node joins the command and arguments with spaces and hands the
11157
- * resulting string to `cmd.exe` without any quoting, so every dynamic argument
11158
- * must be escaped to survive both cmd.exe parsing and the target program's
11159
- * `CommandLineToArgvW` parsing. Mirrors cross-spawn's argument escaping.
11160
- */
11161
- function escapeWindowsShellArg(arg) {
11162
- let escaped = arg.replace(/(\\*)"/g, "$1$1\\\"");
11163
- escaped = escaped.replace(/(\\*)$/, "$1$1");
11164
- escaped = `"${escaped}"`;
11165
- return escaped.replace(WINDOWS_SHELL_META_CHARS, "^$1");
11166
- }
11167
- /**
11168
- * Escapes arguments for shell-mode spawns: applies {@link escapeWindowsShellArg}
11169
- * when the platform is `win32` (where `shell: true` routes through `cmd.exe`)
11170
- * and returns the arguments untouched everywhere else.
11171
- */
11172
- function sanitizeShellModeArgsForPlatform(args, platform) {
11173
- return platform === "win32" ? args.map(escapeWindowsShellArg) : [...args];
11174
- }
11175
- function resolveSpawnExecutableWithNode(command, platform, env) {
11176
- const path = platform === "win32" ? NodePath.win32 : NodePath.posix;
11177
- const windowsPathExtensions = platform === "win32" ? resolveWindowsPathExtensions(env) : [];
11178
- const candidates = resolveCommandCandidates(command, platform, windowsPathExtensions, path.extname);
11179
- const isExecutable = (candidate) => {
11180
- try {
11181
- if (!NodeFS.statSync(candidate).isFile()) return false;
11182
- if (platform === "win32") return windowsPathExtensions.includes(path.extname(candidate).toUpperCase());
11183
- return canExecuteFile(candidate);
11184
- } catch {
11185
- return false;
11186
- }
11187
- };
11188
- if (command.includes("/") || command.includes("\\")) return candidates.find(isExecutable);
11189
- for (const pathEntry of (readEnvPath(env) ?? "").split(pathDelimiterForPlatform(platform))) {
11190
- const normalizedPathEntry = stripWrappingQuotes(pathEntry.trim());
11191
- if (normalizedPathEntry.length === 0) continue;
11192
- for (const candidate of candidates) {
11193
- const candidatePath = path.join(normalizedPathEntry, candidate);
11194
- if (isExecutable(candidatePath)) return candidatePath;
11195
- }
11196
- }
11197
- }
11198
- const SpawnExecutableResolution = Context.Reference("@p4code/shared/shell/SpawnExecutableResolution", { defaultValue: () => resolveSpawnExecutableWithNode });
11199
- function trimNonEmpty(value) {
11200
- const trimmed = value?.trim();
11201
- return trimmed && trimmed.length > 0 ? trimmed : void 0;
11202
- }
11203
- function readUserLoginShell() {
11204
- try {
11205
- return trimNonEmpty(NodeOS.userInfo().shell);
11206
- } catch {
11207
- return;
11208
- }
11209
- }
11210
- function listLoginShellCandidates(platform, shell, userShell = readUserLoginShell()) {
11211
- const fallbackShell = platform === "darwin" ? "/bin/zsh" : platform === "linux" ? "/bin/bash" : void 0;
11212
- const seen = /* @__PURE__ */ new Set();
11213
- const candidates = [];
11214
- for (const candidate of [
11215
- trimNonEmpty(shell),
11216
- trimNonEmpty(userShell),
11217
- fallbackShell
11218
- ]) {
11219
- if (!candidate || seen.has(candidate)) continue;
11220
- seen.add(candidate);
11221
- candidates.push(candidate);
11222
- }
11223
- return candidates;
11224
- }
11225
- function readPathFromLoginShell(shell, execFile = NodeChildProcess.execFileSync) {
11226
- return readEnvironmentFromLoginShell(shell, ["PATH"], execFile).PATH;
11227
- }
11228
- function readPathFromLaunchctl(execFile = NodeChildProcess.execFileSync) {
11229
- try {
11230
- return trimNonEmpty(execFile("/bin/launchctl", ["getenv", "PATH"], {
11231
- encoding: "utf8",
11232
- timeout: 2e3
11233
- }));
11234
- } catch {
11235
- return;
11236
- }
11237
- }
11238
- function mergePathEntries(preferredPath, inheritedPath, platform) {
11239
- const delimiter = platform === "win32" ? ";" : ":";
11240
- const merged = [];
11241
- const seen = /* @__PURE__ */ new Set();
11242
- for (const pathValue of [preferredPath, inheritedPath]) {
11243
- if (!pathValue) continue;
11244
- for (const entry of pathValue.split(delimiter)) {
11245
- const trimmedEntry = entry.trim();
11246
- if (!trimmedEntry || seen.has(trimmedEntry)) continue;
11247
- seen.add(trimmedEntry);
11248
- merged.push(trimmedEntry);
11249
- }
11250
- }
11251
- return merged.length > 0 ? merged.join(delimiter) : void 0;
11252
- }
11253
- function envCaptureStart(name) {
11254
- return `__P4CODE_ENV_${name}_START__`;
11255
- }
11256
- function envCaptureEnd(name) {
11257
- return `__P4CODE_ENV_${name}_END__`;
11258
- }
11259
- function buildEnvironmentCaptureCommand(names) {
11260
- return names.map((name) => {
11261
- if (!SHELL_ENV_NAME_PATTERN.test(name)) throw new Error(`Unsupported environment variable name: ${name}`);
11262
- return [
11263
- `printf '%s\\n' '${envCaptureStart(name)}'`,
11264
- `printenv ${name} || true`,
11265
- `printf '%s\\n' '${envCaptureEnd(name)}'`
11266
- ].join("; ");
11267
- }).join("; ");
11268
- }
11269
- function buildWindowsEnvironmentCaptureCommand(names) {
11270
- return ["$ErrorActionPreference = 'Stop'", ...names.flatMap((name) => {
11271
- if (!SHELL_ENV_NAME_PATTERN.test(name)) throw new Error(`Unsupported environment variable name: ${name}`);
11272
- return [
11273
- `Write-Output '${envCaptureStart(name)}'`,
11274
- `$value = [Environment]::GetEnvironmentVariable('${name}')`,
11275
- "if ($null -ne $value -and $value.Length -gt 0) { Write-Output $value }",
11276
- `Write-Output '${envCaptureEnd(name)}'`
11277
- ];
11278
- })].join("; ");
11279
- }
11280
- function extractEnvironmentValue(output, name) {
11281
- const startMarker = envCaptureStart(name);
11282
- const endMarker = envCaptureEnd(name);
11283
- const startIndex = output.indexOf(startMarker);
11284
- if (startIndex === -1) return void 0;
11285
- const valueStartIndex = startIndex + startMarker.length;
11286
- const endIndex = output.indexOf(endMarker, valueStartIndex);
11287
- if (endIndex === -1) return void 0;
11288
- const value = output.slice(valueStartIndex, endIndex).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
11289
- return value.length > 0 ? value : void 0;
11290
- }
11291
- const readEnvironmentFromLoginShell = (shell, names, execFile = NodeChildProcess.execFileSync) => {
11292
- if (names.length === 0) return {};
11293
- const output = execFile(shell, ["-ilc", buildEnvironmentCaptureCommand(names)], {
11294
- encoding: "utf8",
11295
- timeout: 5e3
11296
- });
11297
- const environment = {};
11298
- for (const name of names) {
11299
- const value = extractEnvironmentValue(output, name);
11300
- if (value !== void 0) environment[name] = value;
11301
- }
11302
- return environment;
11303
- };
11304
- const WindowsShellEnvironment = Context.Reference("@p4code/shared/shell/WindowsShellEnvironment", { defaultValue: () => readEnvironmentFromWindowsShell });
11305
- const CommandAvailability = Context.Reference("@p4code/shared/shell/CommandAvailability", { defaultValue: () => isCommandAvailable });
11306
- function readEnvironmentFromWindowsShell(names, optionsOrExecFile, maybeExecFile) {
11307
- if (names.length === 0) return {};
11308
- const options = typeof optionsOrExecFile === "function" ? {} : optionsOrExecFile ?? {};
11309
- const execFile = typeof optionsOrExecFile === "function" ? optionsOrExecFile : maybeExecFile ?? NodeChildProcess.execFileSync;
11310
- const command = buildWindowsEnvironmentCaptureCommand(names);
11311
- const args = [
11312
- "-NoLogo",
11313
- ...options.loadProfile ? [] : ["-NoProfile"],
11314
- "-NonInteractive",
11315
- "-Command",
11316
- command
11317
- ];
11318
- for (const shell of WINDOWS_SHELL_CANDIDATES) try {
11319
- const output = execFile(shell, args, {
11320
- encoding: "utf8",
11321
- timeout: 5e3
11322
- });
11323
- const environment = {};
11324
- for (const name of names) {
11325
- const value = extractEnvironmentValue(output, name);
11326
- if (value !== void 0) environment[name] = value;
11327
- }
11328
- return environment;
11329
- } catch {
11330
- continue;
11331
- }
11332
- return {};
11333
- }
11334
- function stripWrappingQuotes(value) {
11335
- return value.replace(/^"+|"+$/g, "");
11336
- }
11337
- function pathDelimiterForPlatform(platform) {
11338
- return platform === "win32" ? WINDOWS_PATH_DELIMITER : POSIX_PATH_DELIMITER;
11339
- }
11340
- function normalizePathEntryForComparison(entry, platform) {
11341
- const normalized = stripWrappingQuotes(entry.trim());
11342
- return platform === "win32" ? normalized.toLowerCase() : normalized;
11343
- }
11344
- function mergePathValues(preferredPath, inheritedPath, platform) {
11345
- const delimiter = pathDelimiterForPlatform(platform);
11346
- const merged = [];
11347
- const seen = /* @__PURE__ */ new Set();
11348
- for (const rawValue of [preferredPath, inheritedPath]) {
11349
- if (!rawValue) continue;
11350
- for (const entry of rawValue.split(delimiter)) {
11351
- const trimmed = entry.trim();
11352
- if (trimmed.length === 0) continue;
11353
- const normalized = normalizePathEntryForComparison(trimmed, platform);
11354
- if (normalized.length === 0 || seen.has(normalized)) continue;
11355
- seen.add(normalized);
11356
- merged.push(trimmed);
11357
- }
11358
- }
11359
- return merged.length > 0 ? merged.join(delimiter) : void 0;
11527
+ return provider?.enabled === true && isProviderAvailable(provider) ? selection : settings.textGenerationModelSelection;
11360
11528
  }
11361
- function readEnvPath(env) {
11362
- return env.PATH ?? env.Path ?? env.path;
11529
+ function normalizePersistedServerSettingString(value) {
11530
+ const trimmed = value?.trim();
11531
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
11363
11532
  }
11364
- function resolvePathEnvironmentVariable(env) {
11365
- return readEnvPath(env) ?? "";
11533
+ function extractPersistedServerObservabilitySettings(input) {
11534
+ return {
11535
+ otlpTracesUrl: normalizePersistedServerSettingString(input.observability?.otlpTracesUrl),
11536
+ otlpMetricsUrl: normalizePersistedServerSettingString(input.observability?.otlpMetricsUrl)
11537
+ };
11366
11538
  }
11367
- function resolveWindowsPathExtensions(env) {
11368
- const rawValue = env.PATHEXT;
11369
- const fallback = [
11370
- ".COM",
11371
- ".EXE",
11372
- ".BAT",
11373
- ".CMD"
11374
- ];
11375
- if (!rawValue) return fallback;
11376
- const parsed = [];
11377
- for (const entry of rawValue.split(";")) {
11378
- const trimmed = entry.trim();
11379
- if (trimmed.length === 0) continue;
11380
- parsed.push(trimmed.startsWith(".") ? trimmed.toUpperCase() : `.${trimmed.toUpperCase()}`);
11381
- }
11382
- return parsed.length > 0 ? Array.from(new Set(parsed)) : fallback;
11539
+ function parsePersistedServerObservabilitySettings(raw) {
11540
+ const decoded = decodeServerSettingsJson(raw);
11541
+ if (Option.isSome(decoded)) return extractPersistedServerObservabilitySettings(decoded.value);
11542
+ return {
11543
+ otlpTracesUrl: void 0,
11544
+ otlpMetricsUrl: void 0
11545
+ };
11383
11546
  }
11384
- function resolveCommandCandidates(command, platform, windowsPathExtensions, extname) {
11385
- if (platform !== "win32") return [command];
11386
- const extension = extname(command);
11387
- const normalizedExtension = extension.toUpperCase();
11388
- if (extension.length > 0 && windowsPathExtensions.includes(normalizedExtension)) {
11389
- const commandWithoutExtension = command.slice(0, -extension.length);
11390
- return Array.from(/* @__PURE__ */ new Set([
11391
- command,
11392
- `${commandWithoutExtension}${normalizedExtension}`,
11393
- `${commandWithoutExtension}${normalizedExtension.toLowerCase()}`
11394
- ]));
11395
- }
11396
- const candidates = [];
11397
- for (const candidateExtension of windowsPathExtensions) {
11398
- candidates.push(`${command}${candidateExtension}`);
11399
- candidates.push(`${command}${candidateExtension.toLowerCase()}`);
11400
- }
11401
- return Array.from(new Set(candidates));
11547
+ function shouldReplaceTextGenerationModelSelection(patch) {
11548
+ return Boolean(patch && (patch.instanceId !== void 0 || patch.model !== void 0));
11402
11549
  }
11403
- const isExecutableFile = Effect.fn("shell.isExecutableFile")(function* (filePath, platform, windowsPathExtensions) {
11404
- const fileSystem = yield* FileSystem.FileSystem;
11405
- const path = yield* Path.Path;
11406
- const stat = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null));
11407
- if (stat === null || stat.type !== "File") return false;
11408
- if (platform === "win32") {
11409
- const extension = path.extname(filePath);
11410
- if (extension.length === 0) return false;
11411
- return windowsPathExtensions.includes(extension.toUpperCase());
11550
+ function mergeModelSelectionOptionsById(input) {
11551
+ if (input.patch === void 0) return input.current ? [...input.current] : void 0;
11552
+ if (input.patch.length === 0) return;
11553
+ const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value]));
11554
+ for (const selection of input.patch) merged.set(selection.id, selection.value);
11555
+ return [...merged.entries()].map(([id, value]) => ({
11556
+ id,
11557
+ value
11558
+ }));
11559
+ }
11560
+ function applyServerSettingsPatch(current, patch) {
11561
+ const selectionPatch = patch.textGenerationModelSelection;
11562
+ const { automaticGitFetchInterval, ...patchForMerge } = patch;
11563
+ const nextWithReplacements = {
11564
+ ...deepMerge(current, patchForMerge),
11565
+ ...patch.providerInstances !== void 0 ? { providerInstances: patch.providerInstances } : {},
11566
+ ...patch.sourceControlWriterModelSelection !== void 0 ? { sourceControlWriterModelSelection: patch.sourceControlWriterModelSelection } : {},
11567
+ ...automaticGitFetchInterval !== void 0 ? { automaticGitFetchInterval } : {}
11568
+ };
11569
+ if (!selectionPatch) return nextWithReplacements;
11570
+ const instanceId = selectionPatch.instanceId ?? current.textGenerationModelSelection.instanceId;
11571
+ const model = selectionPatch.model ?? current.textGenerationModelSelection.model;
11572
+ const options = shouldReplaceTextGenerationModelSelection(selectionPatch) ? selectionPatch.options : mergeModelSelectionOptionsById({
11573
+ current: current.textGenerationModelSelection.options,
11574
+ patch: selectionPatch.options
11575
+ });
11576
+ return {
11577
+ ...nextWithReplacements,
11578
+ textGenerationModelSelection: createModelSelection(instanceId, model, options)
11579
+ };
11580
+ }
11581
+ //#endregion
11582
+ //#region src/bootstrap.ts
11583
+ var BootstrapFdStatError = class extends Schema$1.TaggedErrorClass()("BootstrapFdStatError", {
11584
+ fd: Schema$1.Number,
11585
+ cause: Schema$1.Defect()
11586
+ }) {
11587
+ get message() {
11588
+ return `Failed to stat bootstrap file descriptor ${this.fd}.`;
11412
11589
  }
11413
- return canExecuteFile(filePath);
11414
- });
11415
- const resolveCommandPathForPlatform = Effect.fn("shell.resolveCommandPathForPlatform")(function* (command, options) {
11416
- const path = yield* Path.Path;
11417
- const platform = options.platform;
11418
- const env = options.env ?? process.env;
11419
- const windowsPathExtensions = platform === "win32" ? resolveWindowsPathExtensions(env) : [];
11420
- const commandCandidates = resolveCommandCandidates(command, platform, windowsPathExtensions, path.extname);
11421
- if (command.includes("/") || command.includes("\\")) {
11422
- for (const candidate of commandCandidates) if (yield* isExecutableFile(candidate, platform, windowsPathExtensions)) return candidate;
11423
- return yield* new CommandResolutionError({
11424
- command,
11425
- reason: "not-found"
11426
- });
11590
+ };
11591
+ var BootstrapInputStreamOpenError = class extends Schema$1.TaggedErrorClass()("BootstrapInputStreamOpenError", {
11592
+ fd: Schema$1.Number,
11593
+ platform: Schema$1.String,
11594
+ fdPath: Schema$1.optional(Schema$1.String),
11595
+ cause: Schema$1.Defect()
11596
+ }) {
11597
+ get message() {
11598
+ const path = this.fdPath === void 0 ? "" : ` via '${this.fdPath}'`;
11599
+ return `Failed to open bootstrap input stream for file descriptor ${this.fd}${path} on '${this.platform}'.`;
11427
11600
  }
11428
- const pathValue = resolvePathEnvironmentVariable(env);
11429
- if (pathValue.length === 0) return yield* new CommandResolutionError({
11430
- command,
11431
- reason: "not-found"
11432
- });
11433
- const pathEntries = [];
11434
- for (const entry of pathValue.split(pathDelimiterForPlatform(platform))) {
11435
- const pathEntry = stripWrappingQuotes(entry.trim());
11436
- if (pathEntry.length > 0) pathEntries.push(pathEntry);
11601
+ };
11602
+ var BootstrapEnvelopeReadError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeReadError", {
11603
+ fd: Schema$1.Number,
11604
+ cause: Schema$1.Defect()
11605
+ }) {
11606
+ get message() {
11607
+ return `Failed to read bootstrap envelope from file descriptor ${this.fd}.`;
11437
11608
  }
11438
- for (const pathEntry of pathEntries) for (const candidate of commandCandidates) {
11439
- const candidatePath = path.join(pathEntry, candidate);
11440
- if (yield* isExecutableFile(candidatePath, platform, windowsPathExtensions)) return candidatePath;
11609
+ };
11610
+ var BootstrapEnvelopeDecodeError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeDecodeError", {
11611
+ fd: Schema$1.Number,
11612
+ cause: Schema$1.Defect()
11613
+ }) {
11614
+ get message() {
11615
+ return `Failed to decode bootstrap envelope from file descriptor ${this.fd}.`;
11441
11616
  }
11442
- return yield* new CommandResolutionError({
11443
- command,
11444
- reason: "not-found"
11445
- });
11446
- });
11447
- const resolveCommandPath = Effect.fn("shell.resolveCommandPath")(function* (command, options = {}) {
11448
- return yield* resolveCommandPathForPlatform(command, {
11449
- env: options.env ?? (yield* HostProcessEnvironment),
11450
- platform: yield* HostProcessPlatform
11451
- });
11617
+ };
11618
+ Schema$1.Union([
11619
+ BootstrapFdStatError,
11620
+ BootstrapInputStreamOpenError,
11621
+ BootstrapEnvelopeReadError,
11622
+ BootstrapEnvelopeDecodeError
11623
+ ]);
11624
+ const readBootstrapEnvelope = Effect.fn("readBootstrapEnvelope")(function* (schema, fd, options) {
11625
+ if (!(yield* isFdReady(fd))) return Option.none();
11626
+ const stream = yield* makeBootstrapInputStream(fd);
11627
+ const timeoutMs = options?.timeoutMs ?? 1e3;
11628
+ return yield* Effect.callback((resume) => {
11629
+ const input = NodeReadline.createInterface({
11630
+ input: stream,
11631
+ crlfDelay: Infinity
11632
+ });
11633
+ const cleanup = () => {
11634
+ stream.removeListener("error", handleError);
11635
+ input.removeListener("line", handleLine);
11636
+ input.removeListener("close", handleClose);
11637
+ input.close();
11638
+ stream.destroy();
11639
+ };
11640
+ const handleError = (error) => {
11641
+ if (isUnavailableBootstrapFdError(error)) {
11642
+ resume(Effect.succeedNone);
11643
+ return;
11644
+ }
11645
+ resume(Effect.fail(new BootstrapEnvelopeReadError({
11646
+ fd,
11647
+ cause: error
11648
+ })));
11649
+ };
11650
+ const handleLine = (line) => {
11651
+ const parsed = decodeJsonResult(schema)(line);
11652
+ if (Result.isSuccess(parsed)) resume(Effect.succeedSome(parsed.success));
11653
+ else resume(Effect.fail(new BootstrapEnvelopeDecodeError({
11654
+ fd,
11655
+ cause: parsed.failure
11656
+ })));
11657
+ };
11658
+ const handleClose = () => {
11659
+ resume(Effect.succeedNone);
11660
+ };
11661
+ stream.once("error", handleError);
11662
+ input.once("line", handleLine);
11663
+ input.once("close", handleClose);
11664
+ return Effect.sync(cleanup);
11665
+ }).pipe(Effect.timeoutOption(timeoutMs), Effect.map(Option.flatten));
11452
11666
  });
11453
- const resolveSpawnCommand = Effect.fn("shell.resolveSpawnCommand")(function* (command, args, options = {}) {
11667
+ const isUnavailableBootstrapFdError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "EBADF" || _.code === "ENOENT");
11668
+ const isFdReady = (fd) => Effect.try({
11669
+ try: () => NodeFS.fstatSync(fd),
11670
+ catch: (error) => new BootstrapFdStatError({
11671
+ fd,
11672
+ cause: error
11673
+ })
11674
+ }).pipe(Effect.as(true), Effect.catchTags({ BootstrapFdStatError: (error) => isUnavailableBootstrapFdError(error.cause) ? Effect.succeed(false) : Effect.fail(error) }));
11675
+ const makeBootstrapInputStream = (fd) => Effect.gen(function* () {
11454
11676
  const platform = yield* HostProcessPlatform;
11455
- if (platform !== "win32") return {
11456
- command,
11457
- args: [...args],
11458
- shell: false
11459
- };
11460
- const hostEnvironment = yield* HostProcessEnvironment;
11461
- const env = options.env === void 0 ? hostEnvironment : options.extendEnv ? {
11462
- ...hostEnvironment,
11463
- ...options.env
11464
- } : options.env;
11465
- const resolvedCommand = (yield* SpawnExecutableResolution)(command, platform, env) ?? command;
11466
- const extension = NodePath.win32.extname(resolvedCommand).toLowerCase();
11467
- if (extension !== ".cmd" && extension !== ".bat") return {
11468
- command: resolvedCommand,
11469
- args: [...args],
11470
- shell: false
11471
- };
11472
- return {
11473
- command: escapeWindowsShellArg(resolvedCommand),
11474
- args: sanitizeShellModeArgsForPlatform(args, platform),
11475
- shell: true
11476
- };
11477
- });
11478
- const isCommandAvailable = Effect.fn("shell.isCommandAvailable")(function* (command, options = {}) {
11479
- return yield* resolveCommandPath(command, options).pipe(Effect.as(true), Effect.catchTag("CommandResolutionError", () => Effect.succeed(false)));
11677
+ const fdPath = resolveFdPath(fd, platform);
11678
+ return yield* Effect.try({
11679
+ try: () => {
11680
+ if (fdPath === void 0) return makeDirectBootstrapStream(fd);
11681
+ let streamFd;
11682
+ try {
11683
+ streamFd = NodeFS.openSync(fdPath, "r");
11684
+ return NodeFS.createReadStream("", {
11685
+ fd: streamFd,
11686
+ encoding: "utf8",
11687
+ autoClose: true
11688
+ });
11689
+ } catch (error) {
11690
+ if (isBootstrapFdPathDuplicationError(error)) {
11691
+ if (streamFd !== void 0) NodeFS.closeSync(streamFd);
11692
+ return makeDirectBootstrapStream(fd);
11693
+ }
11694
+ throw error;
11695
+ }
11696
+ },
11697
+ catch: (error) => new BootstrapInputStreamOpenError({
11698
+ fd,
11699
+ platform,
11700
+ ...fdPath === void 0 ? {} : { fdPath },
11701
+ cause: error
11702
+ })
11703
+ });
11480
11704
  });
11481
- function resolveKnownWindowsCliDirs(env) {
11482
- const appData = env.APPDATA?.trim();
11483
- const localAppData = env.LOCALAPPDATA?.trim();
11484
- const userProfile = env.USERPROFILE?.trim();
11485
- return [
11486
- ...appData ? [`${appData}\\npm`] : [],
11487
- ...localAppData ? [`${localAppData}\\Programs\\nodejs`, `${localAppData}\\Volta\\bin`] : [],
11488
- ...localAppData ? [`${localAppData}\\pnpm`] : [],
11489
- ...userProfile ? [`${userProfile}\\.bun\\bin`, `${userProfile}\\scoop\\shims`] : []
11490
- ];
11491
- }
11492
- function readWindowsEnvironmentSafely(readEnvironment, names, options) {
11705
+ const makeDirectBootstrapStream = (fd) => {
11493
11706
  try {
11494
- return readEnvironment(names, options);
11707
+ return NodeFS.createReadStream("", {
11708
+ fd,
11709
+ encoding: "utf8",
11710
+ autoClose: true
11711
+ });
11495
11712
  } catch {
11496
- return {};
11497
- }
11498
- }
11499
- function mergeWindowsEnv(currentEnv, patch) {
11500
- const nextEnv = { ...currentEnv };
11501
- for (const [key, value] of Object.entries(patch)) if (value !== void 0) nextEnv[key] = value;
11502
- return nextEnv;
11503
- }
11504
- const resolveWindowsEnvironment = Effect.fn("shell.resolveWindowsEnvironment")(function* (env) {
11505
- const readEnvironment = yield* WindowsShellEnvironment;
11506
- const commandAvailable = yield* CommandAvailability;
11507
- const inheritedPath = readEnvPath(env);
11508
- const shellPath = readWindowsEnvironmentSafely(readEnvironment, ["PATH"], { loadProfile: false }).PATH;
11509
- const mergedPath = mergePathValues(shellPath, inheritedPath, "win32");
11510
- const baselinePath = mergePathValues(resolveKnownWindowsCliDirs(env).join(WINDOWS_PATH_DELIMITER), mergedPath, "win32");
11511
- const baselinePatch = baselinePath ? { PATH: baselinePath } : {};
11512
- if (yield* commandAvailable("node", { env: mergeWindowsEnv(env, baselinePatch) })) return baselinePatch;
11513
- const profiledEnvironment = readWindowsEnvironmentSafely(readEnvironment, [
11514
- "PATH",
11515
- "FNM_DIR",
11516
- "FNM_MULTISHELL_PATH"
11517
- ], { loadProfile: true });
11518
- const profiledPath = mergePathValues(profiledEnvironment.PATH, baselinePath, "win32");
11519
- const profiledPatch = {
11520
- ...profiledPath ? { PATH: profiledPath } : {},
11521
- ...profiledEnvironment.FNM_DIR ? { FNM_DIR: profiledEnvironment.FNM_DIR } : {},
11522
- ...profiledEnvironment.FNM_MULTISHELL_PATH ? { FNM_MULTISHELL_PATH: profiledEnvironment.FNM_MULTISHELL_PATH } : {}
11523
- };
11524
- return Object.keys(profiledPatch).length > 0 ? {
11525
- ...baselinePatch,
11526
- ...profiledPatch
11527
- } : baselinePatch;
11528
- });
11529
- //#endregion
11530
- //#region src/os-jank.ts
11531
- function logPathHydrationWarning(message, error) {
11532
- process.stderr.write(`[server] ${message} ${error instanceof Error ? error.message : error ?? ""}\n`);
11533
- }
11534
- function hydratePosixPath(env, platform) {
11535
- let shellPath;
11536
- for (const shell of listLoginShellCandidates(platform, env.SHELL)) {
11537
- try {
11538
- shellPath = readPathFromLoginShell(shell);
11539
- } catch (error) {
11540
- logPathHydrationWarning(`Failed to read PATH from login shell ${shell}.`, error);
11541
- }
11542
- if (shellPath) break;
11713
+ const stream = new NodeNet.Socket({
11714
+ fd,
11715
+ readable: true,
11716
+ writable: false
11717
+ });
11718
+ stream.setEncoding("utf8");
11719
+ return stream;
11543
11720
  }
11544
- const launchctlPath = platform === "darwin" && !shellPath ? readPathFromLaunchctl() : void 0;
11545
- const mergedPath = mergePathEntries(shellPath ?? launchctlPath, env.PATH, platform);
11546
- if (mergedPath) env.PATH = mergedPath;
11721
+ };
11722
+ const isBootstrapFdPathDuplicationError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "ENXIO" || _.code === "EINVAL" || _.code === "EPERM" || _.code === "EACCES");
11723
+ function resolveFdPath(fd, platform) {
11724
+ if (platform === "linux") return `/proc/self/fd/${fd}`;
11725
+ if (platform === "win32") return;
11726
+ return `/dev/fd/${fd}`;
11547
11727
  }
11548
- const fixPath = Effect.fn("fixPath")(function* () {
11549
- const platform = yield* HostProcessPlatform;
11550
- const env = yield* HostProcessEnvironment;
11551
- if (platform === "win32") {
11552
- const repairedEnvironment = yield* resolveWindowsEnvironment(env).pipe(Effect.catchDefect((defect) => Effect.sync(() => {
11553
- logPathHydrationWarning("Failed to hydrate PATH from the user environment.", defect);
11554
- return {};
11555
- })));
11556
- for (const [key, value] of Object.entries(repairedEnvironment)) if (value !== void 0) env[key] = value;
11557
- return;
11558
- }
11559
- if (platform !== "darwin" && platform !== "linux") return;
11560
- yield* Effect.sync(() => hydratePosixPath(env, platform)).pipe(Effect.catchDefect((defect) => Effect.sync(() => {
11561
- logPathHydrationWarning("Failed to hydrate PATH from the user environment.", defect);
11562
- })));
11563
- });
11564
- const expandHomePath$4 = Effect.fn(function* (input) {
11565
- const { join } = yield* Path.Path;
11566
- if (input === "~") return NodeOS.homedir();
11567
- if (input.startsWith("~/") || input.startsWith("~\\")) return join(NodeOS.homedir(), input.slice(2));
11568
- return input;
11569
- });
11570
- /** The data directory used when neither `--base-dir` nor `P4CODE_HOME` is set. */
11571
- const defaultBaseDir = Effect.fn(function* () {
11572
- const { join } = yield* Path.Path;
11573
- return join(NodeOS.homedir(), ".p4");
11574
- });
11575
- const resolveBaseDir = Effect.fn(function* (raw) {
11576
- const { resolve } = yield* Path.Path;
11577
- if (!raw || raw.trim().length === 0) return yield* defaultBaseDir();
11578
- return resolve(yield* expandHomePath$4(raw.trim()));
11579
- });
11580
11728
  //#endregion
11581
11729
  //#region src/cli/config.ts
11582
- const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), Flag.optional);
11730
+ const modeFlag = Flag.choice("mode", RuntimeMode$1.literals).pipe(Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), Flag.optional);
11583
11731
  const portFlag = Flag.integer("port").pipe(Flag.withSchema(PortSchema), Flag.withDescription("Port for the HTTP/WebSocket server."), Flag.optional);
11584
11732
  const hostFlag = Flag.string("host").pipe(Flag.withDescription("Host/interface to bind (for example 127.0.0.1, 0.0.0.0, or a Tailnet IP)."), Flag.optional);
11585
11733
  const baseDirFlag = Flag.string("base-dir").pipe(Flag.withDescription("Explicit P4Code data directory; runtime state is stored under userdata (equivalent to P4CODE_HOME)."), Flag.optional);
@@ -11602,7 +11750,7 @@ const EnvServerConfig = Config.all({
11602
11750
  otlpMetricsUrl: Config.string("P4CODE_OTLP_METRICS_URL").pipe(Config.option, Config.map(Option.getOrUndefined)),
11603
11751
  otlpExportIntervalMs: Config.int("P4CODE_OTLP_EXPORT_INTERVAL_MS").pipe(Config.withDefault(1e4)),
11604
11752
  otlpServiceName: Config.string("P4CODE_OTLP_SERVICE_NAME").pipe(Config.withDefault("p4-server")),
11605
- mode: Config.schema(RuntimeMode, "P4CODE_MODE").pipe(Config.option, Config.map(Option.getOrUndefined)),
11753
+ mode: Config.schema(RuntimeMode$1, "P4CODE_MODE").pipe(Config.option, Config.map(Option.getOrUndefined)),
11606
11754
  port: Config.port("P4CODE_PORT").pipe(Config.option, Config.map(Option.getOrUndefined)),
11607
11755
  host: Config.string("P4CODE_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)),
11608
11756
  p4Home: Config.string("P4CODE_HOME").pipe(Config.option, Config.map(Option.getOrUndefined)),
@@ -13952,7 +14100,7 @@ const ProjectionThreadSession = Schema$1.Struct({
13952
14100
  status: OrchestrationSessionStatus,
13953
14101
  providerName: Schema$1.NullOr(Schema$1.String),
13954
14102
  providerInstanceId: Schema$1.NullOr(ProviderInstanceId),
13955
- runtimeMode: RuntimeMode$1,
14103
+ runtimeMode: RuntimeMode,
13956
14104
  activeTurnId: Schema$1.NullOr(TurnId),
13957
14105
  lastError: Schema$1.NullOr(Schema$1.String),
13958
14106
  updatedAt: IsoDateTime
@@ -14047,7 +14195,7 @@ const ProjectionThread = Schema$1.Struct({
14047
14195
  projectId: ProjectId,
14048
14196
  title: Schema$1.String,
14049
14197
  modelSelection: ModelSelection,
14050
- runtimeMode: RuntimeMode$1,
14198
+ runtimeMode: RuntimeMode,
14051
14199
  interactionMode: ProviderInteractionMode,
14052
14200
  branch: Schema$1.NullOr(Schema$1.String),
14053
14201
  worktreePath: Schema$1.NullOr(Schema$1.String),
@@ -15368,7 +15516,7 @@ function collectThreadAttachmentRelativePaths(threadId, messages) {
15368
15516
  return relativePaths;
15369
15517
  }
15370
15518
  const runAttachmentSideEffects = Effect.fn("runAttachmentSideEffects")(function* (sideEffects) {
15371
- const serverConfig = yield* Effect.service(ServerConfig);
15519
+ const serverConfig = yield* Effect.service(ServerConfig$1);
15372
15520
  const fileSystem = yield* Effect.service(FileSystem.FileSystem);
15373
15521
  const path = yield* Effect.service(Path.Path);
15374
15522
  const attachmentsRootDir = serverConfig.attachmentsDir;
@@ -15430,7 +15578,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
15430
15578
  const projectionPendingApprovalRepository = yield* ProjectionPendingApprovalRepository;
15431
15579
  const fileSystem = yield* FileSystem.FileSystem;
15432
15580
  const path = yield* Path.Path;
15433
- const serverConfig = yield* ServerConfig;
15581
+ const serverConfig = yield* ServerConfig$1;
15434
15582
  const applyProjectsProjection = Effect.fn("applyProjectsProjection")(function* (event, _attachmentSideEffects) {
15435
15583
  switch (event.type) {
15436
15584
  case "project.created":
@@ -16120,9 +16268,9 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
16120
16268
  })));
16121
16269
  });
16122
16270
  const bootstrapProjector = (projector) => projectionStateRepository.getByProjector({ projector: projector.name }).pipe(Effect.flatMap((stateRow) => Stream.runForEach(eventStore.readFromSequence(Option.isSome(stateRow) ? stateRow.value.lastAppliedSequence : 0), (event) => runProjectorForEvent(projector, event))));
16123
- const projectEvent = (event) => Effect.forEach(projectors, (projector) => runProjectorForEvent(projector, event), { concurrency: 1 }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.provideService(ServerConfig, serverConfig), Effect.asVoid, Effect.catchTag("SqlError", (sqlError) => Effect.fail(toPersistenceSqlError("ProjectionPipeline.projectEvent:query")(sqlError))));
16271
+ const projectEvent = (event) => Effect.forEach(projectors, (projector) => runProjectorForEvent(projector, event), { concurrency: 1 }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.provideService(ServerConfig$1, serverConfig), Effect.asVoid, Effect.catchTag("SqlError", (sqlError) => Effect.fail(toPersistenceSqlError("ProjectionPipeline.projectEvent:query")(sqlError))));
16124
16272
  return {
16125
- bootstrap: Effect.forEach(projectors, bootstrapProjector, { concurrency: 1 }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.provideService(ServerConfig, serverConfig), Effect.asVoid, Effect.tap(() => Effect.logDebug("orchestration projection pipeline bootstrapped").pipe(Effect.annotateLogs({ projectors: projectors.length }))), Effect.catchTag("SqlError", (sqlError) => Effect.fail(toPersistenceSqlError("ProjectionPipeline.bootstrap:query")(sqlError)))),
16273
+ bootstrap: Effect.forEach(projectors, bootstrapProjector, { concurrency: 1 }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, path), Effect.provideService(ServerConfig$1, serverConfig), Effect.asVoid, Effect.tap(() => Effect.logDebug("orchestration projection pipeline bootstrapped").pipe(Effect.annotateLogs({ projectors: projectors.length }))), Effect.catchTag("SqlError", (sqlError) => Effect.fail(toPersistenceSqlError("ProjectionPipeline.bootstrap:query")(sqlError)))),
16126
16274
  projectEvent
16127
16275
  };
16128
16276
  });
@@ -18537,7 +18685,7 @@ function mergeWithDefaultKeybindings(custom) {
18537
18685
  */
18538
18686
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
18539
18687
  const make$56 = Effect.gen(function* () {
18540
- const { keybindingsConfigPath } = yield* ServerConfig;
18688
+ const { keybindingsConfigPath } = yield* ServerConfig$1;
18541
18689
  const fs = yield* FileSystem.FileSystem;
18542
18690
  const path = yield* Path.Path;
18543
18691
  const upsertSemaphore = yield* Semaphore.make(1);
@@ -19186,7 +19334,7 @@ function stripDefaultServerSettings(current, defaults) {
19186
19334
  return Object.is(current, defaults) ? void 0 : current;
19187
19335
  }
19188
19336
  const make$53 = Effect.gen(function* () {
19189
- const { settingsPath } = yield* ServerConfig;
19337
+ const { settingsPath } = yield* ServerConfig$1;
19190
19338
  const fs = yield* FileSystem.FileSystem;
19191
19339
  const pathService = yield* Path.Path;
19192
19340
  const secretStore = yield* ServerSecretStore;
@@ -19517,7 +19665,7 @@ const getClaudeUserId = Effect.fn("TelemetryIdentity.getClaudeUserId")(function*
19517
19665
  });
19518
19666
  const upsertAnonymousId = Effect.gen(function* () {
19519
19667
  const fileSystem = yield* FileSystem.FileSystem;
19520
- const { anonymousIdPath } = yield* ServerConfig;
19668
+ const { anonymousIdPath } = yield* ServerConfig$1;
19521
19669
  const existing = yield* readIdentityFile(fileSystem, "anonymous", anonymousIdPath);
19522
19670
  if (Option.isSome(existing)) return existing.value;
19523
19671
  const anonymousId = yield* Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4), Effect.mapError((cause) => new TelemetryAnonymousIdGenerationError({
@@ -19586,7 +19734,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
19586
19734
  const make$52 = Effect.gen(function* () {
19587
19735
  const telemetryConfig = yield* TelemetryEnvConfig;
19588
19736
  const httpClient = yield* HttpClient$1.HttpClient;
19589
- const serverConfig = yield* ServerConfig;
19737
+ const serverConfig = yield* ServerConfig$1;
19590
19738
  const identifier = yield* getTelemetryIdentifier;
19591
19739
  const bufferRef = yield* Ref.make([]);
19592
19740
  const clientType = serverConfig.mode === "desktop" ? "desktop-app" : "cli-web-client";
@@ -20028,6 +20176,22 @@ const make$51 = Effect.fn("cloud.boot_service.make")(function* (input) {
20028
20176
  serviceTarget
20029
20177
  ]);
20030
20178
  });
20179
+ const restart = Effect.gen(function* () {
20180
+ yield* requireSupportedPlatform;
20181
+ if (isLaunchd) {
20182
+ yield* runStep("restarting the service", "launchctl", [
20183
+ "kickstart",
20184
+ "-k",
20185
+ serviceTarget
20186
+ ]);
20187
+ return;
20188
+ }
20189
+ yield* runStep("restarting the service", "systemctl", [
20190
+ "--user",
20191
+ "restart",
20192
+ BOOT_SERVICE_UNIT_FILE
20193
+ ]);
20194
+ }).pipe(Effect.withSpan("cloud.boot_service.restart"));
20031
20195
  const rollbackFailedInstall = Effect.fn("cloud.boot_service.rollback_failed_install")(function* (previousUnit) {
20032
20196
  if (Option.isSome(previousUnit)) yield* fs.writeFileString(unitPath, previousUnit.value).pipe(Effect.ignore);
20033
20197
  else if (isLaunchd) {
@@ -20105,6 +20269,7 @@ const make$51 = Effect.fn("cloud.boot_service.make")(function* (input) {
20105
20269
  return BootService.of({
20106
20270
  install,
20107
20271
  uninstall,
20272
+ restart,
20108
20273
  status,
20109
20274
  logPath
20110
20275
  });
@@ -20185,7 +20350,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
20185
20350
  });
20186
20351
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
20187
20352
  const make$50 = Effect.fn("cloud.server_self_update.make")(function* (options) {
20188
- const serverConfig = yield* ServerConfig;
20353
+ const serverConfig = yield* ServerConfig$1;
20189
20354
  const fs = yield* FileSystem.FileSystem;
20190
20355
  const path = yield* Path.Path;
20191
20356
  const runner = yield* ProcessRunner;
@@ -20467,7 +20632,7 @@ function platformArch(architecture) {
20467
20632
  const make$49 = Effect.gen(function* () {
20468
20633
  const fileSystem = yield* FileSystem.FileSystem;
20469
20634
  const path = yield* Path.Path;
20470
- const serverConfig = yield* ServerConfig;
20635
+ const serverConfig = yield* ServerConfig$1;
20471
20636
  const crypto = yield* Crypto.Crypto;
20472
20637
  const hostPlatform = yield* HostProcessPlatform;
20473
20638
  const hostArchitecture = yield* HostProcessArchitecture;
@@ -21456,7 +21621,7 @@ const formatHeadlessServeOutput = (accessInfo) => [
21456
21621
  ""
21457
21622
  ].join("\n");
21458
21623
  const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(function* () {
21459
- const serverConfig = yield* ServerConfig;
21624
+ const serverConfig = yield* ServerConfig$1;
21460
21625
  const httpServer = yield* HttpServer.HttpServer;
21461
21626
  const serverAuth = yield* EnvironmentAuth;
21462
21627
  const connectionString = resolveHeadlessConnectionString(serverConfig.host, resolveListeningPort(httpServer.address, serverConfig.port));
@@ -21470,7 +21635,7 @@ const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(f
21470
21635
  //#endregion
21471
21636
  //#region src/serverRuntimeStartup.ts
21472
21637
  var ServerRuntimeStartupError = class extends Schema$1.TaggedErrorClass()("ServerRuntimeStartupError", {
21473
- mode: RuntimeMode,
21638
+ mode: RuntimeMode$1,
21474
21639
  host: Schema$1.NullOr(Schema$1.String),
21475
21640
  port: Schema$1.Number,
21476
21641
  cause: Schema$1.Defect()
@@ -21523,7 +21688,7 @@ const getAutoBootstrapDefaultModelSelection = () => ({
21523
21688
  model: DEFAULT_MODEL
21524
21689
  });
21525
21690
  const resolveWelcomeBase = Effect.gen(function* () {
21526
- const serverConfig = yield* ServerConfig;
21691
+ const serverConfig = yield* ServerConfig$1;
21527
21692
  const segments = serverConfig.cwd.split(/[/\\]/).filter(Boolean);
21528
21693
  const projectName = segments[segments.length - 1] ?? "project";
21529
21694
  return {
@@ -21533,7 +21698,7 @@ const resolveWelcomeBase = Effect.gen(function* () {
21533
21698
  });
21534
21699
  const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
21535
21700
  const randomUUID = (yield* Crypto.Crypto).randomUUIDv4;
21536
- const serverConfig = yield* ServerConfig;
21701
+ const serverConfig = yield* ServerConfig$1;
21537
21702
  const projectionReadModelQuery = yield* ProjectionSnapshotQuery;
21538
21703
  const orchestrationEngine = yield* OrchestrationEngineService;
21539
21704
  const path = yield* Path.Path;
@@ -21591,7 +21756,7 @@ const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
21591
21756
  };
21592
21757
  });
21593
21758
  const resolveStartupBrowserTarget = Effect.gen(function* () {
21594
- const serverConfig = yield* ServerConfig;
21759
+ const serverConfig = yield* ServerConfig$1;
21595
21760
  const serverAuth = yield* EnvironmentAuth;
21596
21761
  const localUrl = `http://localhost:${serverConfig.port}`;
21597
21762
  const bindUrl = serverConfig.host && !isWildcardHost(serverConfig.host) ? `http://${formatHostForUrl(serverConfig.host)}:${serverConfig.port}` : localUrl;
@@ -21599,12 +21764,12 @@ const resolveStartupBrowserTarget = Effect.gen(function* () {
21599
21764
  return yield* Effect.succeed(serverConfig.mode === "desktop" ? baseTarget : void 0).pipe(Effect.flatMap((target) => target ? Effect.succeed(target) : serverAuth.issueStartupPairingUrl(baseTarget)));
21600
21765
  });
21601
21766
  const maybeOpenBrowser = (target) => Effect.gen(function* () {
21602
- if ((yield* ServerConfig).noBrowser) return;
21767
+ if ((yield* ServerConfig$1).noBrowser) return;
21603
21768
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
21604
21769
  });
21605
21770
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
21606
21771
  const make$48 = Effect.gen(function* () {
21607
- const serverConfig = yield* ServerConfig;
21772
+ const serverConfig = yield* ServerConfig$1;
21608
21773
  const keybindings = yield* Keybindings;
21609
21774
  const orchestrationReactor = yield* OrchestrationReactor;
21610
21775
  const providerSessionReaper = yield* ProviderSessionReaper;
@@ -23040,7 +23205,7 @@ const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (input) {
23040
23205
  }
23041
23206
  case "attachment": {
23042
23207
  const attachmentPath = resolveAttachmentPathById({
23043
- attachmentsDir: (yield* ServerConfig).attachmentsDir,
23208
+ attachmentsDir: (yield* ServerConfig$1).attachmentsDir,
23044
23209
  attachmentId: input.resource.attachmentId
23045
23210
  });
23046
23211
  if (!attachmentPath) return yield* new AssetAttachmentNotFoundError({ resource: input.resource });
@@ -23105,7 +23270,7 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
23105
23270
  if (!claims || claims.expiresAt <= (yield* Clock.currentTimeMillis)) return null;
23106
23271
  if (claims.kind === "attachment") {
23107
23272
  const attachmentPath = resolveAttachmentPathById({
23108
- attachmentsDir: (yield* ServerConfig).attachmentsDir,
23273
+ attachmentsDir: (yield* ServerConfig$1).attachmentsDir,
23109
23274
  attachmentId: claims.attachmentId
23110
23275
  });
23111
23276
  if (!attachmentPath) return null;
@@ -23393,7 +23558,7 @@ const compressHttpResponse = Effect.fnUntraced(function* (response, acceptEncodi
23393
23558
  });
23394
23559
  const httpCompressionLayer = HttpRouter.middleware((httpEffect) => Effect.flatMap(Effect.all([httpEffect, HttpServerRequest.HttpServerRequest]), ([response, request]) => compressHttpResponse(response, request.headers["accept-encoding"])), { global: true });
23395
23560
  const browserApiCorsLayer = Layer.unwrap(Effect.gen(function* () {
23396
- const config = yield* ServerConfig;
23561
+ const config = yield* ServerConfig$1;
23397
23562
  const devOrigin = config.devUrl?.origin;
23398
23563
  return HttpRouter.cors({
23399
23564
  ...devOrigin ? {
@@ -23435,7 +23600,7 @@ var DecodeOtlpTraceRecordsError = class extends Data.TaggedError("DecodeOtlpTrac
23435
23600
  const otlpTracesProxyRouteLayer = HttpRouter.add("POST", OTLP_TRACES_PROXY_PATH, Effect.gen(function* () {
23436
23601
  yield* authenticateRawRouteWithScope(AuthOrchestrationOperateScope);
23437
23602
  const request = yield* HttpServerRequest.HttpServerRequest;
23438
- const otlpTracesUrl = (yield* ServerConfig).otlpTracesUrl;
23603
+ const otlpTracesUrl = (yield* ServerConfig$1).otlpTracesUrl;
23439
23604
  const browserTraceCollector = yield* BrowserTraceCollector;
23440
23605
  const httpClient = yield* HttpClient.HttpClient;
23441
23606
  const bodyJson = cast(yield* request.json);
@@ -23480,7 +23645,7 @@ const staticAndDevRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* (
23480
23645
  const request = yield* HttpServerRequest.HttpServerRequest;
23481
23646
  const url = HttpServerRequest.toURL(request);
23482
23647
  if (Option.isNone(url)) return HttpServerResponse.text("Bad Request", { status: 400 });
23483
- const config = yield* ServerConfig;
23648
+ const config = yield* ServerConfig$1;
23484
23649
  if (config.devUrl && isDevProxiedPath(url.value.pathname)) return HttpServerResponse.text("Not Found", { status: 404 });
23485
23650
  if (config.devUrl && isLoopbackHostname(url.value.hostname)) return HttpServerResponse.redirect(resolveDevRedirectUrl(config.devUrl, url.value), { status: 302 });
23486
23651
  const staticDir = config.staticDir ?? (config.devUrl ? yield* resolveStaticDir() : void 0);
@@ -24019,7 +24184,7 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
24019
24184
  const fileSystem = yield* FileSystem.FileSystem;
24020
24185
  const path = yield* Path.Path;
24021
24186
  const commandSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
24022
- const { worktreesDir } = yield* ServerConfig;
24187
+ const { worktreesDir } = yield* ServerConfig$1;
24023
24188
  const crypto = yield* Crypto.Crypto;
24024
24189
  const executeRaw = Effect.fnUntraced(function* (input) {
24025
24190
  const commandInput = {
@@ -26447,7 +26612,7 @@ const normalizeDispatchCommand = (command) => Effect.gen(function* () {
26447
26612
  const canonicalCommand = canonicalizeClientCommandTimestamps(command, receivedAt);
26448
26613
  const fileSystem = yield* FileSystem.FileSystem;
26449
26614
  const path = yield* Path.Path;
26450
- const serverConfig = yield* ServerConfig;
26615
+ const serverConfig = yield* ServerConfig$1;
26451
26616
  const workspacePaths = yield* WorkspacePaths;
26452
26617
  const normalizeProjectWorkspaceRoot = (workspaceRoot) => workspacePaths.normalizeWorkspaceRoot(workspaceRoot).pipe(Effect.mapError((cause) => new OrchestrationDispatchCommandError({ message: cause.message })));
26453
26618
  const normalizeProjectWorkspaceRootForCreate = (workspaceRoot, createIfMissing) => workspacePaths.normalizeWorkspaceRoot(workspaceRoot, { createIfMissing: createIfMissing === true }).pipe(Effect.mapError((cause) => new OrchestrationDispatchCommandError({ message: cause.message })));
@@ -26595,79 +26760,6 @@ const makeProviderMaintenanceCommandCoordinator = Effect.fn("makeProviderMainten
26595
26760
  return { withCommandLock };
26596
26761
  });
26597
26762
  //#endregion
26598
- //#region ../../packages/shared/src/semver.ts
26599
- const SEMVER_NUMBER_SEGMENT = /^\d+$/;
26600
- function normalizeSemverVersion(version) {
26601
- const [main, prerelease] = version.trim().split("-", 2);
26602
- const segments = [];
26603
- for (const segment of (main ?? "").split(".")) {
26604
- const trimmed = segment.trim();
26605
- if (trimmed.length > 0) segments.push(trimmed);
26606
- }
26607
- if (segments.length === 2) segments.push("0");
26608
- return prerelease ? `${segments.join(".")}-${prerelease}` : segments.join(".");
26609
- }
26610
- function parseSemver(value) {
26611
- const [main = "", prerelease] = normalizeSemverVersion(value).replace(/^v/, "").split("-", 2);
26612
- const segments = main.split(".");
26613
- if (segments.length !== 3) return null;
26614
- const [majorSegment, minorSegment, patchSegment] = segments;
26615
- if (majorSegment === void 0 || minorSegment === void 0 || patchSegment === void 0) return null;
26616
- if (!SEMVER_NUMBER_SEGMENT.test(majorSegment) || !SEMVER_NUMBER_SEGMENT.test(minorSegment) || !SEMVER_NUMBER_SEGMENT.test(patchSegment)) return null;
26617
- const major = Number.parseInt(majorSegment, 10);
26618
- const minor = Number.parseInt(minorSegment, 10);
26619
- const patch = Number.parseInt(patchSegment, 10);
26620
- if (![
26621
- major,
26622
- minor,
26623
- patch
26624
- ].every(Number.isInteger)) return null;
26625
- return {
26626
- major,
26627
- minor,
26628
- patch,
26629
- prerelease: parsePrereleaseSegments(prerelease)
26630
- };
26631
- }
26632
- function parsePrereleaseSegments(prerelease) {
26633
- if (prerelease === void 0) return [];
26634
- const segments = [];
26635
- for (const segment of prerelease.split(".")) {
26636
- const trimmed = segment.trim();
26637
- if (trimmed.length > 0) segments.push(trimmed);
26638
- }
26639
- return segments;
26640
- }
26641
- function comparePrereleaseIdentifier(left, right) {
26642
- const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
26643
- const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
26644
- if (leftNumeric && rightNumeric) return Number.parseInt(left, 10) - Number.parseInt(right, 10);
26645
- if (leftNumeric) return -1;
26646
- if (rightNumeric) return 1;
26647
- return left.localeCompare(right);
26648
- }
26649
- function compareSemverVersions(left, right) {
26650
- const parsedLeft = parseSemver(left);
26651
- const parsedRight = parseSemver(right);
26652
- if (!parsedLeft || !parsedRight) return left.localeCompare(right);
26653
- if (parsedLeft.major !== parsedRight.major) return parsedLeft.major - parsedRight.major;
26654
- if (parsedLeft.minor !== parsedRight.minor) return parsedLeft.minor - parsedRight.minor;
26655
- if (parsedLeft.patch !== parsedRight.patch) return parsedLeft.patch - parsedRight.patch;
26656
- if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) return 0;
26657
- if (parsedLeft.prerelease.length === 0) return 1;
26658
- if (parsedRight.prerelease.length === 0) return -1;
26659
- const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
26660
- for (let index = 0; index < length; index += 1) {
26661
- const leftIdentifier = parsedLeft.prerelease[index];
26662
- const rightIdentifier = parsedRight.prerelease[index];
26663
- if (leftIdentifier === void 0) return -1;
26664
- if (rightIdentifier === void 0) return 1;
26665
- const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
26666
- if (comparison !== 0) return comparison;
26667
- }
26668
- return 0;
26669
- }
26670
- //#endregion
26671
26763
  //#region src/provider/providerMaintenance.ts
26672
26764
  const LATEST_VERSION_CACHE_TTL_MS = 3600 * 1e3;
26673
26765
  const LATEST_VERSION_TIMEOUT_MS = 4e3;
@@ -28259,7 +28351,7 @@ function normalizedRuntimeEnv(env) {
28259
28351
  return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
28260
28352
  }
28261
28353
  const make$35 = Effect.fn("TerminalManager.make")(function* () {
28262
- const { terminalLogsDir } = yield* ServerConfig;
28354
+ const { terminalLogsDir } = yield* ServerConfig$1;
28263
28355
  const ptyAdapter = yield* PtyAdapter;
28264
28356
  const portDiscovery = yield* PortDiscovery;
28265
28357
  return yield* makeWithOptions$1({
@@ -33210,7 +33302,7 @@ function bindProviderContext(provider, context) {
33210
33302
  });
33211
33303
  }
33212
33304
  const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProviders")(function* (registrations) {
33213
- const config = yield* ServerConfig;
33305
+ const config = yield* ServerConfig$1;
33214
33306
  const process = yield* VcsProcess;
33215
33307
  const vcsRegistry = yield* VcsDriverRegistry;
33216
33308
  const providers = new Map(registrations.map((registration) => [registration.kind, registration.provider]));
@@ -35021,7 +35113,7 @@ const layer$10 = Layer.effect(VcsProvisioningService, make$15);
35021
35113
  //#region src/review/ReviewService.ts
35022
35114
  var ReviewService = class extends Context.Service()("@p4code/cli/review/ReviewService") {};
35023
35115
  const make$14 = Effect.gen(function* () {
35024
- const config = yield* ServerConfig;
35116
+ const config = yield* ServerConfig$1;
35025
35117
  const fileSystem = yield* FileSystem.FileSystem;
35026
35118
  const path = yield* Path.Path;
35027
35119
  const vcsRegistry = yield* VcsDriverRegistry;
@@ -35932,7 +36024,7 @@ const VCS_PROBES = [{
35932
36024
  }];
35933
36025
  var SourceControlDiscovery = class extends Context.Service()("@p4code/cli/sourceControl/SourceControlDiscovery") {};
35934
36026
  const make$10 = Effect.gen(function* () {
35935
- const config = yield* ServerConfig;
36027
+ const config = yield* ServerConfig$1;
35936
36028
  const process = yield* VcsProcess;
35937
36029
  const sourceControlProviders = yield* SourceControlProviderRegistry;
35938
36030
  const probe = (input) => {
@@ -36014,7 +36106,7 @@ function expandHomePath$1(input, path) {
36014
36106
  return input;
36015
36107
  }
36016
36108
  const make$9 = Effect.gen(function* () {
36017
- const config = yield* ServerConfig;
36109
+ const config = yield* ServerConfig$1;
36018
36110
  const fileSystem = yield* FileSystem.FileSystem;
36019
36111
  const git = yield* GitVcsDriver;
36020
36112
  const path = yield* Path.Path;
@@ -36373,7 +36465,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
36373
36465
  const providerRegistry = yield* ProviderRegistry;
36374
36466
  const providerMaintenanceRunner = yield* ProviderMaintenanceRunner;
36375
36467
  const serverSelfUpdate = yield* ServerSelfUpdate;
36376
- const config = yield* ServerConfig;
36468
+ const config = yield* ServerConfig$1;
36377
36469
  const lifecycleEvents = yield* ServerLifecycleEvents;
36378
36470
  const serverSettings = yield* ServerSettingsService;
36379
36471
  const startup = yield* ServerRuntimeStartup;
@@ -37017,7 +37109,7 @@ const ProviderSessionRuntime = Schema$1.Struct({
37017
37109
  */
37018
37110
  providerInstanceId: Schema$1.NullOr(ProviderInstanceId),
37019
37111
  adapterKey: Schema$1.String,
37020
- runtimeMode: RuntimeMode$1,
37112
+ runtimeMode: RuntimeMode,
37021
37113
  status: ProviderSessionRuntimeStatus,
37022
37114
  lastSeenAt: IsoDateTime,
37023
37115
  resumeCursor: Schema$1.NullOr(Schema$1.Unknown),
@@ -37650,7 +37742,7 @@ var ProviderEventLoggers = class extends Context.Service()("@p4code/cli/provider
37650
37742
  * is `undefined` and writes from that stream become no-ops downstream.
37651
37743
  */
37652
37744
  const ProviderEventLoggersLive = Layer.effect(ProviderEventLoggers, Effect.gen(function* () {
37653
- const { providerEventLogPath } = yield* ServerConfig;
37745
+ const { providerEventLogPath } = yield* ServerConfig$1;
37654
37746
  return {
37655
37747
  native: yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "native" }),
37656
37748
  canonical: yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "canonical" })
@@ -41119,7 +41211,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
41119
41211
  const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("claudeAgent");
41120
41212
  const fileSystem = yield* FileSystem.FileSystem;
41121
41213
  const path = yield* Path.Path;
41122
- const serverConfig = yield* ServerConfig;
41214
+ const serverConfig = yield* ServerConfig$1;
41123
41215
  const crypto = yield* Crypto.Crypto;
41124
41216
  const claudeEnvironment = yield* makeClaudeEnvironment(claudeSettings, options?.environment).pipe(Effect.provideService(Path.Path, path));
41125
41217
  const claudeSdkExecutablePath = yield* resolveClaudeSdkExecutablePath(claudeSettings.binaryPath, claudeEnvironment);
@@ -43002,7 +43094,7 @@ const ClaudeDriver = {
43002
43094
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
43003
43095
  const fileSystem = yield* FileSystem.FileSystem;
43004
43096
  const path = yield* Path.Path;
43005
- const { cwd } = yield* ServerConfig;
43097
+ const { cwd } = yield* ServerConfig$1;
43006
43098
  const httpClient = yield* HttpClient.HttpClient;
43007
43099
  const serverSettings = yield* ServerSettingsService;
43008
43100
  const eventLoggers = yield* ProviderEventLoggers;
@@ -43113,7 +43205,7 @@ const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(function* (
43113
43205
  const fileSystem = yield* FileSystem.FileSystem;
43114
43206
  const path = yield* Path.Path;
43115
43207
  const commandSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
43116
- const serverConfig = yield* Effect.service(ServerConfig);
43208
+ const serverConfig = yield* Effect.service(ServerConfig$1);
43117
43209
  const resolvedEnvironment = environment ?? process.env;
43118
43210
  const readStreamAsString = (operation, stream) => stream.pipe(Stream.decodeText(), Stream.runFold(() => "", (acc, chunk) => acc + chunk), Effect.mapError((cause) => normalizeCliError("codex", operation, cause, "Failed to collect process output")));
43119
43211
  const writeTempFile = (operation, prefix, content) => fileSystem.makeTempFileScoped({ prefix: `p4code-${prefix}-${process.pid}-` }).pipe(Effect.tap((filePath) => fileSystem.writeFileString(filePath, content)), Effect.mapError((cause) => new TextGenerationError({
@@ -63431,7 +63523,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
63431
63523
  const fileSystem = yield* FileSystem.FileSystem;
63432
63524
  const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
63433
63525
  const crypto = yield* Crypto.Crypto;
63434
- const serverConfig = yield* Effect.service(ServerConfig);
63526
+ const serverConfig = yield* Effect.service(ServerConfig$1);
63435
63527
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
63436
63528
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
63437
63529
  const runtimeEventQueue = yield* Queue.unbounded();
@@ -69924,7 +70016,7 @@ function makeCursorAdapter(cursorSettings, options) {
69924
70016
  const fileSystem = yield* FileSystem.FileSystem;
69925
70017
  const path = yield* Path.Path;
69926
70018
  const childProcessSpawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
69927
- const serverConfig = yield* Effect.service(ServerConfig);
70019
+ const serverConfig = yield* Effect.service(ServerConfig$1);
69928
70020
  const crypto = yield* Crypto.Crypto;
69929
70021
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
69930
70022
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
@@ -71043,7 +71135,7 @@ function makeGrokAdapter(grokSettings, options) {
71043
71135
  const fileSystem = yield* FileSystem.FileSystem;
71044
71136
  const path = yield* Path.Path;
71045
71137
  const childProcessSpawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
71046
- const serverConfig = yield* Effect.service(ServerConfig);
71138
+ const serverConfig = yield* Effect.service(ServerConfig$1);
71047
71139
  const crypto = yield* Crypto.Crypto;
71048
71140
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
71049
71141
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
@@ -72217,7 +72309,7 @@ function getOpenCodeTextResponse(parts) {
72217
72309
  return (parts ?? []).filter(isOpenCodeTextPart).map((part) => part.text).join("").trim();
72218
72310
  }
72219
72311
  const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(function* (openCodeSettings, environment) {
72220
- const serverConfig = yield* ServerConfig;
72312
+ const serverConfig = yield* ServerConfig$1;
72221
72313
  const openCodeRuntime = yield* OpenCodeRuntime;
72222
72314
  const resolvedEnvironment = environment ?? process.env;
72223
72315
  const idleFiberScope = yield* Effect.acquireRelease(Scope.make(), (scope) => Scope.close(scope, Exit.void));
@@ -72748,7 +72840,7 @@ const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* (context)
72748
72840
  function makeOpenCodeAdapter(openCodeSettings, options) {
72749
72841
  return Effect.gen(function* () {
72750
72842
  const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("opencode");
72751
- const serverConfig = yield* ServerConfig;
72843
+ const serverConfig = yield* ServerConfig$1;
72752
72844
  const openCodeRuntime = yield* OpenCodeRuntime;
72753
72845
  const crypto = yield* Crypto.Crypto;
72754
72846
  const fileSystem = yield* FileSystem.FileSystem;
@@ -73819,7 +73911,7 @@ const BUILT_IN_DRIVERS = [
73819
73911
  defaultConfig: () => decodeOpenCodeSettings({}),
73820
73912
  create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => Effect.gen(function* () {
73821
73913
  const openCodeRuntime = yield* OpenCodeRuntime;
73822
- const serverConfig = yield* ServerConfig;
73914
+ const serverConfig = yield* ServerConfig$1;
73823
73915
  const httpClient = yield* HttpClient.HttpClient;
73824
73916
  const serverSettings = yield* ServerSettingsService;
73825
73917
  const eventLoggers = yield* ProviderEventLoggers;
@@ -77530,7 +77622,7 @@ const buildSnapshotSource = (instance) => ({
77530
77622
  });
77531
77623
  const ProviderRegistryLive = Layer.effect(ProviderRegistry, Effect.gen(function* () {
77532
77624
  const instanceRegistry = yield* ProviderInstanceRegistry;
77533
- const config = yield* ServerConfig;
77625
+ const config = yield* ServerConfig$1;
77534
77626
  const fileSystem = yield* FileSystem.FileSystem;
77535
77627
  const path = yield* Path.Path;
77536
77628
  const changesPubSub = yield* Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown);
@@ -77757,7 +77849,7 @@ const ProviderRegistryLive = Layer.effect(ProviderRegistry, Effect.gen(function*
77757
77849
  //#endregion
77758
77850
  //#region src/serverLogger.ts
77759
77851
  const ServerLoggerLive = Effect.gen(function* () {
77760
- const config = yield* ServerConfig;
77852
+ const config = yield* ServerConfig$1;
77761
77853
  const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel);
77762
77854
  const loggerLayer = Logger.layer([Logger.consolePretty(), Logger.tracerLogger], { mergeWithExisting: false });
77763
77855
  return Layer.mergeAll(loggerLayer, minimumLogLevelLayer);
@@ -77766,7 +77858,7 @@ const ServerLoggerLive = Effect.gen(function* () {
77766
77858
  //#region src/observability/Layers/Observability.ts
77767
77859
  const otlpSerializationLayer = OtlpSerialization.layerJson;
77768
77860
  const ObservabilityLive = Layer.unwrap(Effect.gen(function* () {
77769
- const config = yield* ServerConfig;
77861
+ const config = yield* ServerConfig$1;
77770
77862
  const traceReferencesLayer = Layer.mergeAll(Layer.succeed(Tracer.MinimumTraceLevel, config.traceMinLevel), Layer.succeed(References.TracerTimingEnabled, config.traceTimingEnabled));
77771
77863
  const tracerLayer = Layer.unwrap(Effect.gen(function* () {
77772
77864
  const sink = yield* makeTraceSink({
@@ -78040,11 +78132,11 @@ const disableTailscaleServe = (input = {}) => Effect.gen(function* () {
78040
78132
  //#region src/server.ts
78041
78133
  const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
78042
78134
  const PtyAdapterLive = Layer.unwrap(Effect.gen(function* () {
78043
- if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("./BunPtyAdapter-DogtLQSx.mjs"))).layer;
78044
- else return (yield* Effect.promise(() => import("./NodePtyAdapter-CS1weRcq.mjs"))).layer;
78135
+ if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("./BunPtyAdapter-CLDq7yr1.mjs"))).layer;
78136
+ else return (yield* Effect.promise(() => import("./NodePtyAdapter-BQAnH2v0.mjs"))).layer;
78045
78137
  }));
78046
78138
  const HttpServerLive = Layer.unwrap(Effect.gen(function* () {
78047
- const config = yield* ServerConfig;
78139
+ const config = yield* ServerConfig$1;
78048
78140
  if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("@effect/platform-bun/BunHttpServer"))).layer({
78049
78141
  port: config.port,
78050
78142
  hostname: config.host ?? "127.0.0.1",
@@ -78095,7 +78187,7 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
78095
78187
  const RuntimeServicesLive = layer$43.pipe(Layer.provideMerge(RuntimeDependenciesLive));
78096
78188
  const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer.pipe(Layer.provide(layer$2))).pipe(Layer.provide(layer$29), Layer.provide(layer$45), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
78097
78189
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
78098
- const config = yield* ServerConfig;
78190
+ const config = yield* ServerConfig$1;
78099
78191
  yield* fixPath();
78100
78192
  const httpListeningLayer = Layer.effectDiscard(Effect.gen(function* () {
78101
78193
  yield* HttpServer.HttpServer;
@@ -78143,7 +78235,7 @@ const runServer = Layer.launch(makeServerLayer);
78143
78235
  //#region src/cli/server.ts
78144
78236
  const runServerCommand = (flags, options) => Effect.gen(function* () {
78145
78237
  const config = yield* resolveServerConfig(flags, yield* GlobalFlag.LogLevel, options);
78146
- return yield* runServer.pipe(Effect.provideService(ServerConfig, config));
78238
+ return yield* runServer.pipe(Effect.provideService(ServerConfig$1, config));
78147
78239
  });
78148
78240
  const startCommand = Command.make("start", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)));
78149
78241
  const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server without opening a browser and print headless pairing details."), Command.withHandler((flags) => runServerCommand(flags, {
@@ -78151,6 +78243,104 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
78151
78243
  forceAutoBootstrapProjectFromCwd: false
78152
78244
  })));
78153
78245
  //#endregion
78246
+ //#region src/cli/completions.ts
78247
+ /**
78248
+ * Installs the zsh completion script for the `p4` command.
78249
+ *
78250
+ * zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
78251
+ * through our subcommands prints "unhandled perforce command: …" from its
78252
+ * fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
78253
+ * order — first match wins — and `_perforce` lives in the *last* fpath entry
78254
+ * (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
78255
+ * directory therefore replaces it outright, with no shell-config edit.
78256
+ */
78257
+ const COMPLETION_FILE = "_p4";
78258
+ const GENERATE_TIMEOUT_MS = 1e4;
78259
+ /**
78260
+ * Directories ahead of `/usr/share/zsh/*` in a default fpath, most specific
78261
+ * first. `~/.zfunc` is the fallback and is the only one needing an fpath line.
78262
+ */
78263
+ function candidateDirectories(input) {
78264
+ return [
78265
+ {
78266
+ dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
78267
+ requiresFpathEntry: false
78268
+ },
78269
+ {
78270
+ dir: "/opt/homebrew/share/zsh/site-functions",
78271
+ requiresFpathEntry: false
78272
+ },
78273
+ {
78274
+ dir: "/usr/local/share/zsh/site-functions",
78275
+ requiresFpathEntry: false
78276
+ },
78277
+ {
78278
+ dir: input.join(input.homeDir, ".zfunc"),
78279
+ requiresFpathEntry: true
78280
+ }
78281
+ ];
78282
+ }
78283
+ /**
78284
+ * Asks the running CLI for its own completion script rather than rendering one
78285
+ * here, so the output cannot drift from what `--completions` produces.
78286
+ */
78287
+ const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
78288
+ const runner = yield* ProcessRunner;
78289
+ const execPath = yield* HostProcessExecutablePath;
78290
+ const entryPath = (yield* HostProcessArguments)[1] ?? "";
78291
+ if (entryPath === "") return null;
78292
+ const result = yield* runner.run({
78293
+ command: execPath,
78294
+ args: [
78295
+ entryPath,
78296
+ "--completions",
78297
+ "zsh"
78298
+ ],
78299
+ timeout: GENERATE_TIMEOUT_MS
78300
+ });
78301
+ if (result.code !== 0) return null;
78302
+ const script = result.stdout.trim();
78303
+ return script.length === 0 ? null : `${script}\n`;
78304
+ });
78305
+ /**
78306
+ * Best-effort: returns null when completions are not applicable (Windows, no
78307
+ * writable directory) rather than failing the command that asked for them.
78308
+ * Installing completions is never the reason an install should fail.
78309
+ */
78310
+ const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
78311
+ if ((yield* HostProcessPlatform) === "win32") return null;
78312
+ const fs = yield* FileSystem.FileSystem;
78313
+ const path = yield* Path.Path;
78314
+ const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
78315
+ if (homeDir === "") return null;
78316
+ const script = yield* generateZshCompletionScript();
78317
+ if (script === null) return null;
78318
+ for (const candidate of candidateDirectories({
78319
+ homeDir,
78320
+ join: path.join
78321
+ })) {
78322
+ if (candidate.requiresFpathEntry) {
78323
+ if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
78324
+ } else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
78325
+ const target = path.join(candidate.dir, COMPLETION_FILE);
78326
+ if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
78327
+ return {
78328
+ path: target,
78329
+ requiresFpathEntry: candidate.requiresFpathEntry
78330
+ };
78331
+ }
78332
+ return null;
78333
+ });
78334
+ function formatZshCompletionInstall(install) {
78335
+ const installed = `Installed zsh completions at ${install.path}.`;
78336
+ if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
78337
+ return [
78338
+ installed,
78339
+ `Add this to ~/.zshrc before compinit runs, then open a new shell:`,
78340
+ ` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
78341
+ ].join("\n");
78342
+ }
78343
+ //#endregion
78154
78344
  //#region src/cli/service.ts
78155
78345
  const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
78156
78346
  const bootServiceLayer = (config) => layer$46({
@@ -78203,7 +78393,7 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
78203
78393
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
78204
78394
  const baseDirNotice = yield* describeNonDefaultBaseDir(config.baseDir);
78205
78395
  if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
78206
- return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(bootServiceLayer(config)));
78396
+ return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(bootServiceLayer(config)), Effect.provide(layer$53));
78207
78397
  });
78208
78398
  const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe(Command.withDescription("Install P4Code as a background service for this user."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
78209
78399
  const result = yield* reconcileService();
@@ -78217,11 +78407,14 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
78217
78407
  cliVersion: version,
78218
78408
  platform: yield* HostProcessPlatform
78219
78409
  }));
78410
+ const completion = yield* installZshCompletion().pipe(Effect.orElseSucceed(() => null));
78411
+ if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
78220
78412
  }))));
78221
78413
  const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(Command.withDescription("Update or repair the background service so it runs this CLI build, then restart it."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
78222
78414
  const result = yield* reconcileService();
78223
78415
  if (!result.changed) {
78224
- yield* Console.log(`P4Code service is already using @p4code/cli@${version}.`);
78416
+ yield* (yield* BootService).restart;
78417
+ yield* Console.log(`Restarted the P4Code service on @p4code/cli@${version}.`);
78225
78418
  return;
78226
78419
  }
78227
78420
  yield* Console.log(formatReconcileSuccess({
@@ -78262,7 +78455,23 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
78262
78455
  ]));
78263
78456
  //#endregion
78264
78457
  //#region src/bin.ts
78265
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$62);
78458
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$62, FetchHttpClient.layer);
78459
+ /**
78460
+ * Prints an update reminder before dispatching, at most once a day.
78461
+ *
78462
+ * On stderr so it never contaminates `--json` output or a redirected
78463
+ * `--completions` script, and fully swallowed: an advisory notice must not be
78464
+ * able to fail a command or change its exit code.
78465
+ */
78466
+ const reportUpdateAvailable = Effect.gen(function* () {
78467
+ const baseDir = yield* resolveBaseDir((yield* HostProcessEnvironment).P4CODE_HOME);
78468
+ const paths = yield* deriveServerPaths(baseDir, void 0);
78469
+ const reminder = yield* resolveUpdateReminder({
78470
+ currentVersion: version,
78471
+ stateDir: paths.stateDir
78472
+ });
78473
+ if (reminder !== null) yield* Console.error(reminder);
78474
+ }).pipe(Effect.ignore);
78266
78475
  const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
78267
78476
  startCommand,
78268
78477
  serveCommand,
@@ -78271,7 +78480,7 @@ const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(C
78271
78480
  serviceCommand
78272
78481
  ]));
78273
78482
  const cli = makeCli();
78274
- if (import.meta.main) Command.run(cli, { version }).pipe(Effect.scoped, Effect.provide(CliRuntimeLayer), NodeRuntime.runMain);
78483
+ if (import.meta.main) reportUpdateAvailable.pipe(Effect.andThen(Command.run(cli, { version })), Effect.scoped, Effect.provide(CliRuntimeLayer), NodeRuntime.runMain);
78275
78484
  //#endregion
78276
78485
  export { cli, makeCli };
78277
78486