@byok-sdk/client 0.15.0 → 0.16.0

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.
@@ -2379,7 +2379,7 @@ function resolveLocalAgentReleaseIdentity(input) {
2379
2379
 
2380
2380
  // src/bin/official-release.ts
2381
2381
  var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
2382
- version: "0.15.0"
2382
+ version: "0.16.0"
2383
2383
  });
2384
2384
 
2385
2385
  // src/bin/config.ts
@@ -14266,6 +14266,10 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
14266
14266
  this.revokedTasks.add(taskId);
14267
14267
  });
14268
14268
  }
14269
+ /** The same terminal classification used by operator receipts and archival. */
14270
+ terminalRecords() {
14271
+ return Object.freeze(this.records().filter((record4) => this.isTerminalEvidence(record4.taskId)));
14272
+ }
14269
14273
  isTerminalEvidence(taskId) {
14270
14274
  return this.revokedTasks.has(taskId) || this.dispositionByTask.get(taskId)?.outcome === "refused";
14271
14275
  }
@@ -14357,7 +14361,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
14357
14361
  async archiveTerminalRecords(archiveDirectory) {
14358
14362
  return this.exclusive(async () => {
14359
14363
  if (!path14__default.isAbsolute(archiveDirectory)) throw new AgentMessageOutboxError("message archive directory must be absolute");
14360
- const terminal = this.records().filter((record4) => this.isTerminalEvidence(record4.taskId));
14364
+ const terminal = this.terminalRecords();
14361
14365
  if (terminal.length === 0) return void 0;
14362
14366
  await ensureSecureDir(archiveDirectory);
14363
14367
  const archivePath = path14__default.join(archiveDirectory, `message-terminal-${randomUUID()}.jsonl`);
@@ -22910,1004 +22914,1192 @@ async function repairDeviceEnrollmentMetadata(config, input) {
22910
22914
  throw new DeviceMetadataRepairError(failureCode);
22911
22915
  }
22912
22916
  }
22913
-
22914
- // src/bin/format.ts
22915
- function quote(text) {
22916
- return JSON.stringify(text);
22917
+ function auditLogPath(storeDir) {
22918
+ return path14__default.join(storeDir, "audit.jsonl");
22917
22919
  }
22918
- function redactedByteCountPlaceholder(text) {
22919
- return `[redacted: ${Buffer.byteLength(text, "utf8")} bytes]`;
22920
+ var AUDIT_LOG_MODE = 384;
22921
+ var AUDIT_STORE_DIR_MODE = 448;
22922
+ var MAX_AUDIT_LOG_BYTES = 10 * 1024 * 1024;
22923
+ var AUDIT_LOG_TRIM_TARGET_LINES = 5e3;
22924
+ var MAX_LIVE_TASK_ANCHORS = 500;
22925
+ function byteSize(text) {
22926
+ return text === void 0 ? void 0 : Buffer.byteLength(text, "utf8");
22927
+ }
22928
+ function valueByteSize(value) {
22929
+ if (value === void 0) return void 0;
22930
+ try {
22931
+ const json = JSON.stringify(value);
22932
+ return json === void 0 ? void 0 : Buffer.byteLength(json, "utf8");
22933
+ } catch {
22934
+ return void 0;
22935
+ }
22936
+ }
22937
+ function placeholderFor(size, spilled) {
22938
+ const suffix = spilled ? ", spilled" : "";
22939
+ return size === void 0 ? `[redacted${suffix}]` : `[redacted: ${size} bytes${suffix}]`;
22920
22940
  }
22921
22941
  var STABLE_GIT_ERROR_CATEGORIES = new Set(GIT_ERROR_CATEGORIES);
22922
22942
  function stableGitErrorCategory(value) {
22923
- return value !== void 0 && STABLE_GIT_ERROR_CATEGORIES.has(value) ? value : void 0;
22943
+ return typeof value === "string" && STABLE_GIT_ERROR_CATEGORIES.has(value) ? value : void 0;
22924
22944
  }
22925
- function formatAgentEvent(event) {
22945
+ function gitCount(value) {
22946
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
22947
+ }
22948
+ function gitDirty(value) {
22949
+ const dirty = asRecord2(value);
22950
+ const staged = gitCount(dirty.staged);
22951
+ const unstaged = gitCount(dirty.unstaged);
22952
+ const untracked = gitCount(dirty.untracked);
22953
+ const conflicted = gitCount(dirty.conflicted);
22954
+ return staged !== void 0 && unstaged !== void 0 && untracked !== void 0 && conflicted !== void 0 ? { staged, unstaged, untracked, conflicted } : void 0;
22955
+ }
22956
+ function redactAgentEvent(event) {
22926
22957
  switch (event.type) {
22927
22958
  case "progress":
22928
- return `progress: ${quote(event.text)}`;
22959
+ return { type: "progress", textSize: byteSize(event.text) };
22929
22960
  case "tool_use":
22930
- return `tool_use: ${event.tool}`;
22961
+ return event.spill !== void 0 ? { type: "tool_use", tool: event.tool, inputSize: event.spill.totalBytes, inputSpilled: true } : { type: "tool_use", tool: event.tool, inputSize: valueByteSize(event.input) };
22931
22962
  case "tool_result":
22932
- return `tool_result: ${event.tool}`;
22963
+ return event.spill !== void 0 ? { type: "tool_result", tool: event.tool, outputSize: event.spill.totalBytes, outputSpilled: true } : { type: "tool_result", tool: event.tool, outputSize: valueByteSize(event.output) };
22933
22964
  case "artifact":
22934
- return `artifact: ${event.name} (${event.contentType})`;
22965
+ return { type: "artifact", name: event.name, contentType: event.contentType };
22935
22966
  case "needs_approval":
22936
- return `needs_approval: ${quote(event.summary)}`;
22967
+ return { type: "needs_approval", summarySize: byteSize(event.summary) };
22937
22968
  case "turn_end":
22938
- return "turn_end";
22969
+ return { type: "turn_end" };
22939
22970
  case "error":
22940
- return `error: ${quote(event.message)}`;
22941
- case "usage": {
22942
- const parts = [];
22943
- if (event.inputTokens !== void 0) parts.push(`in=${event.inputTokens}`);
22944
- if (event.outputTokens !== void 0) parts.push(`out=${event.outputTokens}`);
22945
- if (event.totalTokens !== void 0) parts.push(`total=${event.totalTokens}`);
22946
- return `usage: ${parts.length ? parts.join(" ") : "(no fields reported)"}`;
22947
- }
22971
+ return { type: "error", messageSize: byteSize(event.message) };
22972
+ case "usage":
22973
+ return {
22974
+ type: "usage",
22975
+ inputTokens: event.inputTokens,
22976
+ cachedInputTokens: event.cachedInputTokens,
22977
+ outputTokens: event.outputTokens,
22978
+ reasoningTokens: event.reasoningTokens,
22979
+ totalTokens: event.totalTokens
22980
+ };
22948
22981
  }
22949
22982
  }
22950
- function formatDaemonEventLine(event, options = {}) {
22951
- const prefix = `[${event.ts}]`;
22983
+ function redactForAudit(event) {
22984
+ const base = { kind: event.kind, ts: event.ts };
22952
22985
  switch (event.kind) {
22953
22986
  case "offered":
22954
- return `${prefix} offered taskId=${event.taskId}${event.runtime ? ` runtime=${event.runtime}` : ""}`;
22987
+ return { ...base, taskId: event.taskId, runtime: event.runtime };
22955
22988
  case "claimed":
22956
- return `${prefix} claimed taskId=${event.taskId}${event.claimedRuntime !== void 0 ? ` claimedRuntime=${event.claimedRuntime}` : ""}`;
22989
+ return { ...base, taskId: event.taskId, claimedRuntime: event.claimedRuntime };
22957
22990
  case "started":
22958
- return `${prefix} started taskId=${event.taskId}`;
22991
+ return { ...base, taskId: event.taskId };
22959
22992
  case "progress":
22960
- return `${prefix} progress taskId=${event.taskId} ${formatAgentEvent(event.event)}`;
22993
+ return { ...base, taskId: event.taskId, event: redactAgentEvent(event.event) };
22961
22994
  case "artifact":
22962
- return `${prefix} artifact taskId=${event.taskId} name=${event.name} contentType=${event.contentType}`;
22963
- case "awaiting-approval": {
22964
- const summary = options.redactApprovalSummary ? redactedByteCountPlaceholder(event.summary) : event.summary;
22965
- return `${prefix} awaiting-approval taskId=${event.taskId}${event.approvalId ? ` approvalId=${event.approvalId}` : ""} summary=${quote(summary)}`;
22966
- }
22995
+ return {
22996
+ ...base,
22997
+ taskId: event.taskId,
22998
+ name: event.name,
22999
+ contentType: event.contentType,
23000
+ // `inline` is base64 file bytes (up to the 64KB inline cap) — never
23001
+ // persisted. `blobRef.url` is a presigned URL — itself effectively a
23002
+ // time-limited bearer credential for the actual bytes, so it's
23003
+ // dropped too; only the safe pointer/size metadata survives.
23004
+ inlineSize: byteSize(event.inline),
23005
+ blobRef: event.blobRef ? {
23006
+ blobId: event.blobRef.blobId,
23007
+ contentHash: event.blobRef.contentHash,
23008
+ size: event.blobRef.size,
23009
+ contentType: event.blobRef.contentType
23010
+ } : void 0
23011
+ };
23012
+ case "awaiting-approval":
23013
+ return { ...base, taskId: event.taskId, summarySize: byteSize(event.summary) };
22967
23014
  case "completed":
22968
- return `${prefix} completed taskId=${event.taskId} sessionRef=${event.sessionRef} summary=${quote(event.summary)}`;
23015
+ return { ...base, taskId: event.taskId, summarySize: byteSize(event.summary), sessionRef: event.sessionRef };
22969
23016
  case "failed":
22970
- return `${prefix} failed taskId=${event.taskId} retryable=${event.retryable}${event.preClaim ? " preClaim=true" : ""} reason=${quote(event.reason)}`;
23017
+ return {
23018
+ ...base,
23019
+ taskId: event.taskId,
23020
+ reasonSize: byteSize(event.reason),
23021
+ retryable: event.retryable,
23022
+ preClaim: event.preClaim
23023
+ };
22971
23024
  case "cancelled":
22972
- return `${prefix} cancelled taskId=${event.taskId}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
23025
+ return { ...base, taskId: event.taskId, reasonSize: byteSize(event.reason) };
22973
23026
  case "connection":
22974
- return `${prefix} connection state=${event.state}`;
23027
+ return { ...base, state: event.state };
22975
23028
  case "paired":
22976
- return `${prefix} paired deviceId=${event.deviceId}`;
23029
+ return { ...base, deviceId: event.deviceId };
22977
23030
  case "unpaired":
22978
- return `${prefix} unpaired`;
23031
+ return { ...base };
22979
23032
  case "runtimes-detected":
22980
- return `${prefix} runtimes-detected ids=${event.runtimes.map((r) => r.id).join(",") || "(none)"}`;
23033
+ return { ...base, runtimes: event.runtimes };
22981
23034
  case "shutdown-requested":
22982
- return `${prefix} shutdown-requested reason=${quote(event.reason)}`;
23035
+ return { ...base, reason: event.reason };
22983
23036
  case "shutdown-complete":
22984
- return `${prefix} shutdown-complete reason=${quote(event.reason)}${event.undeliveredOutboxCount !== void 0 ? ` undeliveredOutboxCount=${event.undeliveredOutboxCount}` : ""}`;
23037
+ return { ...base, reason: event.reason, undeliveredOutboxCount: event.undeliveredOutboxCount };
22985
23038
  case "stale-approval-decision":
22986
- return `${prefix} stale-approval-decision taskId=${event.taskId} decision=${event.decision}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
23039
+ return { ...base, taskId: event.taskId, decision: event.decision, reasonSize: byteSize(event.reason) };
22987
23040
  case "runtime-disposal-failed":
22988
- return `${prefix} runtime-disposal-failed taskId=${event.taskId} runtime=${event.runtimeId} stage=${event.stage} reason=${quote(event.reason)}`;
22989
- case "git-workspace": {
22990
- const parts = [
22991
- `${prefix} git-workspace taskId=${event.taskId}`,
22992
- `workspaceId=${event.workspaceId}`,
22993
- `phase=${event.phase}`,
22994
- event.headChanged !== void 0 ? `headChanged=${event.headChanged}` : void 0,
22995
- event.commitsSinceBaseline !== void 0 ? `commits=${event.commitsSinceBaseline}` : void 0,
22996
- event.dirty ? `dirty=${event.dirty.staged}/${event.dirty.unstaged}/${event.dirty.untracked}/${event.dirty.conflicted}` : void 0,
22997
- stableGitErrorCategory(event.errorCategory) ? `errorCategory=${stableGitErrorCategory(event.errorCategory)}` : void 0
22998
- ].filter((part) => part !== void 0);
22999
- return parts.join(" ");
23000
- }
23001
- case "device-assertion": {
23002
- const parts = event.result === "issued" ? [
23003
- `${prefix} device-assertion result=issued`,
23004
- `audience=${quote(event.audience)}`,
23005
- `jti=${event.jti}`,
23006
- `expiresAt=${event.expiresAt}`
23007
- ] : [
23008
- `${prefix} device-assertion result=denied`,
23009
- `reason=${event.reason}`,
23010
- event.audienceSize !== void 0 ? `audienceSize=${event.audienceSize}` : void 0
23011
- ].filter((part) => part !== void 0);
23012
- return parts.join(" ");
23013
- }
23041
+ return { ...base, taskId: event.taskId, runtimeId: event.runtimeId, stage: event.stage, reason: event.reason };
23042
+ case "device-assertion":
23043
+ return event.result === "issued" ? {
23044
+ ...base,
23045
+ result: "issued",
23046
+ audience: event.audience,
23047
+ jti: event.jti,
23048
+ expiresAt: event.expiresAt
23049
+ } : { ...base, result: "denied", reason: event.reason, audienceSize: event.audienceSize };
23050
+ case "git-workspace":
23051
+ return {
23052
+ ...base,
23053
+ taskId: event.taskId,
23054
+ workspaceId: event.workspaceId,
23055
+ phase: event.phase,
23056
+ headChanged: event.headChanged,
23057
+ commitsSinceBaseline: event.commitsSinceBaseline,
23058
+ dirty: event.dirty,
23059
+ errorCategory: stableGitErrorCategory(event.errorCategory)
23060
+ };
23014
23061
  }
23015
23062
  }
23016
- function formatTaskLine(task) {
23017
- const git = "git" in task ? task.git : void 0;
23018
- const gitStatus = git ? [
23019
- `git=${git.phase}`,
23020
- git.commitsSinceBaseline !== void 0 ? `commits=${git.commitsSinceBaseline}` : void 0,
23021
- git.dirty ? `dirty=${git.dirty.staged}/${git.dirty.unstaged}/${git.dirty.untracked}/${git.dirty.conflicted}` : void 0
23022
- ].filter((part) => part !== void 0).join(" ") : void 0;
23023
- const parts = [
23024
- task.taskId,
23025
- task.state,
23026
- task.runtime ? `runtime=${task.runtime}` : void 0,
23027
- gitStatus,
23028
- task.claimedRuntime !== void 0 ? `claimedRuntime=${task.claimedRuntime}` : void 0,
23029
- `updatedAt=${task.updatedAt}`,
23030
- task.sessionRef ? `sessionRef=${task.sessionRef}` : void 0,
23031
- task.declined ? "declined=true" : void 0,
23032
- task.summary ? `summary=${quote(task.summary)}` : void 0
23033
- ].filter((part) => Boolean(part));
23034
- return parts.join(" ");
23063
+ function asRecord2(value) {
23064
+ return value !== null && typeof value === "object" ? value : {};
23035
23065
  }
23036
- function formatTaskListLines(tasks) {
23037
- if (tasks.length === 0) return ["(no tasks observed yet)"];
23038
- return tasks.map(formatTaskLine);
23066
+ function str(value, fallback = "") {
23067
+ return typeof value === "string" ? value : fallback;
23039
23068
  }
23040
- function formatRuntimeLines(runtimes) {
23041
- if (runtimes.length === 0) return ["(no runtimes configured \u2014 check runtimeAllowlist)"];
23042
- return runtimes.map((r) => {
23043
- if (!r.present) return `${r.id}: ${r.outcome}`;
23044
- const caps = [];
23045
- if (r.steer) caps.push("steer");
23046
- if (r.resume) caps.push("resume");
23047
- const parts = [
23048
- "present",
23049
- r.version ? `version=${r.version}` : void 0,
23050
- r.authPresent !== void 0 ? `authPresent=${r.authPresent}` : void 0,
23051
- `capabilities=${caps.length ? caps.join(",") : "(none)"}`,
23052
- `modes=${r.permissionModes.length ? r.permissionModes.join(",") : "(none)"}`
23053
- ].filter((part) => Boolean(part));
23054
- return `${r.id}: ${parts.join(" ")}`;
23055
- });
23069
+ function num(value) {
23070
+ return typeof value === "number" ? value : void 0;
23056
23071
  }
23057
- function formatStatusLines(view) {
23058
- const lines = [];
23059
- const label = view.branding?.displayName ?? view.productName;
23060
- lines.push(`product: ${label} (${view.productId})`);
23061
- lines.push(
23062
- `local-agent-release: ${view.localAgentRelease.version}${view.localAgentRelease.buildId ? ` buildId=${view.localAgentRelease.buildId}` : ""}`
23063
- );
23064
- if (view.branding?.supportUrl) lines.push(`support: ${view.branding.supportUrl}`);
23065
- lines.push(`paired: ${view.paired ? "yes" : "no"}${view.deviceId ? ` deviceId=${view.deviceId}` : ""}`);
23066
- lines.push(
23067
- view.connection ? `connection: last-known=${view.connection.state} at=${view.connection.ts}` : "connection: unknown (no audit log yet \u2014 run `byok-agent start` at least once to begin observing)"
23068
- );
23069
- const runtimeSummary = view.runtimes.map((r) => `${r.id}=${r.present ? "present" : r.outcome}`).join(" ");
23070
- lines.push(`runtimes: ${runtimeSummary || "(none configured)"}`);
23071
- const c = view.taskCounts;
23072
- lines.push(
23073
- `tasks: total=${c.total} offered=${c.Offered} claimed=${c.Claimed} running=${c.Running} awaitApproval=${c.AwaitApproval} complete=${c.Complete} failed=${c.Failed} cancelled=${c.Cancelled}`
23074
- );
23075
- lines.push(`audit-log: ${view.auditLogPath} (${view.auditLogLineCount} event${view.auditLogLineCount === 1 ? "" : "s"})`);
23076
- return lines;
23072
+ function bool(value, fallback) {
23073
+ return typeof value === "boolean" ? value : fallback;
23077
23074
  }
23078
- function formatLiveStatusLines(live) {
23079
- const liveRelease = live.localAgentRelease;
23080
- const lines = [
23081
- `live: pid=${live.pid} uptimeMs=${live.uptimeMs} transport=${live.transport}`,
23082
- liveRelease ? `live-local-agent-release: ${liveRelease.version}${liveRelease.buildId ? ` buildId=${liveRelease.buildId}` : ""}` : "live-local-agent-release: unknown",
23083
- `live-paired: ${live.paired ? "yes" : "no"}${live.deviceId ? ` deviceId=${live.deviceId}` : ""}`,
23084
- `live-runtimes: ${live.runtimeIds.length ? live.runtimeIds.join(",") : "(none)"}`,
23085
- `live-toolsets: revision=${live.toolsets.revision} count=${live.toolsets.toolsets.length}`
23086
- ];
23087
- for (const toolset of live.toolsets.toolsets) lines.push(formatToolsetStatusLine("live-toolset", toolset));
23088
- const health = live.operationalHealth;
23089
- if (health.availability === "unavailable") {
23090
- lines.push(`live-operational-health: unavailable reason=${quote(health.reason)}`);
23091
- } else {
23092
- lines.push(
23093
- `live-operational-health: state=${health.state} failures=${health.failureCount}/${health.failureThreshold} windowMs=${health.windowMs} crashes=${health.crashCount}${health.lastCrashAt ? ` lastCrashAt=${health.lastCrashAt}` : ""}`
23094
- );
23075
+ function reconstructAgentEvent(raw) {
23076
+ const r = asRecord2(raw);
23077
+ const type = str(r.type);
23078
+ switch (type) {
23079
+ case "progress":
23080
+ return { type: "progress", text: placeholderFor(num(r.textSize)) };
23081
+ case "tool_use":
23082
+ return { type: "tool_use", tool: str(r.tool), input: placeholderFor(num(r.inputSize), bool(r.inputSpilled, false)) };
23083
+ case "tool_result":
23084
+ return { type: "tool_result", tool: str(r.tool), output: placeholderFor(num(r.outputSize), bool(r.outputSpilled, false)) };
23085
+ case "artifact":
23086
+ return { type: "artifact", name: str(r.name), contentType: str(r.contentType) };
23087
+ case "needs_approval":
23088
+ return { type: "needs_approval", summary: placeholderFor(num(r.summarySize)) };
23089
+ case "turn_end":
23090
+ return { type: "turn_end" };
23091
+ case "error":
23092
+ return { type: "error", message: placeholderFor(num(r.messageSize)) };
23093
+ case "usage":
23094
+ return {
23095
+ type: "usage",
23096
+ inputTokens: num(r.inputTokens),
23097
+ cachedInputTokens: num(r.cachedInputTokens),
23098
+ outputTokens: num(r.outputTokens),
23099
+ reasoningTokens: num(r.reasoningTokens),
23100
+ totalTokens: num(r.totalTokens)
23101
+ };
23102
+ default:
23103
+ return { type: "error", message: `[unrecognized audit event type: ${type || "(missing)"}]` };
23095
23104
  }
23096
- if (live.activeTasks.length === 0) {
23097
- lines.push("live-active-tasks: (none)");
23098
- } else {
23099
- for (const task of live.activeTasks) {
23100
- lines.push(`live-active-task: ${task.taskId} ${task.state}`);
23105
+ }
23106
+ function reconstructDaemonEvent(raw) {
23107
+ const kind = str(raw.kind);
23108
+ const ts = str(raw.ts);
23109
+ switch (kind) {
23110
+ case "offered":
23111
+ return { kind: "offered", ts, taskId: str(raw.taskId), runtime: typeof raw.runtime === "string" ? raw.runtime : void 0 };
23112
+ case "claimed": {
23113
+ const claimedRuntime = typeof raw.claimedRuntime === "string" ? raw.claimedRuntime : void 0;
23114
+ return claimedRuntime === void 0 ? { kind: "claimed", ts, taskId: str(raw.taskId) } : { kind: "claimed", ts, taskId: str(raw.taskId), claimedRuntime };
23101
23115
  }
23102
- }
23103
- lines.push(`live-approvals-pending: ${live.approvalsPending}`);
23104
- if (live.approvals.length === 0) {
23105
- lines.push("live-approvals: (none)");
23106
- } else {
23107
- for (const approval of live.approvals) {
23108
- lines.push(`live-approval: ${approval.approvalId} taskId=${approval.taskId} summary=${quote(summaryExcerpt(approval.summary))}`);
23116
+ case "started":
23117
+ return { kind: "started", ts, taskId: str(raw.taskId) };
23118
+ case "progress":
23119
+ return { kind: "progress", ts, taskId: str(raw.taskId), event: reconstructAgentEvent(raw.event) };
23120
+ case "artifact": {
23121
+ const blobRefPresent = raw.blobRef !== void 0 && raw.blobRef !== null;
23122
+ const blobRefRaw = asRecord2(raw.blobRef);
23123
+ const blobRef = blobRefPresent ? {
23124
+ blobId: str(blobRefRaw.blobId),
23125
+ contentHash: str(blobRefRaw.contentHash),
23126
+ size: num(blobRefRaw.size) ?? 0,
23127
+ contentType: str(blobRefRaw.contentType)
23128
+ } : void 0;
23129
+ const inlineSize = num(raw.inlineSize);
23130
+ return {
23131
+ kind: "artifact",
23132
+ ts,
23133
+ taskId: str(raw.taskId),
23134
+ name: str(raw.name),
23135
+ contentType: str(raw.contentType),
23136
+ inline: inlineSize === void 0 ? void 0 : placeholderFor(inlineSize),
23137
+ blobRef
23138
+ };
23109
23139
  }
23110
- }
23111
- if (live.queueWatermarks.length === 0) {
23112
- lines.push("live-queue-watermarks: (none)");
23113
- } else {
23114
- for (const watermark of live.queueWatermarks) {
23115
- lines.push(
23116
- `live-queue-watermark: ${watermark.taskId} progressBatcherPending=${watermark.progressBatcherPending} pendingApprovals=${watermark.pendingApprovals}`
23117
- );
23140
+ case "awaiting-approval":
23141
+ return { kind: "awaiting-approval", ts, taskId: str(raw.taskId), summary: placeholderFor(num(raw.summarySize)) };
23142
+ case "completed":
23143
+ return {
23144
+ kind: "completed",
23145
+ ts,
23146
+ taskId: str(raw.taskId),
23147
+ summary: placeholderFor(num(raw.summarySize)),
23148
+ sessionRef: str(raw.sessionRef)
23149
+ };
23150
+ case "failed":
23151
+ return {
23152
+ kind: "failed",
23153
+ ts,
23154
+ taskId: str(raw.taskId),
23155
+ reason: placeholderFor(num(raw.reasonSize)),
23156
+ retryable: bool(raw.retryable, false),
23157
+ preClaim: typeof raw.preClaim === "boolean" ? raw.preClaim : void 0
23158
+ };
23159
+ case "cancelled": {
23160
+ const reasonSize = num(raw.reasonSize);
23161
+ return {
23162
+ kind: "cancelled",
23163
+ ts,
23164
+ taskId: str(raw.taskId),
23165
+ reason: reasonSize === void 0 ? void 0 : placeholderFor(reasonSize)
23166
+ };
23118
23167
  }
23119
- }
23120
- const storage = live.storage;
23121
- if (storage !== void 0) {
23122
- lines.push(
23123
- `live-storage: state=${storage.pressureState} used=${storage.usedBytes} budget=${storage.budgetBytes} free=${storage.freeBytes} measuredAt=${storage.measuredAt}`
23124
- );
23125
- for (const category of storage.categories) {
23126
- lines.push(`live-storage-category: ${category.category} bytes=${category.bytes}${category.approximate ? " (approximate)" : ""}`);
23168
+ case "connection":
23169
+ return { kind: "connection", ts, state: str(raw.state) };
23170
+ case "paired":
23171
+ return { kind: "paired", ts, deviceId: str(raw.deviceId) };
23172
+ case "unpaired":
23173
+ return { kind: "unpaired", ts };
23174
+ case "runtimes-detected":
23175
+ return { kind: "runtimes-detected", ts, runtimes: Array.isArray(raw.runtimes) ? raw.runtimes : [] };
23176
+ case "shutdown-requested":
23177
+ return { kind: "shutdown-requested", ts, reason: str(raw.reason) };
23178
+ case "shutdown-complete":
23179
+ return { kind: "shutdown-complete", ts, reason: str(raw.reason), undeliveredOutboxCount: num(raw.undeliveredOutboxCount) };
23180
+ case "stale-approval-decision": {
23181
+ const reasonSize = num(raw.reasonSize);
23182
+ return {
23183
+ kind: "stale-approval-decision",
23184
+ ts,
23185
+ taskId: str(raw.taskId),
23186
+ decision: str(raw.decision),
23187
+ reason: reasonSize === void 0 ? void 0 : placeholderFor(reasonSize)
23188
+ };
23127
23189
  }
23128
- if (storage.lastCompaction) {
23129
- lines.push(
23130
- `live-storage-compaction: checkpointed=${storage.lastCompaction.checkpointed} walFramesRemaining=${storage.lastCompaction.walFramesRemaining} pagesVacuumed=${storage.lastCompaction.pagesVacuumed} durationMs=${storage.lastCompaction.durationMs} at=${storage.lastCompaction.at}`
23131
- );
23190
+ case "runtime-disposal-failed":
23191
+ return {
23192
+ kind: "runtime-disposal-failed",
23193
+ ts,
23194
+ taskId: str(raw.taskId),
23195
+ runtimeId: str(raw.runtimeId),
23196
+ stage: str(raw.stage),
23197
+ reason: str(raw.reason)
23198
+ };
23199
+ case "device-assertion": {
23200
+ if (raw.result === "issued") {
23201
+ return {
23202
+ kind: "device-assertion",
23203
+ ts,
23204
+ result: "issued",
23205
+ audience: typeof raw.audience === "string" ? raw.audience : "",
23206
+ jti: typeof raw.jti === "string" ? raw.jti : "",
23207
+ expiresAt: typeof raw.expiresAt === "string" ? raw.expiresAt : ""
23208
+ };
23209
+ }
23210
+ const audienceSize = num(raw.audienceSize);
23211
+ return {
23212
+ kind: "device-assertion",
23213
+ ts,
23214
+ result: "denied",
23215
+ reason: typeof raw.reason === "string" ? raw.reason : "",
23216
+ ...audienceSize === void 0 ? {} : { audienceSize }
23217
+ };
23218
+ }
23219
+ case "git-workspace": {
23220
+ const commitsSinceBaseline = gitCount(raw.commitsSinceBaseline);
23221
+ const dirty = gitDirty(raw.dirty);
23222
+ return {
23223
+ kind: "git-workspace",
23224
+ ts,
23225
+ taskId: str(raw.taskId),
23226
+ workspaceId: str(raw.workspaceId),
23227
+ phase: str(raw.phase),
23228
+ headChanged: typeof raw.headChanged === "boolean" ? raw.headChanged : void 0,
23229
+ commitsSinceBaseline,
23230
+ dirty,
23231
+ errorCategory: stableGitErrorCategory(raw.errorCategory)
23232
+ };
23132
23233
  }
23234
+ default:
23235
+ return void 0;
23133
23236
  }
23134
- return lines;
23135
23237
  }
23136
- function formatToolsetStatusLine(prefix, toolset) {
23137
- const observation = toolset.observation;
23138
- return [
23139
- `${prefix}: ${toolset.id}`,
23140
- `servers=${toolset.serverCount}`,
23141
- `definitionRevision=${toolset.definitionRevision}`,
23142
- `state=${observation?.state ?? "unobserved"}`,
23143
- observation?.version === void 0 ? void 0 : `version=${quote(observation.version)}`,
23144
- observation?.observedAt === void 0 ? void 0 : `observedAt=${observation.observedAt}`,
23145
- observation?.reasonCode === void 0 ? void 0 : `reasonCode=${observation.reasonCode}`
23146
- ].filter((part) => part !== void 0).join(" ");
23147
- }
23148
- function formatToolsetsReloadReceiptLines(receipt) {
23149
- return [
23150
- `toolsets-reload: changed=${receipt.changed ? "yes" : "no"} previousRevision=${receipt.previousRevision} revision=${receipt.revision}`,
23151
- ...receipt.toolsets.map((toolset) => formatToolsetStatusLine("toolset", toolset))
23152
- ];
23153
- }
23154
- var SUMMARY_EXCERPT_MAX_LEN = 60;
23155
- function summaryExcerpt(summary) {
23156
- if (!summary) return "(no summary)";
23157
- return summary.length > SUMMARY_EXCERPT_MAX_LEN ? `${summary.slice(0, SUMMARY_EXCERPT_MAX_LEN)}\u2026` : summary;
23158
- }
23159
- function formatAge(ms) {
23160
- const totalSeconds = Math.floor(Math.max(0, ms) / 1e3);
23161
- if (totalSeconds < 60) return `${totalSeconds}s`;
23162
- const totalMinutes = Math.floor(totalSeconds / 60);
23163
- if (totalMinutes < 60) return `${totalMinutes}m`;
23164
- const totalHours = Math.floor(totalMinutes / 60);
23165
- if (totalHours < 24) return `${totalHours}h`;
23166
- const totalDays = Math.floor(totalHours / 24);
23167
- return `${totalDays}d`;
23168
- }
23169
- function formatApprovalsListLines(approvals, nowMs) {
23170
- if (approvals.length === 0) return ["(no pending approvals)"];
23171
- return approvals.map((approval) => {
23172
- const ageMs = nowMs - Date.parse(approval.createdAt);
23173
- return `${approval.approvalId} taskId=${approval.taskId} age=${formatAge(ageMs)} summary=${quote(summaryExcerpt(approval.summary))}`;
23238
+ async function appendAuditEvent(storeDir, event) {
23239
+ await promises.mkdir(storeDir, { recursive: true, mode: AUDIT_STORE_DIR_MODE });
23240
+ await promises.chmod(storeDir, AUDIT_STORE_DIR_MODE).catch(() => {
23174
23241
  });
23175
- }
23176
-
23177
- // src/bin/commands/approvals.ts
23178
- async function runApprovalsCommand(storeDir, productId, deps = {}) {
23179
- const log = deps.log ?? ((line) => console.log(line));
23180
- const error = deps.error ?? ((line) => console.error(line));
23181
- const connectControl = deps.connectControl ?? connectControlClient;
23182
- const now = deps.now ?? (() => Date.now());
23183
- const conn = await connectControl({ storeDir, productId });
23184
- if (!conn.ok) {
23185
- const message = `cannot list approvals: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23186
- error(message);
23187
- throw new Error(message);
23188
- }
23242
+ const filePath = auditLogPath(storeDir);
23243
+ const line = `${JSON.stringify(redactForAudit(event))}
23244
+ `;
23245
+ const handle = await promises.open(filePath, "a", AUDIT_LOG_MODE);
23189
23246
  try {
23190
- const result = await conn.client.request("approvals.list");
23191
- for (const line of formatApprovalsListLines(result.approvals, now())) log(line);
23192
- } catch (err) {
23193
- if (err instanceof ControlError) {
23194
- error(`approvals list failed: ${err.message}`);
23195
- } else {
23196
- error(`approvals list failed: ${err instanceof Error ? err.message : String(err)}`);
23197
- }
23198
- throw err;
23247
+ await handle.chmod(AUDIT_LOG_MODE);
23248
+ await handle.appendFile(line, "utf8");
23199
23249
  } finally {
23200
- conn.client.close();
23250
+ await handle.close();
23201
23251
  }
23252
+ await rotateIfNeeded(filePath);
23202
23253
  }
23203
-
23204
- // src/bin/commands/approve-reject.ts
23205
- async function runApproveCommand(storeDir, productId, approvalId, deps = {}) {
23206
- return resolveApproval(storeDir, productId, approvalId, "approve", void 0, deps);
23254
+ var TERMINAL_EVENT_KINDS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
23255
+ function eventTaskId(event) {
23256
+ if (event.kind === "git-workspace") return void 0;
23257
+ return "taskId" in event ? event.taskId : void 0;
23207
23258
  }
23208
- async function runRejectCommand(storeDir, productId, approvalId, reason, deps = {}) {
23209
- return resolveApproval(storeDir, productId, approvalId, "reject", reason, deps);
23259
+ function compactPreservingLiveTasks(lines, targetLines) {
23260
+ if (lines.length <= targetLines) return [...lines];
23261
+ const cutIndex = lines.length - targetLines;
23262
+ const hasTerminalEvent = /* @__PURE__ */ new Set();
23263
+ const lastIndexForTask = /* @__PURE__ */ new Map();
23264
+ for (let i = 0; i < lines.length; i++) {
23265
+ const line = lines[i];
23266
+ if (line === void 0) continue;
23267
+ const event = parseAuditLine(line);
23268
+ if (!event) continue;
23269
+ const taskId = eventTaskId(event);
23270
+ if (taskId === void 0) continue;
23271
+ lastIndexForTask.set(taskId, i);
23272
+ if (TERMINAL_EVENT_KINDS.has(event.kind)) hasTerminalEvent.add(taskId);
23273
+ }
23274
+ let anchorIndices = [];
23275
+ for (const [taskId, lastIndex] of lastIndexForTask) {
23276
+ if (hasTerminalEvent.has(taskId)) continue;
23277
+ if (lastIndex < cutIndex) anchorIndices.push(lastIndex);
23278
+ }
23279
+ anchorIndices.sort((a, b) => a - b);
23280
+ if (anchorIndices.length > MAX_LIVE_TASK_ANCHORS) {
23281
+ const totalCandidates = anchorIndices.length;
23282
+ const droppedCount = totalCandidates - MAX_LIVE_TASK_ANCHORS;
23283
+ anchorIndices = anchorIndices.slice(-MAX_LIVE_TASK_ANCHORS);
23284
+ console.warn(
23285
+ `[byok/client] audit log rotation: ${totalCandidates} non-terminal task lifecycle anchors exceeded MAX_LIVE_TASK_ANCHORS (${MAX_LIVE_TASK_ANCHORS}) \u2014 dropped the oldest ${droppedCount}, kept the most recently-touched ${MAX_LIVE_TASK_ANCHORS}; dropped tasks will stop appearing in tasks/status once their own events age out of the retained tail`
23286
+ );
23287
+ }
23288
+ return [...anchorIndices.map((i) => lines[i]), ...lines.slice(cutIndex)].filter((l) => l !== void 0);
23210
23289
  }
23211
- async function resolveApproval(storeDir, productId, approvalId, decision, reason, deps) {
23212
- const log = deps.log ?? ((line) => console.log(line));
23213
- const error = deps.error ?? ((line) => console.error(line));
23214
- const connectControl = deps.connectControl ?? connectControlClient;
23215
- const verb = decision === "approve" ? "approved" : "rejected";
23216
- const conn = await connectControl({ storeDir, productId });
23217
- if (!conn.ok) {
23218
- const message = `cannot ${decision} approvalId=${approvalId}: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23219
- error(message);
23220
- throw new Error(message);
23290
+ async function rotateIfNeeded(filePath) {
23291
+ let size;
23292
+ try {
23293
+ size = (await promises.stat(filePath)).size;
23294
+ } catch {
23295
+ return;
23221
23296
  }
23297
+ if (size <= MAX_AUDIT_LOG_BYTES) return;
23298
+ const lines = await readCompleteLines(filePath);
23299
+ const kept = compactPreservingLiveTasks(lines, AUDIT_LOG_TRIM_TARGET_LINES);
23300
+ const content = kept.length > 0 ? `${kept.join("\n")}
23301
+ ` : "";
23302
+ await atomicWriteFile(filePath, content, { mode: AUDIT_LOG_MODE });
23303
+ }
23304
+ function createAuditAppender(storeDir, onError = () => {
23305
+ }) {
23306
+ let chain = Promise.resolve();
23307
+ return (event) => {
23308
+ chain = chain.then(() => appendAuditEvent(storeDir, event)).catch(onError);
23309
+ };
23310
+ }
23311
+ function isAuditRecordShaped(value) {
23312
+ return typeof value === "object" && value !== null && typeof value.kind === "string" && typeof value.ts === "string";
23313
+ }
23314
+ function parseAuditLine(line) {
23315
+ const trimmed = line.trim();
23316
+ if (!trimmed) return void 0;
23222
23317
  try {
23223
- await conn.client.request("approvals.resolve", { approvalId, decision, reason });
23224
- log(`${verb}: approvalId=${approvalId}${reason ? ` reason=${JSON.stringify(reason)}` : ""}`);
23318
+ const parsed = JSON.parse(trimmed);
23319
+ if (!isAuditRecordShaped(parsed)) return void 0;
23320
+ return reconstructDaemonEvent(parsed);
23321
+ } catch {
23322
+ return void 0;
23323
+ }
23324
+ }
23325
+ function completeLines(raw) {
23326
+ const parts = raw.split("\n");
23327
+ parts.pop();
23328
+ return parts;
23329
+ }
23330
+ async function readCompleteLines(filePath) {
23331
+ let raw;
23332
+ try {
23333
+ raw = await promises.readFile(filePath, "utf8");
23225
23334
  } catch (err) {
23226
- if (err instanceof ControlError && err.code === "not_found") {
23227
- error(`${err.message} \u2014 run \`byok-agent approvals\` to see pending approvalIds`);
23228
- } else {
23229
- error(`${decision} failed: ${err instanceof Error ? err.message : String(err)}`);
23230
- }
23335
+ if (err.code === "ENOENT") return [];
23231
23336
  throw err;
23232
- } finally {
23233
- conn.client.close();
23234
23337
  }
23338
+ return completeLines(raw);
23235
23339
  }
23236
-
23237
- // src/bin/commands/pair.ts
23238
- async function runPairCommand(config, code, deps = {}) {
23239
- const log = deps.log ?? ((line) => console.log(line));
23240
- if (deps.daemon) {
23241
- const result2 = await deps.daemon.pair(code);
23242
- log(`paired: deviceId=${result2.deviceId}`);
23243
- return;
23340
+ async function readAuditEvents(storeDir) {
23341
+ const lines = await readCompleteLines(auditLogPath(storeDir));
23342
+ const events = [];
23343
+ for (const line of lines) {
23344
+ const event = parseAuditLine(line);
23345
+ if (event) events.push(event);
23244
23346
  }
23245
- const connectControl = deps.connectControl ?? connectControlClient;
23246
- const conn = await connectControl({ storeDir: resolveStoreDir(config), productId: config.productId });
23247
- if (conn.ok) {
23347
+ return events;
23348
+ }
23349
+ function delay3(ms, signal) {
23350
+ if (signal.aborted) return Promise.resolve();
23351
+ return new Promise((resolve) => {
23352
+ const onAbort = () => {
23353
+ clearTimeout(timer);
23354
+ resolve();
23355
+ };
23356
+ const timer = setTimeout(() => {
23357
+ signal.removeEventListener("abort", onAbort);
23358
+ resolve();
23359
+ }, ms);
23360
+ signal.addEventListener("abort", onAbort, { once: true });
23361
+ });
23362
+ }
23363
+ async function followAuditLog(filePath, onEvent, options) {
23364
+ const { signal, pollIntervalMs = 200, fromEnd = true } = options;
23365
+ let offset = 0;
23366
+ let pending = Buffer.alloc(0);
23367
+ let lastFileIdentity;
23368
+ if (fromEnd) {
23248
23369
  try {
23249
- const result2 = await conn.client.request("enrollment.pair", { pairingCode: code });
23250
- log(`paired: deviceId=${result2.deviceId}`);
23251
- return;
23252
- } finally {
23253
- conn.client.close();
23370
+ const st = await promises.stat(filePath);
23371
+ offset = st.size;
23372
+ lastFileIdentity = `${st.dev}:${st.ino}`;
23373
+ } catch (err) {
23374
+ if (err.code !== "ENOENT") throw err;
23375
+ offset = 0;
23254
23376
  }
23255
23377
  }
23256
- const daemon = createDaemon(config);
23257
- const result = await daemon.pair(code);
23258
- log(`paired: deviceId=${result.deviceId}`);
23259
- }
23260
-
23261
- // src/bin/commands/doctor.ts
23262
- var DoctorConfirmationRequiredError = class extends Error {
23263
- constructor() {
23264
- super("doctor --fix requires explicit --yes confirmation");
23265
- this.name = "DoctorConfirmationRequiredError";
23266
- }
23267
- };
23268
- var DoctorDaemonRunningError = class extends Error {
23269
- constructor() {
23270
- super("doctor --fix refuses while the daemon control socket is reachable; stop the daemon first");
23271
- this.name = "DoctorDaemonRunningError";
23272
- }
23273
- };
23274
- function formatDoctorLines(snapshot, fixResult) {
23275
- const lines = [
23276
- `doctor: productHash=${snapshot.product.nameHash} generatedAt=${snapshot.generatedAt}`,
23277
- `system: node=${snapshot.system.node} platform=${snapshot.system.platform} arch=${snapshot.system.arch} sqlite=${snapshot.system.sqliteAvailable ? "available" : "unavailable"}`
23278
- ];
23279
- for (const check of snapshot.checks) lines.push(`check ${check.id}: ${check.status} (${check.summary})`);
23280
- if (snapshot.health.status === "corrupt" || snapshot.health.status === "unavailable") {
23281
- lines.push(`health-detail: ${snapshot.health.reason}; bytes=${snapshot.health.sizeBytes ?? "unknown"}`);
23282
- }
23283
- if (snapshot.quarantine.truncated) {
23284
- lines.push(`quarantine-detail: inventory truncated after ${snapshot.quarantine.scannedCount} scanned entries`);
23285
- }
23286
- if (fixResult?.status === "quarantined") {
23287
- lines.push(
23288
- `fix: quarantined ${fixResult.sizeBytes} bytes as ${fixResult.evidenceName}; manifest=${fixResult.manifestName}; sha256=${fixResult.sha256}`
23289
- );
23290
- } else if (fixResult) {
23291
- lines.push(`fix: no change (${fixResult.reason})`);
23292
- }
23293
- return lines;
23294
- }
23295
- async function runDoctorCommand(config, options = {}) {
23296
- if (options.repair !== void 0) {
23297
- if (options.fix || options.repair !== "restore-enrollment-metadata") {
23298
- throw new Error("doctor requires one supported repair action; --fix cannot be combined with --repair");
23378
+ async function readNewBytes() {
23379
+ let handle;
23380
+ try {
23381
+ handle = await promises.open(filePath, "r");
23382
+ } catch (err) {
23383
+ if (err.code === "ENOENT") return Buffer.alloc(0);
23384
+ throw err;
23299
23385
  }
23300
- const log2 = options.log ?? ((line) => console.log(line));
23301
23386
  try {
23302
- const repair = await repairDeviceEnrollmentMetadata(config, {
23303
- confirmed: options.confirmed,
23304
- expectedDeviceId: options.expectedDeviceId,
23305
- expectedTenantId: options.expectedTenantId
23306
- });
23307
- log2(options.json ? JSON.stringify({ repair }, null, 2) : `repair ${repair.action}: ${repair.status} (${repair.scope})`);
23308
- return;
23309
- } catch (error) {
23310
- if (options.json && error instanceof DeviceMetadataRepairError) {
23311
- log2(JSON.stringify({ repair: { action: "restore-enrollment-metadata", scope: "device", status: "failed", code: error.code } }, null, 2));
23387
+ const st = await handle.stat();
23388
+ const identity = `${st.dev}:${st.ino}`;
23389
+ if (lastFileIdentity !== void 0 && identity !== lastFileIdentity) {
23390
+ offset = 0;
23391
+ pending = Buffer.alloc(0);
23392
+ } else if (st.size < offset) {
23393
+ offset = 0;
23394
+ pending = Buffer.alloc(0);
23312
23395
  }
23313
- throw error;
23396
+ lastFileIdentity = identity;
23397
+ if (st.size <= offset) return Buffer.alloc(0);
23398
+ const length = st.size - offset;
23399
+ const buffer = Buffer.alloc(length);
23400
+ const { bytesRead } = await handle.read(buffer, 0, length, offset);
23401
+ offset += bytesRead;
23402
+ return buffer.subarray(0, bytesRead);
23403
+ } finally {
23404
+ await handle.close();
23314
23405
  }
23315
23406
  }
23316
- if (options.expectedDeviceId !== void 0 || options.expectedTenantId !== void 0) {
23317
- throw new Error("expected identity flags require --repair restore-enrollment-metadata");
23318
- }
23319
- if (options.fix && !options.confirmed) throw new DoctorConfirmationRequiredError();
23320
- const storeDir = resolveStoreDir(config);
23321
- let snapshot = await collectDiagnostics(config, storeDir, options);
23322
- let fixResult;
23323
- if (options.fix) {
23324
- if (snapshot.control.status === "online") throw new DoctorDaemonRunningError();
23325
- fixResult = await quarantineCorruptOperationalHealth(storeDir, { clock: options.clock });
23326
- snapshot = await collectDiagnostics(config, storeDir, options);
23327
- }
23328
- const log = options.log ?? ((line) => console.log(line));
23329
- if (options.json) {
23330
- log(JSON.stringify({ diagnostics: snapshot, ...fixResult ? { fix: fixResult } : {} }, null, 2));
23331
- return;
23407
+ while (!signal.aborted) {
23408
+ const chunk = await readNewBytes();
23409
+ if (chunk.length > 0) {
23410
+ const combined = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
23411
+ const lastNewline = combined.lastIndexOf(10);
23412
+ if (lastNewline === -1) {
23413
+ pending = combined;
23414
+ } else {
23415
+ const completeText = combined.subarray(0, lastNewline + 1).toString("utf8");
23416
+ pending = Buffer.from(combined.subarray(lastNewline + 1));
23417
+ for (const line of completeText.split("\n")) {
23418
+ if (!line) continue;
23419
+ const event = parseAuditLine(line);
23420
+ if (event) onEvent(event);
23421
+ }
23422
+ }
23423
+ }
23424
+ if (signal.aborted) break;
23425
+ await delay3(pollIntervalMs, signal);
23332
23426
  }
23333
- for (const line of formatDoctorLines(snapshot, fixResult)) log(line);
23334
23427
  }
23335
23428
 
23336
- // src/bin/commands/runtimes.ts
23337
- async function runRuntimesCommand(config, deps = {}) {
23338
- const log = deps.log ?? ((line) => console.log(line));
23339
- const adapters = deps.adapters ?? defaultRuntimeAdapters(config.runtimeAllowlist);
23340
- const runtimes = await probeRuntimes(adapters);
23341
- for (const line of formatRuntimeLines(runtimes)) log(line);
23342
- }
23343
- function buildServiceDefinition(config, configPath, rest) {
23344
- const name = argValue(rest, "--name") ?? config.productId;
23345
- const agentBin = argValue(rest, "--agent-bin") ?? process.argv[1] ?? "byok-agent";
23346
- const nodeBin = argValue(rest, "--node-bin") ?? process.execPath;
23347
- const absoluteConfigPath = path14__default.resolve(configPath);
23348
- const logDir = path14__default.join(resolveStoreDir(config), "service-logs");
23349
- const definition = {
23350
- name,
23351
- displayName: config.branding?.displayName ?? config.productName,
23352
- program: nodeAgentProgram({ agentBin, configPath: absoluteConfigPath, nodeBin }),
23353
- logDir
23354
- };
23355
- const winswBin = argValue(rest, "--winsw-bin");
23356
- if (winswBin) {
23357
- const installDir = argValue(rest, "--winsw-install-dir");
23358
- definition.windows = installDir ? { winswBin, installDir } : { winswBin };
23359
- }
23360
- return definition;
23361
- }
23362
- function buildLifecycle(config, configPath, rest, deps) {
23363
- return deps.lifecycle ?? createServiceLifecycle(buildServiceDefinition(config, configPath, rest));
23364
- }
23365
- function serviceNameFor(config, rest) {
23366
- return argValue(rest, "--name") ?? config.productId;
23367
- }
23368
- async function runInstallCommand(config, configPath, rest, deps = {}) {
23369
- const log = deps.log ?? ((line) => console.log(line));
23370
- const lifecycle = buildLifecycle(config, configPath, rest, deps);
23371
- await lifecycle.install();
23372
- log(`service installed and started: ${serviceNameFor(config, rest)}`);
23373
- }
23374
- async function runUninstallCommand(config, configPath, rest, deps = {}) {
23375
- const log = deps.log ?? ((line) => console.log(line));
23376
- const lifecycle = buildLifecycle(config, configPath, rest, deps);
23377
- await lifecycle.uninstall();
23378
- log(`service uninstalled: ${serviceNameFor(config, rest)}`);
23379
- }
23380
- async function runServiceStartCommand(config, configPath, rest, deps = {}) {
23381
- const log = deps.log ?? ((line) => console.log(line));
23382
- const lifecycle = buildLifecycle(config, configPath, rest, deps);
23383
- await lifecycle.start();
23384
- log(`service started: ${serviceNameFor(config, rest)}`);
23385
- }
23386
- async function runServiceStopCommand(config, configPath, rest, deps = {}) {
23387
- const log = deps.log ?? ((line) => console.log(line));
23388
- const lifecycle = buildLifecycle(config, configPath, rest, deps);
23389
- await lifecycle.stop();
23390
- log(`service stopped: ${serviceNameFor(config, rest)}`);
23391
- }
23392
- async function runServiceStatusCommand(config, configPath, rest, deps = {}) {
23393
- const log = deps.log ?? ((line) => console.log(line));
23394
- const lifecycle = buildLifecycle(config, configPath, rest, deps);
23395
- const status = await lifecycle.status();
23396
- log(`installed: ${status.installed ? "yes" : "no"}`);
23397
- const runningLabel = status.running ? "yes" : status.determinate ? "no" : "unknown (could not query the service manager)";
23398
- log(`running: ${runningLabel}`);
23399
- log(`detail: ${status.detail.trim() || "(none)"}`);
23400
- }
23401
- function auditLogPath(storeDir) {
23402
- return path14__default.join(storeDir, "audit.jsonl");
23429
+ // src/diagnostics/support-bundle.ts
23430
+ var MAX_SUPPORT_AUDIT_TAIL_BYTES = 256 * 1024;
23431
+ var MAX_SUPPORT_AUDIT_FACTS = 200;
23432
+ var AUDIT_KINDS = /* @__PURE__ */ new Set([
23433
+ "artifact",
23434
+ "awaiting-approval",
23435
+ "cancelled",
23436
+ "claimed",
23437
+ "completed",
23438
+ "connection",
23439
+ "failed",
23440
+ "git-workspace",
23441
+ "offered",
23442
+ "paired",
23443
+ "progress",
23444
+ "runtimes-detected",
23445
+ "shutdown-complete",
23446
+ "shutdown-requested",
23447
+ "stale-approval-decision",
23448
+ "started",
23449
+ "unpaired"
23450
+ ]);
23451
+ function isAuditKind(value) {
23452
+ return typeof value === "string" && AUDIT_KINDS.has(value);
23403
23453
  }
23404
- var AUDIT_LOG_MODE = 384;
23405
- var AUDIT_STORE_DIR_MODE = 448;
23406
- var MAX_AUDIT_LOG_BYTES = 10 * 1024 * 1024;
23407
- var AUDIT_LOG_TRIM_TARGET_LINES = 5e3;
23408
- var MAX_LIVE_TASK_ANCHORS = 500;
23409
- function byteSize(text) {
23410
- return text === void 0 ? void 0 : Buffer.byteLength(text, "utf8");
23454
+ function sameFileState6(left, right) {
23455
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23411
23456
  }
23412
- function valueByteSize(value) {
23413
- if (value === void 0) return void 0;
23457
+ async function recentAuditFacts(storeDir) {
23458
+ const filePath = auditLogPath(storeDir);
23459
+ let pathStat;
23414
23460
  try {
23415
- const json = JSON.stringify(value);
23416
- return json === void 0 ? void 0 : Buffer.byteLength(json, "utf8");
23417
- } catch {
23418
- return void 0;
23461
+ pathStat = await promises.lstat(filePath, { bigint: true });
23462
+ if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
23463
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23464
+ }
23465
+ } catch (err) {
23466
+ if (err.code === "ENOENT") {
23467
+ return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23468
+ }
23469
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23419
23470
  }
23420
- }
23421
- function placeholderFor(size, spilled) {
23422
- const suffix = spilled ? ", spilled" : "";
23423
- return size === void 0 ? `[redacted${suffix}]` : `[redacted: ${size} bytes${suffix}]`;
23424
- }
23425
- var STABLE_GIT_ERROR_CATEGORIES2 = new Set(GIT_ERROR_CATEGORIES);
23426
- function stableGitErrorCategory2(value) {
23427
- return typeof value === "string" && STABLE_GIT_ERROR_CATEGORIES2.has(value) ? value : void 0;
23428
- }
23429
- function gitCount(value) {
23430
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
23431
- }
23432
- function gitDirty(value) {
23433
- const dirty = asRecord2(value);
23434
- const staged = gitCount(dirty.staged);
23435
- const unstaged = gitCount(dirty.unstaged);
23436
- const untracked = gitCount(dirty.untracked);
23437
- const conflicted = gitCount(dirty.conflicted);
23438
- return staged !== void 0 && unstaged !== void 0 && untracked !== void 0 && conflicted !== void 0 ? { staged, unstaged, untracked, conflicted } : void 0;
23439
- }
23440
- function redactAgentEvent(event) {
23441
- switch (event.type) {
23442
- case "progress":
23443
- return { type: "progress", textSize: byteSize(event.text) };
23444
- case "tool_use":
23445
- return event.spill !== void 0 ? { type: "tool_use", tool: event.tool, inputSize: event.spill.totalBytes, inputSpilled: true } : { type: "tool_use", tool: event.tool, inputSize: valueByteSize(event.input) };
23446
- case "tool_result":
23447
- return event.spill !== void 0 ? { type: "tool_result", tool: event.tool, outputSize: event.spill.totalBytes, outputSpilled: true } : { type: "tool_result", tool: event.tool, outputSize: valueByteSize(event.output) };
23448
- case "artifact":
23449
- return { type: "artifact", name: event.name, contentType: event.contentType };
23450
- case "needs_approval":
23451
- return { type: "needs_approval", summarySize: byteSize(event.summary) };
23452
- case "turn_end":
23453
- return { type: "turn_end" };
23454
- case "error":
23455
- return { type: "error", messageSize: byteSize(event.message) };
23456
- case "usage":
23457
- return {
23458
- type: "usage",
23459
- inputTokens: event.inputTokens,
23460
- cachedInputTokens: event.cachedInputTokens,
23461
- outputTokens: event.outputTokens,
23462
- reasoningTokens: event.reasoningTokens,
23463
- totalTokens: event.totalTokens
23464
- };
23471
+ let handle;
23472
+ try {
23473
+ handle = await promises.open(
23474
+ filePath,
23475
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
23476
+ );
23477
+ } catch (err) {
23478
+ if (err.code === "ENOENT") {
23479
+ return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23480
+ }
23481
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23465
23482
  }
23466
- }
23467
- function redactForAudit(event) {
23468
- const base = { kind: event.kind, ts: event.ts };
23469
- switch (event.kind) {
23470
- case "offered":
23471
- return { ...base, taskId: event.taskId, runtime: event.runtime };
23472
- case "claimed":
23473
- return { ...base, taskId: event.taskId, claimedRuntime: event.claimedRuntime };
23474
- case "started":
23475
- return { ...base, taskId: event.taskId };
23476
- case "progress":
23477
- return { ...base, taskId: event.taskId, event: redactAgentEvent(event.event) };
23478
- case "artifact":
23479
- return {
23480
- ...base,
23481
- taskId: event.taskId,
23482
- name: event.name,
23483
- contentType: event.contentType,
23484
- // `inline` is base64 file bytes (up to the 64KB inline cap) — never
23485
- // persisted. `blobRef.url` is a presigned URL — itself effectively a
23486
- // time-limited bearer credential for the actual bytes, so it's
23487
- // dropped too; only the safe pointer/size metadata survives.
23488
- inlineSize: byteSize(event.inline),
23489
- blobRef: event.blobRef ? {
23490
- blobId: event.blobRef.blobId,
23491
- contentHash: event.blobRef.contentHash,
23492
- size: event.blobRef.size,
23493
- contentType: event.blobRef.contentType
23494
- } : void 0
23495
- };
23496
- case "awaiting-approval":
23497
- return { ...base, taskId: event.taskId, summarySize: byteSize(event.summary) };
23498
- case "completed":
23499
- return { ...base, taskId: event.taskId, summarySize: byteSize(event.summary), sessionRef: event.sessionRef };
23500
- case "failed":
23501
- return {
23502
- ...base,
23503
- taskId: event.taskId,
23504
- reasonSize: byteSize(event.reason),
23505
- retryable: event.retryable,
23506
- preClaim: event.preClaim
23507
- };
23508
- case "cancelled":
23509
- return { ...base, taskId: event.taskId, reasonSize: byteSize(event.reason) };
23510
- case "connection":
23511
- return { ...base, state: event.state };
23512
- case "paired":
23513
- return { ...base, deviceId: event.deviceId };
23514
- case "unpaired":
23515
- return { ...base };
23516
- case "runtimes-detected":
23517
- return { ...base, runtimes: event.runtimes };
23518
- case "shutdown-requested":
23519
- return { ...base, reason: event.reason };
23520
- case "shutdown-complete":
23521
- return { ...base, reason: event.reason, undeliveredOutboxCount: event.undeliveredOutboxCount };
23522
- case "stale-approval-decision":
23523
- return { ...base, taskId: event.taskId, decision: event.decision, reasonSize: byteSize(event.reason) };
23524
- case "runtime-disposal-failed":
23525
- return { ...base, taskId: event.taskId, runtimeId: event.runtimeId, stage: event.stage, reason: event.reason };
23526
- case "device-assertion":
23527
- return event.result === "issued" ? {
23528
- ...base,
23529
- result: "issued",
23530
- audience: event.audience,
23531
- jti: event.jti,
23532
- expiresAt: event.expiresAt
23533
- } : { ...base, result: "denied", reason: event.reason, audienceSize: event.audienceSize };
23534
- case "git-workspace":
23535
- return {
23536
- ...base,
23537
- taskId: event.taskId,
23538
- workspaceId: event.workspaceId,
23539
- phase: event.phase,
23540
- headChanged: event.headChanged,
23541
- commitsSinceBaseline: event.commitsSinceBaseline,
23542
- dirty: event.dirty,
23543
- errorCategory: stableGitErrorCategory2(event.errorCategory)
23544
- };
23483
+ try {
23484
+ const stat = await handle.stat({ bigint: true });
23485
+ const namedAfterOpen = await promises.lstat(filePath, { bigint: true });
23486
+ if (!stat.isFile() || !namedAfterOpen.isFile() || namedAfterOpen.isSymbolicLink() || !sameFileState6(pathStat, stat) || !sameFileState6(stat, namedAfterOpen)) {
23487
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23488
+ }
23489
+ const sourceSize = Number(stat.size);
23490
+ const length = Math.min(sourceSize, MAX_SUPPORT_AUDIT_TAIL_BYTES);
23491
+ const start = Math.max(0, sourceSize - length);
23492
+ const buffer = Buffer.alloc(length);
23493
+ const { bytesRead } = await handle.read(buffer, 0, length, start);
23494
+ const afterRead = await handle.stat({ bigint: true });
23495
+ const namedAfterRead = await promises.lstat(filePath, { bigint: true });
23496
+ if (bytesRead !== length || namedAfterRead.isSymbolicLink() || !sameFileState6(stat, afterRead) || !sameFileState6(afterRead, namedAfterRead)) {
23497
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23498
+ }
23499
+ let text = buffer.subarray(0, bytesRead).toString("utf8");
23500
+ if (start > 0) text = text.slice(Math.max(0, text.indexOf("\n") + 1));
23501
+ const lines = text.split("\n").filter(Boolean).slice(-MAX_SUPPORT_AUDIT_FACTS);
23502
+ const facts = [];
23503
+ for (const line of lines) {
23504
+ try {
23505
+ const value = JSON.parse(line);
23506
+ if (isAuditKind(value.kind) && typeof value.ts === "string" && Number.isFinite(Date.parse(value.ts))) {
23507
+ facts.push({ kind: value.kind, ts: new Date(Date.parse(value.ts)).toISOString() });
23508
+ }
23509
+ } catch {
23510
+ }
23511
+ }
23512
+ return { status: "available", included: facts.length, sourceBytesRead: bytesRead, sourceTruncated: sourceSize > bytesRead, facts };
23513
+ } catch {
23514
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23515
+ } finally {
23516
+ await handle.close();
23545
23517
  }
23546
23518
  }
23547
- function asRecord2(value) {
23548
- return value !== null && typeof value === "object" ? value : {};
23519
+ function projectControl(control) {
23520
+ if (control.status === "offline") return { status: "offline" };
23521
+ const transport = ["connecting", "open", "closed", "revoked"].includes(control.transport) ? control.transport : "unavailable";
23522
+ const operationalHealth = control.operationalHealth.availability === "available" ? {
23523
+ availability: "available",
23524
+ state: control.operationalHealth.state,
23525
+ failureCount: control.operationalHealth.failureCount,
23526
+ windowMs: control.operationalHealth.windowMs,
23527
+ failureThreshold: control.operationalHealth.failureThreshold,
23528
+ crashCount: control.operationalHealth.crashCount
23529
+ } : { availability: "unavailable" };
23530
+ return {
23531
+ status: "online",
23532
+ pid: control.pid,
23533
+ uptimeMs: control.uptimeMs,
23534
+ transport,
23535
+ activeTaskCount: control.activeTaskCount,
23536
+ pendingApprovalCount: control.pendingApprovalCount,
23537
+ operationalHealth,
23538
+ storagePresent: control.storage !== void 0
23539
+ };
23540
+ }
23541
+ async function createSupportBundle(config, storeDir, options = {}) {
23542
+ const [snapshot, recentEvents] = await Promise.all([collectDiagnostics(config, storeDir, options), recentAuditFacts(storeDir)]);
23543
+ return {
23544
+ version: 1,
23545
+ generatedAt: snapshot.generatedAt,
23546
+ product: snapshot.product,
23547
+ system: snapshot.system,
23548
+ config: {
23549
+ serverProtocol: snapshot.config.serverProtocol,
23550
+ customStoreDir: snapshot.config.customStoreDir,
23551
+ hostedJournal: snapshot.config.hostedJournal,
23552
+ ...snapshot.config.runtimeAllowlistCount === void 0 ? {} : { runtimeAllowlistCount: snapshot.config.runtimeAllowlistCount }
23553
+ },
23554
+ device: {
23555
+ status: snapshot.device.status,
23556
+ ...snapshot.device.deviceIdHash ? { deviceIdHash: snapshot.device.deviceIdHash } : {}
23557
+ },
23558
+ runtimes: snapshot.runtimes,
23559
+ control: projectControl(snapshot.control),
23560
+ health: snapshot.health,
23561
+ journal: snapshot.journal,
23562
+ workspace: snapshot.workspace,
23563
+ quarantine: {
23564
+ status: snapshot.quarantine.status,
23565
+ count: snapshot.quarantine.count,
23566
+ scannedCount: snapshot.quarantine.scannedCount,
23567
+ truncated: snapshot.quarantine.truncated,
23568
+ entries: snapshot.quarantine.entries
23569
+ },
23570
+ checks: snapshot.checks.map(({ id, status }) => ({ id, status })),
23571
+ recentEvents,
23572
+ redaction: {
23573
+ policy: "allowlist-v1",
23574
+ omitted: [
23575
+ "server URL host/path/query",
23576
+ "store/workspace/config paths",
23577
+ "control token and provider credentials",
23578
+ "task, prompt, tool input/output and approval text",
23579
+ "raw audit and quarantine contents"
23580
+ ],
23581
+ transformed: ["productName/productId/deviceId/runtimeId/quarantine filenames -> SHA-256", "audit events -> closed kind/timestamp only"]
23582
+ }
23583
+ };
23549
23584
  }
23550
- function str(value, fallback = "") {
23551
- return typeof value === "string" ? value : fallback;
23585
+ async function writeSupportBundle(outputPath, bundle, secureFileOptions = {}) {
23586
+ const dir = path14__default.dirname(outputPath);
23587
+ const parentStat = await promises.stat(dir);
23588
+ if (!parentStat.isDirectory()) throw new Error("support bundle output parent is not a directory");
23589
+ const privateDir = path14__default.join(dir, `.${path14__default.basename(outputPath)}.${process.pid}.${randomUUID()}.private`);
23590
+ const tempPath = path14__default.join(privateDir, "bundle.tmp");
23591
+ try {
23592
+ await promises.mkdir(privateDir, { mode: 448 });
23593
+ await ensureSecureDir(privateDir, secureFileOptions);
23594
+ await atomicWriteFile(tempPath, `${JSON.stringify(bundle, null, 2)}
23595
+ `, { mode: 384, fsync: true });
23596
+ await ensureSecureFile(tempPath, secureFileOptions);
23597
+ await promises.link(tempPath, outputPath);
23598
+ const published = await promises.open(outputPath, "r+");
23599
+ try {
23600
+ await published.sync();
23601
+ } finally {
23602
+ await published.close();
23603
+ }
23604
+ if (process.platform !== "win32") {
23605
+ const parent = await promises.open(dir, "r");
23606
+ try {
23607
+ await parent.sync();
23608
+ } finally {
23609
+ await parent.close();
23610
+ }
23611
+ }
23612
+ } finally {
23613
+ await promises.rm(privateDir, { recursive: true, force: true }).catch(() => void 0);
23614
+ }
23552
23615
  }
23553
- function num(value) {
23554
- return typeof value === "number" ? value : void 0;
23616
+
23617
+ // src/bin/format.ts
23618
+ function quote(text) {
23619
+ return JSON.stringify(text);
23555
23620
  }
23556
- function bool(value, fallback) {
23557
- return typeof value === "boolean" ? value : fallback;
23621
+ function redactedByteCountPlaceholder(text) {
23622
+ return `[redacted: ${Buffer.byteLength(text, "utf8")} bytes]`;
23558
23623
  }
23559
- function reconstructAgentEvent(raw) {
23560
- const r = asRecord2(raw);
23561
- const type = str(r.type);
23562
- switch (type) {
23624
+ var STABLE_GIT_ERROR_CATEGORIES2 = new Set(GIT_ERROR_CATEGORIES);
23625
+ function stableGitErrorCategory2(value) {
23626
+ return value !== void 0 && STABLE_GIT_ERROR_CATEGORIES2.has(value) ? value : void 0;
23627
+ }
23628
+ function formatAgentEvent(event) {
23629
+ switch (event.type) {
23563
23630
  case "progress":
23564
- return { type: "progress", text: placeholderFor(num(r.textSize)) };
23631
+ return `progress: ${quote(event.text)}`;
23565
23632
  case "tool_use":
23566
- return { type: "tool_use", tool: str(r.tool), input: placeholderFor(num(r.inputSize), bool(r.inputSpilled, false)) };
23633
+ return `tool_use: ${event.tool}`;
23567
23634
  case "tool_result":
23568
- return { type: "tool_result", tool: str(r.tool), output: placeholderFor(num(r.outputSize), bool(r.outputSpilled, false)) };
23635
+ return `tool_result: ${event.tool}`;
23569
23636
  case "artifact":
23570
- return { type: "artifact", name: str(r.name), contentType: str(r.contentType) };
23637
+ return `artifact: ${event.name} (${event.contentType})`;
23571
23638
  case "needs_approval":
23572
- return { type: "needs_approval", summary: placeholderFor(num(r.summarySize)) };
23639
+ return `needs_approval: ${quote(event.summary)}`;
23573
23640
  case "turn_end":
23574
- return { type: "turn_end" };
23641
+ return "turn_end";
23575
23642
  case "error":
23576
- return { type: "error", message: placeholderFor(num(r.messageSize)) };
23577
- case "usage":
23578
- return {
23579
- type: "usage",
23580
- inputTokens: num(r.inputTokens),
23581
- cachedInputTokens: num(r.cachedInputTokens),
23582
- outputTokens: num(r.outputTokens),
23583
- reasoningTokens: num(r.reasoningTokens),
23584
- totalTokens: num(r.totalTokens)
23585
- };
23586
- default:
23587
- return { type: "error", message: `[unrecognized audit event type: ${type || "(missing)"}]` };
23643
+ return `error: ${quote(event.message)}`;
23644
+ case "usage": {
23645
+ const parts = [];
23646
+ if (event.inputTokens !== void 0) parts.push(`in=${event.inputTokens}`);
23647
+ if (event.outputTokens !== void 0) parts.push(`out=${event.outputTokens}`);
23648
+ if (event.totalTokens !== void 0) parts.push(`total=${event.totalTokens}`);
23649
+ return `usage: ${parts.length ? parts.join(" ") : "(no fields reported)"}`;
23650
+ }
23588
23651
  }
23589
23652
  }
23590
- function reconstructDaemonEvent(raw) {
23591
- const kind = str(raw.kind);
23592
- const ts = str(raw.ts);
23593
- switch (kind) {
23653
+ function formatDaemonEventLine(event, options = {}) {
23654
+ const prefix = `[${event.ts}]`;
23655
+ switch (event.kind) {
23594
23656
  case "offered":
23595
- return { kind: "offered", ts, taskId: str(raw.taskId), runtime: typeof raw.runtime === "string" ? raw.runtime : void 0 };
23596
- case "claimed": {
23597
- const claimedRuntime = typeof raw.claimedRuntime === "string" ? raw.claimedRuntime : void 0;
23598
- return claimedRuntime === void 0 ? { kind: "claimed", ts, taskId: str(raw.taskId) } : { kind: "claimed", ts, taskId: str(raw.taskId), claimedRuntime };
23599
- }
23600
- case "started":
23601
- return { kind: "started", ts, taskId: str(raw.taskId) };
23602
- case "progress":
23603
- return { kind: "progress", ts, taskId: str(raw.taskId), event: reconstructAgentEvent(raw.event) };
23604
- case "artifact": {
23605
- const blobRefPresent = raw.blobRef !== void 0 && raw.blobRef !== null;
23606
- const blobRefRaw = asRecord2(raw.blobRef);
23607
- const blobRef = blobRefPresent ? {
23608
- blobId: str(blobRefRaw.blobId),
23609
- contentHash: str(blobRefRaw.contentHash),
23610
- size: num(blobRefRaw.size) ?? 0,
23611
- contentType: str(blobRefRaw.contentType)
23612
- } : void 0;
23613
- const inlineSize = num(raw.inlineSize);
23614
- return {
23615
- kind: "artifact",
23616
- ts,
23617
- taskId: str(raw.taskId),
23618
- name: str(raw.name),
23619
- contentType: str(raw.contentType),
23620
- inline: inlineSize === void 0 ? void 0 : placeholderFor(inlineSize),
23621
- blobRef
23622
- };
23623
- }
23624
- case "awaiting-approval":
23625
- return { kind: "awaiting-approval", ts, taskId: str(raw.taskId), summary: placeholderFor(num(raw.summarySize)) };
23626
- case "completed":
23627
- return {
23628
- kind: "completed",
23629
- ts,
23630
- taskId: str(raw.taskId),
23631
- summary: placeholderFor(num(raw.summarySize)),
23632
- sessionRef: str(raw.sessionRef)
23633
- };
23634
- case "failed":
23635
- return {
23636
- kind: "failed",
23637
- ts,
23638
- taskId: str(raw.taskId),
23639
- reason: placeholderFor(num(raw.reasonSize)),
23640
- retryable: bool(raw.retryable, false),
23641
- preClaim: typeof raw.preClaim === "boolean" ? raw.preClaim : void 0
23642
- };
23643
- case "cancelled": {
23644
- const reasonSize = num(raw.reasonSize);
23645
- return {
23646
- kind: "cancelled",
23647
- ts,
23648
- taskId: str(raw.taskId),
23649
- reason: reasonSize === void 0 ? void 0 : placeholderFor(reasonSize)
23650
- };
23651
- }
23652
- case "connection":
23653
- return { kind: "connection", ts, state: str(raw.state) };
23654
- case "paired":
23655
- return { kind: "paired", ts, deviceId: str(raw.deviceId) };
23656
- case "unpaired":
23657
- return { kind: "unpaired", ts };
23658
- case "runtimes-detected":
23659
- return { kind: "runtimes-detected", ts, runtimes: Array.isArray(raw.runtimes) ? raw.runtimes : [] };
23660
- case "shutdown-requested":
23661
- return { kind: "shutdown-requested", ts, reason: str(raw.reason) };
23662
- case "shutdown-complete":
23663
- return { kind: "shutdown-complete", ts, reason: str(raw.reason), undeliveredOutboxCount: num(raw.undeliveredOutboxCount) };
23664
- case "stale-approval-decision": {
23665
- const reasonSize = num(raw.reasonSize);
23666
- return {
23667
- kind: "stale-approval-decision",
23668
- ts,
23669
- taskId: str(raw.taskId),
23670
- decision: str(raw.decision),
23671
- reason: reasonSize === void 0 ? void 0 : placeholderFor(reasonSize)
23672
- };
23673
- }
23674
- case "runtime-disposal-failed":
23675
- return {
23676
- kind: "runtime-disposal-failed",
23677
- ts,
23678
- taskId: str(raw.taskId),
23679
- runtimeId: str(raw.runtimeId),
23680
- stage: str(raw.stage),
23681
- reason: str(raw.reason)
23682
- };
23683
- case "device-assertion": {
23684
- if (raw.result === "issued") {
23685
- return {
23686
- kind: "device-assertion",
23687
- ts,
23688
- result: "issued",
23689
- audience: typeof raw.audience === "string" ? raw.audience : "",
23690
- jti: typeof raw.jti === "string" ? raw.jti : "",
23691
- expiresAt: typeof raw.expiresAt === "string" ? raw.expiresAt : ""
23692
- };
23693
- }
23694
- const audienceSize = num(raw.audienceSize);
23695
- return {
23696
- kind: "device-assertion",
23697
- ts,
23698
- result: "denied",
23699
- reason: typeof raw.reason === "string" ? raw.reason : "",
23700
- ...audienceSize === void 0 ? {} : { audienceSize }
23701
- };
23657
+ return `${prefix} offered taskId=${event.taskId}${event.runtime ? ` runtime=${event.runtime}` : ""}`;
23658
+ case "claimed":
23659
+ return `${prefix} claimed taskId=${event.taskId}${event.claimedRuntime !== void 0 ? ` claimedRuntime=${event.claimedRuntime}` : ""}`;
23660
+ case "started":
23661
+ return `${prefix} started taskId=${event.taskId}`;
23662
+ case "progress":
23663
+ return `${prefix} progress taskId=${event.taskId} ${formatAgentEvent(event.event)}`;
23664
+ case "artifact":
23665
+ return `${prefix} artifact taskId=${event.taskId} name=${event.name} contentType=${event.contentType}`;
23666
+ case "awaiting-approval": {
23667
+ const summary = options.redactApprovalSummary ? redactedByteCountPlaceholder(event.summary) : event.summary;
23668
+ return `${prefix} awaiting-approval taskId=${event.taskId}${event.approvalId ? ` approvalId=${event.approvalId}` : ""} summary=${quote(summary)}`;
23702
23669
  }
23670
+ case "completed":
23671
+ return `${prefix} completed taskId=${event.taskId} sessionRef=${event.sessionRef} summary=${quote(event.summary)}`;
23672
+ case "failed":
23673
+ return `${prefix} failed taskId=${event.taskId} retryable=${event.retryable}${event.preClaim ? " preClaim=true" : ""} reason=${quote(event.reason)}`;
23674
+ case "cancelled":
23675
+ return `${prefix} cancelled taskId=${event.taskId}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
23676
+ case "connection":
23677
+ return `${prefix} connection state=${event.state}`;
23678
+ case "paired":
23679
+ return `${prefix} paired deviceId=${event.deviceId}`;
23680
+ case "unpaired":
23681
+ return `${prefix} unpaired`;
23682
+ case "runtimes-detected":
23683
+ return `${prefix} runtimes-detected ids=${event.runtimes.map((r) => r.id).join(",") || "(none)"}`;
23684
+ case "shutdown-requested":
23685
+ return `${prefix} shutdown-requested reason=${quote(event.reason)}`;
23686
+ case "shutdown-complete":
23687
+ return `${prefix} shutdown-complete reason=${quote(event.reason)}${event.undeliveredOutboxCount !== void 0 ? ` undeliveredOutboxCount=${event.undeliveredOutboxCount}` : ""}`;
23688
+ case "stale-approval-decision":
23689
+ return `${prefix} stale-approval-decision taskId=${event.taskId} decision=${event.decision}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
23690
+ case "runtime-disposal-failed":
23691
+ return `${prefix} runtime-disposal-failed taskId=${event.taskId} runtime=${event.runtimeId} stage=${event.stage} reason=${quote(event.reason)}`;
23703
23692
  case "git-workspace": {
23704
- const commitsSinceBaseline = gitCount(raw.commitsSinceBaseline);
23705
- const dirty = gitDirty(raw.dirty);
23706
- return {
23707
- kind: "git-workspace",
23708
- ts,
23709
- taskId: str(raw.taskId),
23710
- workspaceId: str(raw.workspaceId),
23711
- phase: str(raw.phase),
23712
- headChanged: typeof raw.headChanged === "boolean" ? raw.headChanged : void 0,
23713
- commitsSinceBaseline,
23714
- dirty,
23715
- errorCategory: stableGitErrorCategory2(raw.errorCategory)
23716
- };
23693
+ const parts = [
23694
+ `${prefix} git-workspace taskId=${event.taskId}`,
23695
+ `workspaceId=${event.workspaceId}`,
23696
+ `phase=${event.phase}`,
23697
+ event.headChanged !== void 0 ? `headChanged=${event.headChanged}` : void 0,
23698
+ event.commitsSinceBaseline !== void 0 ? `commits=${event.commitsSinceBaseline}` : void 0,
23699
+ event.dirty ? `dirty=${event.dirty.staged}/${event.dirty.unstaged}/${event.dirty.untracked}/${event.dirty.conflicted}` : void 0,
23700
+ stableGitErrorCategory2(event.errorCategory) ? `errorCategory=${stableGitErrorCategory2(event.errorCategory)}` : void 0
23701
+ ].filter((part) => part !== void 0);
23702
+ return parts.join(" ");
23703
+ }
23704
+ case "device-assertion": {
23705
+ const parts = event.result === "issued" ? [
23706
+ `${prefix} device-assertion result=issued`,
23707
+ `audience=${quote(event.audience)}`,
23708
+ `jti=${event.jti}`,
23709
+ `expiresAt=${event.expiresAt}`
23710
+ ] : [
23711
+ `${prefix} device-assertion result=denied`,
23712
+ `reason=${event.reason}`,
23713
+ event.audienceSize !== void 0 ? `audienceSize=${event.audienceSize}` : void 0
23714
+ ].filter((part) => part !== void 0);
23715
+ return parts.join(" ");
23717
23716
  }
23718
- default:
23719
- return void 0;
23720
23717
  }
23721
23718
  }
23722
- async function appendAuditEvent(storeDir, event) {
23723
- await promises.mkdir(storeDir, { recursive: true, mode: AUDIT_STORE_DIR_MODE });
23724
- await promises.chmod(storeDir, AUDIT_STORE_DIR_MODE).catch(() => {
23719
+ function formatTaskLine(task) {
23720
+ const git = "git" in task ? task.git : void 0;
23721
+ const gitStatus = git ? [
23722
+ `git=${git.phase}`,
23723
+ git.commitsSinceBaseline !== void 0 ? `commits=${git.commitsSinceBaseline}` : void 0,
23724
+ git.dirty ? `dirty=${git.dirty.staged}/${git.dirty.unstaged}/${git.dirty.untracked}/${git.dirty.conflicted}` : void 0
23725
+ ].filter((part) => part !== void 0).join(" ") : void 0;
23726
+ const parts = [
23727
+ task.taskId,
23728
+ task.state,
23729
+ task.runtime ? `runtime=${task.runtime}` : void 0,
23730
+ gitStatus,
23731
+ task.claimedRuntime !== void 0 ? `claimedRuntime=${task.claimedRuntime}` : void 0,
23732
+ `updatedAt=${task.updatedAt}`,
23733
+ task.sessionRef ? `sessionRef=${task.sessionRef}` : void 0,
23734
+ task.declined ? "declined=true" : void 0,
23735
+ task.summary ? `summary=${quote(task.summary)}` : void 0
23736
+ ].filter((part) => Boolean(part));
23737
+ return parts.join(" ");
23738
+ }
23739
+ function formatTaskListLines(tasks) {
23740
+ if (tasks.length === 0) return ["(no tasks observed yet)"];
23741
+ return tasks.map(formatTaskLine);
23742
+ }
23743
+ function formatRuntimeLines(runtimes) {
23744
+ if (runtimes.length === 0) return ["(no runtimes configured \u2014 check runtimeAllowlist)"];
23745
+ return runtimes.map((r) => {
23746
+ if (!r.present) return `${r.id}: ${r.outcome}`;
23747
+ const caps = [];
23748
+ if (r.steer) caps.push("steer");
23749
+ if (r.resume) caps.push("resume");
23750
+ const parts = [
23751
+ "present",
23752
+ r.version ? `version=${r.version}` : void 0,
23753
+ r.authPresent !== void 0 ? `authPresent=${r.authPresent}` : void 0,
23754
+ `capabilities=${caps.length ? caps.join(",") : "(none)"}`,
23755
+ `modes=${r.permissionModes.length ? r.permissionModes.join(",") : "(none)"}`
23756
+ ].filter((part) => Boolean(part));
23757
+ return `${r.id}: ${parts.join(" ")}`;
23725
23758
  });
23726
- const filePath = auditLogPath(storeDir);
23727
- const line = `${JSON.stringify(redactForAudit(event))}
23728
- `;
23729
- const handle = await promises.open(filePath, "a", AUDIT_LOG_MODE);
23730
- try {
23731
- await handle.chmod(AUDIT_LOG_MODE);
23732
- await handle.appendFile(line, "utf8");
23733
- } finally {
23734
- await handle.close();
23735
- }
23736
- await rotateIfNeeded(filePath);
23737
23759
  }
23738
- var TERMINAL_EVENT_KINDS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
23739
- function eventTaskId(event) {
23740
- if (event.kind === "git-workspace") return void 0;
23741
- return "taskId" in event ? event.taskId : void 0;
23760
+ function formatStatusLines(view) {
23761
+ const lines = [];
23762
+ const label = view.branding?.displayName ?? view.productName;
23763
+ lines.push(`product: ${label} (${view.productId})`);
23764
+ lines.push(
23765
+ `local-agent-release: ${view.localAgentRelease.version}${view.localAgentRelease.buildId ? ` buildId=${view.localAgentRelease.buildId}` : ""}`
23766
+ );
23767
+ if (view.branding?.supportUrl) lines.push(`support: ${view.branding.supportUrl}`);
23768
+ lines.push(`paired: ${view.paired ? "yes" : "no"}${view.deviceId ? ` deviceId=${view.deviceId}` : ""}`);
23769
+ lines.push(
23770
+ view.connection ? `connection: last-known=${view.connection.state} at=${view.connection.ts}` : "connection: unknown (no audit log yet \u2014 run `byok-agent start` at least once to begin observing)"
23771
+ );
23772
+ const runtimeSummary = view.runtimes.map((r) => `${r.id}=${r.present ? "present" : r.outcome}`).join(" ");
23773
+ lines.push(`runtimes: ${runtimeSummary || "(none configured)"}`);
23774
+ const c = view.taskCounts;
23775
+ lines.push(
23776
+ `tasks: total=${c.total} offered=${c.Offered} claimed=${c.Claimed} running=${c.Running} awaitApproval=${c.AwaitApproval} complete=${c.Complete} failed=${c.Failed} cancelled=${c.Cancelled}`
23777
+ );
23778
+ lines.push(`audit-log: ${view.auditLogPath} (${view.auditLogLineCount} event${view.auditLogLineCount === 1 ? "" : "s"})`);
23779
+ return lines;
23742
23780
  }
23743
- function compactPreservingLiveTasks(lines, targetLines) {
23744
- if (lines.length <= targetLines) return [...lines];
23745
- const cutIndex = lines.length - targetLines;
23746
- const hasTerminalEvent = /* @__PURE__ */ new Set();
23747
- const lastIndexForTask = /* @__PURE__ */ new Map();
23748
- for (let i = 0; i < lines.length; i++) {
23749
- const line = lines[i];
23750
- if (line === void 0) continue;
23751
- const event = parseAuditLine(line);
23752
- if (!event) continue;
23753
- const taskId = eventTaskId(event);
23754
- if (taskId === void 0) continue;
23755
- lastIndexForTask.set(taskId, i);
23756
- if (TERMINAL_EVENT_KINDS.has(event.kind)) hasTerminalEvent.add(taskId);
23781
+ function formatLiveStatusLines(live) {
23782
+ const liveRelease = live.localAgentRelease;
23783
+ const lines = [
23784
+ `live: pid=${live.pid} uptimeMs=${live.uptimeMs} transport=${live.transport}`,
23785
+ liveRelease ? `live-local-agent-release: ${liveRelease.version}${liveRelease.buildId ? ` buildId=${liveRelease.buildId}` : ""}` : "live-local-agent-release: unknown",
23786
+ `live-paired: ${live.paired ? "yes" : "no"}${live.deviceId ? ` deviceId=${live.deviceId}` : ""}`,
23787
+ `live-runtimes: ${live.runtimeIds.length ? live.runtimeIds.join(",") : "(none)"}`,
23788
+ `live-toolsets: revision=${live.toolsets.revision} count=${live.toolsets.toolsets.length}`
23789
+ ];
23790
+ for (const toolset of live.toolsets.toolsets) lines.push(formatToolsetStatusLine("live-toolset", toolset));
23791
+ const health = live.operationalHealth;
23792
+ if (health.availability === "unavailable") {
23793
+ lines.push(`live-operational-health: unavailable reason=${quote(health.reason)}`);
23794
+ } else {
23795
+ lines.push(
23796
+ `live-operational-health: state=${health.state} failures=${health.failureCount}/${health.failureThreshold} windowMs=${health.windowMs} crashes=${health.crashCount}${health.lastCrashAt ? ` lastCrashAt=${health.lastCrashAt}` : ""}`
23797
+ );
23757
23798
  }
23758
- let anchorIndices = [];
23759
- for (const [taskId, lastIndex] of lastIndexForTask) {
23760
- if (hasTerminalEvent.has(taskId)) continue;
23761
- if (lastIndex < cutIndex) anchorIndices.push(lastIndex);
23799
+ if (live.activeTasks.length === 0) {
23800
+ lines.push("live-active-tasks: (none)");
23801
+ } else {
23802
+ for (const task of live.activeTasks) {
23803
+ lines.push(`live-active-task: ${task.taskId} ${task.state}`);
23804
+ }
23762
23805
  }
23763
- anchorIndices.sort((a, b) => a - b);
23764
- if (anchorIndices.length > MAX_LIVE_TASK_ANCHORS) {
23765
- const totalCandidates = anchorIndices.length;
23766
- const droppedCount = totalCandidates - MAX_LIVE_TASK_ANCHORS;
23767
- anchorIndices = anchorIndices.slice(-MAX_LIVE_TASK_ANCHORS);
23768
- console.warn(
23769
- `[byok/client] audit log rotation: ${totalCandidates} non-terminal task lifecycle anchors exceeded MAX_LIVE_TASK_ANCHORS (${MAX_LIVE_TASK_ANCHORS}) \u2014 dropped the oldest ${droppedCount}, kept the most recently-touched ${MAX_LIVE_TASK_ANCHORS}; dropped tasks will stop appearing in tasks/status once their own events age out of the retained tail`
23806
+ lines.push(`live-approvals-pending: ${live.approvalsPending}`);
23807
+ if (live.approvals.length === 0) {
23808
+ lines.push("live-approvals: (none)");
23809
+ } else {
23810
+ for (const approval of live.approvals) {
23811
+ lines.push(`live-approval: ${approval.approvalId} taskId=${approval.taskId} summary=${quote(summaryExcerpt(approval.summary))}`);
23812
+ }
23813
+ }
23814
+ if (live.queueWatermarks.length === 0) {
23815
+ lines.push("live-queue-watermarks: (none)");
23816
+ } else {
23817
+ for (const watermark of live.queueWatermarks) {
23818
+ lines.push(
23819
+ `live-queue-watermark: ${watermark.taskId} progressBatcherPending=${watermark.progressBatcherPending} pendingApprovals=${watermark.pendingApprovals}`
23820
+ );
23821
+ }
23822
+ }
23823
+ const storage = live.storage;
23824
+ if (storage !== void 0) {
23825
+ lines.push(
23826
+ `live-storage: state=${storage.pressureState} used=${storage.usedBytes} budget=${storage.budgetBytes} free=${storage.freeBytes} measuredAt=${storage.measuredAt}`
23770
23827
  );
23828
+ for (const category of storage.categories) {
23829
+ lines.push(`live-storage-category: ${category.category} bytes=${category.bytes}${category.approximate ? " (approximate)" : ""}`);
23830
+ }
23831
+ if (storage.lastCompaction) {
23832
+ lines.push(
23833
+ `live-storage-compaction: checkpointed=${storage.lastCompaction.checkpointed} walFramesRemaining=${storage.lastCompaction.walFramesRemaining} pagesVacuumed=${storage.lastCompaction.pagesVacuumed} durationMs=${storage.lastCompaction.durationMs} at=${storage.lastCompaction.at}`
23834
+ );
23835
+ }
23771
23836
  }
23772
- return [...anchorIndices.map((i) => lines[i]), ...lines.slice(cutIndex)].filter((l) => l !== void 0);
23837
+ return lines;
23773
23838
  }
23774
- async function rotateIfNeeded(filePath) {
23775
- let size;
23776
- try {
23777
- size = (await promises.stat(filePath)).size;
23778
- } catch {
23779
- return;
23780
- }
23781
- if (size <= MAX_AUDIT_LOG_BYTES) return;
23782
- const lines = await readCompleteLines(filePath);
23783
- const kept = compactPreservingLiveTasks(lines, AUDIT_LOG_TRIM_TARGET_LINES);
23784
- const content = kept.length > 0 ? `${kept.join("\n")}
23785
- ` : "";
23786
- await atomicWriteFile(filePath, content, { mode: AUDIT_LOG_MODE });
23839
+ function formatToolsetStatusLine(prefix, toolset) {
23840
+ const observation = toolset.observation;
23841
+ return [
23842
+ `${prefix}: ${toolset.id}`,
23843
+ `servers=${toolset.serverCount}`,
23844
+ `definitionRevision=${toolset.definitionRevision}`,
23845
+ `state=${observation?.state ?? "unobserved"}`,
23846
+ observation?.version === void 0 ? void 0 : `version=${quote(observation.version)}`,
23847
+ observation?.observedAt === void 0 ? void 0 : `observedAt=${observation.observedAt}`,
23848
+ observation?.reasonCode === void 0 ? void 0 : `reasonCode=${observation.reasonCode}`
23849
+ ].filter((part) => part !== void 0).join(" ");
23850
+ }
23851
+ function formatToolsetsReloadReceiptLines(receipt) {
23852
+ return [
23853
+ `toolsets-reload: changed=${receipt.changed ? "yes" : "no"} previousRevision=${receipt.previousRevision} revision=${receipt.revision}`,
23854
+ ...receipt.toolsets.map((toolset) => formatToolsetStatusLine("toolset", toolset))
23855
+ ];
23856
+ }
23857
+ var SUMMARY_EXCERPT_MAX_LEN = 60;
23858
+ function summaryExcerpt(summary) {
23859
+ if (!summary) return "(no summary)";
23860
+ return summary.length > SUMMARY_EXCERPT_MAX_LEN ? `${summary.slice(0, SUMMARY_EXCERPT_MAX_LEN)}\u2026` : summary;
23787
23861
  }
23788
- function createAuditAppender(storeDir, onError = () => {
23789
- }) {
23790
- let chain = Promise.resolve();
23791
- return (event) => {
23792
- chain = chain.then(() => appendAuditEvent(storeDir, event)).catch(onError);
23793
- };
23862
+ function formatAge(ms) {
23863
+ const totalSeconds = Math.floor(Math.max(0, ms) / 1e3);
23864
+ if (totalSeconds < 60) return `${totalSeconds}s`;
23865
+ const totalMinutes = Math.floor(totalSeconds / 60);
23866
+ if (totalMinutes < 60) return `${totalMinutes}m`;
23867
+ const totalHours = Math.floor(totalMinutes / 60);
23868
+ if (totalHours < 24) return `${totalHours}h`;
23869
+ const totalDays = Math.floor(totalHours / 24);
23870
+ return `${totalDays}d`;
23794
23871
  }
23795
- function isAuditRecordShaped(value) {
23796
- return typeof value === "object" && value !== null && typeof value.kind === "string" && typeof value.ts === "string";
23872
+ function formatApprovalsListLines(approvals, nowMs) {
23873
+ if (approvals.length === 0) return ["(no pending approvals)"];
23874
+ return approvals.map((approval) => {
23875
+ const ageMs = nowMs - Date.parse(approval.createdAt);
23876
+ return `${approval.approvalId} taskId=${approval.taskId} age=${formatAge(ageMs)} summary=${quote(summaryExcerpt(approval.summary))}`;
23877
+ });
23797
23878
  }
23798
- function parseAuditLine(line) {
23799
- const trimmed = line.trim();
23800
- if (!trimmed) return void 0;
23879
+
23880
+ // src/bin/commands/approvals.ts
23881
+ async function runApprovalsCommand(storeDir, productId, deps = {}) {
23882
+ const log = deps.log ?? ((line) => console.log(line));
23883
+ const error = deps.error ?? ((line) => console.error(line));
23884
+ const connectControl = deps.connectControl ?? connectControlClient;
23885
+ const now = deps.now ?? (() => Date.now());
23886
+ const conn = await connectControl({ storeDir, productId });
23887
+ if (!conn.ok) {
23888
+ const message = `cannot list approvals: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23889
+ error(message);
23890
+ throw new Error(message);
23891
+ }
23801
23892
  try {
23802
- const parsed = JSON.parse(trimmed);
23803
- if (!isAuditRecordShaped(parsed)) return void 0;
23804
- return reconstructDaemonEvent(parsed);
23805
- } catch {
23806
- return void 0;
23893
+ const result = await conn.client.request("approvals.list");
23894
+ for (const line of formatApprovalsListLines(result.approvals, now())) log(line);
23895
+ } catch (err) {
23896
+ if (err instanceof ControlError) {
23897
+ error(`approvals list failed: ${err.message}`);
23898
+ } else {
23899
+ error(`approvals list failed: ${err instanceof Error ? err.message : String(err)}`);
23900
+ }
23901
+ throw err;
23902
+ } finally {
23903
+ conn.client.close();
23807
23904
  }
23808
23905
  }
23809
- function completeLines(raw) {
23810
- const parts = raw.split("\n");
23811
- parts.pop();
23812
- return parts;
23906
+
23907
+ // src/bin/commands/approve-reject.ts
23908
+ async function runApproveCommand(storeDir, productId, approvalId, deps = {}) {
23909
+ return resolveApproval(storeDir, productId, approvalId, "approve", void 0, deps);
23813
23910
  }
23814
- async function readCompleteLines(filePath) {
23815
- let raw;
23911
+ async function runRejectCommand(storeDir, productId, approvalId, reason, deps = {}) {
23912
+ return resolveApproval(storeDir, productId, approvalId, "reject", reason, deps);
23913
+ }
23914
+ async function resolveApproval(storeDir, productId, approvalId, decision, reason, deps) {
23915
+ const log = deps.log ?? ((line) => console.log(line));
23916
+ const error = deps.error ?? ((line) => console.error(line));
23917
+ const connectControl = deps.connectControl ?? connectControlClient;
23918
+ const verb = decision === "approve" ? "approved" : "rejected";
23919
+ const conn = await connectControl({ storeDir, productId });
23920
+ if (!conn.ok) {
23921
+ const message = `cannot ${decision} approvalId=${approvalId}: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23922
+ error(message);
23923
+ throw new Error(message);
23924
+ }
23816
23925
  try {
23817
- raw = await promises.readFile(filePath, "utf8");
23926
+ await conn.client.request("approvals.resolve", { approvalId, decision, reason });
23927
+ log(`${verb}: approvalId=${approvalId}${reason ? ` reason=${JSON.stringify(reason)}` : ""}`);
23818
23928
  } catch (err) {
23819
- if (err.code === "ENOENT") return [];
23929
+ if (err instanceof ControlError && err.code === "not_found") {
23930
+ error(`${err.message} \u2014 run \`byok-agent approvals\` to see pending approvalIds`);
23931
+ } else {
23932
+ error(`${decision} failed: ${err instanceof Error ? err.message : String(err)}`);
23933
+ }
23820
23934
  throw err;
23935
+ } finally {
23936
+ conn.client.close();
23821
23937
  }
23822
- return completeLines(raw);
23823
23938
  }
23824
- async function readAuditEvents(storeDir) {
23825
- const lines = await readCompleteLines(auditLogPath(storeDir));
23826
- const events = [];
23827
- for (const line of lines) {
23828
- const event = parseAuditLine(line);
23829
- if (event) events.push(event);
23939
+
23940
+ // src/bin/commands/pair.ts
23941
+ async function runPairCommand(config, code, deps = {}) {
23942
+ const log = deps.log ?? ((line) => console.log(line));
23943
+ if (deps.daemon) {
23944
+ const result2 = await deps.daemon.pair(code);
23945
+ log(`paired: deviceId=${result2.deviceId}`);
23946
+ return;
23830
23947
  }
23831
- return events;
23832
- }
23833
- function delay3(ms, signal) {
23834
- if (signal.aborted) return Promise.resolve();
23835
- return new Promise((resolve) => {
23836
- const onAbort = () => {
23837
- clearTimeout(timer);
23838
- resolve();
23839
- };
23840
- const timer = setTimeout(() => {
23841
- signal.removeEventListener("abort", onAbort);
23842
- resolve();
23843
- }, ms);
23844
- signal.addEventListener("abort", onAbort, { once: true });
23845
- });
23846
- }
23847
- async function followAuditLog(filePath, onEvent, options) {
23848
- const { signal, pollIntervalMs = 200, fromEnd = true } = options;
23849
- let offset = 0;
23850
- let pending = Buffer.alloc(0);
23851
- let lastFileIdentity;
23852
- if (fromEnd) {
23948
+ const connectControl = deps.connectControl ?? connectControlClient;
23949
+ const conn = await connectControl({ storeDir: resolveStoreDir(config), productId: config.productId });
23950
+ if (conn.ok) {
23853
23951
  try {
23854
- const st = await promises.stat(filePath);
23855
- offset = st.size;
23856
- lastFileIdentity = `${st.dev}:${st.ino}`;
23857
- } catch (err) {
23858
- if (err.code !== "ENOENT") throw err;
23859
- offset = 0;
23952
+ const result2 = await conn.client.request("enrollment.pair", { pairingCode: code });
23953
+ log(`paired: deviceId=${result2.deviceId}`);
23954
+ return;
23955
+ } finally {
23956
+ conn.client.close();
23860
23957
  }
23861
23958
  }
23862
- async function readNewBytes() {
23863
- let handle;
23864
- try {
23865
- handle = await promises.open(filePath, "r");
23866
- } catch (err) {
23867
- if (err.code === "ENOENT") return Buffer.alloc(0);
23868
- throw err;
23959
+ const daemon = createDaemon(config);
23960
+ const result = await daemon.pair(code);
23961
+ log(`paired: deviceId=${result.deviceId}`);
23962
+ }
23963
+
23964
+ // src/bin/commands/doctor.ts
23965
+ var DoctorConfirmationRequiredError = class extends Error {
23966
+ constructor() {
23967
+ super("doctor --fix requires explicit --yes confirmation");
23968
+ this.name = "DoctorConfirmationRequiredError";
23969
+ }
23970
+ };
23971
+ var DoctorDaemonRunningError = class extends Error {
23972
+ constructor() {
23973
+ super("doctor --fix refuses while the daemon control socket is reachable; stop the daemon first");
23974
+ this.name = "DoctorDaemonRunningError";
23975
+ }
23976
+ };
23977
+ function formatDoctorLines(snapshot, fixResult) {
23978
+ const lines = [
23979
+ `doctor: productHash=${snapshot.product.nameHash} generatedAt=${snapshot.generatedAt}`,
23980
+ `system: node=${snapshot.system.node} platform=${snapshot.system.platform} arch=${snapshot.system.arch} sqlite=${snapshot.system.sqliteAvailable ? "available" : "unavailable"}`
23981
+ ];
23982
+ for (const check of snapshot.checks) lines.push(`check ${check.id}: ${check.status} (${check.summary})`);
23983
+ if (snapshot.health.status === "corrupt" || snapshot.health.status === "unavailable") {
23984
+ lines.push(`health-detail: ${snapshot.health.reason}; bytes=${snapshot.health.sizeBytes ?? "unknown"}`);
23985
+ }
23986
+ if (snapshot.quarantine.truncated) {
23987
+ lines.push(`quarantine-detail: inventory truncated after ${snapshot.quarantine.scannedCount} scanned entries`);
23988
+ }
23989
+ if (fixResult?.status === "quarantined") {
23990
+ lines.push(
23991
+ `fix: quarantined ${fixResult.sizeBytes} bytes as ${fixResult.evidenceName}; manifest=${fixResult.manifestName}; sha256=${fixResult.sha256}`
23992
+ );
23993
+ } else if (fixResult) {
23994
+ lines.push(`fix: no change (${fixResult.reason})`);
23995
+ }
23996
+ return lines;
23997
+ }
23998
+ async function runDoctorCommand(config, options = {}) {
23999
+ if (options.repair !== void 0) {
24000
+ if (options.fix || options.repair !== "restore-enrollment-metadata") {
24001
+ throw new Error("doctor requires one supported repair action; --fix cannot be combined with --repair");
23869
24002
  }
24003
+ const log2 = options.log ?? ((line) => console.log(line));
23870
24004
  try {
23871
- const st = await handle.stat();
23872
- const identity = `${st.dev}:${st.ino}`;
23873
- if (lastFileIdentity !== void 0 && identity !== lastFileIdentity) {
23874
- offset = 0;
23875
- pending = Buffer.alloc(0);
23876
- } else if (st.size < offset) {
23877
- offset = 0;
23878
- pending = Buffer.alloc(0);
24005
+ const repair = await repairDeviceEnrollmentMetadata(config, {
24006
+ confirmed: options.confirmed,
24007
+ expectedDeviceId: options.expectedDeviceId,
24008
+ expectedTenantId: options.expectedTenantId
24009
+ });
24010
+ log2(options.json ? JSON.stringify({ repair }, null, 2) : `repair ${repair.action}: ${repair.status} (${repair.scope})`);
24011
+ return;
24012
+ } catch (error) {
24013
+ if (options.json && error instanceof DeviceMetadataRepairError) {
24014
+ log2(JSON.stringify({ repair: { action: "restore-enrollment-metadata", scope: "device", status: "failed", code: error.code } }, null, 2));
23879
24015
  }
23880
- lastFileIdentity = identity;
23881
- if (st.size <= offset) return Buffer.alloc(0);
23882
- const length = st.size - offset;
23883
- const buffer = Buffer.alloc(length);
23884
- const { bytesRead } = await handle.read(buffer, 0, length, offset);
23885
- offset += bytesRead;
23886
- return buffer.subarray(0, bytesRead);
23887
- } finally {
23888
- await handle.close();
24016
+ throw error;
23889
24017
  }
23890
24018
  }
23891
- while (!signal.aborted) {
23892
- const chunk = await readNewBytes();
23893
- if (chunk.length > 0) {
23894
- const combined = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
23895
- const lastNewline = combined.lastIndexOf(10);
23896
- if (lastNewline === -1) {
23897
- pending = combined;
23898
- } else {
23899
- const completeText = combined.subarray(0, lastNewline + 1).toString("utf8");
23900
- pending = Buffer.from(combined.subarray(lastNewline + 1));
23901
- for (const line of completeText.split("\n")) {
23902
- if (!line) continue;
23903
- const event = parseAuditLine(line);
23904
- if (event) onEvent(event);
23905
- }
23906
- }
23907
- }
23908
- if (signal.aborted) break;
23909
- await delay3(pollIntervalMs, signal);
24019
+ if (options.expectedDeviceId !== void 0 || options.expectedTenantId !== void 0) {
24020
+ throw new Error("expected identity flags require --repair restore-enrollment-metadata");
24021
+ }
24022
+ if (options.fix && !options.confirmed) throw new DoctorConfirmationRequiredError();
24023
+ const storeDir = resolveStoreDir(config);
24024
+ let snapshot = await collectDiagnostics(config, storeDir, options);
24025
+ let fixResult;
24026
+ if (options.fix) {
24027
+ if (snapshot.control.status === "online") throw new DoctorDaemonRunningError();
24028
+ fixResult = await quarantineCorruptOperationalHealth(storeDir, { clock: options.clock });
24029
+ snapshot = await collectDiagnostics(config, storeDir, options);
24030
+ }
24031
+ const log = options.log ?? ((line) => console.log(line));
24032
+ if (options.json) {
24033
+ log(JSON.stringify({ diagnostics: snapshot, ...fixResult ? { fix: fixResult } : {} }, null, 2));
24034
+ return;
24035
+ }
24036
+ for (const line of formatDoctorLines(snapshot, fixResult)) log(line);
24037
+ }
24038
+
24039
+ // src/bin/commands/runtimes.ts
24040
+ async function runRuntimesCommand(config, deps = {}) {
24041
+ const log = deps.log ?? ((line) => console.log(line));
24042
+ const adapters = deps.adapters ?? defaultRuntimeAdapters(config.runtimeAllowlist);
24043
+ const runtimes = await probeRuntimes(adapters);
24044
+ for (const line of formatRuntimeLines(runtimes)) log(line);
24045
+ }
24046
+ function buildServiceDefinition(config, configPath, rest) {
24047
+ const name = argValue(rest, "--name") ?? config.productId;
24048
+ const agentBin = argValue(rest, "--agent-bin") ?? process.argv[1] ?? "byok-agent";
24049
+ const nodeBin = argValue(rest, "--node-bin") ?? process.execPath;
24050
+ const absoluteConfigPath = path14__default.resolve(configPath);
24051
+ const logDir = path14__default.join(resolveStoreDir(config), "service-logs");
24052
+ const definition = {
24053
+ name,
24054
+ displayName: config.branding?.displayName ?? config.productName,
24055
+ program: nodeAgentProgram({ agentBin, configPath: absoluteConfigPath, nodeBin }),
24056
+ logDir
24057
+ };
24058
+ const winswBin = argValue(rest, "--winsw-bin");
24059
+ if (winswBin) {
24060
+ const installDir = argValue(rest, "--winsw-install-dir");
24061
+ definition.windows = installDir ? { winswBin, installDir } : { winswBin };
23910
24062
  }
24063
+ return definition;
24064
+ }
24065
+ function buildLifecycle(config, configPath, rest, deps) {
24066
+ return deps.lifecycle ?? createServiceLifecycle(buildServiceDefinition(config, configPath, rest));
24067
+ }
24068
+ function serviceNameFor(config, rest) {
24069
+ return argValue(rest, "--name") ?? config.productId;
24070
+ }
24071
+ async function runInstallCommand(config, configPath, rest, deps = {}) {
24072
+ const log = deps.log ?? ((line) => console.log(line));
24073
+ const lifecycle = buildLifecycle(config, configPath, rest, deps);
24074
+ await lifecycle.install();
24075
+ log(`service installed and started: ${serviceNameFor(config, rest)}`);
24076
+ }
24077
+ async function runUninstallCommand(config, configPath, rest, deps = {}) {
24078
+ const log = deps.log ?? ((line) => console.log(line));
24079
+ const lifecycle = buildLifecycle(config, configPath, rest, deps);
24080
+ await lifecycle.uninstall();
24081
+ log(`service uninstalled: ${serviceNameFor(config, rest)}`);
24082
+ }
24083
+ async function runServiceStartCommand(config, configPath, rest, deps = {}) {
24084
+ const log = deps.log ?? ((line) => console.log(line));
24085
+ const lifecycle = buildLifecycle(config, configPath, rest, deps);
24086
+ await lifecycle.start();
24087
+ log(`service started: ${serviceNameFor(config, rest)}`);
24088
+ }
24089
+ async function runServiceStopCommand(config, configPath, rest, deps = {}) {
24090
+ const log = deps.log ?? ((line) => console.log(line));
24091
+ const lifecycle = buildLifecycle(config, configPath, rest, deps);
24092
+ await lifecycle.stop();
24093
+ log(`service stopped: ${serviceNameFor(config, rest)}`);
24094
+ }
24095
+ async function runServiceStatusCommand(config, configPath, rest, deps = {}) {
24096
+ const log = deps.log ?? ((line) => console.log(line));
24097
+ const lifecycle = buildLifecycle(config, configPath, rest, deps);
24098
+ const status = await lifecycle.status();
24099
+ log(`installed: ${status.installed ? "yes" : "no"}`);
24100
+ const runningLabel = status.running ? "yes" : status.determinate ? "no" : "unknown (could not query the service manager)";
24101
+ log(`running: ${runningLabel}`);
24102
+ log(`detail: ${status.detail.trim() || "(none)"}`);
23911
24103
  }
23912
24104
 
23913
24105
  // src/bin/commands/start.ts
@@ -24082,194 +24274,6 @@ async function runStatusCommand(config, deps = {}) {
24082
24274
  conn.client.close();
24083
24275
  }
24084
24276
  }
24085
- var MAX_SUPPORT_AUDIT_TAIL_BYTES = 256 * 1024;
24086
- var MAX_SUPPORT_AUDIT_FACTS = 200;
24087
- var AUDIT_KINDS = /* @__PURE__ */ new Set([
24088
- "artifact",
24089
- "awaiting-approval",
24090
- "cancelled",
24091
- "claimed",
24092
- "completed",
24093
- "connection",
24094
- "failed",
24095
- "git-workspace",
24096
- "offered",
24097
- "paired",
24098
- "progress",
24099
- "runtimes-detected",
24100
- "shutdown-complete",
24101
- "shutdown-requested",
24102
- "stale-approval-decision",
24103
- "started",
24104
- "unpaired"
24105
- ]);
24106
- function isAuditKind(value) {
24107
- return typeof value === "string" && AUDIT_KINDS.has(value);
24108
- }
24109
- function sameFileState6(left, right) {
24110
- return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
24111
- }
24112
- async function recentAuditFacts(storeDir) {
24113
- const filePath = auditLogPath(storeDir);
24114
- let pathStat;
24115
- try {
24116
- pathStat = await promises.lstat(filePath, { bigint: true });
24117
- if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
24118
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24119
- }
24120
- } catch (err) {
24121
- if (err.code === "ENOENT") {
24122
- return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24123
- }
24124
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24125
- }
24126
- let handle;
24127
- try {
24128
- handle = await promises.open(
24129
- filePath,
24130
- constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
24131
- );
24132
- } catch (err) {
24133
- if (err.code === "ENOENT") {
24134
- return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24135
- }
24136
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24137
- }
24138
- try {
24139
- const stat = await handle.stat({ bigint: true });
24140
- const namedAfterOpen = await promises.lstat(filePath, { bigint: true });
24141
- if (!stat.isFile() || !namedAfterOpen.isFile() || namedAfterOpen.isSymbolicLink() || !sameFileState6(pathStat, stat) || !sameFileState6(stat, namedAfterOpen)) {
24142
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24143
- }
24144
- const sourceSize = Number(stat.size);
24145
- const length = Math.min(sourceSize, MAX_SUPPORT_AUDIT_TAIL_BYTES);
24146
- const start = Math.max(0, sourceSize - length);
24147
- const buffer = Buffer.alloc(length);
24148
- const { bytesRead } = await handle.read(buffer, 0, length, start);
24149
- const afterRead = await handle.stat({ bigint: true });
24150
- const namedAfterRead = await promises.lstat(filePath, { bigint: true });
24151
- if (bytesRead !== length || namedAfterRead.isSymbolicLink() || !sameFileState6(stat, afterRead) || !sameFileState6(afterRead, namedAfterRead)) {
24152
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24153
- }
24154
- let text = buffer.subarray(0, bytesRead).toString("utf8");
24155
- if (start > 0) text = text.slice(Math.max(0, text.indexOf("\n") + 1));
24156
- const lines = text.split("\n").filter(Boolean).slice(-MAX_SUPPORT_AUDIT_FACTS);
24157
- const facts = [];
24158
- for (const line of lines) {
24159
- try {
24160
- const value = JSON.parse(line);
24161
- if (isAuditKind(value.kind) && typeof value.ts === "string" && Number.isFinite(Date.parse(value.ts))) {
24162
- facts.push({ kind: value.kind, ts: new Date(Date.parse(value.ts)).toISOString() });
24163
- }
24164
- } catch {
24165
- }
24166
- }
24167
- return { status: "available", included: facts.length, sourceBytesRead: bytesRead, sourceTruncated: sourceSize > bytesRead, facts };
24168
- } catch {
24169
- return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
24170
- } finally {
24171
- await handle.close();
24172
- }
24173
- }
24174
- function projectControl(control) {
24175
- if (control.status === "offline") return { status: "offline" };
24176
- const transport = ["connecting", "open", "closed", "revoked"].includes(control.transport) ? control.transport : "unavailable";
24177
- const operationalHealth = control.operationalHealth.availability === "available" ? {
24178
- availability: "available",
24179
- state: control.operationalHealth.state,
24180
- failureCount: control.operationalHealth.failureCount,
24181
- windowMs: control.operationalHealth.windowMs,
24182
- failureThreshold: control.operationalHealth.failureThreshold,
24183
- crashCount: control.operationalHealth.crashCount
24184
- } : { availability: "unavailable" };
24185
- return {
24186
- status: "online",
24187
- pid: control.pid,
24188
- uptimeMs: control.uptimeMs,
24189
- transport,
24190
- activeTaskCount: control.activeTaskCount,
24191
- pendingApprovalCount: control.pendingApprovalCount,
24192
- operationalHealth,
24193
- storagePresent: control.storage !== void 0
24194
- };
24195
- }
24196
- async function createSupportBundle(config, storeDir, options = {}) {
24197
- const [snapshot, recentEvents] = await Promise.all([collectDiagnostics(config, storeDir, options), recentAuditFacts(storeDir)]);
24198
- return {
24199
- version: 1,
24200
- generatedAt: snapshot.generatedAt,
24201
- product: snapshot.product,
24202
- system: snapshot.system,
24203
- config: {
24204
- serverProtocol: snapshot.config.serverProtocol,
24205
- customStoreDir: snapshot.config.customStoreDir,
24206
- hostedJournal: snapshot.config.hostedJournal,
24207
- ...snapshot.config.runtimeAllowlistCount === void 0 ? {} : { runtimeAllowlistCount: snapshot.config.runtimeAllowlistCount }
24208
- },
24209
- device: {
24210
- status: snapshot.device.status,
24211
- ...snapshot.device.deviceIdHash ? { deviceIdHash: snapshot.device.deviceIdHash } : {}
24212
- },
24213
- runtimes: snapshot.runtimes,
24214
- control: projectControl(snapshot.control),
24215
- health: snapshot.health,
24216
- journal: snapshot.journal,
24217
- workspace: snapshot.workspace,
24218
- quarantine: {
24219
- status: snapshot.quarantine.status,
24220
- count: snapshot.quarantine.count,
24221
- scannedCount: snapshot.quarantine.scannedCount,
24222
- truncated: snapshot.quarantine.truncated,
24223
- entries: snapshot.quarantine.entries
24224
- },
24225
- checks: snapshot.checks.map(({ id, status }) => ({ id, status })),
24226
- recentEvents,
24227
- redaction: {
24228
- policy: "allowlist-v1",
24229
- omitted: [
24230
- "server URL host/path/query",
24231
- "store/workspace/config paths",
24232
- "control token and provider credentials",
24233
- "task, prompt, tool input/output and approval text",
24234
- "raw audit and quarantine contents"
24235
- ],
24236
- transformed: ["productName/productId/deviceId/runtimeId/quarantine filenames -> SHA-256", "audit events -> closed kind/timestamp only"]
24237
- }
24238
- };
24239
- }
24240
- async function writeSupportBundle(outputPath, bundle, secureFileOptions = {}) {
24241
- const dir = path14__default.dirname(outputPath);
24242
- const parentStat = await promises.stat(dir);
24243
- if (!parentStat.isDirectory()) throw new Error("support bundle output parent is not a directory");
24244
- const privateDir = path14__default.join(dir, `.${path14__default.basename(outputPath)}.${process.pid}.${randomUUID()}.private`);
24245
- const tempPath = path14__default.join(privateDir, "bundle.tmp");
24246
- try {
24247
- await promises.mkdir(privateDir, { mode: 448 });
24248
- await ensureSecureDir(privateDir, secureFileOptions);
24249
- await atomicWriteFile(tempPath, `${JSON.stringify(bundle, null, 2)}
24250
- `, { mode: 384, fsync: true });
24251
- await ensureSecureFile(tempPath, secureFileOptions);
24252
- await promises.link(tempPath, outputPath);
24253
- const published = await promises.open(outputPath, "r+");
24254
- try {
24255
- await published.sync();
24256
- } finally {
24257
- await published.close();
24258
- }
24259
- if (process.platform !== "win32") {
24260
- const parent = await promises.open(dir, "r");
24261
- try {
24262
- await parent.sync();
24263
- } finally {
24264
- await parent.close();
24265
- }
24266
- }
24267
- } finally {
24268
- await promises.rm(privateDir, { recursive: true, force: true }).catch(() => void 0);
24269
- }
24270
- }
24271
-
24272
- // src/bin/commands/support-bundle.ts
24273
24277
  async function runSupportBundleCommand(config, options) {
24274
24278
  if (!options.outputPath) throw new Error("support-bundle requires --output <path>");
24275
24279
  const outputPath = path14__default.resolve(options.outputPath);