runwork 0.25.3 → 0.26.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.
package/dist/index.js CHANGED
@@ -62,7 +62,11 @@ import {
62
62
  createInflateRaw
63
63
  } from "node:zlib";
64
64
  function firstEnv(names) {
65
- for (const variable of names) {
65
+ const keys = Object.keys(process.env);
66
+ for (const candidate of names) {
67
+ const variable = keys.find((k) => k === candidate) ?? keys.find((k) => k.toLowerCase() === candidate.toLowerCase());
68
+ if (variable === undefined)
69
+ continue;
66
70
  const value = process.env[variable];
67
71
  if (value && value.trim() !== "")
68
72
  return { variable, value: value.trim() };
@@ -828,6 +832,10 @@ class ApiClient {
828
832
  async reportTelemetry(workspaceId, events) {
829
833
  await this.request(`/api/workspaces/${workspaceId}/team/telemetry`, { method: "POST", body: JSON.stringify({ events }) });
830
834
  }
835
+ async createDebugReport(workspaceId, body) {
836
+ const res = await this.request(`/api/workspaces/${workspaceId}/debug-reports`, { method: "POST", body: JSON.stringify(body) });
837
+ return res.data;
838
+ }
831
839
  async createSharedConversation(workspaceId, body) {
832
840
  const res = await this.request(`/api/workspaces/${workspaceId}/shared-conversations`, {
833
841
  method: "POST",
@@ -1038,10 +1046,13 @@ var init_workspace_state = __esm(() => {
1038
1046
  // src/utils/subprocess.ts
1039
1047
  import {
1040
1048
  execFileSync as cpExecFileSync,
1041
- spawn as cpSpawn
1049
+ spawn as cpSpawn,
1050
+ spawnSync as cpSpawnSync
1042
1051
  } from "child_process";
1043
1052
  var execFileSync = (file, args, options) => {
1044
1053
  return cpExecFileSync(file, args, { windowsHide: true, ...options ?? {} });
1054
+ }, spawnSync = (file, args, options) => {
1055
+ return cpSpawnSync(file, args, { windowsHide: true, ...options ?? {} });
1045
1056
  }, spawn2 = (command, args, options) => {
1046
1057
  return cpSpawn(command, args ?? [], { windowsHide: true, ...options ?? {} });
1047
1058
  };
@@ -3135,13 +3146,56 @@ function bufToStr2(val) {
3135
3146
  return val.toString("utf-8").trim();
3136
3147
  return "";
3137
3148
  }
3149
+ function pushSyncStash(cwd) {
3150
+ execFileSync("git", ["stash", "push", "-m", SYNC_STASH_TAG], { cwd, stdio: "pipe" });
3151
+ return findSyncStashSha(cwd);
3152
+ }
3153
+ function findSyncStashSha(cwd) {
3154
+ try {
3155
+ const line = execFileSync("git", ["stash", "list", "--format=%H %gs"], { cwd, encoding: "utf-8" }).split(`
3156
+ `).find((l) => l.includes(SYNC_STASH_TAG));
3157
+ return line ? line.split(" ")[0] : null;
3158
+ } catch {
3159
+ return null;
3160
+ }
3161
+ }
3162
+ function stashRefForSha(cwd, sha) {
3163
+ try {
3164
+ const line = execFileSync("git", ["stash", "list", "--format=%H %gd"], { cwd, encoding: "utf-8" }).split(`
3165
+ `).find((l) => l.startsWith(`${sha} `));
3166
+ return line ? line.slice(sha.length + 1).trim() : null;
3167
+ } catch {
3168
+ return null;
3169
+ }
3170
+ }
3171
+ function restoreSyncStash(cwd, sha) {
3172
+ const resolved = sha ?? findSyncStashSha(cwd);
3173
+ if (!resolved)
3174
+ return "missing";
3175
+ const ref = stashRefForSha(cwd, resolved);
3176
+ if (!ref)
3177
+ return "missing";
3178
+ try {
3179
+ execFileSync("git", ["stash", "apply", resolved], { cwd, stdio: "pipe" });
3180
+ } catch {
3181
+ return "conflict";
3182
+ }
3183
+ const dropRef = stashRefForSha(cwd, resolved);
3184
+ if (dropRef) {
3185
+ try {
3186
+ execFileSync("git", ["stash", "drop", dropRef], { cwd, stdio: "pipe" });
3187
+ } catch {}
3188
+ }
3189
+ return "restored";
3190
+ }
3138
3191
  function syncWithRemote(cwd) {
3139
3192
  if (!hasCommits(cwd)) {
3140
3193
  return { status: "skipped", pushed: false };
3141
3194
  }
3142
3195
  const dirty = hasTrackedChanges(cwd);
3196
+ let stashSha = null;
3143
3197
  if (dirty) {
3144
- execFileSync("git", ["stash", "push", "-m", "runwork-dev-sync"], { cwd, stdio: "pipe" });
3198
+ stashSha = pushSyncStash(cwd);
3145
3199
  }
3146
3200
  let status = "synced";
3147
3201
  let syncError;
@@ -3182,9 +3236,8 @@ function syncWithRemote(cwd) {
3182
3236
  syncError = extractGitError(fetchErr);
3183
3237
  }
3184
3238
  if (dirty) {
3185
- try {
3186
- execFileSync("git", ["stash", "pop"], { cwd, stdio: "pipe" });
3187
- } catch {
3239
+ const restored = restoreSyncStash(cwd, stashSha);
3240
+ if (restored !== "restored") {
3188
3241
  return { status, pushed: false, error: "stash-conflict", keptUntracked, remoteOverwrote };
3189
3242
  }
3190
3243
  }
@@ -3201,6 +3254,7 @@ function syncWithRemote(cwd) {
3201
3254
  }
3202
3255
  return { status, pushed, pushError, keptUntracked, remoteOverwrote };
3203
3256
  }
3257
+ var SYNC_STASH_TAG = "runwork-dev-sync";
3204
3258
  var init_sync = __esm(() => {
3205
3259
  init_subprocess();
3206
3260
  init_manifest();
@@ -5105,10 +5159,6 @@ export declare class FileStorageClient {
5105
5159
  */
5106
5160
  export declare function createFileStorageClient(env: Env): FileStorageClient;
5107
5161
  import type { Hono } from 'hono';
5108
- /**
5109
- * Mount file storage routes on the Hono app
5110
- * Provides REST API for R2 bucket operations
5111
- */
5112
5162
  export declare function fileStorageRoutes(app: Hono<{
5113
5163
  Bindings: Env;
5114
5164
  }>): void;
@@ -5285,6 +5335,46 @@ export declare function componentRoutes(app: Hono<{
5285
5335
  `,
5286
5336
  "workspace.d.ts": `export { WorkspaceContext, getWorkspaceContext, listWorkspaceEntity, getWorkspaceEntity, createWorkspaceEntity, updateWorkspaceEntity, deleteWorkspaceEntity, callAppEndpoint, initializeWorkspace, } from './core-workspace';
5287
5337
  export type { WorkspaceUser, ListUsersOptions, ListUsersResponse, NotifyUserParams, NotifyUserResult, ListEntityRequest, ListEntityResponse, GetEntityRequest, CreateEntityRequest, UpdateEntityRequest, DeleteEntityRequest, RegisterEntityRequest, RegisterAppRequest, RegisterComponentRequest, RegisterScheduleRequest, RegisterWorkflowRequest, RegisterEndpointRequest, RegisterIntegrationRequest, CallAppEndpointOptions, CallAppEndpointResult, RegisterAgentRequest as WorkspaceRegisterAgentRequest, } from './core-workspace';
5338
+ `,
5339
+ "client.d.ts": `/**
5340
+ * Framework client entry: browser-side helpers an app's frontend imports as
5341
+ * \`@runworkai/framework/client\`, so app code never carries this plumbing
5342
+ * itself.
5343
+ *
5344
+ * Analytics: what people do in the app, reported to the workspace.
5345
+ *
5346
+ * Envelope follows the Segment Spec (the de-facto standard Google Analytics,
5347
+ * Mixpanel, PostHog and friends all map to), so nothing here needs to change
5348
+ * if the workspace later forwards events to a real analytics tool.
5349
+ *
5350
+ * The app does not interpret events. \`/api/analytics\` stamps the app's
5351
+ * identity and forwards to the workspace, which decides what each one means.
5352
+ * Today a \`page\` counts as one USE of the app, the multiplier behind a
5353
+ * standalone app's value in the workspace's reports. \`track\` events are kept
5354
+ * for a later analytics surface.
5355
+ *
5356
+ * A page load is reported here because the document is served before the
5357
+ * app's Worker runs, leaving no server-side trace otherwise. Once per
5358
+ * document, not per route change: the workspace counts an opened tool, and a
5359
+ * person navigating inside it is still one use.
5360
+ *
5361
+ * Fire-and-forget. Nothing here may ever affect the person using the app.
5362
+ */
5363
+ /** Record a custom event. Use a flat Title Case name ("Report Exported") and put variables in properties. */
5364
+ export declare function track(event: string, properties?: Record<string, unknown>): void;
5365
+ /** Record a named screen or view inside the app. */
5366
+ export declare function screen(name: string, properties?: Record<string, unknown>): void;
5367
+ /**
5368
+ * Tell the workspace who this browser belongs to, in the APP's own terms.
5369
+ * A claim, recorded as data; the workspace never grants anything on it.
5370
+ */
5371
+ export declare function identify(userId: string, traits?: Record<string, unknown>): void;
5372
+ /** Attach this person to a company, account or team the app knows about. */
5373
+ export declare function group(groupId: string, traits?: Record<string, unknown>): void;
5374
+ /** Link a past anonymous identity to a known one. */
5375
+ export declare function alias(userId: string, previousId?: string): void;
5376
+ /** Record that this app was opened. Called once automatically on load. */
5377
+ export declare function page(): void;
5288
5378
  `,
5289
5379
  "core-scheduler.d.ts": `/**
5290
5380
  * Core Scheduled Jobs Framework
@@ -5936,8 +6026,6 @@ export type Doc<T> = {
5936
6026
  * - Security validation on all field names
5937
6027
  */
5938
6028
  export declare class EntityDO extends DurableObject<Env> {
5939
- ctx: DurableObjectState;
5940
- env: Env;
5941
6029
  private _tableReady;
5942
6030
  private _migrationDone;
5943
6031
  constructor(ctx: DurableObjectState, env: Env);
@@ -6942,13 +7030,6 @@ export declare function toChannelName(appName: string): string;
6942
7030
  * these entries in the observability timeline.
6943
7031
  */
6944
7032
  export declare function flog(level: 'info' | 'warn' | 'error', system: string, message: string, data?: Record<string, unknown>): void;
6945
- /**
6946
- * Emit an event to the workspace unified event stream.
6947
- * Fire-and-forget: uses ctx.waitUntil so it doesn't block the response.
6948
- * Silently skips if workspace env vars are not configured (standalone mode).
6949
- *
6950
- * Automatically routes events to a channel derived from APP_NAME when available.
6951
- */
6952
7033
  export declare function emitEvent(ctx: WaitUntilContext, env: EventEnv, event: EmitEventParams): void;
6953
7034
  export {};
6954
7035
  `,
@@ -7023,7 +7104,109 @@ export declare const isStr: (s: unknown) => s is string;
7023
7104
  * that would cause structured clone to fail in ctx.storage.put().
7024
7105
  */
7025
7106
  export declare function safeClone<T>(value: T, fallback?: T): T;
7026
- export declare function platformFetch(env: Env, url: string | URL, init?: RequestInit): Promise<Response>;
7107
+ /**
7108
+ * Platform fetch - routes requests through WorkspaceObject DO for production workers.
7109
+ *
7110
+ * Workers for Platforms (WfP) workers cannot reliably make HTTP requests back to their
7111
+ * parent platform worker (they get 522 timeouts). This utility routes platform API calls
7112
+ * through the WorkspaceObject Durable Object binding, which works across worker boundaries.
7113
+ *
7114
+ * Supported paths:
7115
+ * - /api/proxy/integrations/* - Integration proxy (Nango)
7116
+ * - /api/proxy/openai/* - AI Gateway proxy
7117
+ * - /api/storage/presign - Storage presigned URLs
7118
+ *
7119
+ * For preview containers (DEPLOYMENT_MODE !== 'production'), uses standard fetch.
7120
+ *
7121
+ * @example
7122
+ * \`\`\`typescript
7123
+ * // Instead of:
7124
+ * const response = await fetch('https://runwork.ai/api/proxy/integrations/proxy/contacts', options);
7125
+ *
7126
+ * // Use:
7127
+ * const response = await platformFetch(env, 'https://runwork.ai/api/proxy/integrations/proxy/contacts', options);
7128
+ * \`\`\`
7129
+ */
7130
+ /**
7131
+ * What the app was doing when it made a call.
7132
+ *
7133
+ * Internal plumbing, deliberately not exported from the package: app authors
7134
+ * never set this, the framework establishes it at each entry point (schedule
7135
+ * tick, workflow run, route handler, agent turn) and \`platformFetch\` below
7136
+ * reads it. It lives beside \`platformFetch\` because that is its only consumer.
7137
+ *
7138
+ * WHY IT EXISTS: usage rows record \`appId\` and \`userId\` and nothing about what
7139
+ * was executing, so a HubSpot call made by a nightly schedule cannot be told
7140
+ * apart from one a person made from their own agent. \`runId\` is the field that
7141
+ * cannot be reconstructed later: it links one run's AI calls and integration
7142
+ * calls together, which is what per-run cost is built from.
7143
+ *
7144
+ * Design: docs/plans/2026-08-28-baselines-capture-redesign.md section 4.1.
7145
+ */
7146
+ export interface RunContext {
7147
+ /**
7148
+ * WHAT was executing. Only constructs the framework itself runs, never
7149
+ * "where the caller was" (a browser, MCP, the CLI): that is a different
7150
+ * question and \`audit_logs.actor_type\` owns it.
7151
+ */
7152
+ kind: 'schedule' | 'workflow' | 'endpoint' | 'route' | 'agent';
7153
+ /**
7154
+ * Human-readable name, following the convention the audit rows already use:
7155
+ * a schedule/workflow/agent slug, or \`METHOD /path\` for endpoints and routes.
7156
+ *
7157
+ * OMITTED rather than defaulted when genuinely unknown. A placeholder string
7158
+ * like 'unknown' becomes a value every query has to filter out, and it is
7159
+ * indistinguishable from an app that named something 'unknown'.
7160
+ */
7161
+ name?: string;
7162
+ /**
7163
+ * App-local registry key, so a usage row joins back to a registration. The
7164
+ * app id is a column of its own, so this is the part after it: a slug for
7165
+ * schedules, workflows and agents, \`METHOD /path\` for endpoints.
7166
+ */
7167
+ resourceKey?: string;
7168
+ /** One id shared by everything emitted inside this execution. */
7169
+ runId: string;
7170
+ /** The enclosing run, for workflow steps, nested calls and sub-agents. */
7171
+ parentRunId?: string;
7172
+ trigger?: 'cron' | 'manual' | 'webhook' | 'chat' | 'api';
7173
+ /**
7174
+ * The person who started this run. Named to match the governance program's
7175
+ * \`DelegationChain.triggererUserId\` (\`worker/types/permissions.ts\`), which is
7176
+ * the same fact: one name for it across both programs.
7177
+ *
7178
+ * ATTRIBUTION, NEVER AUTHORITY. This travels on a header from the app, behind
7179
+ * the shared WORKSPACE_API_KEY, so an app can put any user id here. The
7180
+ * platform may RECORD it; nothing may ever AUTHORIZE on it. Whose authority a
7181
+ * run borrows is resolved platform-side by PermissionService, not stated by
7182
+ * the caller.
7183
+ */
7184
+ triggererUserId?: string;
7185
+ attempt?: number;
7186
+ }
7187
+ /** Header carrying the context to the platform side, which writes the audit row. */
7188
+ export declare const RUN_CONTEXT_HEADER = "x-runwork-run-context";
7189
+ /**
7190
+ * Run \`fn\` with \`ctx\` as the ambient execution context.
7191
+ *
7192
+ * Nesting is automatic: entering a context inside another records the outer
7193
+ * \`runId\` as \`parentRunId\` unless the caller set one. That is what makes a
7194
+ * sub-agent's calls point at the run that started it. Re-entering the SAME
7195
+ * run id is not nesting (a workflow resumes into its own run across wake-ups)
7196
+ * and must not make a run its own parent.
7197
+ */
7198
+ export declare function withRunContext<T>(ctx: RunContext, fn: () => T): T;
7199
+ /** The current execution context, or undefined outside any entry point. */
7200
+ export declare function getRunContext(): RunContext | undefined;
7201
+ /**
7202
+ * Merge the context header into request headers.
7203
+ *
7204
+ * One serialised header rather than several, so adding a field never needs a
7205
+ * matching change in the platform's parsing. Never overwrites a header the
7206
+ * caller set: a caller that knows its own context beats the ambient one.
7207
+ */
7208
+ export declare function withRunContextHeader(init?: HeadersInit): HeadersInit | undefined;
7209
+ export declare function platformFetch(env: Env, url: string | URL, rawInit?: RequestInit): Promise<Response>;
7027
7210
  /**
7028
7211
  * Workspace API fetch - routes workspace service requests through WorkspaceObject DO
7029
7212
  * for production WfP workers, avoiding 522 recursive invocation errors.
@@ -7036,7 +7219,7 @@ export declare function platformFetch(env: Env, url: string | URL, init?: Reques
7036
7219
  * @param path - Workspace API sub-path (e.g., '/ingest-event')
7037
7220
  * @param init - Standard fetch options
7038
7221
  */
7039
- export declare function workspaceApiFetch(env: Pick<Env, 'WORKSPACE_API_URL' | 'WORKSPACE_API_KEY' | 'WORKSPACE_ID' | 'DEPLOYMENT_MODE' | 'WorkspaceObject'>, path: string, init?: RequestInit): Promise<Response>;
7222
+ export declare function workspaceApiFetch(env: Pick<Env, 'WORKSPACE_API_URL' | 'WORKSPACE_API_KEY' | 'WORKSPACE_ID' | 'DEPLOYMENT_MODE' | 'WorkspaceObject'>, path: string, rawInit?: RequestInit): Promise<Response>;
7040
7223
  `,
7041
7224
  "workflows.d.ts": `import type { WorkflowDefinition } from './core-workflow-types';
7042
7225
  /**
@@ -7231,7 +7414,7 @@ export declare class BaseAgent extends AIChatAgent<Env> {
7231
7414
  * - Types are defined in core-workflow-types.ts
7232
7415
  * - Native DO-based implementation is in core-workflow-instance.ts and core-workflow-coordinator.ts
7233
7416
  */
7234
- import type { Env } from './core-utils';
7417
+ import { type Env } from './core-utils';
7235
7418
  export type { WorkflowResult, WorkflowStatus, WorkflowInstanceInfo, WorkflowDefinition, WorkflowContext, WorkflowStepUtilities, StepOptions, WaitEventOptions, WorkflowLogger, WorkflowState, WorkflowStatusResponse, } from './core-workflow-types';
7236
7419
  import type { WorkflowInstanceInfo } from './core-workflow-types';
7237
7420
  /**
@@ -7780,8 +7963,8 @@ export declare const integrationApiSchema: z.ZodObject<{
7780
7963
  endpoint: z.ZodString;
7781
7964
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7782
7965
  }, "strip", z.ZodTypeAny, {
7783
- method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7784
7966
  endpoint: string;
7967
+ method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7785
7968
  data?: Record<string, unknown> | undefined;
7786
7969
  }, {
7787
7970
  endpoint: string;
@@ -8022,7 +8205,7 @@ function createKeyboardListener() {
8022
8205
  }
8023
8206
 
8024
8207
  // src/generated/version.ts
8025
- var VERSION = "0.25.3";
8208
+ var VERSION = "0.26.0";
8026
8209
 
8027
8210
  // src/commands/dev.ts
8028
8211
  var exports_dev = {};
@@ -8103,6 +8286,7 @@ async function execDev(options) {
8103
8286
  await ensureGitCredentialHelper(creds.baseUrl);
8104
8287
  }
8105
8288
  ensureRunworkRemote(cwd, client.getGitRemoteUrl(config.workspaceId, config.appId));
8289
+ let editsUnprotected = false;
8106
8290
  const oldManifest = await loadManifest(cwd);
8107
8291
  if (oldManifest) {
8108
8292
  const userEdits = await detectUserEdits(cwd, oldManifest);
@@ -8126,9 +8310,22 @@ async function execDev(options) {
8126
8310
  execFileSync("git", ["add", "--", ...userEdits], { stdio: "pipe" });
8127
8311
  execFileSync("git", ["commit", "-m", "sync: user edits"], { stdio: "pipe" });
8128
8312
  } catch {}
8313
+ editsUnprotected = hasTrackedChanges(cwd);
8314
+ if (editsUnprotected) {
8315
+ if (useJson) {
8316
+ jsonLine({
8317
+ event: "startup",
8318
+ phase: "user_edits_unprotected",
8319
+ timestamp: ts(),
8320
+ warning: "Could not commit your edits (a commit hook or lock may be interfering). Skipping the template update so they are not lost."
8321
+ });
8322
+ } else {
8323
+ console.warn(yellow("Could not commit your edits (a commit hook or lock may be interfering). Skipping the template update so they are not lost."));
8324
+ }
8325
+ }
8129
8326
  }
8130
8327
  }
8131
- if (!noSync) {
8328
+ if (!noSync && !editsUnprotected) {
8132
8329
  if (useJson) {
8133
8330
  jsonLine({ event: "startup", phase: "template_update", timestamp: ts() });
8134
8331
  } else {
@@ -8971,10 +9168,31 @@ function startAppProbeScript(pattern) {
8971
9168
  function isCommandNotFoundExit(code) {
8972
9169
  return typeof code === "number" && COMMAND_NOT_FOUND_EXIT_CODES.includes(code);
8973
9170
  }
8974
- var PATH_REFRESH_FAILED_MARKER = "__runwork_path_refresh_failed__", WINDOWS_PATH_REFRESH, COMMAND_NOT_FOUND_EXIT_CODES;
9171
+ function expandWindowsPathTemplate(path2, resolved) {
9172
+ for (const [placeholder, defaults] of Object.entries(WINDOWS_PATH_PLACEHOLDERS)) {
9173
+ if (!path2.startsWith(placeholder))
9174
+ continue;
9175
+ const rest = path2.slice(placeholder.length).replace(/^[/\\]/, "");
9176
+ const fromEnv = resolved[placeholder];
9177
+ if (fromEnv) {
9178
+ return { path: `${fromEnv.replace(/[/\\]+$/, "")}\\${rest.replace(/\//g, "\\")}`, needsHomeJoin: false };
9179
+ }
9180
+ if ("absoluteDefault" in defaults) {
9181
+ return { path: `${defaults.absoluteDefault}\\${rest.replace(/\//g, "\\")}`, needsHomeJoin: false };
9182
+ }
9183
+ return { path: `${defaults.homeRelativeDefault}/${rest}`, needsHomeJoin: true };
9184
+ }
9185
+ return { path: path2, needsHomeJoin: !/^([A-Za-z]:[\\/]|\\\\|\/)/.test(path2) };
9186
+ }
9187
+ var PATH_REFRESH_FAILED_MARKER = "__runwork_path_refresh_failed__", WINDOWS_PATH_REFRESH, COMMAND_NOT_FOUND_EXIT_CODES, WINDOWS_PATH_PLACEHOLDERS;
8975
9188
  var init_detection_probes = __esm(() => {
8976
9189
  WINDOWS_PATH_REFRESH = "try { $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + " + "[Environment]::GetEnvironmentVariable('Path','User') + ';' + $env:Path } " + `catch { Write-Output '${PATH_REFRESH_FAILED_MARKER}' }; `;
8977
9190
  COMMAND_NOT_FOUND_EXIT_CODES = [127, 9009];
9191
+ WINDOWS_PATH_PLACEHOLDERS = {
9192
+ "%APPDATA%": { homeRelativeDefault: "AppData/Roaming" },
9193
+ "%LOCALAPPDATA%": { homeRelativeDefault: "AppData/Local" },
9194
+ "%ProgramFiles%": { absoluteDefault: "C:\\Program Files" }
9195
+ };
8978
9196
  });
8979
9197
 
8980
9198
  // src/utils/which.ts
@@ -9477,7 +9695,7 @@ var init_registry_data = __esm(() => {
9477
9695
  {
9478
9696
  method: "path",
9479
9697
  target: {
9480
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9698
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9481
9699
  linux: ".config/Claude/claude_desktop_config.json"
9482
9700
  }
9483
9701
  }
@@ -9490,7 +9708,7 @@ var init_registry_data = __esm(() => {
9490
9708
  skillsPaths: { global: ".claude/skills", project: ".claude/skills" },
9491
9709
  mcpConfigPath: {
9492
9710
  macos: "Library/Application Support/Claude/claude_desktop_config.json",
9493
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9711
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9494
9712
  linux: ".config/Claude/claude_desktop_config.json"
9495
9713
  },
9496
9714
  mcpConfigKey: "mcpServers",
@@ -9800,8 +10018,8 @@ var init_registry_data = __esm(() => {
9800
10018
  target: [
9801
10019
  { method: "binary", target: "windsurf" },
9802
10020
  { method: "path", target: { macos: "/Applications/Windsurf.app" } },
9803
- { method: "path", target: { windows: "AppData/Local/Programs/Windsurf" } },
9804
- { method: "path", target: { windows: "C:\\Program Files\\Windsurf" } }
10021
+ { method: "path", target: { windows: "%LOCALAPPDATA%/Programs/Windsurf" } },
10022
+ { method: "path", target: { windows: "%ProgramFiles%/Windsurf" } }
9805
10023
  ]
9806
10024
  },
9807
10025
  launch: { app: { macos: "Windsurf", windows: "Windsurf" } },
@@ -9881,7 +10099,14 @@ var init_registry_data = __esm(() => {
9881
10099
  name: "GitHub Copilot (VS Code)",
9882
10100
  description: "GitHub's AI pair programmer in VS Code",
9883
10101
  category: "extension",
9884
- detection: { method: "binary", target: "code" },
10102
+ detection: {
10103
+ method: "path",
10104
+ target: {
10105
+ macos: "Library/Application Support/Code/User/globalStorage/github.copilot",
10106
+ windows: "%APPDATA%/Code/User/globalStorage/github.copilot",
10107
+ linux: ".config/Code/User/globalStorage/github.copilot"
10108
+ }
10109
+ },
9885
10110
  launch: { app: { macos: "Visual Studio Code", windows: "Code" }, cli: "code" },
9886
10111
  logo: "vscode",
9887
10112
  downloadUrl: "https://marketplace.visualstudio.com/items?itemName=GitHub.copilot",
@@ -11760,7 +11985,7 @@ var verbose = false;
11760
11985
  import { execFile } from "child_process";
11761
11986
  import { existsSync as existsSync31 } from "fs";
11762
11987
  import { homedir as homedir8, platform as platform3 } from "os";
11763
- import { isAbsolute as isAbsolute3, join as join25 } from "path";
11988
+ import { join as join25 } from "path";
11764
11989
  import { promisify } from "util";
11765
11990
  function isWindows() {
11766
11991
  return platform3() === "win32";
@@ -11781,7 +12006,12 @@ function resolveDetectionPath(target) {
11781
12006
  const resolved = resolvePlatformString(target);
11782
12007
  if (!resolved)
11783
12008
  return null;
11784
- return isAbsolute3(resolved) ? resolved : join25(homedir8(), resolved);
12009
+ const expanded = expandWindowsPathTemplate(resolved, {
12010
+ "%APPDATA%": process.env.APPDATA,
12011
+ "%LOCALAPPDATA%": process.env.LOCALAPPDATA,
12012
+ "%ProgramFiles%": process.env.ProgramFiles
12013
+ });
12014
+ return expanded.needsHomeJoin ? join25(homedir8(), expanded.path) : expanded.path;
11785
12015
  }
11786
12016
  function checkPath(target) {
11787
12017
  const absolute = resolveDetectionPath(target);
@@ -11865,8 +12095,70 @@ var init_detection = __esm(() => {
11865
12095
  NOT_DETECTED = { detected: false };
11866
12096
  });
11867
12097
 
12098
+ // src/agents/transcript-sources.ts
12099
+ import { accessSync, constants, statSync as statSync4 } from "fs";
12100
+ function classifyFsErrorCode(code) {
12101
+ if (code === "ENOENT" || code === "ENOTDIR")
12102
+ return "missing-dir";
12103
+ if (code === "EACCES" || code === "EPERM")
12104
+ return "permission-denied";
12105
+ return "error";
12106
+ }
12107
+ function errorCode(err) {
12108
+ const e = err;
12109
+ return e && typeof e === "object" && typeof e.code === "string" ? e.code : undefined;
12110
+ }
12111
+ function probeTranscriptSource(source) {
12112
+ if (source.available && !source.available()) {
12113
+ return { status: "missing-tool", path: source.path, detail: source.tool ?? "unknown tool" };
12114
+ }
12115
+ try {
12116
+ statSync4(source.path);
12117
+ } catch (err) {
12118
+ return { status: classifyFsErrorCode(errorCode(err)), path: source.path, detail: errorCode(err) };
12119
+ }
12120
+ try {
12121
+ accessSync(source.path, source.kind === "dir" ? constants.R_OK | constants.X_OK : constants.R_OK);
12122
+ } catch (err) {
12123
+ return { status: classifyFsErrorCode(errorCode(err)), path: source.path, detail: errorCode(err) };
12124
+ }
12125
+ return { status: "ok", path: source.path };
12126
+ }
12127
+ function diagnoseTranscriptRead(agent, thrown) {
12128
+ const probe = summarizeTranscriptSources(agent.transcriptSources?.() ?? []);
12129
+ const base = { agentSlug: agent.slug, sessions: null };
12130
+ if (probe.status !== "ok" && probe.status !== "no-reader") {
12131
+ return {
12132
+ ...base,
12133
+ status: probe.status,
12134
+ path: probe.path,
12135
+ ...probe.detail ? { detail: probe.detail } : {}
12136
+ };
12137
+ }
12138
+ if (thrown !== undefined) {
12139
+ return { ...base, status: "error", detail: thrown instanceof Error ? thrown.message : String(thrown) };
12140
+ }
12141
+ return probe.status === "ok" ? { ...base, status: "error", path: probe.path, detail: "read returned no result" } : { ...base, status: "no-reader" };
12142
+ }
12143
+ function transcriptSourcesReadable(sources) {
12144
+ return summarizeTranscriptSources(sources).status === "ok";
12145
+ }
12146
+ function summarizeTranscriptSources(sources) {
12147
+ if (sources.length === 0)
12148
+ return { status: "no-reader", path: "" };
12149
+ const probes = sources.map(probeTranscriptSource);
12150
+ const rank = ["permission-denied", "missing-tool", "error", "ok", "missing-dir"];
12151
+ for (const status of rank) {
12152
+ const hit = probes.find((p) => p.status === status);
12153
+ if (hit)
12154
+ return hit;
12155
+ }
12156
+ return probes[0];
12157
+ }
12158
+ var init_transcript_sources = () => {};
12159
+
11868
12160
  // src/agents/claude-code.ts
11869
- import { chmodSync, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync16 } from "fs";
12161
+ import { chmodSync, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
11870
12162
  import { join as join26 } from "path";
11871
12163
  import { homedir as homedir9 } from "os";
11872
12164
  function getPluginJson() {
@@ -11910,6 +12202,7 @@ var init_claude_code = __esm(() => {
11910
12202
  init_instruction_hint();
11911
12203
  init_session_start_hook();
11912
12204
  init_detection();
12205
+ init_transcript_sources();
11913
12206
  ClaudeCodeAdapter = class ClaudeCodeAdapter extends RegistryDetectedAdapter {
11914
12207
  name = "Claude Code";
11915
12208
  slug = "claude-code";
@@ -12207,7 +12500,7 @@ ${instructions}`;
12207
12500
  const filePath = join26(cwdPath, file);
12208
12501
  let stat;
12209
12502
  try {
12210
- stat = statSync4(filePath);
12503
+ stat = statSync5(filePath);
12211
12504
  } catch {
12212
12505
  continue;
12213
12506
  }
@@ -12299,9 +12592,15 @@ ${instructions}`;
12299
12592
  return null;
12300
12593
  }
12301
12594
  }
12595
+ transcriptRoot() {
12596
+ return join26(homedir9(), ".claude", "projects");
12597
+ }
12598
+ transcriptSources() {
12599
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
12600
+ }
12302
12601
  async readSessionDigests(sinceISO) {
12303
12602
  try {
12304
- const projectsDir = join26(homedir9(), ".claude", "projects");
12603
+ const projectsDir = this.transcriptRoot();
12305
12604
  if (!existsSync32(projectsDir))
12306
12605
  return null;
12307
12606
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -12326,7 +12625,7 @@ ${instructions}`;
12326
12625
  const filePath = join26(cwdPath, file);
12327
12626
  let stat;
12328
12627
  try {
12329
- stat = statSync4(filePath);
12628
+ stat = statSync5(filePath);
12330
12629
  } catch {
12331
12630
  continue;
12332
12631
  }
@@ -12350,8 +12649,8 @@ ${instructions}`;
12350
12649
  }
12351
12650
  async listSessions(sinceISO) {
12352
12651
  try {
12353
- const projectsDir = join26(homedir9(), ".claude", "projects");
12354
- if (!existsSync32(projectsDir))
12652
+ const projectsDir = this.transcriptRoot();
12653
+ if (!transcriptSourcesReadable(this.transcriptSources()))
12355
12654
  return null;
12356
12655
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
12357
12656
  let cwdEntries;
@@ -12375,7 +12674,7 @@ ${instructions}`;
12375
12674
  const filePath = join26(cwdPath, file);
12376
12675
  let stat;
12377
12676
  try {
12378
- stat = statSync4(filePath);
12677
+ stat = statSync5(filePath);
12379
12678
  } catch {
12380
12679
  continue;
12381
12680
  }
@@ -12413,7 +12712,7 @@ ${instructions}`;
12413
12712
  }
12414
12713
  async readSkillUsage(lastSyncAt) {
12415
12714
  try {
12416
- const projectsDir = join26(homedir9(), ".claude", "projects");
12715
+ const projectsDir = this.transcriptRoot();
12417
12716
  if (!existsSync32(projectsDir))
12418
12717
  return null;
12419
12718
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -12448,7 +12747,7 @@ ${instructions}`;
12448
12747
  const filePath = join26(cwdPath, file);
12449
12748
  let fileStat;
12450
12749
  try {
12451
- fileStat = statSync4(filePath);
12750
+ fileStat = statSync5(filePath);
12452
12751
  } catch {
12453
12752
  continue;
12454
12753
  }
@@ -12704,7 +13003,7 @@ var init_claude_desktop_plugin_tree = __esm(() => {
12704
13003
  });
12705
13004
 
12706
13005
  // src/agents/claude-desktop.ts
12707
- import { existsSync as existsSync34, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, readFileSync as readFileSync26, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "fs";
13006
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, readFileSync as readFileSync26, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "fs";
12708
13007
  import { dirname as dirname9, join as join28 } from "path";
12709
13008
  import { homedir as homedir10, platform as platform4, tmpdir as tmpdir3 } from "os";
12710
13009
  function isRunworkRpmPluginName(name) {
@@ -12863,6 +13162,7 @@ function setCoworkPluginEnabled(pluginsDir, enabled) {
12863
13162
  var PLUGIN_NAME2 = "runwork", PLUGIN_VERSION2 = "1.0.0", PLUGIN_DESCRIPTION = "Skills and tools from your Runwork workspace", PLUGIN_AUTHOR_NAME = "Runwork", ClaudeDesktopAdapter;
12864
13163
  var init_claude_desktop = __esm(() => {
12865
13164
  init_types();
13165
+ init_transcript_sources();
12866
13166
  init_session_digest();
12867
13167
  init_json_config();
12868
13168
  init_instruction_hint();
@@ -13136,7 +13436,7 @@ var init_claude_desktop = __esm(() => {
13136
13436
  const filePath = join28(projPath, file);
13137
13437
  let stat;
13138
13438
  try {
13139
- stat = statSync5(filePath);
13439
+ stat = statSync6(filePath);
13140
13440
  } catch {
13141
13441
  continue;
13142
13442
  }
@@ -13161,10 +13461,13 @@ var init_claude_desktop = __esm(() => {
13161
13461
  return null;
13162
13462
  }
13163
13463
  }
13464
+ transcriptSources() {
13465
+ return [{ path: getCoworkBaseDir(), kind: "dir" }];
13466
+ }
13164
13467
  async listSessions(sinceISO) {
13165
13468
  try {
13166
13469
  const baseDir = getCoworkBaseDir();
13167
- if (!existsSync34(baseDir))
13470
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13168
13471
  return null;
13169
13472
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
13170
13473
  let accounts;
@@ -13200,7 +13503,7 @@ var init_claude_desktop = __esm(() => {
13200
13503
  const filePath = join28(projPath, file);
13201
13504
  let stat;
13202
13505
  try {
13203
- stat = statSync5(filePath);
13506
+ stat = statSync6(filePath);
13204
13507
  } catch {
13205
13508
  continue;
13206
13509
  }
@@ -13341,7 +13644,7 @@ var init_claude_desktop = __esm(() => {
13341
13644
  continue;
13342
13645
  const orgPath = join28(baseDir, orgDir);
13343
13646
  try {
13344
- if (!statSync5(orgPath).isDirectory())
13647
+ if (!statSync6(orgPath).isDirectory())
13345
13648
  continue;
13346
13649
  } catch {
13347
13650
  continue;
@@ -13351,7 +13654,7 @@ var init_claude_desktop = __esm(() => {
13351
13654
  continue;
13352
13655
  const userPath = join28(orgPath, userDir);
13353
13656
  try {
13354
- if (!statSync5(userPath).isDirectory())
13657
+ if (!statSync6(userPath).isDirectory())
13355
13658
  continue;
13356
13659
  } catch {
13357
13660
  continue;
@@ -13479,6 +13782,7 @@ var init_cursor = __esm(async () => {
13479
13782
  init_skill_removal();
13480
13783
  init_json_config();
13481
13784
  init_detection();
13785
+ init_transcript_sources();
13482
13786
  await init_sqlite();
13483
13787
  CursorAdapter = class CursorAdapter extends RegistryDetectedAdapter {
13484
13788
  name = "Cursor";
@@ -13737,6 +14041,16 @@ ${instructions}`;
13737
14041
  return null;
13738
14042
  }
13739
14043
  }
14044
+ transcriptSources() {
14045
+ return [
14046
+ {
14047
+ path: this.globalStorageDbPath(),
14048
+ kind: "file",
14049
+ tool: "sqlite",
14050
+ available: sqliteAvailable
14051
+ }
14052
+ ];
14053
+ }
13740
14054
  globalStorageDbPath() {
13741
14055
  const os2 = platform5();
13742
14056
  if (os2 === "darwin") {
@@ -13764,7 +14078,7 @@ ${instructions}`;
13764
14078
  async listSessions(sinceISO) {
13765
14079
  try {
13766
14080
  const dbPath = this.globalStorageDbPath();
13767
- if (!existsSync35(dbPath) || !sqliteAvailable())
14081
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13768
14082
  return null;
13769
14083
  const rows = queryReadonlySqlite(dbPath, `SELECT json_object('id', json_extract(value,'$.composerId'), 'name', json_extract(value,'$.name'), 'createdAt', json_extract(value,'$.createdAt'), 'lastUpdatedAt', json_extract(value,'$.lastUpdatedAt')) FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
13770
14084
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -13893,7 +14207,7 @@ ${hint}`;
13893
14207
  });
13894
14208
 
13895
14209
  // src/agents/codex.ts
13896
- import { existsSync as existsSync37, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync27, statSync as statSync6, writeFileSync as writeFileSync21 } from "fs";
14210
+ import { existsSync as existsSync37, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync27, statSync as statSync7, writeFileSync as writeFileSync21 } from "fs";
13897
14211
  import { basename as basename3, join as join31 } from "path";
13898
14212
  import { homedir as homedir13 } from "os";
13899
14213
  import { parse, stringify } from "smol-toml";
@@ -13903,6 +14217,7 @@ function isRunworkManagedCodexKey(key) {
13903
14217
  var CodexAdapter, CodexDesktopAdapter;
13904
14218
  var init_codex = __esm(async () => {
13905
14219
  init_types();
14220
+ init_transcript_sources();
13906
14221
  init_session_digest();
13907
14222
  init_session_listing();
13908
14223
  init_skill_removal();
@@ -14121,6 +14436,12 @@ var init_codex = __esm(async () => {
14121
14436
  return null;
14122
14437
  }
14123
14438
  }
14439
+ transcriptRoot() {
14440
+ return join31(homedir13(), ".codex", "sessions");
14441
+ }
14442
+ transcriptSources() {
14443
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
14444
+ }
14124
14445
  scanRolloutActivity(sinceMs) {
14125
14446
  const days = new Set;
14126
14447
  const result = {
@@ -14131,7 +14452,7 @@ var init_codex = __esm(async () => {
14131
14452
  latestMs: 0,
14132
14453
  activeDays: []
14133
14454
  };
14134
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14455
+ const sessionsDir = this.transcriptRoot();
14135
14456
  if (!existsSync37(sessionsDir))
14136
14457
  return result;
14137
14458
  const files = [];
@@ -14154,7 +14475,7 @@ var init_codex = __esm(async () => {
14154
14475
  for (const file of files) {
14155
14476
  let stat;
14156
14477
  try {
14157
- stat = statSync6(file);
14478
+ stat = statSync7(file);
14158
14479
  } catch {
14159
14480
  continue;
14160
14481
  }
@@ -14221,8 +14542,8 @@ var init_codex = __esm(async () => {
14221
14542
  }
14222
14543
  async readSessionDigests(sinceISO) {
14223
14544
  try {
14224
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14225
- if (!existsSync37(sessionsDir))
14545
+ const sessionsDir = this.transcriptRoot();
14546
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14226
14547
  return null;
14227
14548
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14228
14549
  const files = [];
@@ -14246,7 +14567,7 @@ var init_codex = __esm(async () => {
14246
14567
  for (const file of files) {
14247
14568
  let stat;
14248
14569
  try {
14249
- stat = statSync6(file);
14570
+ stat = statSync7(file);
14250
14571
  } catch {
14251
14572
  continue;
14252
14573
  }
@@ -14269,8 +14590,8 @@ var init_codex = __esm(async () => {
14269
14590
  }
14270
14591
  async listSessions(sinceISO) {
14271
14592
  try {
14272
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14273
- if (!existsSync37(sessionsDir))
14593
+ const sessionsDir = this.transcriptRoot();
14594
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14274
14595
  return null;
14275
14596
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14276
14597
  const files = [];
@@ -14294,7 +14615,7 @@ var init_codex = __esm(async () => {
14294
14615
  for (const file of files) {
14295
14616
  let stat;
14296
14617
  try {
14297
- stat = statSync6(file);
14618
+ stat = statSync7(file);
14298
14619
  } catch {
14299
14620
  continue;
14300
14621
  }
@@ -14333,7 +14654,7 @@ var init_codex = __esm(async () => {
14333
14654
  }
14334
14655
  async readSkillUsage(lastSyncAt) {
14335
14656
  try {
14336
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14657
+ const sessionsDir = this.transcriptRoot();
14337
14658
  if (!existsSync37(sessionsDir))
14338
14659
  return null;
14339
14660
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14350,7 +14671,7 @@ var init_codex = __esm(async () => {
14350
14671
  if (entry.endsWith(".jsonl")) {
14351
14672
  let fileStat;
14352
14673
  try {
14353
- fileStat = statSync6(fullPath);
14674
+ fileStat = statSync7(fullPath);
14354
14675
  } catch {
14355
14676
  continue;
14356
14677
  }
@@ -14359,7 +14680,7 @@ var init_codex = __esm(async () => {
14359
14680
  this.parseRolloutForSkills(fullPath, sinceMs, skillCounts);
14360
14681
  } else {
14361
14682
  try {
14362
- if (statSync6(fullPath).isDirectory())
14683
+ if (statSync7(fullPath).isDirectory())
14363
14684
  walkDir2(fullPath);
14364
14685
  } catch {
14365
14686
  continue;
@@ -14480,6 +14801,12 @@ var init_codex = __esm(async () => {
14480
14801
  async listSessions() {
14481
14802
  return null;
14482
14803
  }
14804
+ async readSessionDigests() {
14805
+ return null;
14806
+ }
14807
+ transcriptSources() {
14808
+ return [];
14809
+ }
14483
14810
  };
14484
14811
  });
14485
14812
 
@@ -14602,7 +14929,7 @@ var init_cline = __esm(() => {
14602
14929
  });
14603
14930
 
14604
14931
  // src/agents/gemini.ts
14605
- import { existsSync as existsSync39, mkdirSync as mkdirSync23, readdirSync as readdirSync13, readFileSync as readFileSync29, statSync as statSync7, writeFileSync as writeFileSync23 } from "fs";
14932
+ import { existsSync as existsSync39, mkdirSync as mkdirSync23, readdirSync as readdirSync13, readFileSync as readFileSync29, statSync as statSync8, writeFileSync as writeFileSync23 } from "fs";
14606
14933
  import { basename as basename4, join as join33 } from "path";
14607
14934
  import { homedir as homedir15 } from "os";
14608
14935
  var GeminiAdapter;
@@ -14612,6 +14939,7 @@ var init_gemini = __esm(() => {
14612
14939
  init_skill_removal();
14613
14940
  init_trash();
14614
14941
  init_types();
14942
+ init_transcript_sources();
14615
14943
  init_instruction_hint();
14616
14944
  init_json_config();
14617
14945
  init_detection();
@@ -14684,7 +15012,7 @@ var init_gemini = __esm(() => {
14684
15012
  }
14685
15013
  async readUsageStats(lastSyncAt) {
14686
15014
  try {
14687
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15015
+ const tmpDir = this.transcriptRoot();
14688
15016
  if (!existsSync39(tmpDir))
14689
15017
  return null;
14690
15018
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14714,7 +15042,7 @@ var init_gemini = __esm(() => {
14714
15042
  const filePath = join33(chatsDir, file.name);
14715
15043
  let stat;
14716
15044
  try {
14717
- stat = statSync7(filePath);
15045
+ stat = statSync8(filePath);
14718
15046
  } catch {
14719
15047
  continue;
14720
15048
  }
@@ -14762,8 +15090,14 @@ var init_gemini = __esm(() => {
14762
15090
  return null;
14763
15091
  }
14764
15092
  }
15093
+ transcriptRoot() {
15094
+ return join33(homedir15(), ".gemini", "tmp");
15095
+ }
15096
+ transcriptSources() {
15097
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
15098
+ }
14765
15099
  *chatFiles(sinceMs) {
14766
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15100
+ const tmpDir = this.transcriptRoot();
14767
15101
  if (!existsSync39(tmpDir))
14768
15102
  return;
14769
15103
  let projects;
@@ -14788,7 +15122,7 @@ var init_gemini = __esm(() => {
14788
15122
  const filePath = join33(chatsDir, file);
14789
15123
  let stat;
14790
15124
  try {
14791
- stat = statSync7(filePath);
15125
+ stat = statSync8(filePath);
14792
15126
  } catch {
14793
15127
  continue;
14794
15128
  }
@@ -14800,6 +15134,8 @@ var init_gemini = __esm(() => {
14800
15134
  }
14801
15135
  async listSessions(sinceISO) {
14802
15136
  try {
15137
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15138
+ return null;
14803
15139
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14804
15140
  const sessions = [];
14805
15141
  for (const { filePath, project, mtimeMs } of this.chatFiles(sinceMs)) {
@@ -14839,6 +15175,8 @@ var init_gemini = __esm(() => {
14839
15175
  }
14840
15176
  async readSessionDigests(sinceISO) {
14841
15177
  try {
15178
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15179
+ return null;
14842
15180
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14843
15181
  const digests = [];
14844
15182
  for (const { filePath, project } of this.chatFiles(sinceMs)) {
@@ -14884,6 +15222,7 @@ var init_generic_adapter = __esm(() => {
14884
15222
  init_json_config();
14885
15223
  init_instruction_hint();
14886
15224
  init_registry();
15225
+ init_detection_probes();
14887
15226
  init_detection();
14888
15227
  init_trash();
14889
15228
  GenericAgentAdapter = class GenericAgentAdapter extends RegistryDetectedAdapter {
@@ -14908,7 +15247,13 @@ var init_generic_adapter = __esm(() => {
14908
15247
  async writeMcpServers(servers, _scope) {
14909
15248
  if (!this.def.mcpConfigPath)
14910
15249
  return;
14911
- const filePath = join34(homedir16(), resolvePlatformString(this.def.mcpConfigPath) || "");
15250
+ const resolved = resolvePlatformString(this.def.mcpConfigPath) || "";
15251
+ const expanded = expandWindowsPathTemplate(resolved, {
15252
+ "%APPDATA%": process.env.APPDATA,
15253
+ "%LOCALAPPDATA%": process.env.LOCALAPPDATA,
15254
+ "%ProgramFiles%": process.env.ProgramFiles
15255
+ });
15256
+ const filePath = expanded.needsHomeJoin ? join34(homedir16(), expanded.path) : expanded.path;
14912
15257
  if (!filePath)
14913
15258
  return;
14914
15259
  const entries = {};
@@ -15061,6 +15406,26 @@ var init_detect = __esm(async () => {
15061
15406
  ALL_ADAPTERS = buildAllAdapters();
15062
15407
  });
15063
15408
 
15409
+ // ../../shared/types/local-conversations.ts
15410
+ function describeScanOutcome(outcome) {
15411
+ switch (outcome.status) {
15412
+ case "ok":
15413
+ case "no-reader":
15414
+ return null;
15415
+ case "missing-dir":
15416
+ return `no conversation history found${outcome.path ? ` at ${outcome.path}` : ""}`;
15417
+ case "permission-denied":
15418
+ return `access denied${outcome.path ? ` to ${outcome.path}` : ""}`;
15419
+ case "missing-tool":
15420
+ return `needs ${outcome.detail ?? "a tool that is not installed"}`;
15421
+ case "error":
15422
+ return `could not be read${outcome.detail ? `: ${outcome.detail}` : ""}`;
15423
+ }
15424
+ }
15425
+ function scanOutcomeNeedsAttention(outcome) {
15426
+ return outcome.status === "permission-denied" || outcome.status === "error";
15427
+ }
15428
+
15064
15429
  // src/utils/insight-id.ts
15065
15430
  import { createHash as createHash3 } from "node:crypto";
15066
15431
  function computeInsightId(userSeed, localKey) {
@@ -15211,7 +15576,7 @@ __export(exports_run_log, {
15211
15576
  RUN_LOG_CAP: () => RUN_LOG_CAP,
15212
15577
  ANALYST_ERROR_MAX_CHARS: () => ANALYST_ERROR_MAX_CHARS
15213
15578
  });
15214
- import { existsSync as existsSync43, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync15, statSync as statSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync25 } from "fs";
15579
+ import { existsSync as existsSync43, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync15, statSync as statSync9, unlinkSync as unlinkSync7, writeFileSync as writeFileSync25 } from "fs";
15215
15580
  import { dirname as dirname10, join as join37 } from "path";
15216
15581
  import { homedir as homedir19 } from "os";
15217
15582
  function runLogPath() {
@@ -15355,7 +15720,7 @@ function acquireRunLock(nowMs = Date.now()) {
15355
15720
  return true;
15356
15721
  } catch {
15357
15722
  try {
15358
- if (nowMs - statSync8(p).mtimeMs < RUN_STALE_MS)
15723
+ if (nowMs - statSync9(p).mtimeMs < RUN_STALE_MS)
15359
15724
  return false;
15360
15725
  unlinkSync7(p);
15361
15726
  } catch {
@@ -16043,6 +16408,7 @@ var init_reflect = __esm(async () => {
16043
16408
  init_store();
16044
16409
  init_client();
16045
16410
  init_resolve();
16411
+ init_transcript_sources();
16046
16412
  init_session_digest();
16047
16413
  init_insight_id();
16048
16414
  init_insight_store();
@@ -16107,13 +16473,13 @@ var init_reflect = __esm(async () => {
16107
16473
  const seen = new Set;
16108
16474
  for (const adapter2 of adapters) {
16109
16475
  if (!adapter2.readSessionDigests) {
16110
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16476
+ perAgent.push({ agentSlug: adapter2.slug, status: "no-reader", sessions: null });
16111
16477
  continue;
16112
16478
  }
16113
16479
  try {
16114
16480
  const sessions = await adapter2.readSessionDigests(sinceISO);
16115
16481
  if (sessions === null) {
16116
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16482
+ perAgent.push(diagnoseTranscriptRead(adapter2));
16117
16483
  continue;
16118
16484
  }
16119
16485
  let added = 0;
@@ -16125,20 +16491,26 @@ var init_reflect = __esm(async () => {
16125
16491
  digests.push(s);
16126
16492
  added++;
16127
16493
  }
16128
- perAgent.push({ slug: adapter2.slug, sessions: added });
16129
- } catch {
16130
- perAgent.push({ slug: adapter2.slug, sessions: "error" });
16494
+ perAgent.push({ agentSlug: adapter2.slug, status: "ok", sessions: added });
16495
+ } catch (err) {
16496
+ perAgent.push(diagnoseTranscriptRead(adapter2, err));
16131
16497
  }
16132
16498
  }
16133
16499
  if (!json) {
16134
16500
  console.error(bold(`
16135
16501
  Reflection over the last ${days} days`));
16136
- for (const a of perAgent)
16137
- console.error(` ${cyan(a.slug)}: ${a.sessions === "unsupported" ? gray("no transcript reader") : a.sessions === "error" ? red("read error") : `${a.sessions} session(s)`}`);
16502
+ for (const a of perAgent) {
16503
+ const reason = describeScanOutcome(a);
16504
+ if (reason === null) {
16505
+ console.error(` ${cyan(a.agentSlug)}: ${a.status === "no-reader" ? gray("no transcript reader") : `${a.sessions} session(s)`}`);
16506
+ } else {
16507
+ console.error(` ${cyan(a.agentSlug)}: ${scanOutcomeNeedsAttention(a) ? red(reason) : gray(reason)}`);
16508
+ }
16509
+ }
16138
16510
  }
16139
16511
  if (digests.length === 0) {
16140
16512
  if (json) {
16141
- jsonOut({ insights: [], reason: "no-sessions" });
16513
+ jsonOut({ insights: [], reason: "no-sessions", perAgent });
16142
16514
  return;
16143
16515
  }
16144
16516
  console.error(yellow(`
@@ -16503,19 +16875,19 @@ async function listLocalConversations(opts = {}) {
16503
16875
  const perAgent = [];
16504
16876
  for (const adapter2 of adapters) {
16505
16877
  if (!adapter2.listSessions) {
16506
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16878
+ perAgent.push({ agentSlug: adapter2.slug, status: "no-reader", sessions: null });
16507
16879
  continue;
16508
16880
  }
16509
16881
  try {
16510
16882
  const sessions = await adapter2.listSessions(sinceISO);
16511
16883
  if (sessions === null) {
16512
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16884
+ perAgent.push(diagnoseTranscriptRead(adapter2));
16513
16885
  continue;
16514
16886
  }
16515
16887
  listings.push(sessions);
16516
- perAgent.push({ slug: adapter2.slug, sessions: sessions.length });
16517
- } catch {
16518
- perAgent.push({ slug: adapter2.slug, sessions: "error" });
16888
+ perAgent.push({ agentSlug: adapter2.slug, status: "ok", sessions: sessions.length });
16889
+ } catch (err) {
16890
+ perAgent.push(diagnoseTranscriptRead(adapter2, err));
16519
16891
  }
16520
16892
  }
16521
16893
  const conversations = mergeSessionListings(listings, {
@@ -16526,6 +16898,7 @@ async function listLocalConversations(opts = {}) {
16526
16898
  }
16527
16899
  var DEFAULT_IDLE_MINUTES = 10;
16528
16900
  var init_conversation_registry = __esm(async () => {
16901
+ init_transcript_sources();
16529
16902
  await init_detect();
16530
16903
  });
16531
16904
 
@@ -17922,7 +18295,7 @@ var init_welcome = __esm(() => {
17922
18295
  });
17923
18296
 
17924
18297
  // src/index.ts
17925
- import { Command as Command39 } from "commander";
18298
+ import { Command as Command40 } from "commander";
17926
18299
 
17927
18300
  // src/commands/login.ts
17928
18301
  init_login_flow();
@@ -18231,7 +18604,18 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
18231
18604
  console.log("Syncing...");
18232
18605
  try {
18233
18606
  commitWorkingTree(cwd, `deploy: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`);
18234
- } catch {}
18607
+ } catch (err) {
18608
+ if (hasTrackedChanges(cwd)) {
18609
+ const detail = err instanceof Error ? err.message : String(err);
18610
+ if (useJson) {
18611
+ jsonOut(buildErrorResponse("deploy", "Could not commit your changes", `Your working tree has uncommitted changes that could not be committed (${detail.slice(0, 300)}). Deploying now would ship the PREVIOUS commit while reporting success.`, ["Check for a failing pre-commit hook or a stale .git/index.lock", "Commit the changes yourself with git commit", "Then re-run runwork deploy"]));
18612
+ process.exit(1);
18613
+ }
18614
+ console.error("Could not commit your changes, so nothing new would be deployed.");
18615
+ console.error("A pre-commit hook or a stale .git/index.lock may be interfering. Commit manually, then re-run `runwork deploy`.");
18616
+ process.exit(1);
18617
+ }
18618
+ }
18235
18619
  if (!hasCommits(cwd)) {
18236
18620
  if (useJson) {
18237
18621
  jsonOut(buildErrorResponse("deploy", "Nothing to deploy", "No commits exist and the working tree has no changes to commit, so there is nothing to sync or deploy.", ["Make changes to your app first", "Run runwork dev to develop and verify changes", "Then run runwork deploy"]));
@@ -20372,9 +20756,14 @@ conversationsCommand.command("list").description("List local conversations acros
20372
20756
  Local conversations, last ${days} day(s)` : `
20373
20757
  Local conversations (all)`));
20374
20758
  for (const a of result.perAgent) {
20375
- if (a.sessions === "unsupported")
20759
+ if (a.status === "no-reader")
20376
20760
  continue;
20377
- console.log(dim(` ${a.slug}: ${a.sessions === "error" ? "read error" : `${a.sessions} session(s)`}`));
20761
+ const reason = describeScanOutcome(a);
20762
+ if (reason === null) {
20763
+ console.log(dim(` ${a.agentSlug}: ${a.sessions} session(s)`));
20764
+ } else {
20765
+ console.log(` ${a.agentSlug}: ${scanOutcomeNeedsAttention(a) ? yellow(reason) : dim(reason)}`);
20766
+ }
20378
20767
  }
20379
20768
  console.log("");
20380
20769
  const width = process.stdout.columns || 120;
@@ -20391,6 +20780,26 @@ Local conversations (all)`));
20391
20780
  if (result.conversations.length === 0)
20392
20781
  console.log(dim(" none"));
20393
20782
  });
20783
+ conversationsCommand.command("status").description("Why each agent did or did not contribute conversations (no conversation data)").option("--days <n>", "Look back this many days (0 = no limit)", String(DEFAULT_LOOKBACK_DAYS)).action(async (opts, command) => {
20784
+ const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
20785
+ const days = parseDays(opts.days);
20786
+ const { perAgent } = await listLocalConversations({ sinceISO: sinceFromDays(days) });
20787
+ if (json) {
20788
+ jsonOut({ perAgent });
20789
+ return;
20790
+ }
20791
+ console.log(bold(days > 0 ? `
20792
+ Conversation sources, last ${days} day(s)` : `
20793
+ Conversation sources`));
20794
+ for (const a of perAgent) {
20795
+ const reason = describeScanOutcome(a);
20796
+ if (reason === null) {
20797
+ console.log(` ${cyan(a.agentSlug)}: ${a.status === "no-reader" ? dim("no transcript reader") : `${a.sessions} conversation(s)`}`);
20798
+ } else {
20799
+ console.log(` ${cyan(a.agentSlug)}: ${scanOutcomeNeedsAttention(a) ? yellow(reason) : dim(reason)}`);
20800
+ }
20801
+ }
20802
+ });
20394
20803
  conversationsCommand.command("scan").description("Detect finished conversations (idle threshold) and queue them for reflection").option("--days <n>", "Look back this many days (0 = no limit)", String(DEFAULT_LOOKBACK_DAYS)).option("--idle-minutes <n>", "Idle threshold that marks a conversation finished", String(DEFAULT_IDLE_MINUTES)).action(async (opts, command) => {
20395
20804
  const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
20396
20805
  const days = parseDays(opts.days);
@@ -20454,11 +20863,12 @@ conversationsCommand.command("scan").description("Detect finished conversations
20454
20863
  const snapshot = {
20455
20864
  generatedAt: new Date().toISOString(),
20456
20865
  idleMinutes,
20457
- conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt))
20866
+ conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt)),
20867
+ perAgent: result.perAgent
20458
20868
  };
20459
20869
  writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
20460
20870
  if (json) {
20461
- jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length });
20871
+ jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length, perAgent: result.perAgent });
20462
20872
  return;
20463
20873
  }
20464
20874
  console.log(green(`Scanned ${result.conversations.length} conversation(s): ${scan.queued} newly queued, ${scan.reopened} reopened, ${pending} pending analysis.`));
@@ -23433,7 +23843,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23433
23843
  if (!action.remoteContent)
23434
23844
  continue;
23435
23845
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23436
- await writeSkillToAgents(skillFile, action.source, ctx);
23846
+ const outcome = await writeSkillToAgents(skillFile, action.source, ctx);
23847
+ if (!writeLanded(outcome)) {
23848
+ vlog(` Pull FAILED for ${action.name} (${outcome.succeeded}/${outcome.attempted} agent writes); will retry next sync`);
23849
+ continue;
23850
+ }
23437
23851
  newHashes[action.name] = {
23438
23852
  localHash: contentHash(buildSkillMd2(skillFile)),
23439
23853
  remoteHash: contentHash(action.remoteContent),
@@ -23475,7 +23889,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23475
23889
  vlog(` Pushed (conflict resolved): ${action.name}`);
23476
23890
  } else if (resolution === "remote" && action.remoteContent) {
23477
23891
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23478
- await writeSkillToAgents(skillFile, action.source, ctx);
23892
+ const outcome = await writeSkillToAgents(skillFile, action.source, ctx);
23893
+ if (!writeLanded(outcome)) {
23894
+ vlog(` Conflict pull FAILED for ${action.name} (${outcome.succeeded}/${outcome.attempted} agent writes); will retry next sync`);
23895
+ continue;
23896
+ }
23479
23897
  newHashes[action.name] = {
23480
23898
  localHash: contentHash(buildSkillMd2(skillFile)),
23481
23899
  remoteHash: contentHash(action.remoteContent),
@@ -23487,6 +23905,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23487
23905
  }
23488
23906
  for (const _action of plan.skips) {}
23489
23907
  const deletionSlugs = plan.deletions.map((action) => toSlug(action.name));
23908
+ let removalsFailed = false;
23490
23909
  if (deletionSlugs.length) {
23491
23910
  for (const adapter2 of ctx.adapters) {
23492
23911
  if (!adapter2.supportsSkills() || !adapter2.removeSkills)
@@ -23494,14 +23913,19 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23494
23913
  for (const scope of ctx.scopes) {
23495
23914
  try {
23496
23915
  await adapter2.removeSkills(deletionSlugs, scope);
23497
- } catch {}
23916
+ } catch {
23917
+ removalsFailed = true;
23918
+ }
23498
23919
  }
23499
23920
  }
23500
23921
  for (const action of plan.deletions) {
23501
- vlog(` Deleted locally: ${action.name}`);
23922
+ vlog(removalsFailed ? ` Deletion of ${action.name} incomplete; will retry next sync` : ` Deleted locally: ${action.name}`);
23502
23923
  }
23503
23924
  }
23504
- return newHashes;
23925
+ return {
23926
+ newHashes,
23927
+ failedDeletions: removalsFailed ? plan.deletions.map((action) => action.name) : []
23928
+ };
23505
23929
  }
23506
23930
  function makeSkillFile(name, content) {
23507
23931
  return {
@@ -23512,17 +23936,25 @@ function makeSkillFile(name, content) {
23512
23936
  };
23513
23937
  }
23514
23938
  async function writeSkillToAgents(skillFile, source, ctx) {
23939
+ let attempted = 0;
23940
+ let succeeded = 0;
23515
23941
  for (const adapter2 of ctx.adapters) {
23516
23942
  if (source === "app" && ctx.hasMcp && adapter2.mcpProvidesSkills)
23517
23943
  continue;
23518
23944
  for (const scope of ctx.scopes) {
23519
23945
  if (!adapter2.supportsSkills())
23520
23946
  continue;
23947
+ attempted++;
23521
23948
  try {
23522
23949
  await adapter2.writeSkills([skillFile], scope);
23950
+ succeeded++;
23523
23951
  } catch {}
23524
23952
  }
23525
23953
  }
23954
+ return { attempted, succeeded };
23955
+ }
23956
+ function writeLanded(outcome) {
23957
+ return outcome.attempted === 0 || outcome.succeeded > 0;
23526
23958
  }
23527
23959
  function extractDescription(content) {
23528
23960
  const match = content.match(/^---\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
@@ -23937,7 +24369,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23937
24369
  if (adapters.length > 0) {
23938
24370
  console.log(` Syncing to: ${adapters.map((a) => a.name).join(", ")}`);
23939
24371
  }
23940
- const newHashes = await executeSyncPlan(plan, resolvedConflicts, {
24372
+ const { newHashes, failedDeletions } = await executeSyncPlan(plan, resolvedConflicts, {
23941
24373
  client,
23942
24374
  workspaceId: state.workspaceId,
23943
24375
  adapters,
@@ -24324,7 +24756,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24324
24756
  for (const [name, hash] of Object.entries(newHashes)) {
24325
24757
  mergedHashes[name] = hash;
24326
24758
  }
24759
+ const retryDeletions = new Set(failedDeletions);
24327
24760
  for (const del of plan.deletions) {
24761
+ if (retryDeletions.has(del.name))
24762
+ continue;
24328
24763
  delete mergedHashes[del.name];
24329
24764
  }
24330
24765
  state.skillHashes = mergedHashes;
@@ -25879,6 +26314,7 @@ async function checkAgentSetup() {
25879
26314
  details.push(`${state.configuredAgents.length} agent(s) configured`);
25880
26315
  }
25881
26316
  let mcpChecked = false;
26317
+ let mcpAllHealthy = true;
25882
26318
  for (const slug of state.configuredAgents) {
25883
26319
  const adapter2 = getAdapterBySlug(slug);
25884
26320
  if (!adapter2 || !adapter2.supportsMcpScope("user"))
@@ -25891,19 +26327,22 @@ async function checkAgentSetup() {
25891
26327
  if (missingMcp.length > 0) {
25892
26328
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
25893
26329
  upgrade("warn");
25894
- } else {
25895
- details.push(`${state.mcpServers.length} MCP server(s) configured`);
26330
+ mcpAllHealthy = false;
25896
26331
  }
25897
26332
  mcpChecked = true;
25898
- break;
25899
26333
  } catch {}
25900
26334
  }
25901
26335
  }
26336
+ if (mcpChecked && mcpAllHealthy && state.mcpServers.length > 0) {
26337
+ details.push(`${state.mcpServers.length} MCP server(s) configured`);
26338
+ }
25902
26339
  if (!mcpChecked && state.mcpServers.length > 0) {
25903
26340
  details.push("could not verify MCP servers");
25904
26341
  upgrade("warn");
25905
26342
  }
25906
26343
  let skillsChecked = false;
26344
+ let skillsAllHealthy = true;
26345
+ let skillsSummaryLine = null;
25907
26346
  for (const slug of state.configuredAgents) {
25908
26347
  const skillsDir = getSkillsDir(slug, "user");
25909
26348
  if (!skillsDir)
@@ -25920,13 +26359,16 @@ async function checkAgentSetup() {
25920
26359
  if (missingSkills.length > 0) {
25921
26360
  details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
25922
26361
  upgrade("warn");
25923
- } else if (state.skills.length > 0) {
26362
+ skillsAllHealthy = false;
26363
+ } else if (state.skills.length > 0 && !skillsSummaryLine) {
25924
26364
  const mcpCoveredCount = state.skills.filter(isCoveredByMcp).length;
25925
26365
  const onDiskCount = state.skills.length - mcpCoveredCount;
25926
- details.push(mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`);
26366
+ skillsSummaryLine = mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`;
25927
26367
  }
25928
26368
  skillsChecked = true;
25929
- break;
26369
+ }
26370
+ if (skillsChecked && skillsAllHealthy && skillsSummaryLine) {
26371
+ details.push(skillsSummaryLine);
25930
26372
  }
25931
26373
  if (!skillsChecked && state.skills.length > 0) {
25932
26374
  details.push("could not verify skills");
@@ -26101,7 +26543,7 @@ async function applyDoctorFixes(ctx, failingNames) {
26101
26543
  }
26102
26544
 
26103
26545
  // src/agents/runtime-detection.ts
26104
- import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync10, readdirSync as readdirSync17 } from "fs";
26546
+ import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync11, readdirSync as readdirSync17 } from "fs";
26105
26547
  import { homedir as homedir33 } from "os";
26106
26548
  import { join as join55 } from "path";
26107
26549
  var RUNWORK_SESSIONS_DIR = join55(homedir33(), ".runwork", "sessions");
@@ -26212,7 +26654,7 @@ function findCodexRolloutFile(threadId) {
26212
26654
  const full = join55(dir, entry);
26213
26655
  let s;
26214
26656
  try {
26215
- s = statSync10(full);
26657
+ s = statSync11(full);
26216
26658
  } catch {
26217
26659
  continue;
26218
26660
  }
@@ -26249,7 +26691,7 @@ function findNewestClaudeCodeSession() {
26249
26691
  continue;
26250
26692
  const full = join55(projectPath, file);
26251
26693
  try {
26252
- const s = statSync10(full);
26694
+ const s = statSync11(full);
26253
26695
  if (!best || s.mtimeMs > best.mtime) {
26254
26696
  best = {
26255
26697
  sessionId: file.replace(/\.jsonl$/, ""),
@@ -26282,7 +26724,7 @@ function findNewestCodexRollout() {
26282
26724
  const full = join55(dir, entry);
26283
26725
  let s;
26284
26726
  try {
26285
- s = statSync10(full);
26727
+ s = statSync11(full);
26286
26728
  } catch {
26287
26729
  continue;
26288
26730
  }
@@ -26506,13 +26948,627 @@ var doctorCommand = new Command34("doctor").description("Check system health: au
26506
26948
  }
26507
26949
  });
26508
26950
 
26951
+ // src/commands/debug.ts
26952
+ init_colors();
26953
+ import { Command as Command35 } from "commander";
26954
+ import { readFileSync as readFileSync48 } from "fs";
26955
+ import { homedir as homedir35, platform as platform7, release, arch, type as osType } from "os";
26956
+ import { join as join58 } from "path";
26957
+
26958
+ // src/debug/capture-plan.ts
26959
+ var CONFIG_MAX_BYTES = 64 * 1024;
26960
+ var LOG_TAIL_BYTES = 64 * 1024;
26961
+ var CENSUS_MAX_ENTRIES = 200;
26962
+ var ENV_NAMES = [
26963
+ "PATH",
26964
+ "SHELL",
26965
+ "HOME",
26966
+ "USERPROFILE",
26967
+ "APPDATA",
26968
+ "LOCALAPPDATA",
26969
+ "XDG_CONFIG_HOME",
26970
+ "HTTP_PROXY",
26971
+ "HTTPS_PROXY",
26972
+ "http_proxy",
26973
+ "https_proxy",
26974
+ "NO_PROXY",
26975
+ "no_proxy",
26976
+ "NODE_EXTRA_CA_CERTS",
26977
+ "RUNWORK_HTTP_TRANSPORT",
26978
+ "RUNWORK_API_URL",
26979
+ "TERM_PROGRAM",
26980
+ "LANG"
26981
+ ];
26982
+ function buildCapturePlan(input) {
26983
+ const sep5 = input.sep ?? (input.platform === "win32" ? "\\" : "/");
26984
+ const join56 = (...parts) => parts.join(sep5);
26985
+ const home = input.homeDir.replace(/[/\\]+$/, "");
26986
+ const rw = (...parts) => join56(home, ".runwork", ...parts);
26987
+ const steps = [];
26988
+ steps.push({ id: "env", kind: "env", names: ENV_NAMES, note: "process environment (proxy credentials stripped)" });
26989
+ for (const [id, file] of [
26990
+ ["runwork.setup", "setup.json"],
26991
+ ["runwork.workspaces", "workspaces.json"],
26992
+ ["runwork.reflect-state", "reflect-state.json"],
26993
+ ["runwork.reflect-runs", "reflect-runs.json"],
26994
+ ["runwork.telemetry-outbox", "telemetry-outbox.json"],
26995
+ ["runwork.session-summaries-outbox", "session-summaries-outbox.json"]
26996
+ ]) {
26997
+ steps.push({ id, kind: "file", path: rw(file), maxBytes: CONFIG_MAX_BYTES });
26998
+ }
26999
+ for (const [id, file] of [
27000
+ ["runwork.conversations", "conversations.json"],
27001
+ ["runwork.reflect-queue", "reflect-queue.json"],
27002
+ ["runwork.insights", "insights.json"],
27003
+ ["runwork.pattern-store", "pattern-store.json"]
27004
+ ]) {
27005
+ steps.push({ id, kind: "stat", path: rw(file), note: "size and mtime only: contains conversation text" });
27006
+ }
27007
+ steps.push({ id: "runwork.dir", kind: "census", path: rw(), depth: 1, maxEntries: CENSUS_MAX_ENTRIES }, { id: "runwork.bin", kind: "census", path: rw("bin"), depth: 1, maxEntries: 50, note: "which CLI binaries are installed here" }, { id: "runwork.sessions", kind: "census", path: rw("sessions"), depth: 1, maxEntries: CENSUS_MAX_ENTRIES, note: "written by the agent hooks we install" }, { id: "runwork.log", kind: "file", path: rw("desktop-launch.log"), maxBytes: LOG_TAIL_BYTES, tail: true, note: "tail: this log has no rotation" });
27008
+ for (const [id, ...parts] of [
27009
+ ["claude.settings", ".claude", "settings.json"],
27010
+ ["claude.md", ".claude", "CLAUDE.md"],
27011
+ ["claude.mcp", ".claude", ".mcp.json"],
27012
+ ["codex.config", ".codex", "config.toml"],
27013
+ ["codex.agents-md", ".codex", "AGENTS.md"],
27014
+ ["gemini.settings", ".gemini", "settings.json"]
27015
+ ]) {
27016
+ steps.push({ id, kind: "file", path: join56(home, ...parts), maxBytes: CONFIG_MAX_BYTES });
27017
+ }
27018
+ for (const [id, ...parts] of [
27019
+ ["claude.skills", ".claude", "skills"],
27020
+ ["claude.plugins", ".claude", "plugins"],
27021
+ ["agents.skills", ".agents", "skills"],
27022
+ ["codex.skills", ".codex", "skills"]
27023
+ ]) {
27024
+ steps.push({ id, kind: "census", path: join56(home, ...parts), depth: 1, maxEntries: CENSUS_MAX_ENTRIES });
27025
+ }
27026
+ steps.push({ id: "transcripts.claude-code", kind: "census", path: join56(home, ".claude", "projects"), depth: 1, maxEntries: CENSUS_MAX_ENTRIES }, { id: "transcripts.codex", kind: "census", path: join56(home, ".codex", "sessions"), depth: 2, maxEntries: CENSUS_MAX_ENTRIES }, { id: "transcripts.gemini", kind: "census", path: join56(home, ".gemini", "tmp"), depth: 1, maxEntries: CENSUS_MAX_ENTRIES }, { id: "transcripts.cowork", kind: "census", path: coworkBaseDir(input, join56), depth: 1, maxEntries: CENSUS_MAX_ENTRIES, note: "macOS TCC can refuse this while it still exists" });
27027
+ for (const profile of shellProfiles(input.platform)) {
27028
+ steps.push({
27029
+ id: `shell${profile}`,
27030
+ kind: "stat",
27031
+ path: join56(home, profile),
27032
+ note: "presence only: profile bodies can contain the user's own secrets"
27033
+ });
27034
+ }
27035
+ steps.push(...binaryResolutionSteps(input));
27036
+ if (input.cliPath) {
27037
+ steps.push({ id: "cli.version", kind: "exec", command: input.cliPath, args: ["--version"] }, { id: "cli.doctor", kind: "exec", command: input.cliPath, args: ["--json", "doctor", "-v"] }, { id: "cli.conversations", kind: "exec", command: input.cliPath, args: ["--json", "conversations", "status", "--days", "0"], note: "per-agent scan outcome, no conversation data" });
27038
+ }
27039
+ return steps;
27040
+ }
27041
+ function coworkBaseDir(input, join56) {
27042
+ const home = input.homeDir.replace(/[/\\]+$/, "");
27043
+ if (input.platform === "darwin") {
27044
+ return join56(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions");
27045
+ }
27046
+ if (input.platform === "win32") {
27047
+ const base = input.appData ?? join56(home, "AppData", "Roaming");
27048
+ return join56(base, "Claude", "local-agent-mode-sessions");
27049
+ }
27050
+ return join56(home, ".config", "Claude", "local-agent-mode-sessions");
27051
+ }
27052
+ function shellProfiles(platform7) {
27053
+ if (platform7 === "win32")
27054
+ return [];
27055
+ return [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"];
27056
+ }
27057
+ function binaryResolutionSteps(input) {
27058
+ const sep5 = input.sep ?? (input.platform === "win32" ? "\\" : "/");
27059
+ const join56 = (...parts) => parts.join(sep5);
27060
+ const home = input.homeDir.replace(/[/\\]+$/, "");
27061
+ if (input.platform === "win32") {
27062
+ return [
27063
+ { id: "binaries.where", kind: "exec", command: "where.exe", args: ["runwork"], note: "every match, not just the first" },
27064
+ { id: "binaries.runwork-bin", kind: "stat", path: join56(home, ".runwork", "bin", "runwork.exe") },
27065
+ { id: "binaries.user-path", kind: "exec", command: "reg.exe", args: ["query", "HKCU\\Environment", "/v", "Path"], note: "the PATH a NEW shell will inherit, not this one" }
27066
+ ];
27067
+ }
27068
+ return [
27069
+ { id: "binaries.which", kind: "exec", command: "which", args: ["-a", "runwork"], note: "every match, not just the first" },
27070
+ { id: "binaries.runwork-bin", kind: "stat", path: join56(home, ".runwork", "bin", "runwork") },
27071
+ { id: "binaries.local-bin", kind: "stat", path: join56(home, ".local", "bin", "runwork") },
27072
+ ...input.platform === "darwin" ? [{ id: "binaries.usr-local-bin", kind: "stat", path: "/usr/local/bin/runwork" }] : []
27073
+ ];
27074
+ }
27075
+
27076
+ // src/debug/capture.ts
27077
+ var CAPTURE_FORMAT = 1;
27078
+ function summarizeBundle(jsonl) {
27079
+ const summary = {
27080
+ formatVersion: CAPTURE_FORMAT,
27081
+ producer: null,
27082
+ stepsPlanned: 0,
27083
+ stepsRun: 0,
27084
+ redactions: 0,
27085
+ withheld: 0,
27086
+ complete: false
27087
+ };
27088
+ for (const line of jsonl.split(`
27089
+ `)) {
27090
+ if (!line.trim())
27091
+ continue;
27092
+ let record;
27093
+ try {
27094
+ record = JSON.parse(line);
27095
+ } catch {
27096
+ continue;
27097
+ }
27098
+ if (record.type === "plan") {
27099
+ summary.formatVersion = record.format;
27100
+ summary.producer = record.producer;
27101
+ summary.stepsPlanned = record.steps.length;
27102
+ } else if (record.type === "step") {
27103
+ summary.stepsRun++;
27104
+ summary.redactions += record.redactions ?? 0;
27105
+ summary.withheld += record.withheld ?? 0;
27106
+ } else if (record.type === "complete") {
27107
+ summary.complete = true;
27108
+ }
27109
+ }
27110
+ return summary;
27111
+ }
27112
+ async function sha256Hex(text2) {
27113
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text2));
27114
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
27115
+ }
27116
+ var NEVER_READ = [".credentials", "credentials.json", "auth.json", ".netrc", "id_rsa"];
27117
+ function isForbiddenPath(path4) {
27118
+ const name = path4.split(/[/\\]/).pop()?.toLowerCase() ?? "";
27119
+ return NEVER_READ.some((deny) => name === deny.toLowerCase());
27120
+ }
27121
+ var SECRET_KEY_NAMES = "token|secret|password|passwd|api[-_]?key|apikey|authorization|access[-_]?token|refresh[-_]?token|client[-_]?secret|private[-_]?key|access[-_]?key|credentials?";
27122
+ var SECRET_KEY_PATTERN = `[A-Za-z0-9_-]*?(?:${SECRET_KEY_NAMES})`;
27123
+ var QUOTED_VALUE = String.raw`"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'`;
27124
+ var SECRET_QUERY_PARAMS = "token|key|secret|password|api[-_]?key|apikey|access[-_]?token|sig|signature";
27125
+ function redactSecrets(text2) {
27126
+ let redactions = 0;
27127
+ const mark = () => {
27128
+ redactions++;
27129
+ return "<redacted>";
27130
+ };
27131
+ const out = text2.replace(new RegExp(`([?&](?:${SECRET_QUERY_PARAMS})=)[^&\\s"'\`]+`, "gi"), (_m, prefix) => `${prefix}${mark()}`).replace(/\b(Bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, (_m, prefix) => `${prefix}${mark()}`).replace(new RegExp(`("?${SECRET_KEY_PATTERN}"?\\s*[:=]\\s*)(${QUOTED_VALUE}|[^\\s,}\\]&"']+)`, "gi"), (match, prefix, value) => {
27132
+ if (value.includes("<redacted>"))
27133
+ return match;
27134
+ const quote = value.startsWith('"') ? '"' : value.startsWith("'") ? "'" : "";
27135
+ return `${prefix}${quote}${mark()}${quote}`;
27136
+ });
27137
+ return { text: out, redactions };
27138
+ }
27139
+ var CONTENT_KEY_NAMES = "currentTitle|title|prompt|userMessages|summary|body|snippet";
27140
+ function redactUserContent(text2) {
27141
+ let withheld = 0;
27142
+ const out = text2.replace(new RegExp(`("?\\b(?:${CONTENT_KEY_NAMES})"?\\s*[:=]\\s*)(${QUOTED_VALUE})`, "gi"), (match, prefix, value) => {
27143
+ if (value === '""' || value === "''")
27144
+ return match;
27145
+ withheld++;
27146
+ const quote = value[0];
27147
+ return `${prefix}${quote}<withheld>${quote}`;
27148
+ });
27149
+ return { text: out, withheld };
27150
+ }
27151
+ function redactEnvValue(value) {
27152
+ if (value === null)
27153
+ return null;
27154
+ return value.replace(/(\w+:\/\/)[^/@\s]*@/g, "$1<redacted>@");
27155
+ }
27156
+ function classifyCaptureError(err) {
27157
+ const e = err;
27158
+ const code = e && typeof e === "object" && typeof e.code === "string" ? e.code : undefined;
27159
+ const message = e instanceof Error ? e.message : String(err);
27160
+ if (code === "ENOENT" || code === "ENOTDIR")
27161
+ return { status: "missing", detail: code };
27162
+ if (code === "EACCES" || code === "EPERM")
27163
+ return { status: "denied", detail: code };
27164
+ if (code === "ETIMEDOUT" || code === "TIMEOUT")
27165
+ return { status: "timeout", detail: code };
27166
+ if (code === "ESCOPE")
27167
+ return { status: "skipped", detail: message };
27168
+ return { status: "error", detail: code ? `${code}: ${message}` : message };
27169
+ }
27170
+ function stepTarget(step) {
27171
+ switch (step.kind) {
27172
+ case "file":
27173
+ case "census":
27174
+ case "stat":
27175
+ return step.path;
27176
+ case "env":
27177
+ return step.names.join(",");
27178
+ case "exec":
27179
+ return [step.command, ...step.args].join(" ");
27180
+ }
27181
+ }
27182
+ function withDeadline(work, ms) {
27183
+ return new Promise((resolve5, reject) => {
27184
+ const timer = setTimeout(() => reject(Object.assign(new Error("step deadline"), { code: "TIMEOUT" })), ms);
27185
+ work.then((value) => {
27186
+ clearTimeout(timer);
27187
+ resolve5(value);
27188
+ }, (err) => {
27189
+ clearTimeout(timer);
27190
+ reject(err);
27191
+ });
27192
+ });
27193
+ }
27194
+ async function runStep(port, step, deadlineMs) {
27195
+ const startedMs = port.now();
27196
+ const base = { type: "step", id: step.id, kind: step.kind, target: stepTarget(step) };
27197
+ const done = (extra) => ({ ...base, status: "ok", durationMs: port.now() - startedMs, ...extra });
27198
+ if ((step.kind === "file" || step.kind === "stat") && isForbiddenPath(step.path)) {
27199
+ return { ...base, status: "skipped", detail: "excluded by policy", durationMs: 0 };
27200
+ }
27201
+ try {
27202
+ switch (step.kind) {
27203
+ case "file": {
27204
+ const raw = await withDeadline(port.readText(step.path, step.maxBytes, step.tail === true), deadlineMs);
27205
+ const secrets = redactSecrets(raw.text);
27206
+ const content = redactUserContent(secrets.text);
27207
+ return done({
27208
+ text: content.text,
27209
+ truncated: raw.truncated,
27210
+ ...secrets.redactions > 0 ? { redactions: secrets.redactions } : {},
27211
+ ...content.withheld > 0 ? { withheld: content.withheld } : {}
27212
+ });
27213
+ }
27214
+ case "census": {
27215
+ const entries = await withDeadline(port.listDir(step.path, step.depth, step.maxEntries), deadlineMs);
27216
+ return done({ entries, entryCount: entries.length });
27217
+ }
27218
+ case "stat": {
27219
+ const info = await withDeadline(port.stat(step.path), deadlineMs);
27220
+ return done({ bytes: info.bytes, modifiedAt: info.modifiedAt });
27221
+ }
27222
+ case "env": {
27223
+ const raw = port.env(step.names);
27224
+ return done({
27225
+ env: Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, redactEnvValue(v)]))
27226
+ });
27227
+ }
27228
+ case "exec": {
27229
+ const result = await withDeadline(port.exec(step.command, step.args), deadlineMs);
27230
+ const out = redactSecrets(result.stdout);
27231
+ const err = redactSecrets(result.stderr);
27232
+ const redactions = out.redactions + err.redactions;
27233
+ return done({
27234
+ text: out.text,
27235
+ exitCode: result.exitCode,
27236
+ stderr: err.text,
27237
+ ...redactions > 0 ? { redactions } : {}
27238
+ });
27239
+ }
27240
+ }
27241
+ } catch (err) {
27242
+ const { status, detail } = classifyCaptureError(err);
27243
+ return { ...base, status, detail, durationMs: port.now() - startedMs };
27244
+ }
27245
+ }
27246
+ async function runCapture(port, producer, steps, opts = {}) {
27247
+ const deadlineMs = opts.stepDeadlineMs ?? 1e4;
27248
+ const startedMs = port.now();
27249
+ await port.append({
27250
+ type: "plan",
27251
+ format: CAPTURE_FORMAT,
27252
+ producer,
27253
+ startedAt: new Date(startedMs).toISOString(),
27254
+ steps: steps.map((s) => ({ id: s.id, kind: s.kind, ...s.note ? { note: s.note } : {} }))
27255
+ });
27256
+ let stepsRun = 0;
27257
+ for (const step of steps) {
27258
+ await port.append(await runStep(port, step, deadlineMs));
27259
+ stepsRun++;
27260
+ }
27261
+ if (opts.claims) {
27262
+ try {
27263
+ for (const claim of await opts.claims())
27264
+ await port.append(claim);
27265
+ } catch (err) {
27266
+ await port.append({
27267
+ type: "claim",
27268
+ id: "claims-failed",
27269
+ producer,
27270
+ value: { error: err instanceof Error ? err.message : String(err) }
27271
+ });
27272
+ }
27273
+ }
27274
+ await port.append({
27275
+ type: "complete",
27276
+ finishedAt: new Date(port.now()).toISOString(),
27277
+ stepsPlanned: steps.length,
27278
+ stepsRun,
27279
+ durationMs: port.now() - startedMs
27280
+ });
27281
+ return { stepsRun };
27282
+ }
27283
+
27284
+ // src/utils/machine-id.ts
27285
+ init_atomic_json();
27286
+ import { randomUUID } from "crypto";
27287
+ import { homedir as homedir34 } from "os";
27288
+ import { join as join56 } from "path";
27289
+ function machineIdPath() {
27290
+ return join56(homedir34(), ".runwork", "machine.json");
27291
+ }
27292
+ function isValidId(value) {
27293
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
27294
+ }
27295
+ function getMachineId() {
27296
+ const path4 = machineIdPath();
27297
+ const existing = readJsonOrNull(path4);
27298
+ if (existing && isValidId(existing.id))
27299
+ return existing.id;
27300
+ const record = { id: randomUUID(), createdAt: new Date().toISOString() };
27301
+ try {
27302
+ writeJsonAtomic(path4, record);
27303
+ } catch {
27304
+ return null;
27305
+ }
27306
+ const settled = readJsonOrNull(path4);
27307
+ return settled && isValidId(settled.id) ? settled.id : record.id;
27308
+ }
27309
+
27310
+ // src/commands/debug.ts
27311
+ init_store();
27312
+ init_client();
27313
+ init_resolve();
27314
+
27315
+ // src/debug/node-port.ts
27316
+ init_subprocess();
27317
+ init_which();
27318
+ import { closeSync as closeSync5, fsyncSync, mkdirSync as mkdirSync29, openSync as openSync5, readSync as readSync3, readdirSync as readdirSync18, statSync as statSync12, writeSync } from "fs";
27319
+ import { dirname as dirname12, join as join57 } from "path";
27320
+ var EXEC_OUTPUT_MAX = 256 * 1024;
27321
+ var EXEC_TIMEOUT_MS = 20000;
27322
+ function truncate5(text2, maxBytes) {
27323
+ return text2.length > maxBytes ? { text: text2.slice(0, maxBytes), truncated: true } : { text: text2, truncated: false };
27324
+ }
27325
+ function entryKind(path4) {
27326
+ try {
27327
+ const s = statSync12(path4);
27328
+ return s.isDirectory() ? "dir" : s.isFile() ? "file" : "other";
27329
+ } catch {
27330
+ return "other";
27331
+ }
27332
+ }
27333
+ function createNodeCapturePort(outputPath) {
27334
+ mkdirSync29(dirname12(outputPath), { recursive: true });
27335
+ const fd = openSync5(outputPath, "w");
27336
+ return {
27337
+ close: () => closeSync5(fd),
27338
+ async append(record) {
27339
+ writeSync(fd, JSON.stringify(record) + `
27340
+ `);
27341
+ try {
27342
+ fsyncSync(fd);
27343
+ } catch {}
27344
+ },
27345
+ async listDir(path4, depth, maxEntries) {
27346
+ const out = [];
27347
+ const queue = [{ dir: path4, prefix: "", level: 0 }];
27348
+ let first = true;
27349
+ while (queue.length > 0 && out.length < maxEntries) {
27350
+ const { dir, prefix, level } = queue.shift();
27351
+ let names;
27352
+ try {
27353
+ names = readdirSync18(dir);
27354
+ } catch (err) {
27355
+ if (first)
27356
+ throw err;
27357
+ continue;
27358
+ } finally {
27359
+ first = false;
27360
+ }
27361
+ for (const name of names) {
27362
+ if (out.length >= maxEntries)
27363
+ break;
27364
+ const full = join57(dir, name);
27365
+ const kind = entryKind(full);
27366
+ let bytes;
27367
+ let modifiedAt;
27368
+ try {
27369
+ const s = statSync12(full);
27370
+ bytes = s.size;
27371
+ modifiedAt = new Date(s.mtimeMs).toISOString();
27372
+ } catch {}
27373
+ out.push({ name: prefix ? `${prefix}/${name}` : name, kind, ...bytes !== undefined ? { bytes } : {}, ...modifiedAt ? { modifiedAt } : {} });
27374
+ if (kind === "dir" && level + 1 < depth) {
27375
+ queue.push({ dir: full, prefix: prefix ? `${prefix}/${name}` : name, level: level + 1 });
27376
+ }
27377
+ }
27378
+ }
27379
+ return out;
27380
+ },
27381
+ async stat(path4) {
27382
+ const s = statSync12(path4);
27383
+ return {
27384
+ bytes: s.size,
27385
+ modifiedAt: new Date(s.mtimeMs).toISOString(),
27386
+ kind: s.isDirectory() ? "dir" : s.isFile() ? "file" : "other"
27387
+ };
27388
+ },
27389
+ async readText(path4, maxBytes, tail) {
27390
+ const s = statSync12(path4);
27391
+ const size = s.size;
27392
+ const length = Math.min(size, maxBytes);
27393
+ const start = tail && size > maxBytes ? size - maxBytes : 0;
27394
+ const buffer = Buffer.alloc(length);
27395
+ const handle = openSync5(path4, "r");
27396
+ try {
27397
+ readSync3(handle, buffer, 0, length, start);
27398
+ } finally {
27399
+ closeSync5(handle);
27400
+ }
27401
+ return { text: buffer.toString("utf-8"), truncated: size > length };
27402
+ },
27403
+ env(names) {
27404
+ return Object.fromEntries(names.map((n) => [n, process.env[n] ?? null]));
27405
+ },
27406
+ async exec(command, args) {
27407
+ const spec = toSpawnSpec(command, args);
27408
+ const result = spawnSync(spec.command, spec.args, {
27409
+ encoding: "utf-8",
27410
+ timeout: EXEC_TIMEOUT_MS,
27411
+ maxBuffer: EXEC_OUTPUT_MAX,
27412
+ ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
27413
+ });
27414
+ if (result.error)
27415
+ throw result.error;
27416
+ return {
27417
+ exitCode: result.status ?? -1,
27418
+ stdout: truncate5(result.stdout ?? "", EXEC_OUTPUT_MAX).text,
27419
+ stderr: truncate5(result.stderr ?? "", 8 * 1024).text
27420
+ };
27421
+ },
27422
+ now: () => Date.now()
27423
+ };
27424
+ }
27425
+
27426
+ // src/commands/debug.ts
27427
+ init_transcript_sources();
27428
+ init_which();
27429
+ await init_detect();
27430
+ function defaultOutputPath(now) {
27431
+ const stamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
27432
+ return join58(homedir35(), ".runwork", "debug", `runwork-debug-${stamp}.jsonl`);
27433
+ }
27434
+ async function cliClaims() {
27435
+ const records = [];
27436
+ records.push({
27437
+ type: "claim",
27438
+ id: "producer.process",
27439
+ producer: "cli",
27440
+ value: {
27441
+ cliVersion: VERSION,
27442
+ execPath: process.execPath,
27443
+ argv0: process.argv[1] ?? null,
27444
+ nodeVersion: process.version,
27445
+ platform: platform7(),
27446
+ osType: osType(),
27447
+ release: release(),
27448
+ arch: arch(),
27449
+ cwd: process.cwd()
27450
+ }
27451
+ });
27452
+ records.push({
27453
+ type: "claim",
27454
+ id: "cli.path-resolution",
27455
+ producer: "cli",
27456
+ derivedFrom: ["binaries.which", "binaries.where", "env"],
27457
+ value: { pathResolved: whichBinary("runwork") }
27458
+ });
27459
+ const adapters = await detectAgents();
27460
+ records.push({
27461
+ type: "claim",
27462
+ id: "agents.detected",
27463
+ producer: "cli",
27464
+ derivedFrom: ["transcripts.claude-code", "transcripts.codex", "transcripts.gemini", "transcripts.cowork"],
27465
+ value: adapters.map((adapter2) => {
27466
+ const sources = adapter2.transcriptSources?.() ?? [];
27467
+ return {
27468
+ slug: adapter2.slug,
27469
+ name: adapter2.name,
27470
+ hasListing: typeof adapter2.listSessions === "function",
27471
+ hasDigests: typeof adapter2.readSessionDigests === "function",
27472
+ transcriptSources: sources.map((s) => s.path),
27473
+ readable: sources.length > 0 ? summarizeTranscriptSources(sources) : null
27474
+ };
27475
+ })
27476
+ });
27477
+ return records;
27478
+ }
27479
+ var debugCommand = new Command35("debug").description("Diagnostics for support");
27480
+ debugCommand.command("capture").description("Collect a diagnostic bundle to a file you can send to support").option("--out <path>", "Where to write the bundle (default: ~/.runwork/debug/)").option("--send", "Also send the bundle to Runwork support (requires sign-in)").option("--note <text>", "What went wrong, in your words. Included with --send").action(async (opts, command) => {
27481
+ const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
27482
+ const outputPath = typeof opts.out === "string" && opts.out ? opts.out : defaultOutputPath(new Date);
27483
+ const cliPath = whichBinary("runwork");
27484
+ const plan = buildCapturePlan({
27485
+ platform: platform7(),
27486
+ homeDir: homedir35(),
27487
+ appData: process.env.APPDATA ?? null,
27488
+ cliPath
27489
+ });
27490
+ if (!json) {
27491
+ console.log(bold(`
27492
+ Collecting diagnostics`));
27493
+ console.log(dim(` ${plan.length} checks, writing to ${outputPath}`));
27494
+ }
27495
+ const port = createNodeCapturePort(outputPath);
27496
+ let stepsRun = 0;
27497
+ try {
27498
+ ({ stepsRun } = await runCapture(port, "cli", plan, { claims: cliClaims }));
27499
+ } finally {
27500
+ port.close();
27501
+ }
27502
+ const sent = opts.send === true ? await sendBundle(outputPath, typeof opts.note === "string" ? opts.note : undefined) : null;
27503
+ if (json) {
27504
+ jsonOut({ path: outputPath, stepsPlanned: plan.length, stepsRun, ...sent ? { sent } : {} });
27505
+ return;
27506
+ }
27507
+ const counted = stepsRun === plan.length ? `${stepsRun}` : `${stepsRun} of ${plan.length}`;
27508
+ console.log(green(`
27509
+ Done. ${counted} checks recorded.`));
27510
+ console.log(`
27511
+ ${cyan(outputPath)}
27512
+ `);
27513
+ if (!cliPath) {
27514
+ console.log(yellow("Note: no `runwork` was found on PATH, so the doctor report is not included.\n"));
27515
+ }
27516
+ if (!sent) {
27517
+ console.log(dim("Send that file to support. It contains no credentials and no conversation text."));
27518
+ console.log(dim("Or re-run with --send to upload it."));
27519
+ return;
27520
+ }
27521
+ if (sent.ok) {
27522
+ console.log(green(`Sent to Runwork support (report ${sent.id}).`));
27523
+ console.log(dim(`It is kept until ${sent.expiresAt?.slice(0, 10)} and then deleted.`));
27524
+ } else {
27525
+ console.log(yellow(`Could not send: ${sent.error}`));
27526
+ console.log(dim("The file above is saved and can be sent to support by hand."));
27527
+ }
27528
+ });
27529
+ async function sendBundle(path4, note) {
27530
+ try {
27531
+ const content = readFileSync48(path4, "utf-8");
27532
+ const summary = summarizeBundle(content);
27533
+ const machineId = getMachineId();
27534
+ if (!machineId)
27535
+ return { ok: false, error: "could not identify this machine" };
27536
+ const credentials = getCredentials();
27537
+ if (!credentials)
27538
+ return { ok: false, error: "not signed in; run `runwork login`" };
27539
+ const api = new ApiClient(credentials);
27540
+ const workspace = await resolveWorkspace2(api);
27541
+ const result = await api.createDebugReport(workspace.workspaceId, {
27542
+ machineId,
27543
+ producer: "cli",
27544
+ content,
27545
+ sha256: await sha256Hex(content),
27546
+ formatVersion: summary.formatVersion,
27547
+ ...note ? { note } : {},
27548
+ context: {
27549
+ cliVersion: VERSION,
27550
+ platform: platform7(),
27551
+ release: release(),
27552
+ stepsPlanned: summary.stepsPlanned,
27553
+ stepsRun: summary.stepsRun,
27554
+ redactions: summary.redactions,
27555
+ withheld: summary.withheld,
27556
+ complete: summary.complete
27557
+ }
27558
+ });
27559
+ return { ok: true, id: result.id, expiresAt: result.expiresAt };
27560
+ } catch (err) {
27561
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
27562
+ }
27563
+ }
27564
+
26509
27565
  // src/commands/share-convo.ts
26510
27566
  init_store();
26511
27567
  init_client();
26512
27568
  init_resolve();
26513
- import { Command as Command35 } from "commander";
26514
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync33, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
26515
- import { join as join56 } from "path";
27569
+ import { Command as Command36 } from "commander";
27570
+ import { readFileSync as readFileSync49, writeFileSync as writeFileSync33, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
27571
+ import { join as join59 } from "path";
26516
27572
  import { tmpdir as tmpdir4 } from "os";
26517
27573
  import { createHash as createHash6 } from "crypto";
26518
27574
 
@@ -26634,13 +27690,13 @@ function resolveLocalSessionShare(opts, conversation) {
26634
27690
  process.exit(1);
26635
27691
  }
26636
27692
  const title = opts.title ?? conversation.title ?? conversation.project;
26637
- const markdown = renderTranscriptMarkdown(readFileSync48(conversation.transcriptPath, "utf8"), family, title);
27693
+ const markdown = renderTranscriptMarkdown(readFileSync49(conversation.transcriptPath, "utf8"), family, title);
26638
27694
  if (!markdown) {
26639
27695
  console.error("Error: this conversation has no shareable content.");
26640
27696
  process.exit(1);
26641
27697
  }
26642
- const tempDir = mkdtempSync4(join56(tmpdir4(), "runwork-share-"));
26643
- const transcriptFile = join56(tempDir, "transcript.md");
27698
+ const tempDir = mkdtempSync4(join59(tmpdir4(), "runwork-share-"));
27699
+ const transcriptFile = join59(tempDir, "transcript.md");
26644
27700
  writeFileSync33(transcriptFile, markdown);
26645
27701
  opts.transcriptFile = transcriptFile;
26646
27702
  opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
@@ -26657,7 +27713,7 @@ function nativeBundleFormatForAgent(slug) {
26657
27713
  return "codex-rollout";
26658
27714
  return null;
26659
27715
  }
26660
- function sha256Hex(content) {
27716
+ function sha256Hex2(content) {
26661
27717
  return createHash6("sha256").update(content, "utf8").digest("hex");
26662
27718
  }
26663
27719
  function utf8ByteLength(content) {
@@ -26699,13 +27755,13 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26699
27755
  const credentials = requireAuth();
26700
27756
  const client = new ApiClient(credentials);
26701
27757
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
26702
- const transcriptContent = readFileSync48(opts.transcriptFile, "utf8");
27758
+ const transcriptContent = readFileSync49(opts.transcriptFile, "utf8");
26703
27759
  const bundles = [
26704
27760
  {
26705
27761
  format: "transcript",
26706
27762
  content: transcriptContent,
26707
27763
  sizeBytes: utf8ByteLength(transcriptContent),
26708
- sha256: sha256Hex(transcriptContent)
27764
+ sha256: sha256Hex2(transcriptContent)
26709
27765
  }
26710
27766
  ];
26711
27767
  const detected = detectCurrentAgent();
@@ -26724,12 +27780,12 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26724
27780
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
26725
27781
  if (nativeFormat) {
26726
27782
  try {
26727
- const content = readFileSync48(nativeFilePath, "utf8");
27783
+ const content = readFileSync49(nativeFilePath, "utf8");
26728
27784
  bundles.push({
26729
27785
  format: nativeFormat,
26730
27786
  content,
26731
27787
  sizeBytes: utf8ByteLength(content),
26732
- sha256: sha256Hex(content)
27788
+ sha256: sha256Hex2(content)
26733
27789
  });
26734
27790
  } catch (err) {
26735
27791
  console.error(`Warning: could not read native file ${nativeFilePath}: ${err instanceof Error ? err.message : err}`);
@@ -26740,7 +27796,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26740
27796
  let metadata = {};
26741
27797
  if (opts.metadataFile) {
26742
27798
  try {
26743
- metadata = JSON.parse(readFileSync48(opts.metadataFile, "utf8"));
27799
+ metadata = JSON.parse(readFileSync49(opts.metadataFile, "utf8"));
26744
27800
  } catch (err) {
26745
27801
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
26746
27802
  process.exit(1);
@@ -26795,18 +27851,18 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
26795
27851
  process.exit(1);
26796
27852
  }
26797
27853
  }
26798
- var shareConvoCommand = new Command35("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required unless --session)").option("--session <key>", "Share a LOCAL conversation by registry key (transcript rendered mechanically)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo(opts, command, false));
27854
+ var shareConvoCommand = new Command36("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required unless --session)").option("--session <key>", "Share a LOCAL conversation by registry key (transcript rendered mechanically)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo(opts, command, false));
26799
27855
 
26800
27856
  // src/commands/save-convo.ts
26801
- import { Command as Command36 } from "commander";
26802
- var saveConvoCommand = new Command36("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
27857
+ import { Command as Command37 } from "commander";
27858
+ var saveConvoCommand = new Command37("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
26803
27859
 
26804
27860
  // src/commands/inbox.ts
26805
27861
  init_store();
26806
27862
  init_client();
26807
27863
  init_resolve();
26808
- import { Command as Command37 } from "commander";
26809
- var inboxCommand = new Command37("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
27864
+ import { Command as Command38 } from "commander";
27865
+ var inboxCommand = new Command38("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
26810
27866
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26811
27867
  const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
26812
27868
  const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
@@ -26848,10 +27904,10 @@ init_store();
26848
27904
  init_client();
26849
27905
  init_resolve();
26850
27906
  init_registry_data();
26851
- import { Command as Command38 } from "commander";
26852
- import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync29, realpathSync } from "fs";
26853
- import { homedir as homedir34 } from "os";
26854
- import { join as join57 } from "path";
27907
+ import { Command as Command39 } from "commander";
27908
+ import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync30, realpathSync } from "fs";
27909
+ import { homedir as homedir36 } from "os";
27910
+ import { join as join60 } from "path";
26855
27911
  import { spawn as spawn5 } from "child_process";
26856
27912
  init_registry();
26857
27913
  init_which();
@@ -26890,9 +27946,9 @@ function extractCodexUuid(rolloutContent) {
26890
27946
  }
26891
27947
  function placeClaudeJsonl(uuid, content, recipientCwd) {
26892
27948
  const encoded = encodeClaudeCodeCwd(recipientCwd);
26893
- const projectDir = join57(homedir34(), ".claude", "projects", encoded);
26894
- mkdirSync29(projectDir, { recursive: true });
26895
- const placedAt = join57(projectDir, `${uuid}.jsonl`);
27949
+ const projectDir = join60(homedir36(), ".claude", "projects", encoded);
27950
+ mkdirSync30(projectDir, { recursive: true });
27951
+ const placedAt = join60(projectDir, `${uuid}.jsonl`);
26896
27952
  writeFileSync34(placedAt, content);
26897
27953
  return { placedAt, runFromCwd: recipientCwd };
26898
27954
  }
@@ -26901,10 +27957,10 @@ function placeCodexRollout(uuid, content) {
26901
27957
  const yyyy = String(now.getUTCFullYear());
26902
27958
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
26903
27959
  const dd = String(now.getUTCDate()).padStart(2, "0");
26904
- const dir = join57(homedir34(), ".codex", "sessions", yyyy, mm, dd);
26905
- mkdirSync29(dir, { recursive: true });
27960
+ const dir = join60(homedir36(), ".codex", "sessions", yyyy, mm, dd);
27961
+ mkdirSync30(dir, { recursive: true });
26906
27962
  const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
26907
- const placedAt = join57(dir, `rollout-${ts}-${uuid}.jsonl`);
27963
+ const placedAt = join60(dir, `rollout-${ts}-${uuid}.jsonl`);
26908
27964
  writeFileSync34(placedAt, content);
26909
27965
  return { placedAt };
26910
27966
  }
@@ -26924,7 +27980,15 @@ function isAgentInstalled(agent) {
26924
27980
  }
26925
27981
  return false;
26926
27982
  }
26927
- var resumeCommand2 = new Command38("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <name-or-id>", "Workspace name or ID").action(async (shareId, opts, command) => {
27983
+ function buildResumeCommand(template, uuid, resolvedCli, cliName) {
27984
+ const argv = template.replace(/\{uuid\}/g, uuid).split(" ").filter(Boolean);
27985
+ if (resolvedCli && cliName && argv[0] === cliName) {
27986
+ argv[0] = resolvedCli;
27987
+ }
27988
+ const display = [argv[0]?.includes(" ") ? `"${argv[0]}"` : argv[0], ...argv.slice(1)].filter((part) => Boolean(part)).join(" ");
27989
+ return { argv, display };
27990
+ }
27991
+ var resumeCommand2 = new Command39("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <name-or-id>", "Workspace name or ID").action(async (shareId, opts, command) => {
26928
27992
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26929
27993
  const credentials = requireAuth();
26930
27994
  const client = new ApiClient(credentials);
@@ -27018,12 +28082,8 @@ Bundle placed at: ${placement.placedAt}`);
27018
28082
  return;
27019
28083
  }
27020
28084
  if (cap2.mode === "cli-resume") {
27021
- let cmd = (cap2.cliResumeCommand ?? "").replace(/\{uuid\}/g, nativeUuid);
27022
- const resolvedCli = resolveAgentCliCommand(target.slug);
27023
- const cliName = getAgent(target.slug)?.launch?.cli;
27024
- if (resolvedCli && cliName && resolvedCli !== cliName && cmd.startsWith(`${cliName} `)) {
27025
- cmd = `"${resolvedCli}" ${cmd.slice(cliName.length + 1)}`;
27026
- }
28085
+ const { argv, display } = buildResumeCommand(cap2.cliResumeCommand ?? "", nativeUuid, resolveAgentCliCommand(target.slug), getAgent(target.slug)?.launch?.cli);
28086
+ const cmd = display;
27027
28087
  const detected = detectCurrentAgent();
27028
28088
  const insideSameAgent = detected && detected.slug === target.slug;
27029
28089
  const shouldPrintOnly = opts.dryRun || insideSameAgent;
@@ -27055,10 +28115,11 @@ Or ask the assistant to continue the conversation in THIS session by ` + `fetchi
27055
28115
  }
27056
28116
  return;
27057
28117
  }
27058
- const parts = cmd.split(" ");
27059
- const child = spawn5(parts[0], parts.slice(1), {
28118
+ const spec = toSpawnSpec(argv[0], argv.slice(1));
28119
+ const child = spawn5(spec.command, spec.args, {
27060
28120
  cwd: placement.runFromCwd ?? recipientCwd,
27061
- stdio: "inherit"
28121
+ stdio: "inherit",
28122
+ ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
27062
28123
  });
27063
28124
  child.on("exit", (code) => {
27064
28125
  process.exit(code ?? 0);
@@ -27154,7 +28215,7 @@ process.on("uncaughtException", (err) => {
27154
28215
  console.error(`Uncaught exception: ${formatError(err)}`);
27155
28216
  process.exit(1);
27156
28217
  });
27157
- var program = new Command39;
28218
+ var program = new Command40;
27158
28219
  program.name("runwork").description("Runwork CLI - local development for Runwork apps").version(VERSION).option("--json", "Output as JSON (auto-enabled when stdout is not a TTY)");
27159
28220
  program.addCommand(infoCommand);
27160
28221
  program.addCommand(loginCommand);
@@ -27187,6 +28248,7 @@ program.addCommand(appsCommand);
27187
28248
  program.addCommand(membersCommand);
27188
28249
  program.addCommand(apiCommand);
27189
28250
  program.addCommand(doctorCommand);
28251
+ program.addCommand(debugCommand);
27190
28252
  program.addCommand(shareConvoCommand);
27191
28253
  program.addCommand(saveConvoCommand);
27192
28254
  program.addCommand(inboxCommand);