copilot-relay 0.4.1 → 0.4.2

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/main.js CHANGED
@@ -264,7 +264,9 @@ const fileLevelByMethod = {
264
264
  debug: consolaLevelByName.debug
265
265
  };
266
266
  const loggingSuppressed = new AsyncLocalStorage();
267
+ const consoleSuppressed = new AsyncLocalStorage();
267
268
  const withoutLogging = (run) => loggingSuppressed.run(true, run);
269
+ const withoutConsoleLogging = (run) => consoleSuppressed.run(true, run);
268
270
  let currentLogLevel = consolaLevelByName.info;
269
271
  const pendingLogWrites = /* @__PURE__ */ new Set();
270
272
  const registeredLogSecrets = /* @__PURE__ */ new Set();
@@ -423,15 +425,16 @@ const wrapFileLog = (level, fn) => ((...args) => {
423
425
  if (loggingSuppressed.getStore()) return;
424
426
  const methodLevel = fileLevelByMethod[level] ?? consolaLevelByName.info;
425
427
  const writesToFile = methodLevel <= currentLogLevel;
426
- const writesToConsole = methodLevel <= consola.level;
427
- if (!writesToFile && !writesToConsole) return fn(...args);
428
+ const quiet = consoleSuppressed.getStore() === true;
429
+ const writesToConsole = !quiet && methodLevel <= consola.level;
430
+ if (!writesToFile && !writesToConsole) return quiet ? void 0 : fn(...args);
428
431
  const rendered = [boundLogText(args.map((value) => boundLogText(escapeLogLineSeparators(scrubLogSecrets(scrubSensitiveUrls(formatLogValue(value)))), maxLogArgumentBytes)).join(" "), maxLogPayloadBytes)];
429
432
  if (writesToFile) {
430
433
  const pending = writeLogFile(level, rendered).catch(() => void 0);
431
434
  pendingLogWrites.add(pending);
432
435
  pending.then(() => pendingLogWrites.delete(pending));
433
436
  }
434
- return fn(...rendered);
437
+ return quiet ? void 0 : fn(...rendered);
435
438
  });
436
439
  consola.error = wrapFileLog("error", consola.error.bind(consola));
437
440
  consola.info = wrapFileLog("info", consola.info.bind(consola));
@@ -609,7 +612,8 @@ const ensureGitHubToken = async (options = {}) => {
609
612
  return existingToken;
610
613
  }
611
614
  const deviceCode = await getDeviceCode();
612
- log.info(`Open ${deviceCode.verification_uri} and enter code ${deviceCode.user_code}`);
615
+ if (options.onDeviceCode) options.onDeviceCode(deviceCode.verification_uri, deviceCode.user_code);
616
+ else log.info(`Open ${deviceCode.verification_uri} and enter code ${deviceCode.user_code}`);
613
617
  const githubToken = await pollAccessToken(deviceCode);
614
618
  await writeGitHubToken(githubToken);
615
619
  log.info(`GitHub token synced to ${paths.githubTokenPath}`);
@@ -807,7 +811,7 @@ const resolveTarget = async (filePath, remainingLinks = 40) => {
807
811
  return path.join(await resolveTarget(parent, remainingLinks), path.basename(filePath));
808
812
  }
809
813
  };
810
- const readFileSnapshot = async (filePath) => {
814
+ const readStableSnapshot = async (filePath) => {
811
815
  const requestedPath = path.resolve(filePath);
812
816
  const resolvedPath = await resolveTarget(requestedPath);
813
817
  const before = await fs.lstat(resolvedPath).catch((error) => {
@@ -830,14 +834,46 @@ const readFileSnapshot = async (filePath) => {
830
834
  if (isMissing(error)) throw new FileConflictError();
831
835
  throw error;
832
836
  }
833
- if (!after.isFile() || !sameIdentity(identityOf(before), identityOf(after)) || await resolveTarget(requestedPath) !== resolvedPath) throw new FileConflictError();
834
- return {
837
+ const snapshot = {
835
838
  requestedPath,
836
839
  resolvedPath,
837
840
  raw,
838
841
  mode: before.mode & 511,
839
842
  identity: identityOf(before)
840
843
  };
844
+ if (!after.isFile() || before.mode !== after.mode || await resolveTarget(requestedPath) !== resolvedPath) throw new FileConflictError();
845
+ if (!sameIdentity(snapshot.identity, identityOf(after))) {
846
+ if (sameReadContent(snapshot, {
847
+ ...snapshot,
848
+ identity: identityOf(after)
849
+ })) throw new MetadataReadConflictError(snapshot);
850
+ throw new FileConflictError();
851
+ }
852
+ return snapshot;
853
+ };
854
+ const sameReadContent = (left, right) => left.resolvedPath === right.resolvedPath && left.raw === right.raw && left.mode === right.mode && left.identity !== null && right.identity !== null && sameIdentity(left.identity, {
855
+ ...right.identity,
856
+ ctimeMs: left.identity.ctimeMs
857
+ });
858
+ var MetadataReadConflictError = class extends FileConflictError {
859
+ snapshot;
860
+ constructor(snapshot) {
861
+ super();
862
+ this.snapshot = snapshot;
863
+ }
864
+ };
865
+ const readFileSnapshot = async (filePath) => {
866
+ let firstRead;
867
+ for (let attempt = 1;; attempt++) try {
868
+ const snapshot = await readStableSnapshot(filePath);
869
+ if (firstRead && !sameReadContent(firstRead, snapshot)) throw new FileConflictError();
870
+ return snapshot;
871
+ } catch (error) {
872
+ if (!(error instanceof MetadataReadConflictError)) throw error;
873
+ if (attempt >= 3) throw new FileConflictError();
874
+ if (firstRead && !sameReadContent(firstRead, error.snapshot)) throw new FileConflictError();
875
+ firstRead ??= error.snapshot;
876
+ }
841
877
  };
842
878
  const assertUnchanged = async (expected) => {
843
879
  const current = await readFileSnapshot(expected.requestedPath);
@@ -1306,6 +1342,7 @@ const maxQueuedBytes = 8 * 1024 * 1024;
1306
1342
  const activeCaptures = /* @__PURE__ */ new Set();
1307
1343
  const traceContext = new AsyncLocalStorage();
1308
1344
  const transportContext = new AsyncLocalStorage();
1345
+ const traceObserver = new AsyncLocalStorage();
1309
1346
  const responseExchanges = /* @__PURE__ */ new WeakMap();
1310
1347
  const pendingCaptures = /* @__PURE__ */ new Set();
1311
1348
  let captureRetentionDays = 3;
@@ -1331,6 +1368,11 @@ const errorName = (error) => error instanceof Error && [
1331
1368
  "SyntaxError"
1332
1369
  ].includes(error.name) ? error.name : "Error";
1333
1370
  const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1371
+ const withTraceObserver = (observe, run) => traceObserver.run(observe, run);
1372
+ const getReplayRequestId = () => {
1373
+ const id = transportContext.getStore()?.requestId;
1374
+ return id && uuid$1.test(id) ? id : void 0;
1375
+ };
1334
1376
  const withRecordedTransport = (transport, run) => transportContext.run(transport, run);
1335
1377
  const withRequestTrace = (trace, run) => traceContext.run(trace, run);
1336
1378
  const getRequestTrace = () => traceContext.getStore();
@@ -1495,6 +1537,109 @@ var RequestTrace = class RequestTrace {
1495
1537
  deferredHandler = false;
1496
1538
  removeAbort;
1497
1539
  credentials = /* @__PURE__ */ new Set();
1540
+ captureRequested = false;
1541
+ finishedSettling = false;
1542
+ failure;
1543
+ recordFailure(failure) {
1544
+ this.failure = failure;
1545
+ }
1546
+ diagnosticCaptureState() {
1547
+ if (!this.captureRequested) return "off";
1548
+ if (!this.captureDirectory) return "failed";
1549
+ if (!this.finishedSettling) return "pending";
1550
+ return this.manifest.captureState === "complete" ? "complete" : "incomplete";
1551
+ }
1552
+ diagnosticSnapshot() {
1553
+ const known = (value, choices) => typeof value === "string" && choices.includes(value) ? value : void 0;
1554
+ const id = (value) => {
1555
+ const text$1 = this.metadataValue(value);
1556
+ return text$1 && /^[A-Za-z0-9_.:-]{1,160}$/.test(text$1) ? text$1 : void 0;
1557
+ };
1558
+ const modelId = (value) => {
1559
+ const text$1 = this.metadataValue(value);
1560
+ return text$1 && /^[A-Za-z0-9_.\[\]-]{1,128}$/.test(text$1) ? text$1 : void 0;
1561
+ };
1562
+ const bodyState = (value) => known(value, [
1563
+ "pending",
1564
+ "complete",
1565
+ "cancelled",
1566
+ "error",
1567
+ "absent"
1568
+ ]);
1569
+ const status$1 = (value) => typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : void 0;
1570
+ const reasons$1 = [
1571
+ "end_turn",
1572
+ "stop_sequence",
1573
+ "tool_use",
1574
+ "pause_turn",
1575
+ "refusal",
1576
+ "max_tokens"
1577
+ ];
1578
+ return {
1579
+ requestId: this.requestId,
1580
+ status: status$1(this.manifest.status),
1581
+ responseState: bodyState(this.manifest.response?.state),
1582
+ stopReason: known(this.manifest.outcome?.stop_reason, reasons$1),
1583
+ reportedModel: modelId(this.manifest.outcome?.model),
1584
+ terminal: this.manifest.outcome?.terminal === true,
1585
+ failure: this.failure,
1586
+ capture: { state: this.diagnosticCaptureState() },
1587
+ exchanges: this.manifest.exchanges.map((exchange) => ({
1588
+ order: exchange.order,
1589
+ path: known(exchange.path, [
1590
+ "/models",
1591
+ "/chat/completions",
1592
+ "/responses",
1593
+ "/v1/messages"
1594
+ ]) ?? "unknown",
1595
+ upstreamRequestId: id(exchange.upstreamRequestId),
1596
+ providerRequestId: [
1597
+ "x-copilot-service-request-id",
1598
+ "x-github-request-id",
1599
+ "x-request-id"
1600
+ ].map((key) => id(exchange.responseHeaders?.[key])).find(Boolean),
1601
+ messageId: id(exchange.outcome?.message_id),
1602
+ model: modelId(exchange.outcome?.model),
1603
+ status: status$1(exchange.status),
1604
+ responseState: bodyState(exchange.response?.state),
1605
+ finishReason: known(exchange.outcome?.finish_reason, [
1606
+ "stop",
1607
+ "length",
1608
+ "tool_calls",
1609
+ "content_filter"
1610
+ ]),
1611
+ stopReason: known(exchange.outcome?.stop_reason, reasons$1),
1612
+ responseStatus: known(exchange.outcome?.response_status, [
1613
+ "completed",
1614
+ "failed",
1615
+ "cancelled",
1616
+ "incomplete"
1617
+ ]),
1618
+ refusalCategory: known(exchange.outcome?.refusal_category, [
1619
+ "cyber",
1620
+ "bio",
1621
+ "reasoning_extraction",
1622
+ "general_harms",
1623
+ "frontier_llm",
1624
+ "unknown"
1625
+ ]),
1626
+ incompleteReason: known(exchange.outcome?.incomplete_reason, [
1627
+ "max_output_tokens",
1628
+ "content_filter",
1629
+ "unknown"
1630
+ ]),
1631
+ error: known(exchange.error ?? exchange.response?.error, [
1632
+ "AbortError",
1633
+ "TimeoutError",
1634
+ "TypeError",
1635
+ "SyntaxError",
1636
+ "Error"
1637
+ ]),
1638
+ discarded: exchange.discarded === true
1639
+ })),
1640
+ refreshes: this.manifest.refreshes.map((refresh) => refresh.outcome)
1641
+ };
1642
+ }
1498
1643
  protectCredential(value) {
1499
1644
  if (value) this.credentials.add(value);
1500
1645
  registerLogSecret(value);
@@ -1599,6 +1744,8 @@ var RequestTrace = class RequestTrace {
1599
1744
  }
1600
1745
  static async create(id, request, config, runtime, capture) {
1601
1746
  const trace = new RequestTrace(id, request, config, runtime);
1747
+ trace.captureRequested = capture;
1748
+ traceObserver.getStore()?.(trace);
1602
1749
  if (!capture) return trace;
1603
1750
  try {
1604
1751
  if (!uuid$1.test(id)) throw new Error("Invalid capture identifier.");
@@ -1881,6 +2028,7 @@ var RequestTrace = class RequestTrace {
1881
2028
  await Promise.all([...this.openFiles.values()].map((handle) => handle.close().catch(() => {})));
1882
2029
  this.openFiles.clear();
1883
2030
  if (this.captureDirectory) activeCaptures.delete(this.captureDirectory);
2031
+ this.finishedSettling = true;
1884
2032
  this.resolveFinished();
1885
2033
  });
1886
2034
  }
@@ -2297,6 +2445,145 @@ async function boundModelOutputTokens(config, model, requested) {
2297
2445
  return bounded;
2298
2446
  }
2299
2447
 
2448
+ //#endregion
2449
+ //#region src/lib/terminal.ts
2450
+ const colorEnabled = (env = process.env, isTTY = Boolean(process.stdout.isTTY)) => {
2451
+ if (env.NO_COLOR !== void 0 || env.FORCE_COLOR === "0") return false;
2452
+ if (env.FORCE_COLOR !== void 0) return env.FORCE_COLOR === "" || [
2453
+ "1",
2454
+ "2",
2455
+ "3",
2456
+ "true"
2457
+ ].includes(env.FORCE_COLOR);
2458
+ return isTTY && env.TERM !== "dumb";
2459
+ };
2460
+ const codes = {
2461
+ good: 32,
2462
+ bad: 31,
2463
+ warning: 33,
2464
+ muted: 90
2465
+ };
2466
+ const terminalText = (value) => sanitizeTerminalString(value).replace(/[\x80-\x9f]|\p{Cf}|\p{Zl}|\p{Zp}/gu, "");
2467
+ const colorText = (text$1, tone, enabled) => enabled ? `\u001b[${codes[tone]}m${text$1}\u001b[0m` : text$1;
2468
+
2469
+ //#endregion
2470
+ //#region src/lib/model-probe-output.ts
2471
+ const tones = {
2472
+ PASS: "good",
2473
+ FAIL: "bad",
2474
+ INCOMPLETE: "warning",
2475
+ SKIPPED: "muted",
2476
+ NOT_TESTED: "muted"
2477
+ };
2478
+ const reasons = {
2479
+ "completed-text": "Ready",
2480
+ "probe-timeout": "Timed out",
2481
+ "upstream-timeout-or-HTTP-504": "Upstream timeout (504)",
2482
+ "authentication-rejected": "Auth rejected (401)",
2483
+ "access-denied": "Access denied (403)",
2484
+ "rate-limited": "Rate limited (429)",
2485
+ "cancelled": "Cancelled",
2486
+ "total-deadline": "Time budget reached",
2487
+ "unsupported-model-type": "Not a chat model",
2488
+ "unsupported-relay-endpoint": "Unsupported route",
2489
+ "unsupported-api": "Unsupported API",
2490
+ "unsupported-effort": "Unsupported effort",
2491
+ "unsafe-or-noncanonical-id": "Unsupported model ID",
2492
+ "upstream-response-failed": "Upstream failed",
2493
+ "upstream-response-incomplete": "Incomplete response",
2494
+ "malformed-response": "Invalid response",
2495
+ "model-mismatch": "Model mismatch",
2496
+ "reachable-output-budget-exhausted": "Output limit reached",
2497
+ "output-budget-exhausted-usage-unreported": "Output limit reached",
2498
+ "refusal-or-unexpected-completion": "Unexpected completion",
2499
+ "refusal": "Refused",
2500
+ "empty-completed-response": "Empty response",
2501
+ "network-or-invalid-response": "Request failed",
2502
+ "transport-error": "Connection failed",
2503
+ "refresh-failed": "Token refresh failed",
2504
+ "invalid-tool-input": "Invalid tool input",
2505
+ "local-validation": "Request rejected",
2506
+ "unknown-error": "Unknown API error"
2507
+ };
2508
+ const probeReason = (code) => reasons[code] ?? (/^HTTP-\d{3}$/.test(code) ? `HTTP ${code.slice(5)}` : "Unknown result");
2509
+ const probeColumnWidth = (ids, columns) => Math.min(Math.max(14, ...ids.map((id) => terminalText(id).length)), Math.max(14, columns - 42));
2510
+ const renderProbeHeader = (width) => `${"MODEL".padEnd(width)} ${"STATUS".padEnd(10)} ${"TIME".padStart(6)} RESULT`;
2511
+ const wrap = (text$1, width) => {
2512
+ const lines = [];
2513
+ for (let index = 0; index < text$1.length; index += width) lines.push(text$1.slice(index, index + width));
2514
+ return lines;
2515
+ };
2516
+ const probeTime = (row) => {
2517
+ if (!row.sent) return "-";
2518
+ const seconds = row.latency / 1e3;
2519
+ if (seconds < 1e3) return `${seconds.toFixed(1)}s`;
2520
+ if (seconds < 3600) return `${(seconds / 60).toFixed(1)}m`;
2521
+ return `${(seconds / 3600).toFixed(1)}h`;
2522
+ };
2523
+ const renderProbeRow = (row, width, color, columns = 80) => {
2524
+ const models$1 = wrap(terminalText(row.id), width);
2525
+ const time = probeTime(row);
2526
+ const results = wrap(probeReason(row.detail) + (row.unverified ? " *" : ""), Math.max(1, columns - width - 22));
2527
+ const status$1 = colorText(row.status, tones[row.status], color) + " ".repeat(10 - row.status.length);
2528
+ return Array.from({ length: Math.max(models$1.length, results.length) }, (_, index) => {
2529
+ return `${(models$1[index] ?? "").padEnd(width)}${index === 0 ? ` ${status$1} ${time.padStart(6)} ` : " ".repeat(22)}${results[index] ?? ""}`.trimEnd();
2530
+ });
2531
+ };
2532
+ const renderProbeSummary = (rows) => {
2533
+ if (!rows.length) return "Summary: no models to test";
2534
+ const labels = {
2535
+ PASS: "passed",
2536
+ FAIL: "failed",
2537
+ INCOMPLETE: "incomplete",
2538
+ SKIPPED: "skipped",
2539
+ NOT_TESTED: "not tested"
2540
+ };
2541
+ return "Summary: " + Object.keys(labels).flatMap((status$1) => {
2542
+ const count = rows.filter((row) => row.status === status$1).length;
2543
+ return count ? [`${count} ${labels[status$1]}`] : [];
2544
+ }).join(" · ");
2545
+ };
2546
+ const renderProbeDetails = (row, diagnostic) => {
2547
+ const lines = [` effort=${row.effort} max_tokens=${row.maxTokens} planned_route=${row.endpoint}`, ` reported=${row.reported} reason=${row.detail}`];
2548
+ if (!diagnostic) return [...lines, " request=not started"];
2549
+ lines.push(` request_id=${diagnostic.requestId}`);
2550
+ lines.push(` client_http=${diagnostic.status ?? "unknown"} response=${diagnostic.responseState ?? "unknown"} terminal=${diagnostic.terminal ? "observed" : "not observed"}`);
2551
+ for (const exchange of diagnostic.exchanges) {
2552
+ lines.push(` route=${exchange.path} upstream_http=${exchange.status ?? "unknown"} response=${exchange.responseState ?? "unknown"}${exchange.discarded ? " discarded=yes" : ""}`);
2553
+ lines.push(` upstream_request_id=${exchange.upstreamRequestId ?? "unknown"}`);
2554
+ if (exchange.providerRequestId) lines.push(` provider_request_id=${exchange.providerRequestId}`);
2555
+ if (exchange.model && exchange.model !== row.reported) lines.push(` upstream_model=${exchange.model}`);
2556
+ if (exchange.messageId) lines.push(` message_id=${exchange.messageId}`);
2557
+ const outcome = exchange.stopReason ?? exchange.finishReason ?? exchange.responseStatus;
2558
+ if (outcome || exchange.error) lines.push(` outcome=${outcome ?? "unknown"}${exchange.error ? ` transport=${exchange.error}` : ""}`);
2559
+ if (exchange.refusalCategory) lines.push(` refusal_category=${exchange.refusalCategory}`);
2560
+ if (exchange.incompleteReason) lines.push(` incomplete_reason=${exchange.incompleteReason}`);
2561
+ }
2562
+ if (diagnostic.refreshes.length) lines.push(` refresh=${diagnostic.refreshes.join(",")}`);
2563
+ lines.push(` capture=${diagnostic.capture.state}`);
2564
+ if (diagnostic.capture.state === "complete") lines.push(` Offline replay: copilot-relay replay ${diagnostic.requestId}`);
2565
+ else if (diagnostic.capture.state === "off") lines.push(" Replay unavailable: debug was off; capture privately if the error repeats.");
2566
+ else lines.push(" Replay unavailable: capture is not complete.");
2567
+ return lines;
2568
+ };
2569
+ const probeHint = (code) => {
2570
+ if (code === "probe-timeout" || code === "upstream-timeout-or-HTTP-504") return "Timeout: check connection and configured deadline.";
2571
+ if (["authentication-rejected", "refresh-failed"].includes(code)) return "Authentication: run copilot-relay auth before another probe.";
2572
+ if (code === "access-denied") return "Access: check this account's model entitlement and gateway policy.";
2573
+ if (code === "rate-limited") return "Rate limit: wait before retrying; repeated probes consume usage.";
2574
+ if (code === "refusal") return "Refusal: inspect the failing request's evidence; a short probe does not explain its cause.";
2575
+ if (code.includes("budget-exhausted")) return "Output limit: reachability is not completion; review the probe's output budget.";
2576
+ if (code === "model-mismatch") return "Model mismatch: compare selected, sent and reported IDs with --details.";
2577
+ if ([
2578
+ "invalid-tool-input",
2579
+ "malformed-response",
2580
+ "upstream-response-failed",
2581
+ "unknown-error",
2582
+ "network-or-invalid-response",
2583
+ "transport-error"
2584
+ ].includes(code) || /^HTTP-5\d\d$/.test(code)) return "API failure: the request ID locates the local summary; --details shows evidence on a new probe.";
2585
+ };
2586
+
2300
2587
  //#endregion
2301
2588
  //#region src/claude/utils.ts
2302
2589
  const isEffortOnlyControl = (control) => control !== null && typeof control === "object" && !Array.isArray(control) && Object.keys(control).length === 1 && "effort" in control && isConfiguredReasoningEffort(control.effort);
@@ -2324,6 +2611,43 @@ function mapOpenAIStopReasonToClaude(finishReason) {
2324
2611
  content_filter: "refusal"
2325
2612
  }[finishReason];
2326
2613
  }
2614
+ /**
2615
+ * Upstream sends an empty `arguments` string for tools that take no
2616
+ * parameters (#114). Blank text is the empty object; anything else must be a
2617
+ * JSON object, and a failure names the tool rather than surfacing a bare
2618
+ * SyntaxError. Argument text is never echoed: it can carry user data.
2619
+ */
2620
+ function parseUpstreamToolInput(toolName, argumentsText) {
2621
+ const text$1 = argumentsText ?? "";
2622
+ if (!text$1.trim()) return {};
2623
+ let parsed;
2624
+ try {
2625
+ parsed = JSON.parse(text$1);
2626
+ } catch {
2627
+ throw invalidUpstreamToolInput(toolName, "is not valid JSON");
2628
+ }
2629
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw invalidUpstreamToolInput(toolName, "is not a JSON object");
2630
+ return parsed;
2631
+ }
2632
+ function invalidUpstreamToolInputMessage(toolName, reason) {
2633
+ return `Upstream returned tool input for "${toolName}" that ${reason}.`;
2634
+ }
2635
+ /** Mapped 502 whose message is client-safe: it names the tool, never the arguments. */
2636
+ var UpstreamToolInputError = class extends HTTPError {
2637
+ constructor(message) {
2638
+ super(message, Response.json({
2639
+ type: "error",
2640
+ error: {
2641
+ type: "api_error",
2642
+ message
2643
+ }
2644
+ }, { status: 502 }), message);
2645
+ this.name = "UpstreamToolInputError";
2646
+ }
2647
+ };
2648
+ function invalidUpstreamToolInput(toolName, reason) {
2649
+ return new UpstreamToolInputError(invalidUpstreamToolInputMessage(toolName, reason));
2650
+ }
2327
2651
 
2328
2652
  //#endregion
2329
2653
  //#region src/claude/tool-names.ts
@@ -2638,17 +2962,15 @@ function getClaudeTextBlocks(messageContent) {
2638
2962
  }
2639
2963
  function getClaudeToolUseBlocks(toolCalls, toolNameMapper) {
2640
2964
  if (!toolCalls) return [];
2641
- return toolCalls.map((toolCall) => ({
2642
- type: "tool_use",
2643
- id: toolCall.id,
2644
- name: toolNameMapper.toClaude(toolCall.function.name),
2645
- input: safeJsonParse(toolCall.function.arguments)
2646
- }));
2647
- }
2648
- function safeJsonParse(input) {
2649
- const parsed = JSON.parse(input);
2650
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Upstream tool input must be a JSON object.");
2651
- return parsed;
2965
+ return toolCalls.map((toolCall) => {
2966
+ const name = toolNameMapper.toClaude(toolCall.function.name);
2967
+ return {
2968
+ type: "tool_use",
2969
+ id: toolCall.id,
2970
+ name,
2971
+ input: parseUpstreamToolInput(name, toolCall.function.arguments)
2972
+ };
2973
+ });
2652
2974
  }
2653
2975
 
2654
2976
  //#endregion
@@ -2752,8 +3074,8 @@ function translateChunkToClaudeEvents(chunk, state, toolNameMapper = createClaud
2752
3074
  closeOpenContentBlock(events$1, state);
2753
3075
  if (choice.finish_reason === "tool_calls") for (const toolCall of Object.values(state.toolCalls)) {
2754
3076
  const argumentsText = toolCall.arguments ?? "";
2755
- const input = JSON.parse(argumentsText);
2756
- if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Upstream tool input must be a JSON object.");
3077
+ parseUpstreamToolInput(toolCall.name, argumentsText);
3078
+ const partialJson = argumentsText.trim() ? argumentsText : "{}";
2757
3079
  const index = state.contentBlockIndex++;
2758
3080
  toolCall.claudeBlockIndex = index;
2759
3081
  events$1.push({
@@ -2770,7 +3092,7 @@ function translateChunkToClaudeEvents(chunk, state, toolNameMapper = createClaud
2770
3092
  index,
2771
3093
  delta: {
2772
3094
  type: "input_json_delta",
2773
- partial_json: argumentsText
3095
+ partial_json: partialJson
2774
3096
  }
2775
3097
  }, {
2776
3098
  type: "content_block_stop",
@@ -2792,12 +3114,12 @@ function translateChunkToClaudeEvents(chunk, state, toolNameMapper = createClaud
2792
3114
  }
2793
3115
  return events$1;
2794
3116
  }
2795
- function translateErrorToClaudeErrorEvent() {
3117
+ function translateErrorToClaudeErrorEvent(error) {
2796
3118
  return {
2797
3119
  type: "error",
2798
3120
  error: {
2799
3121
  type: "api_error",
2800
- message: "An unexpected error occurred during streaming."
3122
+ message: error instanceof UpstreamToolInputError ? error.message : "An unexpected error occurred during streaming."
2801
3123
  }
2802
3124
  };
2803
3125
  }
@@ -5030,8 +5352,25 @@ claudeRoutes.post("/messages", async (c) => {
5030
5352
  } catch (error) {
5031
5353
  if (!(error instanceof HTTPError)) throw error;
5032
5354
  c.set("requestErrorMessage", error.message);
5355
+ const trace = c.get("requestTrace");
5356
+ if (error.response.status === 400 && trace?.manifest.exchanges.length === 0) trace.recordFailure("local-validation");
5033
5357
  log.error(`request_id=${requestId} ${error.message}`);
5034
- return error.response;
5358
+ const headers = new Headers();
5359
+ for (const name of [
5360
+ "content-type",
5361
+ "retry-after",
5362
+ "x-request-id",
5363
+ "x-github-request-id",
5364
+ "x-copilot-service-request-id"
5365
+ ]) {
5366
+ const value = error.response.headers.get(name);
5367
+ if (value !== null) headers.set(name, value);
5368
+ }
5369
+ return new Response(await error.response.arrayBuffer(), {
5370
+ status: error.response.status,
5371
+ statusText: error.response.statusText,
5372
+ headers
5373
+ });
5035
5374
  }
5036
5375
  if (claudePayload.stream) {
5037
5376
  const trace = c.get("requestTrace");
@@ -5046,7 +5385,10 @@ claudeRoutes.post("/messages", async (c) => {
5046
5385
  await handleClaudeMessageRequest(config, claudePayload, requestSignal, requestId, writeEvent, c.req.raw.headers);
5047
5386
  } catch (error) {
5048
5387
  log.error(`request_id=${requestId} Error during Claude stream request:`, error);
5049
- await writeEvent(translateErrorToClaudeErrorEvent());
5388
+ if (error instanceof UpstreamToolInputError) trace?.recordFailure("invalid-tool-input");
5389
+ const errorEvent = translateErrorToClaudeErrorEvent(error);
5390
+ if (errorEvent.type === "error") errorEvent.error.message += ` (request_id=${requestId})`;
5391
+ await writeEvent(errorEvent);
5050
5392
  } finally {
5051
5393
  trace?.handlerSettled();
5052
5394
  log.info(`request_id=${requestId} stream completed ${Math.round(performance.now() - streamStarted)}ms`);
@@ -5064,6 +5406,7 @@ claudeRoutes.post("/messages", async (c) => {
5064
5406
  } }, 501);
5065
5407
  }
5066
5408
  if (error instanceof HTTPError) {
5409
+ if (error instanceof UpstreamToolInputError) c.get("requestTrace")?.recordFailure("invalid-tool-input");
5067
5410
  const text$1 = await error.response.text().catch(() => "");
5068
5411
  c.set("requestErrorMessage", error.detail ?? text$1.slice(0, 240));
5069
5412
  return new Response(text$1, {
@@ -5154,7 +5497,7 @@ const createServer = (config) => {
5154
5497
  const { host, port } = config;
5155
5498
  const configuredHost = parseAuthority(host.includes(":") && !host.startsWith("[") ? `[${host}]` : host, "http:")?.hostname;
5156
5499
  app.use("*", async (c, next) => {
5157
- const requestId = randomUUID();
5500
+ const requestId = getReplayRequestId() ?? randomUUID();
5158
5501
  c.set("config", config);
5159
5502
  c.set("requestId", requestId);
5160
5503
  c.header("x-copilot-relay-request-id", requestId);
@@ -5163,6 +5506,7 @@ const createServer = (config) => {
5163
5506
  try {
5164
5507
  await next();
5165
5508
  } finally {
5509
+ c.header("x-copilot-relay-request-id", requestId);
5166
5510
  const ms = Math.round(performance.now() - started);
5167
5511
  log.info(formatStatusLog(requestId, c.req.method, c.req.path, c.res.status, ms, c.get("requestErrorMessage")));
5168
5512
  }
@@ -5222,7 +5566,8 @@ const createServer = (config) => {
5222
5566
  requestId: c.get("requestId"),
5223
5567
  error
5224
5568
  });
5225
- return c.json({ error: { message: "Internal server error" } }, 500);
5569
+ c.get("requestTrace")?.recordFailure("internal-error");
5570
+ return c.json({ error: { message: `Internal server error (request_id=${c.get("requestId")})` } }, 500);
5226
5571
  });
5227
5572
  app.get("/", (c) => c.json({
5228
5573
  name: "copilot-relay",
@@ -5279,9 +5624,34 @@ const httpCategory = (status$1) => {
5279
5624
  if (status$1 === 504) return "upstream-timeout-or-HTTP-504";
5280
5625
  return `HTTP-${status$1}`;
5281
5626
  };
5627
+ const settleDiagnostic = async (trace) => {
5628
+ let timer;
5629
+ const deadline = new Promise((resolve$1) => {
5630
+ timer = setTimeout(resolve$1, 1e3);
5631
+ });
5632
+ try {
5633
+ await Promise.race([trace.finished, deadline]);
5634
+ } finally {
5635
+ clearTimeout(timer);
5636
+ }
5637
+ };
5638
+ const failureFromEvidence = (row, diagnostic) => {
5639
+ if ([
5640
+ "probe-timeout",
5641
+ "cancelled",
5642
+ "total-deadline"
5643
+ ].includes(row.detail)) return row.detail;
5644
+ if (diagnostic.refreshes.includes("failure")) return "refresh-failed";
5645
+ if (diagnostic.failure === "invalid-tool-input" || diagnostic.failure === "local-validation") return diagnostic.failure;
5646
+ const lastExchange = diagnostic.exchanges.findLast((exchange) => !exchange.discarded);
5647
+ if (lastExchange?.error && ["HTTP-500", "network-or-invalid-response"].includes(row.detail)) return lastExchange.error === "SyntaxError" ? "malformed-response" : "transport-error";
5648
+ if (diagnostic.failure === "internal-error" || row.detail === "network-or-invalid-response") return "unknown-error";
5649
+ return row.detail;
5650
+ };
5282
5651
  async function probeModels(config, entries, options) {
5283
- console.log("\nDeep availability checks — isolated relay pipeline; not running-daemon health");
5284
- console.log(`Real inference usage: up to ${entries.length} sequential probes, ${options.maxTokens} output tokens each (bounded client retries may add calls).`);
5652
+ console.log(`\nModel check · ${entries.length} ${entries.length === 1 ? "model" : "models"} · real Copilot usage`);
5653
+ console.log("Isolated relay pipeline; not running-daemon health.");
5654
+ console.log(`Up to ${options.maxTokens} output tokens/probe; existing retries may add calls.\n`);
5285
5655
  const controller = new AbortController();
5286
5656
  const interrupt = () => controller.abort();
5287
5657
  process.on("SIGINT", interrupt);
@@ -5294,21 +5664,14 @@ async function probeModels(config, entries, options) {
5294
5664
  };
5295
5665
  const results = [];
5296
5666
  const app = createServer(config);
5297
- const width = entries.reduce((width$1, [id]) => Math.max(width$1, safeId(id, config.copilotToken) ? id.length : 16), 5);
5298
- const reportWidth = width * 2 + 6;
5299
- console.log(`${"MODEL".padEnd(width)} STATUS ${"SENT/REPORTED".padEnd(reportWidth)} LATENCY DETAILS`);
5667
+ const columns = Math.max(40, process.stdout.columns || 80);
5668
+ const width = probeColumnWidth(entries.map(([id]) => safeId(id, config.copilotToken) ? id : "[unsupported ID]"), columns);
5669
+ const color = colorEnabled();
5670
+ if (entries.length) console.log(renderProbeHeader(width));
5300
5671
  try {
5301
5672
  runtimeState.modelCatalog = config.modelCatalog;
5302
5673
  runtimeState.upstreamBaseUrl = config.copilotBaseUrl;
5303
5674
  for (const [id, model] of entries) {
5304
- const row = {
5305
- id,
5306
- status: "NOT_TESTED",
5307
- reported: "-",
5308
- sent: false,
5309
- latency: 0,
5310
- detail: ""
5311
- };
5312
5675
  const efforts = [
5313
5676
  "none",
5314
5677
  "low",
@@ -5321,6 +5684,18 @@ async function probeModels(config, entries, options) {
5321
5684
  const maxTokens = Math.min(options.maxTokens, model.limits?.max_output_tokens ?? options.maxTokens, model.limits?.max_non_streaming_output_tokens ?? options.maxTokens);
5322
5685
  const timeoutMs = Math.min(options.timeoutMs, config.upstreamTimeoutMs > 0 ? config.upstreamTimeoutMs : Infinity);
5323
5686
  const endpoint = shouldUseNativeMessages(config, id) ? "/v1/messages" : shouldUseResponsesApiForModel(id) ? "/responses" : "/chat/completions";
5687
+ const row = {
5688
+ id: safeId(id, config.copilotToken) ? id : "[unsupported ID]",
5689
+ status: "NOT_TESTED",
5690
+ reported: "-",
5691
+ sent: false,
5692
+ endpoint,
5693
+ latency: 0,
5694
+ detail: "",
5695
+ maxTokens,
5696
+ effort,
5697
+ unverified: model.reasoningEfforts === void 0 || model.supportedEndpoints === void 0
5698
+ };
5324
5699
  if (controller.signal.aborted || total.aborted) row.detail = controller.signal.aborted ? "cancelled" : "total-deadline";
5325
5700
  else if (!safeId(id, config.copilotToken) || normalizeCopilotModelId(id) !== id) {
5326
5701
  row.status = "SKIPPED";
@@ -5346,23 +5721,33 @@ async function probeModels(config, entries, options) {
5346
5721
  };
5347
5722
  const started = performance.now();
5348
5723
  row.sent = true;
5724
+ let trace;
5725
+ let completedFetch = false;
5349
5726
  try {
5350
- const response = await withoutLogging(() => app.fetch(new Request(`http://localhost${config.port ? `:${config.port}` : ""}/v1/messages`, {
5351
- method: "POST",
5352
- headers: { "content-type": "application/json" },
5353
- signal,
5354
- body: JSON.stringify({
5355
- model: id,
5356
- stream: false,
5357
- max_tokens: maxTokens,
5358
- output_config: { effort },
5359
- messages: [{
5360
- role: "user",
5361
- content: "Reply with OK only."
5362
- }]
5363
- })
5364
- })));
5365
- const body = await response.json().catch(() => void 0);
5727
+ const { response, body } = await withoutLogging(() => withTraceObserver((value) => {
5728
+ trace = value;
5729
+ }, async () => {
5730
+ const response$1 = await app.fetch(new Request(`http://localhost${config.port ? `:${config.port}` : ""}/v1/messages`, {
5731
+ method: "POST",
5732
+ headers: { "content-type": "application/json" },
5733
+ signal,
5734
+ body: JSON.stringify({
5735
+ model: id,
5736
+ stream: false,
5737
+ max_tokens: maxTokens,
5738
+ output_config: { effort },
5739
+ messages: [{
5740
+ role: "user",
5741
+ content: "Reply with OK only."
5742
+ }]
5743
+ })
5744
+ }));
5745
+ return {
5746
+ response: response$1,
5747
+ body: await response$1.json().catch(() => void 0)
5748
+ };
5749
+ }));
5750
+ completedFetch = true;
5366
5751
  row.status = "FAIL";
5367
5752
  if (signal.aborted) {
5368
5753
  row.status = controller.signal.aborted || total.aborted ? "NOT_TESTED" : "FAIL";
@@ -5385,7 +5770,7 @@ async function probeModels(config, entries, options) {
5385
5770
  row.status = "INCOMPLETE";
5386
5771
  const tokens = record$1(body.usage) ? body.usage.output_tokens : void 0;
5387
5772
  row.detail = typeof tokens === "number" && Number.isSafeInteger(tokens) && tokens > 0 ? "reachable-output-budget-exhausted" : "output-budget-exhausted-usage-unreported";
5388
- } else if (body.stop_reason !== "end_turn" || body.content.some((part) => record$1(part) && part.type !== "text" && part.type !== "thinking")) row.detail = "refusal-or-unexpected-completion";
5773
+ } else if (body.stop_reason !== "end_turn" || body.content.some((part) => record$1(part) && part.type !== "text" && part.type !== "thinking")) row.detail = body.stop_reason === "refusal" ? "refusal" : "refusal-or-unexpected-completion";
5389
5774
  else if (body.content.some((part) => record$1(part) && part.type === "text" && typeof part.text === "string" && part.text.trim())) {
5390
5775
  row.status = "PASS";
5391
5776
  row.detail = "completed-text";
@@ -5396,11 +5781,19 @@ async function probeModels(config, entries, options) {
5396
5781
  row.detail = controller.signal.aborted ? "cancelled" : total.aborted ? "total-deadline" : signal.aborted ? "probe-timeout" : "network-or-invalid-response";
5397
5782
  }
5398
5783
  row.latency = Math.round(performance.now() - started);
5784
+ if (trace) {
5785
+ if (completedFetch) await settleDiagnostic(trace);
5786
+ row.diagnostic = trace.diagnosticSnapshot();
5787
+ if (row.reported !== "-" && !row.diagnostic.reportedModel) row.reported = "unreported";
5788
+ if (row.status === "FAIL") row.detail = failureFromEvidence(row, row.diagnostic);
5789
+ }
5399
5790
  }
5400
- row.detail += ` tokens=${maxTokens} effort=${effort}${model.reasoningEfforts === void 0 ? "(unverified)" : ""}${model.supportedEndpoints === void 0 ? " endpoints=unverified" : ""}`;
5791
+ if (!safeId(id, config.copilotToken)) row.id = "[unsupported ID]";
5401
5792
  results.push(row);
5402
- const displayId = safeId(id, config.copilotToken) ? id : "[unsupported ID]";
5403
- console.log(scrubSensitiveUrls(`${displayId.padEnd(width)} ${row.status.padEnd(10)} ${(row.sent ? `${id}/${row.reported}` : "-/-").padEnd(reportWidth)} ${`${row.latency}ms`.padEnd(8)} ${row.detail}`));
5793
+ for (const line of renderProbeRow(row, width, color, columns)) console.log(scrubSensitiveUrls(line));
5794
+ if (options.details) for (const line of renderProbeDetails(row, row.diagnostic)) console.log(line);
5795
+ else if (row.status !== "PASS" && row.diagnostic) console.log(` request_id=${row.diagnostic.requestId}`);
5796
+ if (row.status !== "PASS" && row.sent) withoutConsoleLogging(() => log.info(`request_id=${row.diagnostic?.requestId ?? "unknown"} model_probe model=${row.id} status=${row.status} reason=${row.detail}`));
5404
5797
  }
5405
5798
  } finally {
5406
5799
  for (const key of [
@@ -5414,9 +5807,11 @@ async function probeModels(config, entries, options) {
5414
5807
  process.off("SIGINT", interrupt);
5415
5808
  process.off("SIGTERM", interrupt);
5416
5809
  }
5417
- const count = (status$1) => results.filter((row) => row.status === status$1).length;
5418
- console.log(`\nSummary: ${count("PASS")} passed, ${count("FAIL")} failed, ${count("INCOMPLETE")} incomplete, ${count("SKIPPED")} skipped, ${count("NOT_TESTED")} not tested`);
5419
- console.log("PASS means completed text from the reported model, not tool/effort coverage or running-daemon health.");
5810
+ console.log(`\n${renderProbeSummary(results)}`);
5811
+ if (results.some((row) => row.unverified)) console.log("* Effort or endpoint metadata is unverified.");
5812
+ const hints = new Set(results.filter((row) => row.status !== "PASS").map((row) => probeHint(row.detail)).filter(Boolean));
5813
+ for (const hint of hints) console.log(hint);
5814
+ if (!options.details && results.some((row) => row.status !== "PASS" && row.sent)) console.log("Use --details for request evidence; another deep run consumes usage.");
5420
5815
  return controller.signal.aborted ? 130 : results.length > 0 && results.every((row) => row.status === "PASS") ? 0 : 2;
5421
5816
  }
5422
5817
 
@@ -5432,6 +5827,10 @@ const models = defineCommand({
5432
5827
  type: "boolean",
5433
5828
  description: "Send real inference probes through an isolated relay pipeline; consumes Copilot usage."
5434
5829
  },
5830
+ details: {
5831
+ type: "boolean",
5832
+ description: "Show safe request, route and replay evidence for each probe (requires --deep)."
5833
+ },
5435
5834
  model: {
5436
5835
  type: "string",
5437
5836
  description: "Test only this exact upstream ID (requires --deep)."
@@ -5462,29 +5861,36 @@ const models = defineCommand({
5462
5861
  if (!/^\d+$/.test(value) || !Number.isSafeInteger(number) || number <= 0 || number > 2147483) throw new Error("invalid option");
5463
5862
  return number;
5464
5863
  };
5465
- if (!args.deep && [
5864
+ if (!args.deep && (args.details || [
5466
5865
  args.model,
5467
5866
  args.effort,
5468
5867
  args["max-tokens"],
5469
5868
  args.timeout,
5470
5869
  args["total-timeout"]
5471
- ].some((value) => value !== void 0)) throw new Error("deep required");
5870
+ ].some((value) => value !== void 0))) throw new Error("deep required");
5472
5871
  if (args.effort !== void 0 && !isReasoningEffort(args.effort)) throw new Error("invalid effort");
5473
5872
  const probeOptions = {
5474
5873
  maxTokens: positive(args["max-tokens"], 4096),
5475
5874
  timeoutMs: positive(args.timeout, 30) * 1e3,
5476
5875
  totalTimeoutMs: positive(args["total-timeout"], 300) * 1e3,
5477
- effort: args.effort
5876
+ effort: args.effort,
5877
+ details: Boolean(args.details)
5478
5878
  };
5479
- failure = "Could not load relay configuration";
5480
- const appConfig = await readAppConfig();
5481
- setLogLevel(appConfig.logLevel);
5482
- registerSensitiveOrigin(appConfig.copilotBaseUrl);
5483
- const config = readProxyConfig(appConfig);
5484
- failure = "Could not authenticate with GitHub Copilot";
5485
- await setupProxyAuth(config);
5486
- failure = "Could not fetch upstream model catalog";
5487
- const catalog = await loadCopilotModelCatalog(config);
5879
+ const prepare = async () => {
5880
+ failure = "Could not load relay configuration";
5881
+ const appConfig = await readAppConfig();
5882
+ setLogLevel(appConfig.logLevel);
5883
+ registerSensitiveOrigin(appConfig.copilotBaseUrl);
5884
+ const config$1 = readProxyConfig(appConfig);
5885
+ failure = "Could not authenticate with GitHub Copilot";
5886
+ await setupProxyAuth(config$1, args.deep ? { onDeviceCode: (url, code) => console.error(`Sign in: open ${terminalText(url)} and enter ${terminalText(code)}.`) } : void 0);
5887
+ failure = "Could not fetch upstream model catalog";
5888
+ return {
5889
+ config: config$1,
5890
+ catalog: await loadCopilotModelCatalog(config$1)
5891
+ };
5892
+ };
5893
+ const { config, catalog } = args.deep ? await withoutConsoleLogging(prepare) : await prepare();
5488
5894
  if (args.deep) {
5489
5895
  failure = "Selected model is not advertised by upstream";
5490
5896
  if (args.model !== void 0 && !catalog.models.has(args.model)) throw new Error("unknown model");
@@ -5504,6 +5910,8 @@ const models = defineCommand({
5504
5910
  }
5505
5911
  log.error(`${failure}: ${detail}`);
5506
5912
  process.exitCode = 1;
5913
+ } finally {
5914
+ await flushLogs();
5507
5915
  }
5508
5916
  }
5509
5917
  });
@@ -5977,6 +6385,7 @@ const replayCapture = async (target) => {
5977
6385
  return operation;
5978
6386
  };
5979
6387
  await withoutLogging(() => withRuntimeState(runtime, () => withRecordedTransport({
6388
+ requestId: manifest.requestId,
5980
6389
  fetch: async (request) => {
5981
6390
  const operation = take("fetch");
5982
6391
  if (operation.kind !== "fetch") throw new Error("Unrecorded replay attempt");
@@ -6104,23 +6513,45 @@ const commandArgs = (command) => {
6104
6513
  if (tokens.join(" ") !== command.trim().replace(/\s+(?=(?:[^"']*["'][^"']*["'])*[^"']*$)/g, " ")) return [];
6105
6514
  return tokens.map((token) => token.replace(/^(["'])(.*)\1$/, "$2").replaceAll("\\", "/"));
6106
6515
  };
6107
- const isRelayStartProcess = (command, cwd) => {
6108
- const args = commandArgs(command);
6109
- const executable = posix.basename(args[0] ?? "");
6110
- const isDaemonCommand = (value) => value === "start" || value === "restart";
6111
- if (/^copilot-relay(?:\.cmd|\.exe)?$/i.test(executable)) return isDaemonCommand(args[1]);
6112
- if (!/^node(?:\.exe)?$/i.test(executable)) return false;
6516
+ const isDaemonCommand = (value) => value === "start" || value === "restart";
6517
+ const nodeDaemonEntry = (args) => {
6518
+ if (!/^node(?:\.exe)?$/i.test(posix.basename(args[0] ?? ""))) return void 0;
6113
6519
  let index = 1;
6114
6520
  if (args[index] === "--import" && args[index + 1] === "tsx") index += 2;
6115
6521
  else if (args[index] === "--import=tsx") index++;
6116
6522
  const entry = args[index];
6117
- if (!entry || !isDaemonCommand(args[index + 1])) return false;
6118
- if (posix.basename(entry) === "copilot-relay") return true;
6523
+ return entry && isDaemonCommand(args[index + 1]) ? entry : void 0;
6524
+ };
6525
+ const resolveEntry = (entry, cwd) => {
6119
6526
  const normalizedCwd = cwd?.replaceAll("\\", "/");
6120
6527
  const absolute = posix.isAbsolute(entry) || /^[A-Za-z]:\//.test(entry);
6121
- if (!absolute && !normalizedCwd) return false;
6122
- const resolved = posix.normalize(absolute ? entry : `${normalizedCwd}/${entry}`);
6123
- return /(?:^|\/)copilot-relay(?:-[A-Za-z0-9._-]+)?\/(?:dist\/main\.js|src\/main\.ts)$/.test(resolved);
6528
+ if (!absolute && !normalizedCwd) return void 0;
6529
+ return posix.normalize(absolute ? entry : `${normalizedCwd}/${entry}`);
6530
+ };
6531
+ const isRelayStartProcess = (command, cwd) => {
6532
+ const args = commandArgs(command);
6533
+ if (/^copilot-relay(?:\.cmd|\.exe)?$/i.test(posix.basename(args[0] ?? ""))) return isDaemonCommand(args[1]);
6534
+ const entry = nodeDaemonEntry(args);
6535
+ if (!entry) return false;
6536
+ if (posix.basename(entry) === "copilot-relay") return true;
6537
+ const resolved = resolveEntry(entry, cwd);
6538
+ return resolved !== void 0 && /(?:^|\/)copilot-relay(?:-[A-Za-z0-9._-]+)?\/(?:dist\/main\.js|src\/main\.ts)$/.test(resolved);
6539
+ };
6540
+ const packagedEntryCandidate = (command, cwd) => {
6541
+ const entry = nodeDaemonEntry(commandArgs(command));
6542
+ const resolved = entry === void 0 ? void 0 : resolveEntry(entry, cwd);
6543
+ return resolved && /\/(?:dist\/main\.js|src\/main\.ts)$/.test(resolved) ? resolved : void 0;
6544
+ };
6545
+ const packageEntryProof = async (entry) => {
6546
+ try {
6547
+ if (!(await fs.stat(entry)).isFile()) return "nonrelay";
6548
+ const canonical = (await fs.realpath(entry)).replaceAll("\\", "/");
6549
+ if (!/\/(?:dist\/main\.js|src\/main\.ts)$/.test(canonical)) return "nonrelay";
6550
+ const manifest = JSON.parse(await fs.readFile(posix.join(posix.dirname(posix.dirname(canonical)), "package.json"), "utf8"));
6551
+ return typeof manifest === "object" && manifest !== null && "name" in manifest && manifest.name === "copilot-relay" ? "relay" : "nonrelay";
6552
+ } catch {
6553
+ return "nonrelay";
6554
+ }
6124
6555
  };
6125
6556
  var RelayInspectionError = class extends Error {
6126
6557
  constructor(pid) {
@@ -6154,6 +6585,8 @@ const flatInvocation = (command) => {
6154
6585
  };
6155
6586
  const verifyCommand = async (command, cwd) => {
6156
6587
  if (isRelayStartProcess(command, cwd)) return "relay";
6588
+ const packaged = packagedEntryCandidate(command, cwd);
6589
+ if (packaged && await packageEntryProof(packaged) === "relay") return "relay";
6157
6590
  const flat = flatInvocation(command);
6158
6591
  if (!flat) return "nonrelay";
6159
6592
  if (!cwd) return "unknown";
@@ -6204,16 +6637,6 @@ const processState = (pid) => {
6204
6637
  }
6205
6638
  };
6206
6639
  const isProcessAlive = (pid) => processState(pid) !== "gone";
6207
- /**
6208
- * Full pid-file record, for callers that need the listening address or start
6209
- * time rather than just the pid.
6210
- *
6211
- * Deliberately tolerant: a corrupt or half-written pid file yields undefined
6212
- * rather than throwing, because `status` must be able to report "not running"
6213
- * instead of crashing on a file the user may have been mid-edit on.
6214
- *
6215
- * Returns undefined for the legacy bare-pid format, which carries no address.
6216
- */
6217
6640
  const readRelayPidFileEntry = async () => {
6218
6641
  try {
6219
6642
  const trimmed = (await fs.readFile(paths.pidPath, "utf8")).trim();
@@ -6416,7 +6839,7 @@ const findRelayProcesses = async (config, snapshot) => {
6416
6839
  if (typeof config.port === "number" && Number.isInteger(config.port) && config.port > 0 && config.port <= 65535) for (const pid of await getPortListenerPids(config.port)) candidates.add(pid);
6417
6840
  for (const { command, pid } of await getProcessList()) {
6418
6841
  if (pid === process.pid) continue;
6419
- if (isRelayStartProcess(command) || flatInvocation(command) || isRelayStartProcess(command, "/copilot-relay")) candidates.add(pid);
6842
+ if (isRelayStartProcess(command) || flatInvocation(command) || isRelayStartProcess(command, "/copilot-relay") || packagedEntryCandidate(command)) candidates.add(pid);
6420
6843
  }
6421
6844
  const relays = [];
6422
6845
  for (const pid of candidates) {
@@ -6865,20 +7288,20 @@ const renderConfig = (config, running) => {
6865
7288
  * Pure state-to-lines mapping, kept free of IO so it can be tested directly
6866
7289
  * without spawning a relay or opening a socket.
6867
7290
  */
6868
- const renderStatus = (status$1) => {
7291
+ const renderStatus = (status$1, color = false) => {
6869
7292
  const lines = [`copilot-relay ${status$1.version}`];
6870
7293
  if (!status$1.running) {
6871
- lines.push(" process not running", ` log ${status$1.logPath}`, ` config ${status$1.configPath}`, ...renderConfig(status$1.config, false), "", " Start it with: copilot-relay start");
7294
+ lines.push(` process ${colorText("not running", "warning", color)}`, ` log ${status$1.logPath}`, ` config ${status$1.configPath}`, ...renderConfig(status$1.config, false), "", " Start it with: copilot-relay start");
6872
7295
  return lines;
6873
7296
  }
6874
- lines.push(` process running (pid ${status$1.pid}, up ${formatUptime(status$1.startedAt)})`);
6875
- lines.push(status$1.daemonVersion === void 0 ? " version unknown (daemon predates version reporting — restart to report it)" : hasVersionMismatch(status$1) ? ` version ${status$1.daemonVersion} — MISMATCH, ${status$1.version} is installed` : ` version ${status$1.daemonVersion}`);
7297
+ lines.push(` process ${colorText("running", "good", color)} (pid ${status$1.pid}, up ${formatUptime(status$1.startedAt)})`);
7298
+ lines.push(status$1.daemonVersion === void 0 ? ` version ${colorText("unknown", "muted", color)} (daemon predates version reporting — restart to report it)` : hasVersionMismatch(status$1) ? ` version ${status$1.daemonVersion} — ${colorText("MISMATCH", "warning", color)}, ${status$1.version} is installed` : ` version ${status$1.daemonVersion}`);
6876
7299
  lines.push(` listening ${status$1.baseUrl}`);
6877
7300
  const health = status$1.health;
6878
- lines.push(health?.ok ? ` health ok (${health.ms}ms)` : ` health FAILED${health?.detail ? ` — ${health.detail}` : ""}`);
6879
- lines.push(status$1.models?.length ? ` models ${status$1.models.join(", ")}` : " models unavailable");
6880
- if (status$1.deep) lines.push(status$1.deep.ok ? ` upstream ok (${status$1.deep.ms}ms) — ${status$1.deep.detail ?? "end-to-end Copilot round trip"}` : ` upstream FAILED${status$1.deep.detail ? ` — ${status$1.deep.detail}` : ""}`);
6881
- else lines.push(" upstream not checked (use --deep)");
7301
+ lines.push(health?.ok ? ` health ${colorText("ok", "good", color)} (${health.ms}ms)` : ` health ${colorText("FAILED", "bad", color)}${health?.detail ? ` — ${health.detail}` : ""}`);
7302
+ lines.push(status$1.models?.length ? ` models ${status$1.models.join(", ")}` : ` models ${colorText("unavailable", "warning", color)}`);
7303
+ if (status$1.deep) lines.push(status$1.deep.ok ? ` upstream ${colorText("ok", "good", color)} (${status$1.deep.ms}ms) — ${status$1.deep.detail ?? "end-to-end Copilot round trip"}` : ` upstream ${colorText("FAILED", "bad", color)}${status$1.deep.detail ? ` — ${status$1.deep.detail}` : ""}`);
7304
+ else lines.push(` upstream ${colorText("not checked", "muted", color)} (use --deep)`);
6882
7305
  lines.push(` log ${status$1.logPath}`, ` config ${status$1.configPath}`, ...renderConfig(status$1.config, true));
6883
7306
  if (status$1.health?.ok && status$1.deep && !status$1.deep.ok) lines.push("", " The relay is listening but cannot reach Copilot.", " Try: copilot-relay auth");
6884
7307
  if (hasVersionMismatch(status$1)) lines.push("", ` The running relay is ${status$1.daemonVersion}; ${status$1.version} is installed.`, " Restart it to serve the installed version: copilot-relay restart");
@@ -7060,7 +7483,7 @@ const status = defineCommand({
7060
7483
  return;
7061
7484
  }
7062
7485
  if (args.json) console.log(scrubSensitiveUrls(JSON.stringify(result, null, 2)));
7063
- else console.log(scrubSensitiveUrls(renderStatus(result).join("\n")));
7486
+ else console.log(scrubSensitiveUrls(renderStatus(result, colorEnabled()).join("\n")));
7064
7487
  process.exitCode = resolveExitCode(result);
7065
7488
  }
7066
7489
  });