@p4code/cli 0.0.35 → 0.0.37

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,713 @@ 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.35";
235
+ var version = "0.0.37";
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
+ /**
920
+ * Both commands, because `npm i -g` only replaces the binary on disk. The
921
+ * service is a running process still executing the old code until
922
+ * `service update` restarts it onto the new one.
923
+ */
924
+ function formatUpdateReminder(input) {
925
+ return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest && p4 service update`].join("\n");
926
+ }
927
+ /**
928
+ * The reminder line, or null when there is nothing to say. Callers print it;
929
+ * keeping the decision pure makes it testable without a console.
930
+ */
931
+ const resolveUpdateReminder = Effect.fn("cli.updateCheck.reminder")(function* (input) {
932
+ if (((yield* HostProcessEnvironment)["P4CODE_NO_UPDATE_CHECK"] ?? "") !== "") return null;
933
+ if (input.currentVersion.includes("-")) return null;
934
+ const latestVersion = yield* resolveLatestVersion(input.stateDir);
935
+ if (latestVersion === null) return null;
936
+ if (compareSemverVersions(input.currentVersion, latestVersion) >= 0) return null;
937
+ return formatUpdateReminder({
938
+ currentVersion: input.currentVersion,
939
+ latestVersion
940
+ });
941
+ });
236
942
  //#endregion
237
943
  //#region ../../packages/contracts/src/baseSchemas.ts
238
944
  const TrimmedString = Schema$1.String.pipe(Schema$1.decodeTo(Schema$1.String, SchemaTransformation.transformOrFail({
@@ -928,7 +1634,7 @@ const ModelSelection = Schema$1.Struct({
928
1634
  return Effect.succeed(base);
929
1635
  }
930
1636
  })));
931
- const RuntimeMode$1 = Schema$1.Literals([
1637
+ const RuntimeMode = Schema$1.Literals([
932
1638
  "approval-required",
933
1639
  "auto-accept-edits",
934
1640
  "auto",
@@ -1050,7 +1756,7 @@ const OrchestrationSession = Schema$1.Struct({
1050
1756
  status: OrchestrationSessionStatus,
1051
1757
  providerName: Schema$1.NullOr(TrimmedNonEmptyString),
1052
1758
  providerInstanceId: Schema$1.optional(ProviderInstanceId),
1053
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1759
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1054
1760
  activeTurnId: Schema$1.NullOr(TurnId),
1055
1761
  lastError: Schema$1.NullOr(TrimmedNonEmptyString),
1056
1762
  updatedAt: IsoDateTime
@@ -1111,7 +1817,7 @@ const OrchestrationThread = Schema$1.Struct({
1111
1817
  projectId: ProjectId,
1112
1818
  title: TrimmedNonEmptyString,
1113
1819
  modelSelection: ModelSelection,
1114
- runtimeMode: RuntimeMode$1,
1820
+ runtimeMode: RuntimeMode,
1115
1821
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1116
1822
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1117
1823
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1151,7 +1857,7 @@ const OrchestrationThreadShell = Schema$1.Struct({
1151
1857
  projectId: ProjectId,
1152
1858
  title: TrimmedNonEmptyString,
1153
1859
  modelSelection: ModelSelection,
1154
- runtimeMode: RuntimeMode$1,
1860
+ runtimeMode: RuntimeMode,
1155
1861
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1156
1862
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1157
1863
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1273,7 +1979,7 @@ const ThreadCreateCommand = Schema$1.Struct({
1273
1979
  projectId: ProjectId,
1274
1980
  title: TrimmedNonEmptyString,
1275
1981
  modelSelection: ModelSelection,
1276
- runtimeMode: RuntimeMode$1,
1982
+ runtimeMode: RuntimeMode,
1277
1983
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1278
1984
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1279
1985
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1331,7 +2037,7 @@ const ThreadRuntimeModeSetCommand = Schema$1.Struct({
1331
2037
  type: Schema$1.Literal("thread.runtime-mode.set"),
1332
2038
  commandId: CommandId,
1333
2039
  threadId: ThreadId,
1334
- runtimeMode: RuntimeMode$1,
2040
+ runtimeMode: RuntimeMode,
1335
2041
  createdAt: IsoDateTime
1336
2042
  });
1337
2043
  const ThreadInteractionModeSetCommand = Schema$1.Struct({
@@ -1345,7 +2051,7 @@ const ThreadTurnStartBootstrapCreateThread = Schema$1.Struct({
1345
2051
  projectId: ProjectId,
1346
2052
  title: TrimmedNonEmptyString,
1347
2053
  modelSelection: ModelSelection,
1348
- runtimeMode: RuntimeMode$1,
2054
+ runtimeMode: RuntimeMode,
1349
2055
  interactionMode: ProviderInteractionMode,
1350
2056
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1351
2057
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1374,7 +2080,7 @@ const ThreadTurnStartCommand = Schema$1.Struct({
1374
2080
  }),
1375
2081
  modelSelection: Schema$1.optional(ModelSelection),
1376
2082
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1377
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2083
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1378
2084
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1379
2085
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
1380
2086
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
@@ -1392,7 +2098,7 @@ const ClientThreadTurnStartCommand = Schema$1.Struct({
1392
2098
  }),
1393
2099
  modelSelection: Schema$1.optional(ModelSelection),
1394
2100
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1395
- runtimeMode: RuntimeMode$1,
2101
+ runtimeMode: RuntimeMode,
1396
2102
  interactionMode: ProviderInteractionMode,
1397
2103
  bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
1398
2104
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
@@ -1608,7 +2314,7 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
1608
2314
  projectId: ProjectId,
1609
2315
  title: TrimmedNonEmptyString,
1610
2316
  modelSelection: ModelSelection,
1611
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2317
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1612
2318
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1613
2319
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
1614
2320
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
@@ -1659,7 +2365,7 @@ const ThreadMetaUpdatedPayload$1 = Schema$1.Struct({
1659
2365
  });
1660
2366
  const ThreadRuntimeModeSetPayload$1 = Schema$1.Struct({
1661
2367
  threadId: ThreadId,
1662
- runtimeMode: RuntimeMode$1,
2368
+ runtimeMode: RuntimeMode,
1663
2369
  updatedAt: IsoDateTime
1664
2370
  });
1665
2371
  const ThreadInteractionModeSetPayload$1 = Schema$1.Struct({
@@ -1683,7 +2389,7 @@ const ThreadTurnStartRequestedPayload = Schema$1.Struct({
1683
2389
  messageId: MessageId,
1684
2390
  modelSelection: Schema$1.optional(ModelSelection),
1685
2391
  titleSeed: Schema$1.optional(TrimmedNonEmptyString),
1686
- runtimeMode: RuntimeMode$1.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
2392
+ runtimeMode: RuntimeMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE$2))),
1687
2393
  interactionMode: ProviderInteractionMode.pipe(Schema$1.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE))),
1688
2394
  sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
1689
2395
  createdAt: IsoDateTime
@@ -3801,7 +4507,7 @@ Schema$1.Struct({
3801
4507
  provider: ProviderDriverKind,
3802
4508
  providerInstanceId: Schema$1.optional(ProviderInstanceId),
3803
4509
  status: ProviderSessionStatus,
3804
- runtimeMode: RuntimeMode$1,
4510
+ runtimeMode: RuntimeMode,
3805
4511
  cwd: Schema$1.optional(TrimmedNonEmptyString),
3806
4512
  model: Schema$1.optional(TrimmedNonEmptyString),
3807
4513
  threadId: ThreadId,
@@ -3820,7 +4526,7 @@ const ProviderSessionStartInput = Schema$1.Struct({
3820
4526
  resumeCursor: Schema$1.optional(Schema$1.Unknown),
3821
4527
  approvalPolicy: Schema$1.optional(ProviderApprovalPolicy),
3822
4528
  sandboxMode: Schema$1.optional(ProviderSandboxMode),
3823
- runtimeMode: RuntimeMode$1
4529
+ runtimeMode: RuntimeMode
3824
4530
  });
3825
4531
  const ProviderSendTurnInput = Schema$1.Struct({
3826
4532
  threadId: ThreadId,
@@ -5371,7 +6077,7 @@ const ServerSignalProcessResult = Schema$1.Struct({
5371
6077
  signaled: Schema$1.Boolean,
5372
6078
  message: Schema$1.Option(TrimmedNonEmptyString)
5373
6079
  });
5374
- const ServerConfig$1 = Schema$1.Struct({
6080
+ const ServerConfig = Schema$1.Struct({
5375
6081
  environment: ExecutionEnvironmentDescriptor,
5376
6082
  auth: ServerAuthDescriptor,
5377
6083
  cwd: TrimmedNonEmptyString,
@@ -5418,7 +6124,7 @@ const ServerConfigSettingsUpdatedPayload = Schema$1.Struct({ settings: ServerSet
5418
6124
  const ServerConfigStreamSnapshotEvent = Schema$1.Struct({
5419
6125
  version: Schema$1.Literal(1),
5420
6126
  type: Schema$1.Literal("snapshot"),
5421
- config: ServerConfig$1
6127
+ config: ServerConfig
5422
6128
  });
5423
6129
  const ServerConfigStreamKeybindingsUpdatedEvent = Schema$1.Struct({
5424
6130
  version: Schema$1.Literal(1),
@@ -6623,7 +7329,7 @@ const WsServerProbeRpc = Rpc.make(WS_METHODS.serverProbe, {
6623
7329
  });
6624
7330
  const WsServerGetConfigRpc = Rpc.make(WS_METHODS.serverGetConfig, {
6625
7331
  payload: Schema$1.Struct({}),
6626
- success: ServerConfig$1,
7332
+ success: ServerConfig,
6627
7333
  error: Schema$1.Union([
6628
7334
  KeybindingsConfigError,
6629
7335
  ServerSettingsError,
@@ -7006,117 +7712,6 @@ function parseAllowedOAuthScope(input) {
7006
7712
  return scopes;
7007
7713
  }
7008
7714
  //#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
7715
  //#region src/auth/utils.ts
7121
7716
  const SESSION_COOKIE_NAME = "p4_session";
7122
7717
  /**
@@ -7220,7 +7815,7 @@ function deriveAuthClientMetadata(input) {
7220
7815
  //#region src/auth/EnvironmentAuthPolicy.ts
7221
7816
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
7222
7817
  const make$65 = Effect.gen(function* () {
7223
- const config = yield* ServerConfig;
7818
+ const config = yield* ServerConfig$1;
7224
7819
  const isRemoteReachable = isRemoteReachableHost(config.host);
7225
7820
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
7226
7821
  const descriptor = {
@@ -7599,7 +8194,7 @@ const PAIRING_TOKEN_LENGTH = 12;
7599
8194
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
7600
8195
  const make$63 = Effect.gen(function* () {
7601
8196
  const crypto = yield* Crypto.Crypto;
7602
- const config = yield* ServerConfig;
8197
+ const config = yield* ServerConfig$1;
7603
8198
  const pairingLinks = yield* AuthPairingLinkRepository;
7604
8199
  const seededGrantsRef = yield* Ref.make(/* @__PURE__ */ new Map());
7605
8200
  const changesPubSub = yield* PubSub.unbounded();
@@ -7866,7 +8461,7 @@ const make$62 = Effect.gen(function* () {
7866
8461
  const crypto = yield* Crypto.Crypto;
7867
8462
  const fileSystem = yield* FileSystem.FileSystem;
7868
8463
  const path = yield* Path.Path;
7869
- const serverConfig = yield* ServerConfig;
8464
+ const serverConfig = yield* ServerConfig$1;
7870
8465
  yield* fileSystem.makeDirectory(serverConfig.secretsDir, { recursive: true });
7871
8466
  yield* fileSystem.chmod(serverConfig.secretsDir, 448).pipe(Effect.mapError((cause) => new SecretStoreSecureError({
7872
8467
  resource: `secrets directory ${serverConfig.secretsDir}`,
@@ -8410,7 +9005,7 @@ function toAuthClientSession(input) {
8410
9005
  }
8411
9006
  const make$60 = Effect.gen(function* () {
8412
9007
  const crypto = yield* Crypto.Crypto;
8413
- const serverConfig = yield* ServerConfig;
9008
+ const serverConfig = yield* ServerConfig$1;
8414
9009
  const secretStore = yield* ServerSecretStore;
8415
9010
  const authSessions = yield* AuthSessionRepository;
8416
9011
  const signingSecret = yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32);
@@ -10271,7 +10866,7 @@ const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")(functio
10271
10866
  }));
10272
10867
  }, Layer.unwrap);
10273
10868
  Layer.provideMerge(setup, makeRuntimeSqliteLayer({ filename: ":memory:" }));
10274
- const layerConfig = Layer.unwrap(Effect.map(Effect.service(ServerConfig), ({ dbPath }) => makeSqlitePersistenceLive(dbPath)));
10869
+ const layerConfig = Layer.unwrap(Effect.map(Effect.service(ServerConfig$1), ({ dbPath }) => makeSqlitePersistenceLive(dbPath)));
10275
10870
  const INTERNAL_ADMINISTRATIVE_BOOTSTRAP_SUBJECT = "administrative-bootstrap";
10276
10871
  const serverAuthInternalErrorContext = { cause: Schema$1.Defect() };
10277
10872
  var ServerAuthBootstrapCredentialValidationError = class extends Schema$1.TaggedErrorClass()("ServerAuthBootstrapCredentialValidationError", { ...serverAuthInternalErrorContext }) {
@@ -10940,646 +11535,204 @@ function normalizePersistedServerSettingString(value) {
10940
11535
  const trimmed = value?.trim();
10941
11536
  return trimmed && trimmed.length > 0 ? trimmed : void 0;
10942
11537
  }
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;
11538
+ function extractPersistedServerObservabilitySettings(input) {
11539
+ return {
11540
+ otlpTracesUrl: normalizePersistedServerSettingString(input.observability?.otlpTracesUrl),
11541
+ otlpMetricsUrl: normalizePersistedServerSettingString(input.observability?.otlpMetricsUrl)
11542
+ };
11360
11543
  }
11361
- function readEnvPath(env) {
11362
- return env.PATH ?? env.Path ?? env.path;
11544
+ function parsePersistedServerObservabilitySettings(raw) {
11545
+ const decoded = decodeServerSettingsJson(raw);
11546
+ if (Option.isSome(decoded)) return extractPersistedServerObservabilitySettings(decoded.value);
11547
+ return {
11548
+ otlpTracesUrl: void 0,
11549
+ otlpMetricsUrl: void 0
11550
+ };
11363
11551
  }
11364
- function resolvePathEnvironmentVariable(env) {
11365
- return readEnvPath(env) ?? "";
11552
+ function shouldReplaceTextGenerationModelSelection(patch) {
11553
+ return Boolean(patch && (patch.instanceId !== void 0 || patch.model !== void 0));
11366
11554
  }
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;
11555
+ function mergeModelSelectionOptionsById(input) {
11556
+ if (input.patch === void 0) return input.current ? [...input.current] : void 0;
11557
+ if (input.patch.length === 0) return;
11558
+ const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value]));
11559
+ for (const selection of input.patch) merged.set(selection.id, selection.value);
11560
+ return [...merged.entries()].map(([id, value]) => ({
11561
+ id,
11562
+ value
11563
+ }));
11383
11564
  }
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));
11565
+ function applyServerSettingsPatch(current, patch) {
11566
+ const selectionPatch = patch.textGenerationModelSelection;
11567
+ const { automaticGitFetchInterval, ...patchForMerge } = patch;
11568
+ const nextWithReplacements = {
11569
+ ...deepMerge(current, patchForMerge),
11570
+ ...patch.providerInstances !== void 0 ? { providerInstances: patch.providerInstances } : {},
11571
+ ...patch.sourceControlWriterModelSelection !== void 0 ? { sourceControlWriterModelSelection: patch.sourceControlWriterModelSelection } : {},
11572
+ ...automaticGitFetchInterval !== void 0 ? { automaticGitFetchInterval } : {}
11573
+ };
11574
+ if (!selectionPatch) return nextWithReplacements;
11575
+ const instanceId = selectionPatch.instanceId ?? current.textGenerationModelSelection.instanceId;
11576
+ const model = selectionPatch.model ?? current.textGenerationModelSelection.model;
11577
+ const options = shouldReplaceTextGenerationModelSelection(selectionPatch) ? selectionPatch.options : mergeModelSelectionOptionsById({
11578
+ current: current.textGenerationModelSelection.options,
11579
+ patch: selectionPatch.options
11580
+ });
11581
+ return {
11582
+ ...nextWithReplacements,
11583
+ textGenerationModelSelection: createModelSelection(instanceId, model, options)
11584
+ };
11402
11585
  }
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());
11586
+ //#endregion
11587
+ //#region src/bootstrap.ts
11588
+ var BootstrapFdStatError = class extends Schema$1.TaggedErrorClass()("BootstrapFdStatError", {
11589
+ fd: Schema$1.Number,
11590
+ cause: Schema$1.Defect()
11591
+ }) {
11592
+ get message() {
11593
+ return `Failed to stat bootstrap file descriptor ${this.fd}.`;
11412
11594
  }
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
- });
11595
+ };
11596
+ var BootstrapInputStreamOpenError = class extends Schema$1.TaggedErrorClass()("BootstrapInputStreamOpenError", {
11597
+ fd: Schema$1.Number,
11598
+ platform: Schema$1.String,
11599
+ fdPath: Schema$1.optional(Schema$1.String),
11600
+ cause: Schema$1.Defect()
11601
+ }) {
11602
+ get message() {
11603
+ const path = this.fdPath === void 0 ? "" : ` via '${this.fdPath}'`;
11604
+ return `Failed to open bootstrap input stream for file descriptor ${this.fd}${path} on '${this.platform}'.`;
11427
11605
  }
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);
11606
+ };
11607
+ var BootstrapEnvelopeReadError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeReadError", {
11608
+ fd: Schema$1.Number,
11609
+ cause: Schema$1.Defect()
11610
+ }) {
11611
+ get message() {
11612
+ return `Failed to read bootstrap envelope from file descriptor ${this.fd}.`;
11437
11613
  }
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;
11614
+ };
11615
+ var BootstrapEnvelopeDecodeError = class extends Schema$1.TaggedErrorClass()("BootstrapEnvelopeDecodeError", {
11616
+ fd: Schema$1.Number,
11617
+ cause: Schema$1.Defect()
11618
+ }) {
11619
+ get message() {
11620
+ return `Failed to decode bootstrap envelope from file descriptor ${this.fd}.`;
11441
11621
  }
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
- });
11622
+ };
11623
+ Schema$1.Union([
11624
+ BootstrapFdStatError,
11625
+ BootstrapInputStreamOpenError,
11626
+ BootstrapEnvelopeReadError,
11627
+ BootstrapEnvelopeDecodeError
11628
+ ]);
11629
+ const readBootstrapEnvelope = Effect.fn("readBootstrapEnvelope")(function* (schema, fd, options) {
11630
+ if (!(yield* isFdReady(fd))) return Option.none();
11631
+ const stream = yield* makeBootstrapInputStream(fd);
11632
+ const timeoutMs = options?.timeoutMs ?? 1e3;
11633
+ return yield* Effect.callback((resume) => {
11634
+ const input = NodeReadline.createInterface({
11635
+ input: stream,
11636
+ crlfDelay: Infinity
11637
+ });
11638
+ const cleanup = () => {
11639
+ stream.removeListener("error", handleError);
11640
+ input.removeListener("line", handleLine);
11641
+ input.removeListener("close", handleClose);
11642
+ input.close();
11643
+ stream.destroy();
11644
+ };
11645
+ const handleError = (error) => {
11646
+ if (isUnavailableBootstrapFdError(error)) {
11647
+ resume(Effect.succeedNone);
11648
+ return;
11649
+ }
11650
+ resume(Effect.fail(new BootstrapEnvelopeReadError({
11651
+ fd,
11652
+ cause: error
11653
+ })));
11654
+ };
11655
+ const handleLine = (line) => {
11656
+ const parsed = decodeJsonResult(schema)(line);
11657
+ if (Result.isSuccess(parsed)) resume(Effect.succeedSome(parsed.success));
11658
+ else resume(Effect.fail(new BootstrapEnvelopeDecodeError({
11659
+ fd,
11660
+ cause: parsed.failure
11661
+ })));
11662
+ };
11663
+ const handleClose = () => {
11664
+ resume(Effect.succeedNone);
11665
+ };
11666
+ stream.once("error", handleError);
11667
+ input.once("line", handleLine);
11668
+ input.once("close", handleClose);
11669
+ return Effect.sync(cleanup);
11670
+ }).pipe(Effect.timeoutOption(timeoutMs), Effect.map(Option.flatten));
11452
11671
  });
11453
- const resolveSpawnCommand = Effect.fn("shell.resolveSpawnCommand")(function* (command, args, options = {}) {
11672
+ const isUnavailableBootstrapFdError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "EBADF" || _.code === "ENOENT");
11673
+ const isFdReady = (fd) => Effect.try({
11674
+ try: () => NodeFS.fstatSync(fd),
11675
+ catch: (error) => new BootstrapFdStatError({
11676
+ fd,
11677
+ cause: error
11678
+ })
11679
+ }).pipe(Effect.as(true), Effect.catchTags({ BootstrapFdStatError: (error) => isUnavailableBootstrapFdError(error.cause) ? Effect.succeed(false) : Effect.fail(error) }));
11680
+ const makeBootstrapInputStream = (fd) => Effect.gen(function* () {
11454
11681
  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)));
11682
+ const fdPath = resolveFdPath(fd, platform);
11683
+ return yield* Effect.try({
11684
+ try: () => {
11685
+ if (fdPath === void 0) return makeDirectBootstrapStream(fd);
11686
+ let streamFd;
11687
+ try {
11688
+ streamFd = NodeFS.openSync(fdPath, "r");
11689
+ return NodeFS.createReadStream("", {
11690
+ fd: streamFd,
11691
+ encoding: "utf8",
11692
+ autoClose: true
11693
+ });
11694
+ } catch (error) {
11695
+ if (isBootstrapFdPathDuplicationError(error)) {
11696
+ if (streamFd !== void 0) NodeFS.closeSync(streamFd);
11697
+ return makeDirectBootstrapStream(fd);
11698
+ }
11699
+ throw error;
11700
+ }
11701
+ },
11702
+ catch: (error) => new BootstrapInputStreamOpenError({
11703
+ fd,
11704
+ platform,
11705
+ ...fdPath === void 0 ? {} : { fdPath },
11706
+ cause: error
11707
+ })
11708
+ });
11480
11709
  });
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) {
11710
+ const makeDirectBootstrapStream = (fd) => {
11493
11711
  try {
11494
- return readEnvironment(names, options);
11712
+ return NodeFS.createReadStream("", {
11713
+ fd,
11714
+ encoding: "utf8",
11715
+ autoClose: true
11716
+ });
11495
11717
  } 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;
11718
+ const stream = new NodeNet.Socket({
11719
+ fd,
11720
+ readable: true,
11721
+ writable: false
11722
+ });
11723
+ stream.setEncoding("utf8");
11724
+ return stream;
11543
11725
  }
11544
- const launchctlPath = platform === "darwin" && !shellPath ? readPathFromLaunchctl() : void 0;
11545
- const mergedPath = mergePathEntries(shellPath ?? launchctlPath, env.PATH, platform);
11546
- if (mergedPath) env.PATH = mergedPath;
11726
+ };
11727
+ const isBootstrapFdPathDuplicationError = Predicate.compose(Predicate.hasProperty("code"), (_) => _.code === "ENXIO" || _.code === "EINVAL" || _.code === "EPERM" || _.code === "EACCES");
11728
+ function resolveFdPath(fd, platform) {
11729
+ if (platform === "linux") return `/proc/self/fd/${fd}`;
11730
+ if (platform === "win32") return;
11731
+ return `/dev/fd/${fd}`;
11547
11732
  }
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
11733
  //#endregion
11581
11734
  //#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);
11735
+ const modeFlag = Flag.choice("mode", RuntimeMode$1.literals).pipe(Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), Flag.optional);
11583
11736
  const portFlag = Flag.integer("port").pipe(Flag.withSchema(PortSchema), Flag.withDescription("Port for the HTTP/WebSocket server."), Flag.optional);
11584
11737
  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
11738
  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 +11755,7 @@ const EnvServerConfig = Config.all({
11602
11755
  otlpMetricsUrl: Config.string("P4CODE_OTLP_METRICS_URL").pipe(Config.option, Config.map(Option.getOrUndefined)),
11603
11756
  otlpExportIntervalMs: Config.int("P4CODE_OTLP_EXPORT_INTERVAL_MS").pipe(Config.withDefault(1e4)),
11604
11757
  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)),
11758
+ mode: Config.schema(RuntimeMode$1, "P4CODE_MODE").pipe(Config.option, Config.map(Option.getOrUndefined)),
11606
11759
  port: Config.port("P4CODE_PORT").pipe(Config.option, Config.map(Option.getOrUndefined)),
11607
11760
  host: Config.string("P4CODE_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)),
11608
11761
  p4Home: Config.string("P4CODE_HOME").pipe(Config.option, Config.map(Option.getOrUndefined)),
@@ -13952,7 +14105,7 @@ const ProjectionThreadSession = Schema$1.Struct({
13952
14105
  status: OrchestrationSessionStatus,
13953
14106
  providerName: Schema$1.NullOr(Schema$1.String),
13954
14107
  providerInstanceId: Schema$1.NullOr(ProviderInstanceId),
13955
- runtimeMode: RuntimeMode$1,
14108
+ runtimeMode: RuntimeMode,
13956
14109
  activeTurnId: Schema$1.NullOr(TurnId),
13957
14110
  lastError: Schema$1.NullOr(Schema$1.String),
13958
14111
  updatedAt: IsoDateTime
@@ -14047,7 +14200,7 @@ const ProjectionThread = Schema$1.Struct({
14047
14200
  projectId: ProjectId,
14048
14201
  title: Schema$1.String,
14049
14202
  modelSelection: ModelSelection,
14050
- runtimeMode: RuntimeMode$1,
14203
+ runtimeMode: RuntimeMode,
14051
14204
  interactionMode: ProviderInteractionMode,
14052
14205
  branch: Schema$1.NullOr(Schema$1.String),
14053
14206
  worktreePath: Schema$1.NullOr(Schema$1.String),
@@ -15368,7 +15521,7 @@ function collectThreadAttachmentRelativePaths(threadId, messages) {
15368
15521
  return relativePaths;
15369
15522
  }
15370
15523
  const runAttachmentSideEffects = Effect.fn("runAttachmentSideEffects")(function* (sideEffects) {
15371
- const serverConfig = yield* Effect.service(ServerConfig);
15524
+ const serverConfig = yield* Effect.service(ServerConfig$1);
15372
15525
  const fileSystem = yield* Effect.service(FileSystem.FileSystem);
15373
15526
  const path = yield* Effect.service(Path.Path);
15374
15527
  const attachmentsRootDir = serverConfig.attachmentsDir;
@@ -15430,7 +15583,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
15430
15583
  const projectionPendingApprovalRepository = yield* ProjectionPendingApprovalRepository;
15431
15584
  const fileSystem = yield* FileSystem.FileSystem;
15432
15585
  const path = yield* Path.Path;
15433
- const serverConfig = yield* ServerConfig;
15586
+ const serverConfig = yield* ServerConfig$1;
15434
15587
  const applyProjectsProjection = Effect.fn("applyProjectsProjection")(function* (event, _attachmentSideEffects) {
15435
15588
  switch (event.type) {
15436
15589
  case "project.created":
@@ -16120,9 +16273,9 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
16120
16273
  })));
16121
16274
  });
16122
16275
  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))));
16276
+ 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
16277
  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)))),
16278
+ 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
16279
  projectEvent
16127
16280
  };
16128
16281
  });
@@ -18537,7 +18690,7 @@ function mergeWithDefaultKeybindings(custom) {
18537
18690
  */
18538
18691
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
18539
18692
  const make$56 = Effect.gen(function* () {
18540
- const { keybindingsConfigPath } = yield* ServerConfig;
18693
+ const { keybindingsConfigPath } = yield* ServerConfig$1;
18541
18694
  const fs = yield* FileSystem.FileSystem;
18542
18695
  const path = yield* Path.Path;
18543
18696
  const upsertSemaphore = yield* Semaphore.make(1);
@@ -19186,7 +19339,7 @@ function stripDefaultServerSettings(current, defaults) {
19186
19339
  return Object.is(current, defaults) ? void 0 : current;
19187
19340
  }
19188
19341
  const make$53 = Effect.gen(function* () {
19189
- const { settingsPath } = yield* ServerConfig;
19342
+ const { settingsPath } = yield* ServerConfig$1;
19190
19343
  const fs = yield* FileSystem.FileSystem;
19191
19344
  const pathService = yield* Path.Path;
19192
19345
  const secretStore = yield* ServerSecretStore;
@@ -19517,7 +19670,7 @@ const getClaudeUserId = Effect.fn("TelemetryIdentity.getClaudeUserId")(function*
19517
19670
  });
19518
19671
  const upsertAnonymousId = Effect.gen(function* () {
19519
19672
  const fileSystem = yield* FileSystem.FileSystem;
19520
- const { anonymousIdPath } = yield* ServerConfig;
19673
+ const { anonymousIdPath } = yield* ServerConfig$1;
19521
19674
  const existing = yield* readIdentityFile(fileSystem, "anonymous", anonymousIdPath);
19522
19675
  if (Option.isSome(existing)) return existing.value;
19523
19676
  const anonymousId = yield* Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4), Effect.mapError((cause) => new TelemetryAnonymousIdGenerationError({
@@ -19586,7 +19739,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
19586
19739
  const make$52 = Effect.gen(function* () {
19587
19740
  const telemetryConfig = yield* TelemetryEnvConfig;
19588
19741
  const httpClient = yield* HttpClient$1.HttpClient;
19589
- const serverConfig = yield* ServerConfig;
19742
+ const serverConfig = yield* ServerConfig$1;
19590
19743
  const identifier = yield* getTelemetryIdentifier;
19591
19744
  const bufferRef = yield* Ref.make([]);
19592
19745
  const clientType = serverConfig.mode === "desktop" ? "desktop-app" : "cli-web-client";
@@ -20202,7 +20355,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
20202
20355
  });
20203
20356
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
20204
20357
  const make$50 = Effect.fn("cloud.server_self_update.make")(function* (options) {
20205
- const serverConfig = yield* ServerConfig;
20358
+ const serverConfig = yield* ServerConfig$1;
20206
20359
  const fs = yield* FileSystem.FileSystem;
20207
20360
  const path = yield* Path.Path;
20208
20361
  const runner = yield* ProcessRunner;
@@ -20484,7 +20637,7 @@ function platformArch(architecture) {
20484
20637
  const make$49 = Effect.gen(function* () {
20485
20638
  const fileSystem = yield* FileSystem.FileSystem;
20486
20639
  const path = yield* Path.Path;
20487
- const serverConfig = yield* ServerConfig;
20640
+ const serverConfig = yield* ServerConfig$1;
20488
20641
  const crypto = yield* Crypto.Crypto;
20489
20642
  const hostPlatform = yield* HostProcessPlatform;
20490
20643
  const hostArchitecture = yield* HostProcessArchitecture;
@@ -21473,7 +21626,7 @@ const formatHeadlessServeOutput = (accessInfo) => [
21473
21626
  ""
21474
21627
  ].join("\n");
21475
21628
  const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(function* () {
21476
- const serverConfig = yield* ServerConfig;
21629
+ const serverConfig = yield* ServerConfig$1;
21477
21630
  const httpServer = yield* HttpServer.HttpServer;
21478
21631
  const serverAuth = yield* EnvironmentAuth;
21479
21632
  const connectionString = resolveHeadlessConnectionString(serverConfig.host, resolveListeningPort(httpServer.address, serverConfig.port));
@@ -21487,7 +21640,7 @@ const issueHeadlessServeAccessInfo = Effect.fn("issueHeadlessServeAccessInfo")(f
21487
21640
  //#endregion
21488
21641
  //#region src/serverRuntimeStartup.ts
21489
21642
  var ServerRuntimeStartupError = class extends Schema$1.TaggedErrorClass()("ServerRuntimeStartupError", {
21490
- mode: RuntimeMode,
21643
+ mode: RuntimeMode$1,
21491
21644
  host: Schema$1.NullOr(Schema$1.String),
21492
21645
  port: Schema$1.Number,
21493
21646
  cause: Schema$1.Defect()
@@ -21540,7 +21693,7 @@ const getAutoBootstrapDefaultModelSelection = () => ({
21540
21693
  model: DEFAULT_MODEL
21541
21694
  });
21542
21695
  const resolveWelcomeBase = Effect.gen(function* () {
21543
- const serverConfig = yield* ServerConfig;
21696
+ const serverConfig = yield* ServerConfig$1;
21544
21697
  const segments = serverConfig.cwd.split(/[/\\]/).filter(Boolean);
21545
21698
  const projectName = segments[segments.length - 1] ?? "project";
21546
21699
  return {
@@ -21550,7 +21703,7 @@ const resolveWelcomeBase = Effect.gen(function* () {
21550
21703
  });
21551
21704
  const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
21552
21705
  const randomUUID = (yield* Crypto.Crypto).randomUUIDv4;
21553
- const serverConfig = yield* ServerConfig;
21706
+ const serverConfig = yield* ServerConfig$1;
21554
21707
  const projectionReadModelQuery = yield* ProjectionSnapshotQuery;
21555
21708
  const orchestrationEngine = yield* OrchestrationEngineService;
21556
21709
  const path = yield* Path.Path;
@@ -21608,7 +21761,7 @@ const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
21608
21761
  };
21609
21762
  });
21610
21763
  const resolveStartupBrowserTarget = Effect.gen(function* () {
21611
- const serverConfig = yield* ServerConfig;
21764
+ const serverConfig = yield* ServerConfig$1;
21612
21765
  const serverAuth = yield* EnvironmentAuth;
21613
21766
  const localUrl = `http://localhost:${serverConfig.port}`;
21614
21767
  const bindUrl = serverConfig.host && !isWildcardHost(serverConfig.host) ? `http://${formatHostForUrl(serverConfig.host)}:${serverConfig.port}` : localUrl;
@@ -21616,12 +21769,12 @@ const resolveStartupBrowserTarget = Effect.gen(function* () {
21616
21769
  return yield* Effect.succeed(serverConfig.mode === "desktop" ? baseTarget : void 0).pipe(Effect.flatMap((target) => target ? Effect.succeed(target) : serverAuth.issueStartupPairingUrl(baseTarget)));
21617
21770
  });
21618
21771
  const maybeOpenBrowser = (target) => Effect.gen(function* () {
21619
- if ((yield* ServerConfig).noBrowser) return;
21772
+ if ((yield* ServerConfig$1).noBrowser) return;
21620
21773
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
21621
21774
  });
21622
21775
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
21623
21776
  const make$48 = Effect.gen(function* () {
21624
- const serverConfig = yield* ServerConfig;
21777
+ const serverConfig = yield* ServerConfig$1;
21625
21778
  const keybindings = yield* Keybindings;
21626
21779
  const orchestrationReactor = yield* OrchestrationReactor;
21627
21780
  const providerSessionReaper = yield* ProviderSessionReaper;
@@ -23057,7 +23210,7 @@ const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (input) {
23057
23210
  }
23058
23211
  case "attachment": {
23059
23212
  const attachmentPath = resolveAttachmentPathById({
23060
- attachmentsDir: (yield* ServerConfig).attachmentsDir,
23213
+ attachmentsDir: (yield* ServerConfig$1).attachmentsDir,
23061
23214
  attachmentId: input.resource.attachmentId
23062
23215
  });
23063
23216
  if (!attachmentPath) return yield* new AssetAttachmentNotFoundError({ resource: input.resource });
@@ -23122,7 +23275,7 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
23122
23275
  if (!claims || claims.expiresAt <= (yield* Clock.currentTimeMillis)) return null;
23123
23276
  if (claims.kind === "attachment") {
23124
23277
  const attachmentPath = resolveAttachmentPathById({
23125
- attachmentsDir: (yield* ServerConfig).attachmentsDir,
23278
+ attachmentsDir: (yield* ServerConfig$1).attachmentsDir,
23126
23279
  attachmentId: claims.attachmentId
23127
23280
  });
23128
23281
  if (!attachmentPath) return null;
@@ -23410,7 +23563,7 @@ const compressHttpResponse = Effect.fnUntraced(function* (response, acceptEncodi
23410
23563
  });
23411
23564
  const httpCompressionLayer = HttpRouter.middleware((httpEffect) => Effect.flatMap(Effect.all([httpEffect, HttpServerRequest.HttpServerRequest]), ([response, request]) => compressHttpResponse(response, request.headers["accept-encoding"])), { global: true });
23412
23565
  const browserApiCorsLayer = Layer.unwrap(Effect.gen(function* () {
23413
- const config = yield* ServerConfig;
23566
+ const config = yield* ServerConfig$1;
23414
23567
  const devOrigin = config.devUrl?.origin;
23415
23568
  return HttpRouter.cors({
23416
23569
  ...devOrigin ? {
@@ -23452,7 +23605,7 @@ var DecodeOtlpTraceRecordsError = class extends Data.TaggedError("DecodeOtlpTrac
23452
23605
  const otlpTracesProxyRouteLayer = HttpRouter.add("POST", OTLP_TRACES_PROXY_PATH, Effect.gen(function* () {
23453
23606
  yield* authenticateRawRouteWithScope(AuthOrchestrationOperateScope);
23454
23607
  const request = yield* HttpServerRequest.HttpServerRequest;
23455
- const otlpTracesUrl = (yield* ServerConfig).otlpTracesUrl;
23608
+ const otlpTracesUrl = (yield* ServerConfig$1).otlpTracesUrl;
23456
23609
  const browserTraceCollector = yield* BrowserTraceCollector;
23457
23610
  const httpClient = yield* HttpClient.HttpClient;
23458
23611
  const bodyJson = cast(yield* request.json);
@@ -23497,7 +23650,7 @@ const staticAndDevRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* (
23497
23650
  const request = yield* HttpServerRequest.HttpServerRequest;
23498
23651
  const url = HttpServerRequest.toURL(request);
23499
23652
  if (Option.isNone(url)) return HttpServerResponse.text("Bad Request", { status: 400 });
23500
- const config = yield* ServerConfig;
23653
+ const config = yield* ServerConfig$1;
23501
23654
  if (config.devUrl && isDevProxiedPath(url.value.pathname)) return HttpServerResponse.text("Not Found", { status: 404 });
23502
23655
  if (config.devUrl && isLoopbackHostname(url.value.hostname)) return HttpServerResponse.redirect(resolveDevRedirectUrl(config.devUrl, url.value), { status: 302 });
23503
23656
  const staticDir = config.staticDir ?? (config.devUrl ? yield* resolveStaticDir() : void 0);
@@ -24036,7 +24189,7 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
24036
24189
  const fileSystem = yield* FileSystem.FileSystem;
24037
24190
  const path = yield* Path.Path;
24038
24191
  const commandSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
24039
- const { worktreesDir } = yield* ServerConfig;
24192
+ const { worktreesDir } = yield* ServerConfig$1;
24040
24193
  const crypto = yield* Crypto.Crypto;
24041
24194
  const executeRaw = Effect.fnUntraced(function* (input) {
24042
24195
  const commandInput = {
@@ -26464,7 +26617,7 @@ const normalizeDispatchCommand = (command) => Effect.gen(function* () {
26464
26617
  const canonicalCommand = canonicalizeClientCommandTimestamps(command, receivedAt);
26465
26618
  const fileSystem = yield* FileSystem.FileSystem;
26466
26619
  const path = yield* Path.Path;
26467
- const serverConfig = yield* ServerConfig;
26620
+ const serverConfig = yield* ServerConfig$1;
26468
26621
  const workspacePaths = yield* WorkspacePaths;
26469
26622
  const normalizeProjectWorkspaceRoot = (workspaceRoot) => workspacePaths.normalizeWorkspaceRoot(workspaceRoot).pipe(Effect.mapError((cause) => new OrchestrationDispatchCommandError({ message: cause.message })));
26470
26623
  const normalizeProjectWorkspaceRootForCreate = (workspaceRoot, createIfMissing) => workspacePaths.normalizeWorkspaceRoot(workspaceRoot, { createIfMissing: createIfMissing === true }).pipe(Effect.mapError((cause) => new OrchestrationDispatchCommandError({ message: cause.message })));
@@ -26612,79 +26765,6 @@ const makeProviderMaintenanceCommandCoordinator = Effect.fn("makeProviderMainten
26612
26765
  return { withCommandLock };
26613
26766
  });
26614
26767
  //#endregion
26615
- //#region ../../packages/shared/src/semver.ts
26616
- const SEMVER_NUMBER_SEGMENT = /^\d+$/;
26617
- function normalizeSemverVersion(version) {
26618
- const [main, prerelease] = version.trim().split("-", 2);
26619
- const segments = [];
26620
- for (const segment of (main ?? "").split(".")) {
26621
- const trimmed = segment.trim();
26622
- if (trimmed.length > 0) segments.push(trimmed);
26623
- }
26624
- if (segments.length === 2) segments.push("0");
26625
- return prerelease ? `${segments.join(".")}-${prerelease}` : segments.join(".");
26626
- }
26627
- function parseSemver(value) {
26628
- const [main = "", prerelease] = normalizeSemverVersion(value).replace(/^v/, "").split("-", 2);
26629
- const segments = main.split(".");
26630
- if (segments.length !== 3) return null;
26631
- const [majorSegment, minorSegment, patchSegment] = segments;
26632
- if (majorSegment === void 0 || minorSegment === void 0 || patchSegment === void 0) return null;
26633
- if (!SEMVER_NUMBER_SEGMENT.test(majorSegment) || !SEMVER_NUMBER_SEGMENT.test(minorSegment) || !SEMVER_NUMBER_SEGMENT.test(patchSegment)) return null;
26634
- const major = Number.parseInt(majorSegment, 10);
26635
- const minor = Number.parseInt(minorSegment, 10);
26636
- const patch = Number.parseInt(patchSegment, 10);
26637
- if (![
26638
- major,
26639
- minor,
26640
- patch
26641
- ].every(Number.isInteger)) return null;
26642
- return {
26643
- major,
26644
- minor,
26645
- patch,
26646
- prerelease: parsePrereleaseSegments(prerelease)
26647
- };
26648
- }
26649
- function parsePrereleaseSegments(prerelease) {
26650
- if (prerelease === void 0) return [];
26651
- const segments = [];
26652
- for (const segment of prerelease.split(".")) {
26653
- const trimmed = segment.trim();
26654
- if (trimmed.length > 0) segments.push(trimmed);
26655
- }
26656
- return segments;
26657
- }
26658
- function comparePrereleaseIdentifier(left, right) {
26659
- const leftNumeric = SEMVER_NUMBER_SEGMENT.test(left);
26660
- const rightNumeric = SEMVER_NUMBER_SEGMENT.test(right);
26661
- if (leftNumeric && rightNumeric) return Number.parseInt(left, 10) - Number.parseInt(right, 10);
26662
- if (leftNumeric) return -1;
26663
- if (rightNumeric) return 1;
26664
- return left.localeCompare(right);
26665
- }
26666
- function compareSemverVersions(left, right) {
26667
- const parsedLeft = parseSemver(left);
26668
- const parsedRight = parseSemver(right);
26669
- if (!parsedLeft || !parsedRight) return left.localeCompare(right);
26670
- if (parsedLeft.major !== parsedRight.major) return parsedLeft.major - parsedRight.major;
26671
- if (parsedLeft.minor !== parsedRight.minor) return parsedLeft.minor - parsedRight.minor;
26672
- if (parsedLeft.patch !== parsedRight.patch) return parsedLeft.patch - parsedRight.patch;
26673
- if (parsedLeft.prerelease.length === 0 && parsedRight.prerelease.length === 0) return 0;
26674
- if (parsedLeft.prerelease.length === 0) return 1;
26675
- if (parsedRight.prerelease.length === 0) return -1;
26676
- const length = Math.max(parsedLeft.prerelease.length, parsedRight.prerelease.length);
26677
- for (let index = 0; index < length; index += 1) {
26678
- const leftIdentifier = parsedLeft.prerelease[index];
26679
- const rightIdentifier = parsedRight.prerelease[index];
26680
- if (leftIdentifier === void 0) return -1;
26681
- if (rightIdentifier === void 0) return 1;
26682
- const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
26683
- if (comparison !== 0) return comparison;
26684
- }
26685
- return 0;
26686
- }
26687
- //#endregion
26688
26768
  //#region src/provider/providerMaintenance.ts
26689
26769
  const LATEST_VERSION_CACHE_TTL_MS = 3600 * 1e3;
26690
26770
  const LATEST_VERSION_TIMEOUT_MS = 4e3;
@@ -28276,7 +28356,7 @@ function normalizedRuntimeEnv(env) {
28276
28356
  return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
28277
28357
  }
28278
28358
  const make$35 = Effect.fn("TerminalManager.make")(function* () {
28279
- const { terminalLogsDir } = yield* ServerConfig;
28359
+ const { terminalLogsDir } = yield* ServerConfig$1;
28280
28360
  const ptyAdapter = yield* PtyAdapter;
28281
28361
  const portDiscovery = yield* PortDiscovery;
28282
28362
  return yield* makeWithOptions$1({
@@ -33227,7 +33307,7 @@ function bindProviderContext(provider, context) {
33227
33307
  });
33228
33308
  }
33229
33309
  const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProviders")(function* (registrations) {
33230
- const config = yield* ServerConfig;
33310
+ const config = yield* ServerConfig$1;
33231
33311
  const process = yield* VcsProcess;
33232
33312
  const vcsRegistry = yield* VcsDriverRegistry;
33233
33313
  const providers = new Map(registrations.map((registration) => [registration.kind, registration.provider]));
@@ -35038,7 +35118,7 @@ const layer$10 = Layer.effect(VcsProvisioningService, make$15);
35038
35118
  //#region src/review/ReviewService.ts
35039
35119
  var ReviewService = class extends Context.Service()("@p4code/cli/review/ReviewService") {};
35040
35120
  const make$14 = Effect.gen(function* () {
35041
- const config = yield* ServerConfig;
35121
+ const config = yield* ServerConfig$1;
35042
35122
  const fileSystem = yield* FileSystem.FileSystem;
35043
35123
  const path = yield* Path.Path;
35044
35124
  const vcsRegistry = yield* VcsDriverRegistry;
@@ -35949,7 +36029,7 @@ const VCS_PROBES = [{
35949
36029
  }];
35950
36030
  var SourceControlDiscovery = class extends Context.Service()("@p4code/cli/sourceControl/SourceControlDiscovery") {};
35951
36031
  const make$10 = Effect.gen(function* () {
35952
- const config = yield* ServerConfig;
36032
+ const config = yield* ServerConfig$1;
35953
36033
  const process = yield* VcsProcess;
35954
36034
  const sourceControlProviders = yield* SourceControlProviderRegistry;
35955
36035
  const probe = (input) => {
@@ -36031,7 +36111,7 @@ function expandHomePath$1(input, path) {
36031
36111
  return input;
36032
36112
  }
36033
36113
  const make$9 = Effect.gen(function* () {
36034
- const config = yield* ServerConfig;
36114
+ const config = yield* ServerConfig$1;
36035
36115
  const fileSystem = yield* FileSystem.FileSystem;
36036
36116
  const git = yield* GitVcsDriver;
36037
36117
  const path = yield* Path.Path;
@@ -36390,7 +36470,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
36390
36470
  const providerRegistry = yield* ProviderRegistry;
36391
36471
  const providerMaintenanceRunner = yield* ProviderMaintenanceRunner;
36392
36472
  const serverSelfUpdate = yield* ServerSelfUpdate;
36393
- const config = yield* ServerConfig;
36473
+ const config = yield* ServerConfig$1;
36394
36474
  const lifecycleEvents = yield* ServerLifecycleEvents;
36395
36475
  const serverSettings = yield* ServerSettingsService;
36396
36476
  const startup = yield* ServerRuntimeStartup;
@@ -37034,7 +37114,7 @@ const ProviderSessionRuntime = Schema$1.Struct({
37034
37114
  */
37035
37115
  providerInstanceId: Schema$1.NullOr(ProviderInstanceId),
37036
37116
  adapterKey: Schema$1.String,
37037
- runtimeMode: RuntimeMode$1,
37117
+ runtimeMode: RuntimeMode,
37038
37118
  status: ProviderSessionRuntimeStatus,
37039
37119
  lastSeenAt: IsoDateTime,
37040
37120
  resumeCursor: Schema$1.NullOr(Schema$1.Unknown),
@@ -37667,7 +37747,7 @@ var ProviderEventLoggers = class extends Context.Service()("@p4code/cli/provider
37667
37747
  * is `undefined` and writes from that stream become no-ops downstream.
37668
37748
  */
37669
37749
  const ProviderEventLoggersLive = Layer.effect(ProviderEventLoggers, Effect.gen(function* () {
37670
- const { providerEventLogPath } = yield* ServerConfig;
37750
+ const { providerEventLogPath } = yield* ServerConfig$1;
37671
37751
  return {
37672
37752
  native: yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "native" }),
37673
37753
  canonical: yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "canonical" })
@@ -41136,7 +41216,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
41136
41216
  const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("claudeAgent");
41137
41217
  const fileSystem = yield* FileSystem.FileSystem;
41138
41218
  const path = yield* Path.Path;
41139
- const serverConfig = yield* ServerConfig;
41219
+ const serverConfig = yield* ServerConfig$1;
41140
41220
  const crypto = yield* Crypto.Crypto;
41141
41221
  const claudeEnvironment = yield* makeClaudeEnvironment(claudeSettings, options?.environment).pipe(Effect.provideService(Path.Path, path));
41142
41222
  const claudeSdkExecutablePath = yield* resolveClaudeSdkExecutablePath(claudeSettings.binaryPath, claudeEnvironment);
@@ -43019,7 +43099,7 @@ const ClaudeDriver = {
43019
43099
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
43020
43100
  const fileSystem = yield* FileSystem.FileSystem;
43021
43101
  const path = yield* Path.Path;
43022
- const { cwd } = yield* ServerConfig;
43102
+ const { cwd } = yield* ServerConfig$1;
43023
43103
  const httpClient = yield* HttpClient.HttpClient;
43024
43104
  const serverSettings = yield* ServerSettingsService;
43025
43105
  const eventLoggers = yield* ProviderEventLoggers;
@@ -43130,7 +43210,7 @@ const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(function* (
43130
43210
  const fileSystem = yield* FileSystem.FileSystem;
43131
43211
  const path = yield* Path.Path;
43132
43212
  const commandSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
43133
- const serverConfig = yield* Effect.service(ServerConfig);
43213
+ const serverConfig = yield* Effect.service(ServerConfig$1);
43134
43214
  const resolvedEnvironment = environment ?? process.env;
43135
43215
  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")));
43136
43216
  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({
@@ -63448,7 +63528,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
63448
63528
  const fileSystem = yield* FileSystem.FileSystem;
63449
63529
  const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
63450
63530
  const crypto = yield* Crypto.Crypto;
63451
- const serverConfig = yield* Effect.service(ServerConfig);
63531
+ const serverConfig = yield* Effect.service(ServerConfig$1);
63452
63532
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
63453
63533
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
63454
63534
  const runtimeEventQueue = yield* Queue.unbounded();
@@ -69941,7 +70021,7 @@ function makeCursorAdapter(cursorSettings, options) {
69941
70021
  const fileSystem = yield* FileSystem.FileSystem;
69942
70022
  const path = yield* Path.Path;
69943
70023
  const childProcessSpawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
69944
- const serverConfig = yield* Effect.service(ServerConfig);
70024
+ const serverConfig = yield* Effect.service(ServerConfig$1);
69945
70025
  const crypto = yield* Crypto.Crypto;
69946
70026
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
69947
70027
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
@@ -71060,7 +71140,7 @@ function makeGrokAdapter(grokSettings, options) {
71060
71140
  const fileSystem = yield* FileSystem.FileSystem;
71061
71141
  const path = yield* Path.Path;
71062
71142
  const childProcessSpawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
71063
- const serverConfig = yield* Effect.service(ServerConfig);
71143
+ const serverConfig = yield* Effect.service(ServerConfig$1);
71064
71144
  const crypto = yield* Crypto.Crypto;
71065
71145
  const nativeEventLogger = options?.nativeEventLogger ?? (options?.nativeEventLogPath !== void 0 ? yield* makeEventNdjsonLogger(options.nativeEventLogPath, { stream: "native" }) : void 0);
71066
71146
  const managedNativeEventLogger = options?.nativeEventLogger === void 0 ? nativeEventLogger : void 0;
@@ -72234,7 +72314,7 @@ function getOpenCodeTextResponse(parts) {
72234
72314
  return (parts ?? []).filter(isOpenCodeTextPart).map((part) => part.text).join("").trim();
72235
72315
  }
72236
72316
  const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration")(function* (openCodeSettings, environment) {
72237
- const serverConfig = yield* ServerConfig;
72317
+ const serverConfig = yield* ServerConfig$1;
72238
72318
  const openCodeRuntime = yield* OpenCodeRuntime;
72239
72319
  const resolvedEnvironment = environment ?? process.env;
72240
72320
  const idleFiberScope = yield* Effect.acquireRelease(Scope.make(), (scope) => Scope.close(scope, Exit.void));
@@ -72765,7 +72845,7 @@ const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* (context)
72765
72845
  function makeOpenCodeAdapter(openCodeSettings, options) {
72766
72846
  return Effect.gen(function* () {
72767
72847
  const boundInstanceId = options?.instanceId ?? ProviderInstanceId.make("opencode");
72768
- const serverConfig = yield* ServerConfig;
72848
+ const serverConfig = yield* ServerConfig$1;
72769
72849
  const openCodeRuntime = yield* OpenCodeRuntime;
72770
72850
  const crypto = yield* Crypto.Crypto;
72771
72851
  const fileSystem = yield* FileSystem.FileSystem;
@@ -73836,7 +73916,7 @@ const BUILT_IN_DRIVERS = [
73836
73916
  defaultConfig: () => decodeOpenCodeSettings({}),
73837
73917
  create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => Effect.gen(function* () {
73838
73918
  const openCodeRuntime = yield* OpenCodeRuntime;
73839
- const serverConfig = yield* ServerConfig;
73919
+ const serverConfig = yield* ServerConfig$1;
73840
73920
  const httpClient = yield* HttpClient.HttpClient;
73841
73921
  const serverSettings = yield* ServerSettingsService;
73842
73922
  const eventLoggers = yield* ProviderEventLoggers;
@@ -77547,7 +77627,7 @@ const buildSnapshotSource = (instance) => ({
77547
77627
  });
77548
77628
  const ProviderRegistryLive = Layer.effect(ProviderRegistry, Effect.gen(function* () {
77549
77629
  const instanceRegistry = yield* ProviderInstanceRegistry;
77550
- const config = yield* ServerConfig;
77630
+ const config = yield* ServerConfig$1;
77551
77631
  const fileSystem = yield* FileSystem.FileSystem;
77552
77632
  const path = yield* Path.Path;
77553
77633
  const changesPubSub = yield* Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown);
@@ -77774,7 +77854,7 @@ const ProviderRegistryLive = Layer.effect(ProviderRegistry, Effect.gen(function*
77774
77854
  //#endregion
77775
77855
  //#region src/serverLogger.ts
77776
77856
  const ServerLoggerLive = Effect.gen(function* () {
77777
- const config = yield* ServerConfig;
77857
+ const config = yield* ServerConfig$1;
77778
77858
  const minimumLogLevelLayer = Layer.succeed(References.MinimumLogLevel, config.logLevel);
77779
77859
  const loggerLayer = Logger.layer([Logger.consolePretty(), Logger.tracerLogger], { mergeWithExisting: false });
77780
77860
  return Layer.mergeAll(loggerLayer, minimumLogLevelLayer);
@@ -77783,7 +77863,7 @@ const ServerLoggerLive = Effect.gen(function* () {
77783
77863
  //#region src/observability/Layers/Observability.ts
77784
77864
  const otlpSerializationLayer = OtlpSerialization.layerJson;
77785
77865
  const ObservabilityLive = Layer.unwrap(Effect.gen(function* () {
77786
- const config = yield* ServerConfig;
77866
+ const config = yield* ServerConfig$1;
77787
77867
  const traceReferencesLayer = Layer.mergeAll(Layer.succeed(Tracer.MinimumTraceLevel, config.traceMinLevel), Layer.succeed(References.TracerTimingEnabled, config.traceTimingEnabled));
77788
77868
  const tracerLayer = Layer.unwrap(Effect.gen(function* () {
77789
77869
  const sink = yield* makeTraceSink({
@@ -78057,11 +78137,11 @@ const disableTailscaleServe = (input = {}) => Effect.gen(function* () {
78057
78137
  //#region src/server.ts
78058
78138
  const HTTP_PREEMPTIVE_SHUTDOWN_GRACE_MS = 0;
78059
78139
  const PtyAdapterLive = Layer.unwrap(Effect.gen(function* () {
78060
- if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("./BunPtyAdapter-DogtLQSx.mjs"))).layer;
78061
- else return (yield* Effect.promise(() => import("./NodePtyAdapter-CS1weRcq.mjs"))).layer;
78140
+ if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("./BunPtyAdapter-CLDq7yr1.mjs"))).layer;
78141
+ else return (yield* Effect.promise(() => import("./NodePtyAdapter-BQAnH2v0.mjs"))).layer;
78062
78142
  }));
78063
78143
  const HttpServerLive = Layer.unwrap(Effect.gen(function* () {
78064
- const config = yield* ServerConfig;
78144
+ const config = yield* ServerConfig$1;
78065
78145
  if (typeof Bun !== "undefined") return (yield* Effect.promise(() => import("@effect/platform-bun/BunHttpServer"))).layer({
78066
78146
  port: config.port,
78067
78147
  hostname: config.host ?? "127.0.0.1",
@@ -78112,7 +78192,7 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
78112
78192
  const RuntimeServicesLive = layer$43.pipe(Layer.provideMerge(RuntimeDependenciesLive));
78113
78193
  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));
78114
78194
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
78115
- const config = yield* ServerConfig;
78195
+ const config = yield* ServerConfig$1;
78116
78196
  yield* fixPath();
78117
78197
  const httpListeningLayer = Layer.effectDiscard(Effect.gen(function* () {
78118
78198
  yield* HttpServer.HttpServer;
@@ -78160,7 +78240,7 @@ const runServer = Layer.launch(makeServerLayer);
78160
78240
  //#region src/cli/server.ts
78161
78241
  const runServerCommand = (flags, options) => Effect.gen(function* () {
78162
78242
  const config = yield* resolveServerConfig(flags, yield* GlobalFlag.LogLevel, options);
78163
- return yield* runServer.pipe(Effect.provideService(ServerConfig, config));
78243
+ return yield* runServer.pipe(Effect.provideService(ServerConfig$1, config));
78164
78244
  });
78165
78245
  const startCommand = Command.make("start", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)));
78166
78246
  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, {
@@ -78168,6 +78248,104 @@ const serveCommand = Command.make("serve", { ...sharedServerCommandFlags }).pipe
78168
78248
  forceAutoBootstrapProjectFromCwd: false
78169
78249
  })));
78170
78250
  //#endregion
78251
+ //#region src/cli/completions.ts
78252
+ /**
78253
+ * Installs the zsh completion script for the `p4` command.
78254
+ *
78255
+ * zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
78256
+ * through our subcommands prints "unhandled perforce command: …" from its
78257
+ * fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
78258
+ * order — first match wins — and `_perforce` lives in the *last* fpath entry
78259
+ * (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
78260
+ * directory therefore replaces it outright, with no shell-config edit.
78261
+ */
78262
+ const COMPLETION_FILE = "_p4";
78263
+ const GENERATE_TIMEOUT_MS = 1e4;
78264
+ /**
78265
+ * Directories ahead of `/usr/share/zsh/*` in a default fpath, most specific
78266
+ * first. `~/.zfunc` is the fallback and is the only one needing an fpath line.
78267
+ */
78268
+ function candidateDirectories(input) {
78269
+ return [
78270
+ {
78271
+ dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
78272
+ requiresFpathEntry: false
78273
+ },
78274
+ {
78275
+ dir: "/opt/homebrew/share/zsh/site-functions",
78276
+ requiresFpathEntry: false
78277
+ },
78278
+ {
78279
+ dir: "/usr/local/share/zsh/site-functions",
78280
+ requiresFpathEntry: false
78281
+ },
78282
+ {
78283
+ dir: input.join(input.homeDir, ".zfunc"),
78284
+ requiresFpathEntry: true
78285
+ }
78286
+ ];
78287
+ }
78288
+ /**
78289
+ * Asks the running CLI for its own completion script rather than rendering one
78290
+ * here, so the output cannot drift from what `--completions` produces.
78291
+ */
78292
+ const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
78293
+ const runner = yield* ProcessRunner;
78294
+ const execPath = yield* HostProcessExecutablePath;
78295
+ const entryPath = (yield* HostProcessArguments)[1] ?? "";
78296
+ if (entryPath === "") return null;
78297
+ const result = yield* runner.run({
78298
+ command: execPath,
78299
+ args: [
78300
+ entryPath,
78301
+ "--completions",
78302
+ "zsh"
78303
+ ],
78304
+ timeout: GENERATE_TIMEOUT_MS
78305
+ });
78306
+ if (result.code !== 0) return null;
78307
+ const script = result.stdout.trim();
78308
+ return script.length === 0 ? null : `${script}\n`;
78309
+ });
78310
+ /**
78311
+ * Best-effort: returns null when completions are not applicable (Windows, no
78312
+ * writable directory) rather than failing the command that asked for them.
78313
+ * Installing completions is never the reason an install should fail.
78314
+ */
78315
+ const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
78316
+ if ((yield* HostProcessPlatform) === "win32") return null;
78317
+ const fs = yield* FileSystem.FileSystem;
78318
+ const path = yield* Path.Path;
78319
+ const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
78320
+ if (homeDir === "") return null;
78321
+ const script = yield* generateZshCompletionScript();
78322
+ if (script === null) return null;
78323
+ for (const candidate of candidateDirectories({
78324
+ homeDir,
78325
+ join: path.join
78326
+ })) {
78327
+ if (candidate.requiresFpathEntry) {
78328
+ if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
78329
+ } else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
78330
+ const target = path.join(candidate.dir, COMPLETION_FILE);
78331
+ if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
78332
+ return {
78333
+ path: target,
78334
+ requiresFpathEntry: candidate.requiresFpathEntry
78335
+ };
78336
+ }
78337
+ return null;
78338
+ });
78339
+ function formatZshCompletionInstall(install) {
78340
+ const installed = `Installed zsh completions at ${install.path}.`;
78341
+ if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
78342
+ return [
78343
+ installed,
78344
+ `Add this to ~/.zshrc before compinit runs, then open a new shell:`,
78345
+ ` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
78346
+ ].join("\n");
78347
+ }
78348
+ //#endregion
78171
78349
  //#region src/cli/service.ts
78172
78350
  const isBootServiceCommandError = Schema$1.is(BootServiceCommandError);
78173
78351
  const bootServiceLayer = (config) => layer$46({
@@ -78220,7 +78398,7 @@ const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
78220
78398
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
78221
78399
  const baseDirNotice = yield* describeNonDefaultBaseDir(config.baseDir);
78222
78400
  if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
78223
- 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)));
78401
+ 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));
78224
78402
  });
78225
78403
  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* () {
78226
78404
  const result = yield* reconcileService();
@@ -78234,6 +78412,8 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
78234
78412
  cliVersion: version,
78235
78413
  platform: yield* HostProcessPlatform
78236
78414
  }));
78415
+ const completion = yield* installZshCompletion().pipe(Effect.orElseSucceed(() => null));
78416
+ if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
78237
78417
  }))));
78238
78418
  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* () {
78239
78419
  const result = yield* reconcileService();
@@ -78280,7 +78460,23 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
78280
78460
  ]));
78281
78461
  //#endregion
78282
78462
  //#region src/bin.ts
78283
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$62);
78463
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$62, FetchHttpClient.layer);
78464
+ /**
78465
+ * Prints an update reminder before dispatching, at most once a day.
78466
+ *
78467
+ * On stderr so it never contaminates `--json` output or a redirected
78468
+ * `--completions` script, and fully swallowed: an advisory notice must not be
78469
+ * able to fail a command or change its exit code.
78470
+ */
78471
+ const reportUpdateAvailable = Effect.gen(function* () {
78472
+ const baseDir = yield* resolveBaseDir((yield* HostProcessEnvironment).P4CODE_HOME);
78473
+ const paths = yield* deriveServerPaths(baseDir, void 0);
78474
+ const reminder = yield* resolveUpdateReminder({
78475
+ currentVersion: version,
78476
+ stateDir: paths.stateDir
78477
+ });
78478
+ if (reminder !== null) yield* Console.error(reminder);
78479
+ }).pipe(Effect.ignore);
78284
78480
  const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
78285
78481
  startCommand,
78286
78482
  serveCommand,
@@ -78289,7 +78485,7 @@ const makeCli = () => Command.make("p4", { ...sharedServerCommandFlags }).pipe(C
78289
78485
  serviceCommand
78290
78486
  ]));
78291
78487
  const cli = makeCli();
78292
- if (import.meta.main) Command.run(cli, { version }).pipe(Effect.scoped, Effect.provide(CliRuntimeLayer), NodeRuntime.runMain);
78488
+ if (import.meta.main) reportUpdateAvailable.pipe(Effect.andThen(Command.run(cli, { version })), Effect.scoped, Effect.provide(CliRuntimeLayer), NodeRuntime.runMain);
78293
78489
  //#endregion
78294
78490
  export { cli, makeCli };
78295
78491