runwork 0.25.2 → 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
@@ -61,7 +61,46 @@ import {
61
61
  createInflate,
62
62
  createInflateRaw
63
63
  } from "node:zlib";
64
- function pickTransport() {
64
+ function firstEnv(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;
70
+ const value = process.env[variable];
71
+ if (value && value.trim() !== "")
72
+ return { variable, value: value.trim() };
73
+ }
74
+ return null;
75
+ }
76
+ function isProxyExempt(hostname) {
77
+ const raw = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
78
+ const host = hostname.toLowerCase();
79
+ for (const entry of raw.split(",")) {
80
+ const rule = entry.trim().toLowerCase().replace(/:\d+$/, "");
81
+ if (!rule)
82
+ continue;
83
+ if (rule === "*")
84
+ return true;
85
+ const bare = rule.startsWith(".") ? rule.slice(1) : rule;
86
+ if (host === bare || host.endsWith(`.${bare}`))
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ function proxyForUrl(url) {
92
+ let parsed;
93
+ try {
94
+ parsed = new URL2(url);
95
+ } catch {
96
+ return null;
97
+ }
98
+ if (isProxyExempt(parsed.hostname))
99
+ return null;
100
+ const schemeVars = parsed.protocol === "http:" ? ["HTTP_PROXY", "http_proxy"] : ["HTTPS_PROXY", "https_proxy"];
101
+ return firstEnv([...schemeVars, "ALL_PROXY", "all_proxy"]);
102
+ }
103
+ function pickTransport(url) {
65
104
  if (transportOverride === "curl")
66
105
  return "curl";
67
106
  if (transportOverride === "node")
@@ -69,10 +108,14 @@ function pickTransport() {
69
108
  const env = (process.env.RUNWORK_HTTP_TRANSPORT || "").toLowerCase().trim();
70
109
  if (env === "curl")
71
110
  return "curl";
111
+ if (env === "node")
112
+ return "node";
113
+ if (url && proxyForUrl(url))
114
+ return "curl";
72
115
  return "node";
73
116
  }
74
117
  async function httpFetch(url, init = {}) {
75
- const transport = pickTransport();
118
+ const transport = pickTransport(url);
76
119
  if (transport === "curl")
77
120
  return curlFetch(url, init);
78
121
  return doRequest(url, init, 0);
@@ -789,6 +832,10 @@ class ApiClient {
789
832
  async reportTelemetry(workspaceId, events) {
790
833
  await this.request(`/api/workspaces/${workspaceId}/team/telemetry`, { method: "POST", body: JSON.stringify({ events }) });
791
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
+ }
792
839
  async createSharedConversation(workspaceId, body) {
793
840
  const res = await this.request(`/api/workspaces/${workspaceId}/shared-conversations`, {
794
841
  method: "POST",
@@ -999,10 +1046,13 @@ var init_workspace_state = __esm(() => {
999
1046
  // src/utils/subprocess.ts
1000
1047
  import {
1001
1048
  execFileSync as cpExecFileSync,
1002
- spawn as cpSpawn
1049
+ spawn as cpSpawn,
1050
+ spawnSync as cpSpawnSync
1003
1051
  } from "child_process";
1004
1052
  var execFileSync = (file, args, options) => {
1005
1053
  return cpExecFileSync(file, args, { windowsHide: true, ...options ?? {} });
1054
+ }, spawnSync = (file, args, options) => {
1055
+ return cpSpawnSync(file, args, { windowsHide: true, ...options ?? {} });
1006
1056
  }, spawn2 = (command, args, options) => {
1007
1057
  return cpSpawn(command, args ?? [], { windowsHide: true, ...options ?? {} });
1008
1058
  };
@@ -3096,13 +3146,56 @@ function bufToStr2(val) {
3096
3146
  return val.toString("utf-8").trim();
3097
3147
  return "";
3098
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
+ }
3099
3191
  function syncWithRemote(cwd) {
3100
3192
  if (!hasCommits(cwd)) {
3101
3193
  return { status: "skipped", pushed: false };
3102
3194
  }
3103
3195
  const dirty = hasTrackedChanges(cwd);
3196
+ let stashSha = null;
3104
3197
  if (dirty) {
3105
- execFileSync("git", ["stash", "push", "-m", "runwork-dev-sync"], { cwd, stdio: "pipe" });
3198
+ stashSha = pushSyncStash(cwd);
3106
3199
  }
3107
3200
  let status = "synced";
3108
3201
  let syncError;
@@ -3143,9 +3236,8 @@ function syncWithRemote(cwd) {
3143
3236
  syncError = extractGitError(fetchErr);
3144
3237
  }
3145
3238
  if (dirty) {
3146
- try {
3147
- execFileSync("git", ["stash", "pop"], { cwd, stdio: "pipe" });
3148
- } catch {
3239
+ const restored = restoreSyncStash(cwd, stashSha);
3240
+ if (restored !== "restored") {
3149
3241
  return { status, pushed: false, error: "stash-conflict", keptUntracked, remoteOverwrote };
3150
3242
  }
3151
3243
  }
@@ -3162,6 +3254,7 @@ function syncWithRemote(cwd) {
3162
3254
  }
3163
3255
  return { status, pushed, pushError, keptUntracked, remoteOverwrote };
3164
3256
  }
3257
+ var SYNC_STASH_TAG = "runwork-dev-sync";
3165
3258
  var init_sync = __esm(() => {
3166
3259
  init_subprocess();
3167
3260
  init_manifest();
@@ -5066,10 +5159,6 @@ export declare class FileStorageClient {
5066
5159
  */
5067
5160
  export declare function createFileStorageClient(env: Env): FileStorageClient;
5068
5161
  import type { Hono } from 'hono';
5069
- /**
5070
- * Mount file storage routes on the Hono app
5071
- * Provides REST API for R2 bucket operations
5072
- */
5073
5162
  export declare function fileStorageRoutes(app: Hono<{
5074
5163
  Bindings: Env;
5075
5164
  }>): void;
@@ -5246,6 +5335,46 @@ export declare function componentRoutes(app: Hono<{
5246
5335
  `,
5247
5336
  "workspace.d.ts": `export { WorkspaceContext, getWorkspaceContext, listWorkspaceEntity, getWorkspaceEntity, createWorkspaceEntity, updateWorkspaceEntity, deleteWorkspaceEntity, callAppEndpoint, initializeWorkspace, } from './core-workspace';
5248
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;
5249
5378
  `,
5250
5379
  "core-scheduler.d.ts": `/**
5251
5380
  * Core Scheduled Jobs Framework
@@ -5897,8 +6026,6 @@ export type Doc<T> = {
5897
6026
  * - Security validation on all field names
5898
6027
  */
5899
6028
  export declare class EntityDO extends DurableObject<Env> {
5900
- ctx: DurableObjectState;
5901
- env: Env;
5902
6029
  private _tableReady;
5903
6030
  private _migrationDone;
5904
6031
  constructor(ctx: DurableObjectState, env: Env);
@@ -6903,13 +7030,6 @@ export declare function toChannelName(appName: string): string;
6903
7030
  * these entries in the observability timeline.
6904
7031
  */
6905
7032
  export declare function flog(level: 'info' | 'warn' | 'error', system: string, message: string, data?: Record<string, unknown>): void;
6906
- /**
6907
- * Emit an event to the workspace unified event stream.
6908
- * Fire-and-forget: uses ctx.waitUntil so it doesn't block the response.
6909
- * Silently skips if workspace env vars are not configured (standalone mode).
6910
- *
6911
- * Automatically routes events to a channel derived from APP_NAME when available.
6912
- */
6913
7033
  export declare function emitEvent(ctx: WaitUntilContext, env: EventEnv, event: EmitEventParams): void;
6914
7034
  export {};
6915
7035
  `,
@@ -6984,7 +7104,109 @@ export declare const isStr: (s: unknown) => s is string;
6984
7104
  * that would cause structured clone to fail in ctx.storage.put().
6985
7105
  */
6986
7106
  export declare function safeClone<T>(value: T, fallback?: T): T;
6987
- 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>;
6988
7210
  /**
6989
7211
  * Workspace API fetch - routes workspace service requests through WorkspaceObject DO
6990
7212
  * for production WfP workers, avoiding 522 recursive invocation errors.
@@ -6997,7 +7219,7 @@ export declare function platformFetch(env: Env, url: string | URL, init?: Reques
6997
7219
  * @param path - Workspace API sub-path (e.g., '/ingest-event')
6998
7220
  * @param init - Standard fetch options
6999
7221
  */
7000
- 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>;
7001
7223
  `,
7002
7224
  "workflows.d.ts": `import type { WorkflowDefinition } from './core-workflow-types';
7003
7225
  /**
@@ -7192,7 +7414,7 @@ export declare class BaseAgent extends AIChatAgent<Env> {
7192
7414
  * - Types are defined in core-workflow-types.ts
7193
7415
  * - Native DO-based implementation is in core-workflow-instance.ts and core-workflow-coordinator.ts
7194
7416
  */
7195
- import type { Env } from './core-utils';
7417
+ import { type Env } from './core-utils';
7196
7418
  export type { WorkflowResult, WorkflowStatus, WorkflowInstanceInfo, WorkflowDefinition, WorkflowContext, WorkflowStepUtilities, StepOptions, WaitEventOptions, WorkflowLogger, WorkflowState, WorkflowStatusResponse, } from './core-workflow-types';
7197
7419
  import type { WorkflowInstanceInfo } from './core-workflow-types';
7198
7420
  /**
@@ -7741,8 +7963,8 @@ export declare const integrationApiSchema: z.ZodObject<{
7741
7963
  endpoint: z.ZodString;
7742
7964
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7743
7965
  }, "strip", z.ZodTypeAny, {
7744
- method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7745
7966
  endpoint: string;
7967
+ method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7746
7968
  data?: Record<string, unknown> | undefined;
7747
7969
  }, {
7748
7970
  endpoint: string;
@@ -7983,7 +8205,7 @@ function createKeyboardListener() {
7983
8205
  }
7984
8206
 
7985
8207
  // src/generated/version.ts
7986
- var VERSION = "0.25.2";
8208
+ var VERSION = "0.26.0";
7987
8209
 
7988
8210
  // src/commands/dev.ts
7989
8211
  var exports_dev = {};
@@ -8064,6 +8286,7 @@ async function execDev(options) {
8064
8286
  await ensureGitCredentialHelper(creds.baseUrl);
8065
8287
  }
8066
8288
  ensureRunworkRemote(cwd, client.getGitRemoteUrl(config.workspaceId, config.appId));
8289
+ let editsUnprotected = false;
8067
8290
  const oldManifest = await loadManifest(cwd);
8068
8291
  if (oldManifest) {
8069
8292
  const userEdits = await detectUserEdits(cwd, oldManifest);
@@ -8087,9 +8310,22 @@ async function execDev(options) {
8087
8310
  execFileSync("git", ["add", "--", ...userEdits], { stdio: "pipe" });
8088
8311
  execFileSync("git", ["commit", "-m", "sync: user edits"], { stdio: "pipe" });
8089
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
+ }
8090
8326
  }
8091
8327
  }
8092
- if (!noSync) {
8328
+ if (!noSync && !editsUnprotected) {
8093
8329
  if (useJson) {
8094
8330
  jsonLine({ event: "startup", phase: "template_update", timestamp: ts() });
8095
8331
  } else {
@@ -8912,6 +9148,53 @@ var init_dev = __esm(() => {
8912
9148
  devCommand.addCommand(devAttachCommand);
8913
9149
  });
8914
9150
 
9151
+ // src/agents/detection-probes.ts
9152
+ function powershellQuote(value) {
9153
+ return `'${value.replace(/'/g, "''")}'`;
9154
+ }
9155
+ function isValidBundleId(id) {
9156
+ return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
9157
+ }
9158
+ function macosBundleIdProbeScript(id) {
9159
+ return `p=$(mdfind "kMDItemCFBundleIdentifier == '${id}'" 2>/dev/null | head -1); ` + `if [ -n "$p" ]; then exit 0; fi; ` + `for a in /Applications/*.app "$HOME"/Applications/*.app; do ` + `[ -e "$a" ] || continue; ` + `if [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$a/Contents/Info.plist" 2>/dev/null)" = "${id}" ]; then exit 0; fi; ` + `done; exit 1`;
9160
+ }
9161
+ function appxPackageProbeScript(pkg) {
9162
+ return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
9163
+ }
9164
+ function startAppProbeScript(pattern) {
9165
+ const p = powershellQuote(pattern);
9166
+ return `$a = Get-StartApps -ErrorAction SilentlyContinue | Where-Object { $_.Name -like ${p} -or $_.AppID -like ${p} } | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
9167
+ }
9168
+ function isCommandNotFoundExit(code) {
9169
+ return typeof code === "number" && COMMAND_NOT_FOUND_EXIT_CODES.includes(code);
9170
+ }
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;
9188
+ var init_detection_probes = __esm(() => {
9189
+ WINDOWS_PATH_REFRESH = "try { $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + " + "[Environment]::GetEnvironmentVariable('Path','User') + ';' + $env:Path } " + `catch { Write-Output '${PATH_REFRESH_FAILED_MARKER}' }; `;
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
+ };
9196
+ });
9197
+
8915
9198
  // src/utils/which.ts
8916
9199
  import { platform } from "os";
8917
9200
  import { isAbsolute } from "path";
@@ -8932,11 +9215,25 @@ function whichAllLines(name) {
8932
9215
  return [];
8933
9216
  }
8934
9217
  }
8935
- function isBinaryRunnable(binary) {
9218
+ function classifyProbeError(err) {
9219
+ const e = err;
9220
+ if (!e || typeof e !== "object")
9221
+ return "spawn-error";
9222
+ if (e.signal || e.code === "ETIMEDOUT")
9223
+ return "timeout";
9224
+ if (e.code === "ENOENT")
9225
+ return "not-found";
9226
+ if (isCommandNotFoundExit(e.status))
9227
+ return "not-found";
9228
+ if (typeof e.status === "number")
9229
+ return "exit-nonzero";
9230
+ return "spawn-error";
9231
+ }
9232
+ function probeBinaryRunnable(binary) {
8936
9233
  const cached = runnableCache.get(binary);
8937
9234
  if (cached !== undefined)
8938
9235
  return cached;
8939
- let ok = false;
9236
+ let probe;
8940
9237
  try {
8941
9238
  const spec = toSpawnSpec(binary, ["--version"]);
8942
9239
  execFileSync(spec.command, spec.args, {
@@ -8944,12 +9241,16 @@ function isBinaryRunnable(binary) {
8944
9241
  timeout: RUNNABLE_PROBE_TIMEOUT_MS,
8945
9242
  ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
8946
9243
  });
8947
- ok = true;
8948
- } catch {
8949
- ok = false;
9244
+ probe = { runnable: true, reason: "ran" };
9245
+ } catch (err) {
9246
+ probe = { runnable: false, reason: classifyProbeError(err) };
8950
9247
  }
8951
- runnableCache.set(binary, ok);
8952
- return ok;
9248
+ if (probe.reason !== "timeout")
9249
+ runnableCache.set(binary, probe);
9250
+ return probe;
9251
+ }
9252
+ function isBinaryRunnable(binary) {
9253
+ return probeBinaryRunnable(binary).runnable;
8953
9254
  }
8954
9255
  function toSpawnSpec(binary, args) {
8955
9256
  if (isAbsolute(binary)) {
@@ -8997,6 +9298,7 @@ function isAppRunning(appName) {
8997
9298
  var RUNNABLE_PROBE_TIMEOUT_MS = 1e4, runnableCache;
8998
9299
  var init_which = __esm(() => {
8999
9300
  init_subprocess();
9301
+ init_detection_probes();
9000
9302
  runnableCache = new Map;
9001
9303
  });
9002
9304
 
@@ -9393,7 +9695,7 @@ var init_registry_data = __esm(() => {
9393
9695
  {
9394
9696
  method: "path",
9395
9697
  target: {
9396
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9698
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9397
9699
  linux: ".config/Claude/claude_desktop_config.json"
9398
9700
  }
9399
9701
  }
@@ -9406,7 +9708,7 @@ var init_registry_data = __esm(() => {
9406
9708
  skillsPaths: { global: ".claude/skills", project: ".claude/skills" },
9407
9709
  mcpConfigPath: {
9408
9710
  macos: "Library/Application Support/Claude/claude_desktop_config.json",
9409
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9711
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9410
9712
  linux: ".config/Claude/claude_desktop_config.json"
9411
9713
  },
9412
9714
  mcpConfigKey: "mcpServers",
@@ -9716,8 +10018,8 @@ var init_registry_data = __esm(() => {
9716
10018
  target: [
9717
10019
  { method: "binary", target: "windsurf" },
9718
10020
  { method: "path", target: { macos: "/Applications/Windsurf.app" } },
9719
- { method: "path", target: { windows: "AppData/Local/Programs/Windsurf" } },
9720
- { method: "path", target: { windows: "C:\\Program Files\\Windsurf" } }
10021
+ { method: "path", target: { windows: "%LOCALAPPDATA%/Programs/Windsurf" } },
10022
+ { method: "path", target: { windows: "%ProgramFiles%/Windsurf" } }
9721
10023
  ]
9722
10024
  },
9723
10025
  launch: { app: { macos: "Windsurf", windows: "Windsurf" } },
@@ -9797,7 +10099,14 @@ var init_registry_data = __esm(() => {
9797
10099
  name: "GitHub Copilot (VS Code)",
9798
10100
  description: "GitHub's AI pair programmer in VS Code",
9799
10101
  category: "extension",
9800
- 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
+ },
9801
10110
  launch: { app: { macos: "Visual Studio Code", windows: "Code" }, cli: "code" },
9802
10111
  logo: "vscode",
9803
10112
  downloadUrl: "https://marketplace.visualstudio.com/items?itemName=GitHub.copilot",
@@ -9820,7 +10129,7 @@ var init_registry_data = __esm(() => {
9820
10129
  name: "GitHub Copilot CLI",
9821
10130
  description: "GitHub Copilot in the terminal",
9822
10131
  category: "cli",
9823
- detection: { method: "binary", target: "gh" },
10132
+ detection: { method: "binary", target: "copilot" },
9824
10133
  logo: "vscode",
9825
10134
  skillsPaths: { global: ".copilot/skills", project: ".github/skills" },
9826
10135
  mcpConfigPath: ".copilot/mcp-config.json",
@@ -9841,7 +10150,7 @@ var init_registry_data = __esm(() => {
9841
10150
  { slug: "droid", name: "Droid", aliases: ["Droid (Factory AI)"], description: "Factory AI's coding agent", category: "cli", detection: { method: "binary", target: "droid" }, skillsPaths: { global: ".factory/skills", project: ".factory/skills" } },
9842
10151
  { slug: "firebender", name: "Firebender", description: "AI coding agent", category: "cli", detection: { method: "binary", target: "firebender" }, skillsPaths: { global: ".firebender/skills", project: ".firebender/skills" } },
9843
10152
  { slug: "goose", name: "Goose", description: "AI coding agent by Block", category: "cli", detection: { method: "binary", target: "goose" }, skillsPaths: { global: ".config/goose/skills", project: ".goose/skills" } },
9844
- { slug: "hermes", name: "Hermes", aliases: ["hermes-agent"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "hermes" }, skillsPaths: { global: ".hermes/skills", project: ".hermes/skills" } },
10153
+ { slug: "hermes", name: "Hermes", aliases: ["hermes-agent"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "hermes" }, launch: { cli: "hermes" }, skillsPaths: { global: ".hermes/skills", project: ".hermes/skills" } },
9845
10154
  { slug: "iflow", name: "iFlow CLI", aliases: ["iflow-cli"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "iflow" }, skillsPaths: { global: ".iflow/skills", project: ".iflow/skills" } },
9846
10155
  { slug: "junie", name: "Junie", description: "JetBrains AI coding agent", category: "ide", detection: { method: "binary", target: "junie" }, skillsPaths: { global: ".junie/skills", project: ".junie/skills" } },
9847
10156
  { slug: "kilocode", name: "Kilo Code", aliases: ["kilo"], description: "AI coding agent", category: "extension", detection: { method: "binary", target: "kilocode" }, skillsPaths: { global: ".kilocode/skills", project: ".kilocode/skills" } },
@@ -11034,11 +11343,33 @@ function readJsonConfig(filePath) {
11034
11343
  return {};
11035
11344
  try {
11036
11345
  return JSON.parse(readFileSync23(filePath, "utf-8"));
11037
- } catch {
11346
+ } catch (err) {
11347
+ console.warn(` [config] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}`);
11038
11348
  return {};
11039
11349
  }
11040
11350
  }
11351
+ function existingContentIsUnparseable(filePath) {
11352
+ if (!existsSync28(filePath))
11353
+ return { bad: false };
11354
+ let raw;
11355
+ try {
11356
+ raw = readFileSync23(filePath, "utf-8");
11357
+ } catch {
11358
+ return { bad: false };
11359
+ }
11360
+ if (raw.trim() === "")
11361
+ return { bad: false };
11362
+ try {
11363
+ JSON.parse(raw);
11364
+ return { bad: false };
11365
+ } catch (cause) {
11366
+ return { bad: true, cause };
11367
+ }
11368
+ }
11041
11369
  function writeJsonConfig(filePath, config) {
11370
+ const { bad, cause } = existingContentIsUnparseable(filePath);
11371
+ if (bad)
11372
+ throw new ConfigParseError(filePath, cause);
11042
11373
  mkdirSync14(dirname7(filePath), { recursive: true });
11043
11374
  writeFileSync14(filePath, JSON.stringify(config, null, 2) + `
11044
11375
  `);
@@ -11082,9 +11413,20 @@ function mergeJsonMcpServers(filePath, servers, topKey) {
11082
11413
  writeJsonConfig(filePath, config);
11083
11414
  return true;
11084
11415
  }
11416
+ var ConfigParseError;
11085
11417
  var init_json_config = __esm(() => {
11086
11418
  init_types();
11087
11419
  init_hash();
11420
+ ConfigParseError = class ConfigParseError extends Error {
11421
+ filePath;
11422
+ cause;
11423
+ constructor(filePath, cause) {
11424
+ super(`${filePath} is not valid JSON, so Runwork left it untouched. ` + `Fix the file (or move it aside) and run the command again.`);
11425
+ this.filePath = filePath;
11426
+ this.cause = cause;
11427
+ this.name = "ConfigParseError";
11428
+ }
11429
+ };
11088
11430
  });
11089
11431
 
11090
11432
  // src/agents/utils/skill-removal.ts
@@ -11639,29 +11981,11 @@ function vlog(...args) {
11639
11981
  }
11640
11982
  var verbose = false;
11641
11983
 
11642
- // src/agents/detection-probes.ts
11643
- function powershellQuote(value) {
11644
- return `'${value.replace(/'/g, "''")}'`;
11645
- }
11646
- function isValidBundleId(id) {
11647
- return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
11648
- }
11649
- function macosBundleIdProbeScript(id) {
11650
- return `p=$(mdfind "kMDItemCFBundleIdentifier == '${id}'" 2>/dev/null | head -1); ` + `if [ -n "$p" ]; then exit 0; fi; ` + `for a in /Applications/*.app "$HOME"/Applications/*.app; do ` + `[ -e "$a" ] || continue; ` + `if [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$a/Contents/Info.plist" 2>/dev/null)" = "${id}" ]; then exit 0; fi; ` + `done; exit 1`;
11651
- }
11652
- function appxPackageProbeScript(pkg) {
11653
- return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
11654
- }
11655
- function startAppProbeScript(pattern) {
11656
- const p = powershellQuote(pattern);
11657
- return `$a = Get-StartApps -ErrorAction SilentlyContinue | Where-Object { $_.Name -like ${p} -or $_.AppID -like ${p} } | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
11658
- }
11659
-
11660
11984
  // src/agents/detection.ts
11661
11985
  import { execFile } from "child_process";
11662
11986
  import { existsSync as existsSync31 } from "fs";
11663
11987
  import { homedir as homedir8, platform as platform3 } from "os";
11664
- import { isAbsolute as isAbsolute3, join as join25 } from "path";
11988
+ import { join as join25 } from "path";
11665
11989
  import { promisify } from "util";
11666
11990
  function isWindows() {
11667
11991
  return platform3() === "win32";
@@ -11682,7 +12006,12 @@ function resolveDetectionPath(target) {
11682
12006
  const resolved = resolvePlatformString(target);
11683
12007
  if (!resolved)
11684
12008
  return null;
11685
- 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;
11686
12015
  }
11687
12016
  function checkPath(target) {
11688
12017
  const absolute = resolveDetectionPath(target);
@@ -11762,11 +12091,74 @@ var init_detection = __esm(() => {
11762
12091
  init_which();
11763
12092
  init_registry_data();
11764
12093
  init_registry();
12094
+ init_detection_probes();
11765
12095
  NOT_DETECTED = { detected: false };
11766
12096
  });
11767
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
+
11768
12160
  // src/agents/claude-code.ts
11769
- 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";
11770
12162
  import { join as join26 } from "path";
11771
12163
  import { homedir as homedir9 } from "os";
11772
12164
  function getPluginJson() {
@@ -11810,6 +12202,7 @@ var init_claude_code = __esm(() => {
11810
12202
  init_instruction_hint();
11811
12203
  init_session_start_hook();
11812
12204
  init_detection();
12205
+ init_transcript_sources();
11813
12206
  ClaudeCodeAdapter = class ClaudeCodeAdapter extends RegistryDetectedAdapter {
11814
12207
  name = "Claude Code";
11815
12208
  slug = "claude-code";
@@ -11986,8 +12379,7 @@ ${instructions}`;
11986
12379
  }
11987
12380
  if (!hadFile && !config.modelPreference && !config.permissionRules)
11988
12381
  return;
11989
- mkdirSync16(join26(settingsPath, ".."), { recursive: true });
11990
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
12382
+ writeJsonConfig(settingsPath, settings);
11991
12383
  }
11992
12384
  async readManagedBlock(_scope) {
11993
12385
  return;
@@ -12034,7 +12426,7 @@ ${instructions}`;
12034
12426
  delete settings.enabledPlugins[key];
12035
12427
  }
12036
12428
  }
12037
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
12429
+ writeJsonConfig(settingsPath, settings);
12038
12430
  } catch {}
12039
12431
  }
12040
12432
  const pluginDir = this.getPluginDir();
@@ -12108,7 +12500,7 @@ ${instructions}`;
12108
12500
  const filePath = join26(cwdPath, file);
12109
12501
  let stat;
12110
12502
  try {
12111
- stat = statSync4(filePath);
12503
+ stat = statSync5(filePath);
12112
12504
  } catch {
12113
12505
  continue;
12114
12506
  }
@@ -12200,9 +12592,15 @@ ${instructions}`;
12200
12592
  return null;
12201
12593
  }
12202
12594
  }
12595
+ transcriptRoot() {
12596
+ return join26(homedir9(), ".claude", "projects");
12597
+ }
12598
+ transcriptSources() {
12599
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
12600
+ }
12203
12601
  async readSessionDigests(sinceISO) {
12204
12602
  try {
12205
- const projectsDir = join26(homedir9(), ".claude", "projects");
12603
+ const projectsDir = this.transcriptRoot();
12206
12604
  if (!existsSync32(projectsDir))
12207
12605
  return null;
12208
12606
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -12227,7 +12625,7 @@ ${instructions}`;
12227
12625
  const filePath = join26(cwdPath, file);
12228
12626
  let stat;
12229
12627
  try {
12230
- stat = statSync4(filePath);
12628
+ stat = statSync5(filePath);
12231
12629
  } catch {
12232
12630
  continue;
12233
12631
  }
@@ -12251,8 +12649,8 @@ ${instructions}`;
12251
12649
  }
12252
12650
  async listSessions(sinceISO) {
12253
12651
  try {
12254
- const projectsDir = join26(homedir9(), ".claude", "projects");
12255
- if (!existsSync32(projectsDir))
12652
+ const projectsDir = this.transcriptRoot();
12653
+ if (!transcriptSourcesReadable(this.transcriptSources()))
12256
12654
  return null;
12257
12655
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
12258
12656
  let cwdEntries;
@@ -12276,7 +12674,7 @@ ${instructions}`;
12276
12674
  const filePath = join26(cwdPath, file);
12277
12675
  let stat;
12278
12676
  try {
12279
- stat = statSync4(filePath);
12677
+ stat = statSync5(filePath);
12280
12678
  } catch {
12281
12679
  continue;
12282
12680
  }
@@ -12314,7 +12712,7 @@ ${instructions}`;
12314
12712
  }
12315
12713
  async readSkillUsage(lastSyncAt) {
12316
12714
  try {
12317
- const projectsDir = join26(homedir9(), ".claude", "projects");
12715
+ const projectsDir = this.transcriptRoot();
12318
12716
  if (!existsSync32(projectsDir))
12319
12717
  return null;
12320
12718
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -12349,7 +12747,7 @@ ${instructions}`;
12349
12747
  const filePath = join26(cwdPath, file);
12350
12748
  let fileStat;
12351
12749
  try {
12352
- fileStat = statSync4(filePath);
12750
+ fileStat = statSync5(filePath);
12353
12751
  } catch {
12354
12752
  continue;
12355
12753
  }
@@ -12605,7 +13003,7 @@ var init_claude_desktop_plugin_tree = __esm(() => {
12605
13003
  });
12606
13004
 
12607
13005
  // src/agents/claude-desktop.ts
12608
- 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";
12609
13007
  import { dirname as dirname9, join as join28 } from "path";
12610
13008
  import { homedir as homedir10, platform as platform4, tmpdir as tmpdir3 } from "os";
12611
13009
  function isRunworkRpmPluginName(name) {
@@ -12764,6 +13162,7 @@ function setCoworkPluginEnabled(pluginsDir, enabled) {
12764
13162
  var PLUGIN_NAME2 = "runwork", PLUGIN_VERSION2 = "1.0.0", PLUGIN_DESCRIPTION = "Skills and tools from your Runwork workspace", PLUGIN_AUTHOR_NAME = "Runwork", ClaudeDesktopAdapter;
12765
13163
  var init_claude_desktop = __esm(() => {
12766
13164
  init_types();
13165
+ init_transcript_sources();
12767
13166
  init_session_digest();
12768
13167
  init_json_config();
12769
13168
  init_instruction_hint();
@@ -12926,7 +13325,7 @@ var init_claude_desktop = __esm(() => {
12926
13325
  prefs.ccdScheduledTasksEnabled = false;
12927
13326
  }
12928
13327
  }
12929
- writeFileSync18(configPath, JSON.stringify(desktopConfig, null, 2));
13328
+ writeJsonConfig(configPath, desktopConfig);
12930
13329
  }
12931
13330
  async cleanup(_scope, _manifest) {
12932
13331
  removeRunworkMcpServers(getMcpConfigPath(), "mcpServers");
@@ -13037,7 +13436,7 @@ var init_claude_desktop = __esm(() => {
13037
13436
  const filePath = join28(projPath, file);
13038
13437
  let stat;
13039
13438
  try {
13040
- stat = statSync5(filePath);
13439
+ stat = statSync6(filePath);
13041
13440
  } catch {
13042
13441
  continue;
13043
13442
  }
@@ -13062,10 +13461,13 @@ var init_claude_desktop = __esm(() => {
13062
13461
  return null;
13063
13462
  }
13064
13463
  }
13464
+ transcriptSources() {
13465
+ return [{ path: getCoworkBaseDir(), kind: "dir" }];
13466
+ }
13065
13467
  async listSessions(sinceISO) {
13066
13468
  try {
13067
13469
  const baseDir = getCoworkBaseDir();
13068
- if (!existsSync34(baseDir))
13470
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13069
13471
  return null;
13070
13472
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
13071
13473
  let accounts;
@@ -13101,7 +13503,7 @@ var init_claude_desktop = __esm(() => {
13101
13503
  const filePath = join28(projPath, file);
13102
13504
  let stat;
13103
13505
  try {
13104
- stat = statSync5(filePath);
13506
+ stat = statSync6(filePath);
13105
13507
  } catch {
13106
13508
  continue;
13107
13509
  }
@@ -13242,7 +13644,7 @@ var init_claude_desktop = __esm(() => {
13242
13644
  continue;
13243
13645
  const orgPath = join28(baseDir, orgDir);
13244
13646
  try {
13245
- if (!statSync5(orgPath).isDirectory())
13647
+ if (!statSync6(orgPath).isDirectory())
13246
13648
  continue;
13247
13649
  } catch {
13248
13650
  continue;
@@ -13252,7 +13654,7 @@ var init_claude_desktop = __esm(() => {
13252
13654
  continue;
13253
13655
  const userPath = join28(orgPath, userDir);
13254
13656
  try {
13255
- if (!statSync5(userPath).isDirectory())
13657
+ if (!statSync6(userPath).isDirectory())
13256
13658
  continue;
13257
13659
  } catch {
13258
13660
  continue;
@@ -13380,6 +13782,7 @@ var init_cursor = __esm(async () => {
13380
13782
  init_skill_removal();
13381
13783
  init_json_config();
13382
13784
  init_detection();
13785
+ init_transcript_sources();
13383
13786
  await init_sqlite();
13384
13787
  CursorAdapter = class CursorAdapter extends RegistryDetectedAdapter {
13385
13788
  name = "Cursor";
@@ -13638,6 +14041,16 @@ ${instructions}`;
13638
14041
  return null;
13639
14042
  }
13640
14043
  }
14044
+ transcriptSources() {
14045
+ return [
14046
+ {
14047
+ path: this.globalStorageDbPath(),
14048
+ kind: "file",
14049
+ tool: "sqlite",
14050
+ available: sqliteAvailable
14051
+ }
14052
+ ];
14053
+ }
13641
14054
  globalStorageDbPath() {
13642
14055
  const os2 = platform5();
13643
14056
  if (os2 === "darwin") {
@@ -13665,7 +14078,7 @@ ${instructions}`;
13665
14078
  async listSessions(sinceISO) {
13666
14079
  try {
13667
14080
  const dbPath = this.globalStorageDbPath();
13668
- if (!existsSync35(dbPath) || !sqliteAvailable())
14081
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13669
14082
  return null;
13670
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:%'`);
13671
14084
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -13794,7 +14207,7 @@ ${hint}`;
13794
14207
  });
13795
14208
 
13796
14209
  // src/agents/codex.ts
13797
- 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";
13798
14211
  import { basename as basename3, join as join31 } from "path";
13799
14212
  import { homedir as homedir13 } from "os";
13800
14213
  import { parse, stringify } from "smol-toml";
@@ -13804,6 +14217,7 @@ function isRunworkManagedCodexKey(key) {
13804
14217
  var CodexAdapter, CodexDesktopAdapter;
13805
14218
  var init_codex = __esm(async () => {
13806
14219
  init_types();
14220
+ init_transcript_sources();
13807
14221
  init_session_digest();
13808
14222
  init_session_listing();
13809
14223
  init_skill_removal();
@@ -14022,6 +14436,12 @@ var init_codex = __esm(async () => {
14022
14436
  return null;
14023
14437
  }
14024
14438
  }
14439
+ transcriptRoot() {
14440
+ return join31(homedir13(), ".codex", "sessions");
14441
+ }
14442
+ transcriptSources() {
14443
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
14444
+ }
14025
14445
  scanRolloutActivity(sinceMs) {
14026
14446
  const days = new Set;
14027
14447
  const result = {
@@ -14032,7 +14452,7 @@ var init_codex = __esm(async () => {
14032
14452
  latestMs: 0,
14033
14453
  activeDays: []
14034
14454
  };
14035
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14455
+ const sessionsDir = this.transcriptRoot();
14036
14456
  if (!existsSync37(sessionsDir))
14037
14457
  return result;
14038
14458
  const files = [];
@@ -14055,7 +14475,7 @@ var init_codex = __esm(async () => {
14055
14475
  for (const file of files) {
14056
14476
  let stat;
14057
14477
  try {
14058
- stat = statSync6(file);
14478
+ stat = statSync7(file);
14059
14479
  } catch {
14060
14480
  continue;
14061
14481
  }
@@ -14122,8 +14542,8 @@ var init_codex = __esm(async () => {
14122
14542
  }
14123
14543
  async readSessionDigests(sinceISO) {
14124
14544
  try {
14125
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14126
- if (!existsSync37(sessionsDir))
14545
+ const sessionsDir = this.transcriptRoot();
14546
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14127
14547
  return null;
14128
14548
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14129
14549
  const files = [];
@@ -14147,7 +14567,7 @@ var init_codex = __esm(async () => {
14147
14567
  for (const file of files) {
14148
14568
  let stat;
14149
14569
  try {
14150
- stat = statSync6(file);
14570
+ stat = statSync7(file);
14151
14571
  } catch {
14152
14572
  continue;
14153
14573
  }
@@ -14170,8 +14590,8 @@ var init_codex = __esm(async () => {
14170
14590
  }
14171
14591
  async listSessions(sinceISO) {
14172
14592
  try {
14173
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14174
- if (!existsSync37(sessionsDir))
14593
+ const sessionsDir = this.transcriptRoot();
14594
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14175
14595
  return null;
14176
14596
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14177
14597
  const files = [];
@@ -14195,7 +14615,7 @@ var init_codex = __esm(async () => {
14195
14615
  for (const file of files) {
14196
14616
  let stat;
14197
14617
  try {
14198
- stat = statSync6(file);
14618
+ stat = statSync7(file);
14199
14619
  } catch {
14200
14620
  continue;
14201
14621
  }
@@ -14234,7 +14654,7 @@ var init_codex = __esm(async () => {
14234
14654
  }
14235
14655
  async readSkillUsage(lastSyncAt) {
14236
14656
  try {
14237
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14657
+ const sessionsDir = this.transcriptRoot();
14238
14658
  if (!existsSync37(sessionsDir))
14239
14659
  return null;
14240
14660
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14251,7 +14671,7 @@ var init_codex = __esm(async () => {
14251
14671
  if (entry.endsWith(".jsonl")) {
14252
14672
  let fileStat;
14253
14673
  try {
14254
- fileStat = statSync6(fullPath);
14674
+ fileStat = statSync7(fullPath);
14255
14675
  } catch {
14256
14676
  continue;
14257
14677
  }
@@ -14260,7 +14680,7 @@ var init_codex = __esm(async () => {
14260
14680
  this.parseRolloutForSkills(fullPath, sinceMs, skillCounts);
14261
14681
  } else {
14262
14682
  try {
14263
- if (statSync6(fullPath).isDirectory())
14683
+ if (statSync7(fullPath).isDirectory())
14264
14684
  walkDir2(fullPath);
14265
14685
  } catch {
14266
14686
  continue;
@@ -14381,6 +14801,12 @@ var init_codex = __esm(async () => {
14381
14801
  async listSessions() {
14382
14802
  return null;
14383
14803
  }
14804
+ async readSessionDigests() {
14805
+ return null;
14806
+ }
14807
+ transcriptSources() {
14808
+ return [];
14809
+ }
14384
14810
  };
14385
14811
  });
14386
14812
 
@@ -14468,7 +14894,7 @@ var init_cline = __esm(() => {
14468
14894
  }
14469
14895
  }
14470
14896
  mkdirSync22(join32(globalStatePath, ".."), { recursive: true });
14471
- writeFileSync22(globalStatePath, JSON.stringify(state, null, 2));
14897
+ writeJsonConfig(globalStatePath, state);
14472
14898
  }
14473
14899
  async removeSkills(skillFilenames, scope) {
14474
14900
  if (scope !== "project")
@@ -14503,7 +14929,7 @@ var init_cline = __esm(() => {
14503
14929
  });
14504
14930
 
14505
14931
  // src/agents/gemini.ts
14506
- 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";
14507
14933
  import { basename as basename4, join as join33 } from "path";
14508
14934
  import { homedir as homedir15 } from "os";
14509
14935
  var GeminiAdapter;
@@ -14513,6 +14939,7 @@ var init_gemini = __esm(() => {
14513
14939
  init_skill_removal();
14514
14940
  init_trash();
14515
14941
  init_types();
14942
+ init_transcript_sources();
14516
14943
  init_instruction_hint();
14517
14944
  init_json_config();
14518
14945
  init_detection();
@@ -14581,11 +15008,11 @@ var init_gemini = __esm(() => {
14581
15008
  settings.tools.exclude = config.permissionRules.deny;
14582
15009
  }
14583
15010
  mkdirSync23(join33(settingsPath, ".."), { recursive: true });
14584
- writeFileSync23(settingsPath, JSON.stringify(settings, null, 2));
15011
+ writeJsonConfig(settingsPath, settings);
14585
15012
  }
14586
15013
  async readUsageStats(lastSyncAt) {
14587
15014
  try {
14588
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15015
+ const tmpDir = this.transcriptRoot();
14589
15016
  if (!existsSync39(tmpDir))
14590
15017
  return null;
14591
15018
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14615,7 +15042,7 @@ var init_gemini = __esm(() => {
14615
15042
  const filePath = join33(chatsDir, file.name);
14616
15043
  let stat;
14617
15044
  try {
14618
- stat = statSync7(filePath);
15045
+ stat = statSync8(filePath);
14619
15046
  } catch {
14620
15047
  continue;
14621
15048
  }
@@ -14663,8 +15090,14 @@ var init_gemini = __esm(() => {
14663
15090
  return null;
14664
15091
  }
14665
15092
  }
15093
+ transcriptRoot() {
15094
+ return join33(homedir15(), ".gemini", "tmp");
15095
+ }
15096
+ transcriptSources() {
15097
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
15098
+ }
14666
15099
  *chatFiles(sinceMs) {
14667
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15100
+ const tmpDir = this.transcriptRoot();
14668
15101
  if (!existsSync39(tmpDir))
14669
15102
  return;
14670
15103
  let projects;
@@ -14689,7 +15122,7 @@ var init_gemini = __esm(() => {
14689
15122
  const filePath = join33(chatsDir, file);
14690
15123
  let stat;
14691
15124
  try {
14692
- stat = statSync7(filePath);
15125
+ stat = statSync8(filePath);
14693
15126
  } catch {
14694
15127
  continue;
14695
15128
  }
@@ -14701,6 +15134,8 @@ var init_gemini = __esm(() => {
14701
15134
  }
14702
15135
  async listSessions(sinceISO) {
14703
15136
  try {
15137
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15138
+ return null;
14704
15139
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14705
15140
  const sessions = [];
14706
15141
  for (const { filePath, project, mtimeMs } of this.chatFiles(sinceMs)) {
@@ -14740,6 +15175,8 @@ var init_gemini = __esm(() => {
14740
15175
  }
14741
15176
  async readSessionDigests(sinceISO) {
14742
15177
  try {
15178
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15179
+ return null;
14743
15180
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14744
15181
  const digests = [];
14745
15182
  for (const { filePath, project } of this.chatFiles(sinceMs)) {
@@ -14775,7 +15212,7 @@ var init_gemini = __esm(() => {
14775
15212
  });
14776
15213
 
14777
15214
  // src/agents/generic-adapter.ts
14778
- import { existsSync as existsSync40, mkdirSync as mkdirSync24, rmSync as rmSync12, writeFileSync as writeFileSync24 } from "fs";
15215
+ import { mkdirSync as mkdirSync24, writeFileSync as writeFileSync24 } from "fs";
14779
15216
  import { join as join34 } from "path";
14780
15217
  import { homedir as homedir16 } from "os";
14781
15218
  var GenericAgentAdapter;
@@ -14785,7 +15222,9 @@ var init_generic_adapter = __esm(() => {
14785
15222
  init_json_config();
14786
15223
  init_instruction_hint();
14787
15224
  init_registry();
15225
+ init_detection_probes();
14788
15226
  init_detection();
15227
+ init_trash();
14789
15228
  GenericAgentAdapter = class GenericAgentAdapter extends RegistryDetectedAdapter {
14790
15229
  name;
14791
15230
  slug;
@@ -14808,7 +15247,13 @@ var init_generic_adapter = __esm(() => {
14808
15247
  async writeMcpServers(servers, _scope) {
14809
15248
  if (!this.def.mcpConfigPath)
14810
15249
  return;
14811
- 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;
14812
15257
  if (!filePath)
14813
15258
  return;
14814
15259
  const entries = {};
@@ -14832,12 +15277,7 @@ var init_generic_adapter = __esm(() => {
14832
15277
  return 0;
14833
15278
  for (const skill of skills) {
14834
15279
  if (skill.name !== skill.filename) {
14835
- const oldDir = join34(baseDir, skill.name);
14836
- if (existsSync40(oldDir)) {
14837
- try {
14838
- rmSync12(oldDir, { recursive: true, force: true });
14839
- } catch {}
14840
- }
15280
+ moveToTrash(join34(baseDir, skill.name), `skill renamed to ${skill.filename}`);
14841
15281
  }
14842
15282
  const skillDir = join34(baseDir, skill.filename);
14843
15283
  mkdirSync24(skillDir, { recursive: true });
@@ -14966,6 +15406,26 @@ var init_detect = __esm(async () => {
14966
15406
  ALL_ADAPTERS = buildAllAdapters();
14967
15407
  });
14968
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
+
14969
15429
  // src/utils/insight-id.ts
14970
15430
  import { createHash as createHash3 } from "node:crypto";
14971
15431
  function computeInsightId(userSeed, localKey) {
@@ -15116,7 +15576,7 @@ __export(exports_run_log, {
15116
15576
  RUN_LOG_CAP: () => RUN_LOG_CAP,
15117
15577
  ANALYST_ERROR_MAX_CHARS: () => ANALYST_ERROR_MAX_CHARS
15118
15578
  });
15119
- 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";
15120
15580
  import { dirname as dirname10, join as join37 } from "path";
15121
15581
  import { homedir as homedir19 } from "os";
15122
15582
  function runLogPath() {
@@ -15260,7 +15720,7 @@ function acquireRunLock(nowMs = Date.now()) {
15260
15720
  return true;
15261
15721
  } catch {
15262
15722
  try {
15263
- if (nowMs - statSync8(p).mtimeMs < RUN_STALE_MS)
15723
+ if (nowMs - statSync9(p).mtimeMs < RUN_STALE_MS)
15264
15724
  return false;
15265
15725
  unlinkSync7(p);
15266
15726
  } catch {
@@ -15594,10 +16054,12 @@ function selectAnalyst(requestedSlug) {
15594
16054
  const resolved = resolve3(requested);
15595
16055
  if (!resolved)
15596
16056
  return { candidates: [], broken };
15597
- if (isBinaryRunnable(resolved.command)) {
16057
+ const probe = probeBinaryRunnable(resolved.command);
16058
+ if (probe.runnable) {
15598
16059
  return { chosen: resolved, candidates: [resolved], broken };
15599
16060
  }
15600
- broken.push(requested.binary);
16061
+ if (probe.reason !== "timeout")
16062
+ broken.push(requested.binary);
15601
16063
  return { candidates: [], broken };
15602
16064
  }
15603
16065
  const candidates = [];
@@ -15605,9 +16067,10 @@ function selectAnalyst(requestedSlug) {
15605
16067
  const resolved = resolve3(a);
15606
16068
  if (!resolved)
15607
16069
  continue;
15608
- if (isBinaryRunnable(resolved.command))
16070
+ const probe = probeBinaryRunnable(resolved.command);
16071
+ if (probe.runnable)
15609
16072
  candidates.push(resolved);
15610
- else
16073
+ else if (probe.reason !== "timeout")
15611
16074
  broken.push(a.binary);
15612
16075
  }
15613
16076
  return { chosen: candidates[0], candidates, broken };
@@ -15945,6 +16408,7 @@ var init_reflect = __esm(async () => {
15945
16408
  init_store();
15946
16409
  init_client();
15947
16410
  init_resolve();
16411
+ init_transcript_sources();
15948
16412
  init_session_digest();
15949
16413
  init_insight_id();
15950
16414
  init_insight_store();
@@ -16009,13 +16473,13 @@ var init_reflect = __esm(async () => {
16009
16473
  const seen = new Set;
16010
16474
  for (const adapter2 of adapters) {
16011
16475
  if (!adapter2.readSessionDigests) {
16012
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16476
+ perAgent.push({ agentSlug: adapter2.slug, status: "no-reader", sessions: null });
16013
16477
  continue;
16014
16478
  }
16015
16479
  try {
16016
16480
  const sessions = await adapter2.readSessionDigests(sinceISO);
16017
16481
  if (sessions === null) {
16018
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16482
+ perAgent.push(diagnoseTranscriptRead(adapter2));
16019
16483
  continue;
16020
16484
  }
16021
16485
  let added = 0;
@@ -16027,20 +16491,26 @@ var init_reflect = __esm(async () => {
16027
16491
  digests.push(s);
16028
16492
  added++;
16029
16493
  }
16030
- perAgent.push({ slug: adapter2.slug, sessions: added });
16031
- } catch {
16032
- 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));
16033
16497
  }
16034
16498
  }
16035
16499
  if (!json) {
16036
16500
  console.error(bold(`
16037
16501
  Reflection over the last ${days} days`));
16038
- for (const a of perAgent)
16039
- 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
+ }
16040
16510
  }
16041
16511
  if (digests.length === 0) {
16042
16512
  if (json) {
16043
- jsonOut({ insights: [], reason: "no-sessions" });
16513
+ jsonOut({ insights: [], reason: "no-sessions", perAgent });
16044
16514
  return;
16045
16515
  }
16046
16516
  console.error(yellow(`
@@ -16405,19 +16875,19 @@ async function listLocalConversations(opts = {}) {
16405
16875
  const perAgent = [];
16406
16876
  for (const adapter2 of adapters) {
16407
16877
  if (!adapter2.listSessions) {
16408
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16878
+ perAgent.push({ agentSlug: adapter2.slug, status: "no-reader", sessions: null });
16409
16879
  continue;
16410
16880
  }
16411
16881
  try {
16412
16882
  const sessions = await adapter2.listSessions(sinceISO);
16413
16883
  if (sessions === null) {
16414
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16884
+ perAgent.push(diagnoseTranscriptRead(adapter2));
16415
16885
  continue;
16416
16886
  }
16417
16887
  listings.push(sessions);
16418
- perAgent.push({ slug: adapter2.slug, sessions: sessions.length });
16419
- } catch {
16420
- 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));
16421
16891
  }
16422
16892
  }
16423
16893
  const conversations = mergeSessionListings(listings, {
@@ -16428,6 +16898,7 @@ async function listLocalConversations(opts = {}) {
16428
16898
  }
16429
16899
  var DEFAULT_IDLE_MINUTES = 10;
16430
16900
  var init_conversation_registry = __esm(async () => {
16901
+ init_transcript_sources();
16431
16902
  await init_detect();
16432
16903
  });
16433
16904
 
@@ -17824,7 +18295,7 @@ var init_welcome = __esm(() => {
17824
18295
  });
17825
18296
 
17826
18297
  // src/index.ts
17827
- import { Command as Command39 } from "commander";
18298
+ import { Command as Command40 } from "commander";
17828
18299
 
17829
18300
  // src/commands/login.ts
17830
18301
  init_login_flow();
@@ -18133,7 +18604,18 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
18133
18604
  console.log("Syncing...");
18134
18605
  try {
18135
18606
  commitWorkingTree(cwd, `deploy: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`);
18136
- } 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
+ }
18137
18619
  if (!hasCommits(cwd)) {
18138
18620
  if (useJson) {
18139
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"]));
@@ -20274,9 +20756,14 @@ conversationsCommand.command("list").description("List local conversations acros
20274
20756
  Local conversations, last ${days} day(s)` : `
20275
20757
  Local conversations (all)`));
20276
20758
  for (const a of result.perAgent) {
20277
- if (a.sessions === "unsupported")
20759
+ if (a.status === "no-reader")
20278
20760
  continue;
20279
- 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
+ }
20280
20767
  }
20281
20768
  console.log("");
20282
20769
  const width = process.stdout.columns || 120;
@@ -20293,6 +20780,26 @@ Local conversations (all)`));
20293
20780
  if (result.conversations.length === 0)
20294
20781
  console.log(dim(" none"));
20295
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
+ });
20296
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) => {
20297
20804
  const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
20298
20805
  const days = parseDays(opts.days);
@@ -20356,11 +20863,12 @@ conversationsCommand.command("scan").description("Detect finished conversations
20356
20863
  const snapshot = {
20357
20864
  generatedAt: new Date().toISOString(),
20358
20865
  idleMinutes,
20359
- 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
20360
20868
  };
20361
20869
  writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
20362
20870
  if (json) {
20363
- 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 });
20364
20872
  return;
20365
20873
  }
20366
20874
  console.log(green(`Scanned ${result.conversations.length} conversation(s): ${scan.queued} newly queued, ${scan.reopened} reopened, ${pending} pending analysis.`));
@@ -23335,7 +23843,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23335
23843
  if (!action.remoteContent)
23336
23844
  continue;
23337
23845
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23338
- 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
+ }
23339
23851
  newHashes[action.name] = {
23340
23852
  localHash: contentHash(buildSkillMd2(skillFile)),
23341
23853
  remoteHash: contentHash(action.remoteContent),
@@ -23377,7 +23889,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23377
23889
  vlog(` Pushed (conflict resolved): ${action.name}`);
23378
23890
  } else if (resolution === "remote" && action.remoteContent) {
23379
23891
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23380
- 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
+ }
23381
23897
  newHashes[action.name] = {
23382
23898
  localHash: contentHash(buildSkillMd2(skillFile)),
23383
23899
  remoteHash: contentHash(action.remoteContent),
@@ -23389,6 +23905,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23389
23905
  }
23390
23906
  for (const _action of plan.skips) {}
23391
23907
  const deletionSlugs = plan.deletions.map((action) => toSlug(action.name));
23908
+ let removalsFailed = false;
23392
23909
  if (deletionSlugs.length) {
23393
23910
  for (const adapter2 of ctx.adapters) {
23394
23911
  if (!adapter2.supportsSkills() || !adapter2.removeSkills)
@@ -23396,15 +23913,20 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23396
23913
  for (const scope of ctx.scopes) {
23397
23914
  try {
23398
23915
  await adapter2.removeSkills(deletionSlugs, scope);
23399
- } catch {}
23916
+ } catch {
23917
+ removalsFailed = true;
23918
+ }
23400
23919
  }
23401
23920
  }
23402
23921
  for (const action of plan.deletions) {
23403
- vlog(` Deleted locally: ${action.name}`);
23922
+ vlog(removalsFailed ? ` Deletion of ${action.name} incomplete; will retry next sync` : ` Deleted locally: ${action.name}`);
23404
23923
  }
23405
23924
  }
23406
- return newHashes;
23407
- }
23925
+ return {
23926
+ newHashes,
23927
+ failedDeletions: removalsFailed ? plan.deletions.map((action) => action.name) : []
23928
+ };
23929
+ }
23408
23930
  function makeSkillFile(name, content) {
23409
23931
  return {
23410
23932
  name,
@@ -23414,17 +23936,25 @@ function makeSkillFile(name, content) {
23414
23936
  };
23415
23937
  }
23416
23938
  async function writeSkillToAgents(skillFile, source, ctx) {
23939
+ let attempted = 0;
23940
+ let succeeded = 0;
23417
23941
  for (const adapter2 of ctx.adapters) {
23418
23942
  if (source === "app" && ctx.hasMcp && adapter2.mcpProvidesSkills)
23419
23943
  continue;
23420
23944
  for (const scope of ctx.scopes) {
23421
23945
  if (!adapter2.supportsSkills())
23422
23946
  continue;
23947
+ attempted++;
23423
23948
  try {
23424
23949
  await adapter2.writeSkills([skillFile], scope);
23950
+ succeeded++;
23425
23951
  } catch {}
23426
23952
  }
23427
23953
  }
23954
+ return { attempted, succeeded };
23955
+ }
23956
+ function writeLanded(outcome) {
23957
+ return outcome.attempted === 0 || outcome.succeeded > 0;
23428
23958
  }
23429
23959
  function extractDescription(content) {
23430
23960
  const match = content.match(/^---\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
@@ -23839,7 +24369,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23839
24369
  if (adapters.length > 0) {
23840
24370
  console.log(` Syncing to: ${adapters.map((a) => a.name).join(", ")}`);
23841
24371
  }
23842
- const newHashes = await executeSyncPlan(plan, resolvedConflicts, {
24372
+ const { newHashes, failedDeletions } = await executeSyncPlan(plan, resolvedConflicts, {
23843
24373
  client,
23844
24374
  workspaceId: state.workspaceId,
23845
24375
  adapters,
@@ -23896,9 +24426,15 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23896
24426
  };
23897
24427
  const mcpFailedAdapters = new Set;
23898
24428
  const skillFailedAdapters = new Set;
24429
+ const instructionFailedAdapters = new Set;
24430
+ const configFailedAdapters = new Set;
24431
+ const hookFailedAdapters = new Set;
24432
+ const agentOutcomes = {};
24433
+ const syncStartedAt = new Date().toISOString();
23899
24434
  for (const adapter2 of adapters) {
23900
24435
  if (isConnectOnlyAgent(getRegistryAgent(adapter2.slug))) {
23901
24436
  vlog(` [${adapter2.name}] Connect-only agent: no local files to sync`);
24437
+ agentOutcomes[adapter2.slug] = { at: syncStartedAt, ok: true, skipped: true };
23902
24438
  summary.adaptersProcessed++;
23903
24439
  continue;
23904
24440
  }
@@ -23960,15 +24496,32 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23960
24496
  await adapter2.writeInstructionHint(instructionHint, scope);
23961
24497
  summary.instructionHintWrites++;
23962
24498
  vlog(` [${adapter2.name}] Updated instruction hints (${scope})`);
23963
- if (adapter2.writeBuiltInHooks) {
24499
+ } catch (err) {
24500
+ adapterFailedAnyScope = true;
24501
+ instructionFailedAdapters.add(adapter2.slug);
24502
+ console.warn(` [${adapter2.name}] Failed instructions (${scope}): ${err instanceof Error ? err.message : err}`);
24503
+ }
24504
+ if (adapter2.writeBuiltInHooks) {
24505
+ try {
23964
24506
  await adapter2.writeBuiltInHooks(scope);
23965
24507
  summary.hookInstallCalls++;
24508
+ } catch (err) {
24509
+ adapterFailedAnyScope = true;
24510
+ hookFailedAdapters.add(adapter2.slug);
24511
+ console.warn(` [${adapter2.name}] Failed hooks (${scope}): ${err instanceof Error ? err.message : err}`);
23966
24512
  }
23967
- } catch (err) {
23968
- adapterFailedAnyScope = true;
23969
- console.warn(` [${adapter2.name}] Failed (${scope}): ${err instanceof Error ? err.message : err}`);
23970
24513
  }
23971
24514
  }
24515
+ const failedDimensions = [];
24516
+ if (skillFailedAdapters.has(adapter2.slug))
24517
+ failedDimensions.push("skills");
24518
+ if (mcpFailedAdapters.has(adapter2.slug))
24519
+ failedDimensions.push("mcp");
24520
+ if (instructionFailedAdapters.has(adapter2.slug))
24521
+ failedDimensions.push("instructions");
24522
+ if (hookFailedAdapters.has(adapter2.slug))
24523
+ failedDimensions.push("hooks");
24524
+ agentOutcomes[adapter2.slug] = failedDimensions.length > 0 ? { at: syncStartedAt, ok: false, failed: failedDimensions } : { at: syncStartedAt, ok: true };
23972
24525
  summary.adaptersProcessed++;
23973
24526
  if (adapterFailedAnyScope)
23974
24527
  summary.adaptersFailed++;
@@ -23999,7 +24552,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23999
24552
  await adapter2.writeTeamInstructions(fullInstructions, scope);
24000
24553
  teamInstructionsApplied = true;
24001
24554
  vlog(` [${adapter2.name}] Updated team instructions (${scope})`);
24002
- } catch {}
24555
+ } catch (err) {
24556
+ instructionFailedAdapters.add(adapter2.slug);
24557
+ console.warn(` [${adapter2.name}] Failed team instructions (${scope}): ${err instanceof Error ? err.message : err}`);
24558
+ }
24003
24559
  }
24004
24560
  }
24005
24561
  if (agentConfigs) {
@@ -24020,7 +24576,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24020
24576
  agentConfigsApplied++;
24021
24577
  const configKeys = Object.keys(configWithoutInstructions).join(", ");
24022
24578
  vlog(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
24023
- } catch {}
24579
+ } catch (err) {
24580
+ configFailedAdapters.add(adapter2.slug);
24581
+ console.warn(` [${adapter2.name}] Failed agent config (${scope}): ${err instanceof Error ? err.message : err}`);
24582
+ }
24024
24583
  }
24025
24584
  }
24026
24585
  }
@@ -24102,7 +24661,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24102
24661
  }
24103
24662
  if (team)
24104
24663
  agentConfigsApplied++;
24105
- } catch {}
24664
+ } catch (err) {
24665
+ configFailedAdapters.add(adapter2.slug);
24666
+ console.warn(` [${adapter2.name}] Failed agent config: ${err instanceof Error ? err.message : err}`);
24667
+ }
24106
24668
  }
24107
24669
  state.agentDefaultsVersion = AGENT_DEFAULTS_SCHEMA_VERSION;
24108
24670
  }
@@ -24118,6 +24680,21 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24118
24680
  }
24119
24681
  const prevMcpNames = state.mcpServers ?? [];
24120
24682
  state.lastSyncAt = new Date().toISOString();
24683
+ const lateFailures = [
24684
+ [configFailedAdapters, "config"],
24685
+ [instructionFailedAdapters, "instructions"],
24686
+ [hookFailedAdapters, "hooks"]
24687
+ ];
24688
+ for (const [slugs, dimension] of lateFailures) {
24689
+ for (const slug of slugs) {
24690
+ const existing = agentOutcomes[slug];
24691
+ if (existing?.failed?.includes(dimension))
24692
+ continue;
24693
+ const failed = [...existing?.failed ?? [], dimension];
24694
+ agentOutcomes[slug] = { at: existing?.at ?? syncStartedAt, ok: false, failed };
24695
+ }
24696
+ }
24697
+ state.lastSyncAgents = agentOutcomes;
24121
24698
  state.mcpServers = mcpEntries.map((e) => e.name);
24122
24699
  state.skills = remoteSkills.map((s) => s.name);
24123
24700
  state.skillFilenames = [
@@ -24179,7 +24756,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24179
24756
  for (const [name, hash] of Object.entries(newHashes)) {
24180
24757
  mergedHashes[name] = hash;
24181
24758
  }
24759
+ const retryDeletions = new Set(failedDeletions);
24182
24760
  for (const del of plan.deletions) {
24761
+ if (retryDeletions.has(del.name))
24762
+ continue;
24183
24763
  delete mergedHashes[del.name];
24184
24764
  }
24185
24765
  state.skillHashes = mergedHashes;
@@ -24700,10 +25280,11 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24700
25280
 
24701
25281
  // src/commands/uninstall.ts
24702
25282
  init_prompt();
25283
+ init_subprocess();
24703
25284
  await init_detect();
24704
25285
  import { Command as Command30 } from "commander";
24705
- import { existsSync as existsSync55, readFileSync as readFileSync44, rmSync as rmSync13, unlinkSync as unlinkSync9 } from "fs";
24706
- import { join as join52 } from "path";
25286
+ import { existsSync as existsSync55, readFileSync as readFileSync44, writeFileSync as writeFileSync32, readdirSync as readdirSync16, rmSync as rmSync12, unlinkSync as unlinkSync9, lstatSync, readlinkSync } from "fs";
25287
+ import { join as join52, resolve as resolve4, relative as relative5, isAbsolute as isAbsolute5 } from "path";
24707
25288
  import { homedir as homedir31 } from "os";
24708
25289
  function loadSetupState4(filePath) {
24709
25290
  if (!existsSync55(filePath))
@@ -24714,6 +25295,125 @@ function loadSetupState4(filePath) {
24714
25295
  return null;
24715
25296
  }
24716
25297
  }
25298
+ var PRESERVED_ENTRIES = ["bin", "apps", "trash"];
25299
+ function removeRunworkState(stateDir, opts) {
25300
+ const result = { removed: [], preserved: [], errors: [] };
25301
+ if (!existsSync55(stateDir))
25302
+ return result;
25303
+ const preserve = new Set(PRESERVED_ENTRIES);
25304
+ if (opts.keepAuth)
25305
+ preserve.add(".credentials");
25306
+ let entries;
25307
+ try {
25308
+ entries = readdirSync16(stateDir);
25309
+ } catch (err) {
25310
+ result.errors.push(`${stateDir}: ${err instanceof Error ? err.message : err}`);
25311
+ return result;
25312
+ }
25313
+ for (const entry of entries) {
25314
+ const target = join52(stateDir, entry);
25315
+ if (preserve.has(entry)) {
25316
+ if (existsSync55(target))
25317
+ result.preserved.push(target);
25318
+ continue;
25319
+ }
25320
+ try {
25321
+ rmSync12(target, { recursive: true, force: true });
25322
+ result.removed.push(target);
25323
+ } catch (err) {
25324
+ result.errors.push(`${target}: ${err instanceof Error ? err.message : err}`);
25325
+ }
25326
+ }
25327
+ return result;
25328
+ }
25329
+ var SHELL_PROFILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"];
25330
+ var BIN_DIR_PATTERN = /\.runwork[\\/]bin/;
25331
+ function stripRunworkPathLines(file) {
25332
+ if (!existsSync55(file))
25333
+ return false;
25334
+ let content;
25335
+ try {
25336
+ content = readFileSync44(file, "utf-8");
25337
+ } catch {
25338
+ return false;
25339
+ }
25340
+ if (!content.includes("Added by Runwork"))
25341
+ return false;
25342
+ const lines = content.split(`
25343
+ `);
25344
+ const kept = [];
25345
+ for (let i = 0;i < lines.length; i++) {
25346
+ if (/^\s*#\s*Added by Runwork\b/.test(lines[i])) {
25347
+ if (i + 1 < lines.length && BIN_DIR_PATTERN.test(lines[i + 1]))
25348
+ i++;
25349
+ continue;
25350
+ }
25351
+ kept.push(lines[i]);
25352
+ }
25353
+ const next = kept.join(`
25354
+ `);
25355
+ if (next === content)
25356
+ return false;
25357
+ try {
25358
+ writeFileSync32(file, next);
25359
+ return true;
25360
+ } catch {
25361
+ return false;
25362
+ }
25363
+ }
25364
+ function cleanShellProfilePathEntries() {
25365
+ return SHELL_PROFILES.map((name) => join52(homedir31(), name)).filter(stripRunworkPathLines);
25366
+ }
25367
+ function cleanPowerShellProfilePathEntries() {
25368
+ if (process.platform !== "win32")
25369
+ return [];
25370
+ const touched = [];
25371
+ const seen = new Set;
25372
+ for (const host of ["powershell", "pwsh"]) {
25373
+ let profilePath;
25374
+ try {
25375
+ profilePath = execFileSync(host, ["-NoProfile", "-Command", "$PROFILE.CurrentUserCurrentHost"], {
25376
+ encoding: "utf-8",
25377
+ stdio: "pipe"
25378
+ }).trim();
25379
+ } catch {
25380
+ continue;
25381
+ }
25382
+ if (!profilePath || seen.has(profilePath))
25383
+ continue;
25384
+ seen.add(profilePath);
25385
+ if (stripRunworkPathLines(profilePath))
25386
+ touched.push(profilePath);
25387
+ }
25388
+ return touched;
25389
+ }
25390
+ function removeRunworkSymlink(linkPath) {
25391
+ let stat;
25392
+ try {
25393
+ stat = lstatSync(linkPath);
25394
+ } catch {
25395
+ return false;
25396
+ }
25397
+ if (!stat.isSymbolicLink())
25398
+ return false;
25399
+ let target;
25400
+ try {
25401
+ target = resolve4(linkPath, "..", readlinkSync(linkPath));
25402
+ } catch {
25403
+ return false;
25404
+ }
25405
+ const ours = resolve4(join52(homedir31(), ".runwork", "bin"));
25406
+ const rel = relative5(ours, resolve4(target));
25407
+ const insideOurs = rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
25408
+ if (!insideOurs)
25409
+ return false;
25410
+ try {
25411
+ unlinkSync9(linkPath);
25412
+ return true;
25413
+ } catch {
25414
+ return false;
25415
+ }
25416
+ }
24717
25417
  var uninstallCommand = new Command30("uninstall").description("Remove all Runwork configuration from local agents (MCP servers, skills, instructions)").option("-y, --yes", "Skip confirmation prompt").option("--keep-auth", "Keep authentication credentials (only remove agent configs)").action(async (opts) => {
24718
25418
  const projectStatePath = join52(process.cwd(), ".runwork", "setup.json");
24719
25419
  const userStatePath = join52(homedir31(), ".runwork", "setup.json");
@@ -24754,6 +25454,13 @@ This will remove all Runwork configuration from your local agents:
24754
25454
  } else {
24755
25455
  console.log(" - Setup state and auth credentials (~/.runwork/)");
24756
25456
  }
25457
+ console.log(" - The PATH line we added to your shell profiles");
25458
+ console.log(" - Symlinks that point at our own binary");
25459
+ console.log(`
25460
+ What will be KEPT:`);
25461
+ console.log(" - ~/.runwork/bin the CLI binary itself");
25462
+ console.log(" - ~/.runwork/apps your own app source");
25463
+ console.log(" - ~/.runwork/trash recoverable copies of removed files");
24757
25464
  console.log("");
24758
25465
  if (!opts.yes) {
24759
25466
  const confirmed = await promptConfirm("Proceed with uninstall?");
@@ -24772,7 +25479,7 @@ This will remove all Runwork configuration from your local agents:
24772
25479
  for (const { state, label } of entries) {
24773
25480
  const scopes = state.scope === "both" ? ["project", "user"] : [state.scope];
24774
25481
  const manifest = {
24775
- skillFilenames: state.skillFilenames ?? state.skills.map((s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-")),
25482
+ skillFilenames: state.skillFilenames ?? (state.skills ?? []).map((s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-")),
24776
25483
  mcpServerNames: state.mcpServers ?? []
24777
25484
  };
24778
25485
  for (const slug of state.configuredAgents) {
@@ -24797,27 +25504,50 @@ This will remove all Runwork configuration from your local agents:
24797
25504
  }
24798
25505
  }
24799
25506
  const stateDir = label === "project" ? join52(process.cwd(), ".runwork") : join52(homedir31(), ".runwork");
24800
- if (opts.keepAuth && label === "user") {
24801
- const setupFile = join52(stateDir, "setup.json");
24802
- if (existsSync55(setupFile)) {
24803
- try {
24804
- unlinkSync9(setupFile);
24805
- console.log(` Removed ${setupFile} (kept credentials)`);
24806
- } catch (err) {
24807
- console.warn(` Failed to remove ${setupFile}: ${err instanceof Error ? err.message : err}`);
24808
- errors++;
24809
- }
25507
+ if (existsSync55(stateDir)) {
25508
+ const outcome = removeRunworkState(stateDir, {
25509
+ keepAuth: Boolean(opts.keepAuth) && label === "user"
25510
+ });
25511
+ if (outcome.removed.length > 0) {
25512
+ console.log(` Removed Runwork state from ${stateDir}`);
24810
25513
  }
24811
- } else if (existsSync55(stateDir)) {
24812
- try {
24813
- rmSync13(stateDir, { recursive: true, force: true });
24814
- console.log(` Removed ${stateDir}`);
24815
- } catch (err) {
24816
- console.warn(` Failed to remove ${stateDir}: ${err instanceof Error ? err.message : err}`);
25514
+ for (const problem of outcome.errors) {
25515
+ console.warn(` Failed to remove ${problem}`);
24817
25516
  errors++;
24818
25517
  }
25518
+ if (outcome.preserved.length > 0) {
25519
+ console.log("");
25520
+ console.log(" Kept (not Runwork's to delete):");
25521
+ for (const kept of outcome.preserved)
25522
+ console.log(` - ${kept}`);
25523
+ }
24819
25524
  }
24820
25525
  }
25526
+ const touchedProfiles = [
25527
+ ...cleanShellProfilePathEntries(),
25528
+ ...cleanPowerShellProfilePathEntries()
25529
+ ];
25530
+ if (touchedProfiles.length > 0) {
25531
+ console.log("");
25532
+ console.log(" Removed the Runwork PATH line from:");
25533
+ for (const file of touchedProfiles)
25534
+ console.log(` - ${file}`);
25535
+ }
25536
+ const removedLinks = [
25537
+ join52(homedir31(), ".local", "bin", "runwork"),
25538
+ "/usr/local/bin/runwork"
25539
+ ].filter(removeRunworkSymlink);
25540
+ if (removedLinks.length > 0) {
25541
+ console.log("");
25542
+ console.log(" Removed symlinks:");
25543
+ for (const link of removedLinks)
25544
+ console.log(` - ${link}`);
25545
+ }
25546
+ if (process.platform === "win32") {
25547
+ console.log("");
25548
+ console.log(" Still on your PATH (remove by hand if you want it gone):");
25549
+ console.log(` ${join52(homedir31(), ".runwork", "bin")} in your user PATH`);
25550
+ }
24821
25551
  console.log("");
24822
25552
  if (errors > 0) {
24823
25553
  console.log(`Uninstall completed with ${errors} warning${errors > 1 ? "s" : ""}. ${cleanedAgents} agent${cleanedAgents > 1 ? "s" : ""} cleaned.`);
@@ -25041,6 +25771,7 @@ import { Command as Command34 } from "commander";
25041
25771
 
25042
25772
  // src/health/checks.ts
25043
25773
  init_subprocess();
25774
+ init_http();
25044
25775
  init_store();
25045
25776
  init_client();
25046
25777
  init_http();
@@ -25242,9 +25973,20 @@ async function checkAuthAndNetwork(ctx) {
25242
25973
  network: { name: "network", status: "pass", message: `API reachable (${elapsed}ms)` }
25243
25974
  };
25244
25975
  }
25976
+ const proxy = proxyForUrl(ctx.credentials.baseUrl || BASE_URL2);
25977
+ const network = proxy ? {
25978
+ name: "network",
25979
+ status: "fail",
25980
+ message: `API unreachable via ${proxy.variable} (${proxy.value}): ${message}`,
25981
+ details: [
25982
+ `A proxy is configured through ${proxy.variable}.`,
25983
+ "Runwork routes requests through `curl` when a proxy is set, so curl must be installed and able to reach the proxy.",
25984
+ "If this host should bypass the proxy, add it to NO_PROXY."
25985
+ ]
25986
+ } : { name: "network", status: "fail", message: `API unreachable: ${message}` };
25245
25987
  return {
25246
25988
  auth: { name: "auth", status: "skip", message: "could not verify (network error)" },
25247
- network: { name: "network", status: "fail", message: `API unreachable: ${message}` }
25989
+ network
25248
25990
  };
25249
25991
  }
25250
25992
  }
@@ -25572,6 +26314,7 @@ async function checkAgentSetup() {
25572
26314
  details.push(`${state.configuredAgents.length} agent(s) configured`);
25573
26315
  }
25574
26316
  let mcpChecked = false;
26317
+ let mcpAllHealthy = true;
25575
26318
  for (const slug of state.configuredAgents) {
25576
26319
  const adapter2 = getAdapterBySlug(slug);
25577
26320
  if (!adapter2 || !adapter2.supportsMcpScope("user"))
@@ -25584,19 +26327,22 @@ async function checkAgentSetup() {
25584
26327
  if (missingMcp.length > 0) {
25585
26328
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
25586
26329
  upgrade("warn");
25587
- } else {
25588
- details.push(`${state.mcpServers.length} MCP server(s) configured`);
26330
+ mcpAllHealthy = false;
25589
26331
  }
25590
26332
  mcpChecked = true;
25591
- break;
25592
26333
  } catch {}
25593
26334
  }
25594
26335
  }
26336
+ if (mcpChecked && mcpAllHealthy && state.mcpServers.length > 0) {
26337
+ details.push(`${state.mcpServers.length} MCP server(s) configured`);
26338
+ }
25595
26339
  if (!mcpChecked && state.mcpServers.length > 0) {
25596
26340
  details.push("could not verify MCP servers");
25597
26341
  upgrade("warn");
25598
26342
  }
25599
26343
  let skillsChecked = false;
26344
+ let skillsAllHealthy = true;
26345
+ let skillsSummaryLine = null;
25600
26346
  for (const slug of state.configuredAgents) {
25601
26347
  const skillsDir = getSkillsDir(slug, "user");
25602
26348
  if (!skillsDir)
@@ -25613,13 +26359,16 @@ async function checkAgentSetup() {
25613
26359
  if (missingSkills.length > 0) {
25614
26360
  details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
25615
26361
  upgrade("warn");
25616
- } else if (state.skills.length > 0) {
26362
+ skillsAllHealthy = false;
26363
+ } else if (state.skills.length > 0 && !skillsSummaryLine) {
25617
26364
  const mcpCoveredCount = state.skills.filter(isCoveredByMcp).length;
25618
26365
  const onDiskCount = state.skills.length - mcpCoveredCount;
25619
- 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`;
25620
26367
  }
25621
26368
  skillsChecked = true;
25622
- break;
26369
+ }
26370
+ if (skillsChecked && skillsAllHealthy && skillsSummaryLine) {
26371
+ details.push(skillsSummaryLine);
25623
26372
  }
25624
26373
  if (!skillsChecked && state.skills.length > 0) {
25625
26374
  details.push("could not verify skills");
@@ -25794,7 +26543,7 @@ async function applyDoctorFixes(ctx, failingNames) {
25794
26543
  }
25795
26544
 
25796
26545
  // src/agents/runtime-detection.ts
25797
- import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
26546
+ import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync11, readdirSync as readdirSync17 } from "fs";
25798
26547
  import { homedir as homedir33 } from "os";
25799
26548
  import { join as join55 } from "path";
25800
26549
  var RUNWORK_SESSIONS_DIR = join55(homedir33(), ".runwork", "sessions");
@@ -25877,7 +26626,7 @@ function findClaudeCodeSessionFile(sessionId) {
25877
26626
  return null;
25878
26627
  let projectDirs;
25879
26628
  try {
25880
- projectDirs = readdirSync16(root);
26629
+ projectDirs = readdirSync17(root);
25881
26630
  } catch {
25882
26631
  return null;
25883
26632
  }
@@ -25897,7 +26646,7 @@ function findCodexRolloutFile(threadId) {
25897
26646
  const dir = stack.pop();
25898
26647
  let entries;
25899
26648
  try {
25900
- entries = readdirSync16(dir);
26649
+ entries = readdirSync17(dir);
25901
26650
  } catch {
25902
26651
  continue;
25903
26652
  }
@@ -25905,7 +26654,7 @@ function findCodexRolloutFile(threadId) {
25905
26654
  const full = join55(dir, entry);
25906
26655
  let s;
25907
26656
  try {
25908
- s = statSync10(full);
26657
+ s = statSync11(full);
25909
26658
  } catch {
25910
26659
  continue;
25911
26660
  }
@@ -25924,7 +26673,7 @@ function findNewestClaudeCodeSession() {
25924
26673
  return null;
25925
26674
  let projectDirs;
25926
26675
  try {
25927
- projectDirs = readdirSync16(root);
26676
+ projectDirs = readdirSync17(root);
25928
26677
  } catch {
25929
26678
  return null;
25930
26679
  }
@@ -25933,7 +26682,7 @@ function findNewestClaudeCodeSession() {
25933
26682
  const projectPath = join55(root, dir);
25934
26683
  let files;
25935
26684
  try {
25936
- files = readdirSync16(projectPath);
26685
+ files = readdirSync17(projectPath);
25937
26686
  } catch {
25938
26687
  continue;
25939
26688
  }
@@ -25942,7 +26691,7 @@ function findNewestClaudeCodeSession() {
25942
26691
  continue;
25943
26692
  const full = join55(projectPath, file);
25944
26693
  try {
25945
- const s = statSync10(full);
26694
+ const s = statSync11(full);
25946
26695
  if (!best || s.mtimeMs > best.mtime) {
25947
26696
  best = {
25948
26697
  sessionId: file.replace(/\.jsonl$/, ""),
@@ -25967,7 +26716,7 @@ function findNewestCodexRollout() {
25967
26716
  const dir = stack.pop();
25968
26717
  let entries;
25969
26718
  try {
25970
- entries = readdirSync16(dir);
26719
+ entries = readdirSync17(dir);
25971
26720
  } catch {
25972
26721
  continue;
25973
26722
  }
@@ -25975,7 +26724,7 @@ function findNewestCodexRollout() {
25975
26724
  const full = join55(dir, entry);
25976
26725
  let s;
25977
26726
  try {
25978
- s = statSync10(full);
26727
+ s = statSync11(full);
25979
26728
  } catch {
25980
26729
  continue;
25981
26730
  }
@@ -26199,13 +26948,627 @@ var doctorCommand = new Command34("doctor").description("Check system health: au
26199
26948
  }
26200
26949
  });
26201
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
+
26202
27565
  // src/commands/share-convo.ts
26203
27566
  init_store();
26204
27567
  init_client();
26205
27568
  init_resolve();
26206
- import { Command as Command35 } from "commander";
26207
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync32, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
26208
- 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";
26209
27572
  import { tmpdir as tmpdir4 } from "os";
26210
27573
  import { createHash as createHash6 } from "crypto";
26211
27574
 
@@ -26327,14 +27690,14 @@ function resolveLocalSessionShare(opts, conversation) {
26327
27690
  process.exit(1);
26328
27691
  }
26329
27692
  const title = opts.title ?? conversation.title ?? conversation.project;
26330
- const markdown = renderTranscriptMarkdown(readFileSync48(conversation.transcriptPath, "utf8"), family, title);
27693
+ const markdown = renderTranscriptMarkdown(readFileSync49(conversation.transcriptPath, "utf8"), family, title);
26331
27694
  if (!markdown) {
26332
27695
  console.error("Error: this conversation has no shareable content.");
26333
27696
  process.exit(1);
26334
27697
  }
26335
- const tempDir = mkdtempSync4(join56(tmpdir4(), "runwork-share-"));
26336
- const transcriptFile = join56(tempDir, "transcript.md");
26337
- writeFileSync32(transcriptFile, markdown);
27698
+ const tempDir = mkdtempSync4(join59(tmpdir4(), "runwork-share-"));
27699
+ const transcriptFile = join59(tempDir, "transcript.md");
27700
+ writeFileSync33(transcriptFile, markdown);
26338
27701
  opts.transcriptFile = transcriptFile;
26339
27702
  opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
26340
27703
  opts.sourceAgent = opts.sourceAgent ?? conversation.agentSlug;
@@ -26350,7 +27713,7 @@ function nativeBundleFormatForAgent(slug) {
26350
27713
  return "codex-rollout";
26351
27714
  return null;
26352
27715
  }
26353
- function sha256Hex(content) {
27716
+ function sha256Hex2(content) {
26354
27717
  return createHash6("sha256").update(content, "utf8").digest("hex");
26355
27718
  }
26356
27719
  function utf8ByteLength(content) {
@@ -26392,13 +27755,13 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26392
27755
  const credentials = requireAuth();
26393
27756
  const client = new ApiClient(credentials);
26394
27757
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
26395
- const transcriptContent = readFileSync48(opts.transcriptFile, "utf8");
27758
+ const transcriptContent = readFileSync49(opts.transcriptFile, "utf8");
26396
27759
  const bundles = [
26397
27760
  {
26398
27761
  format: "transcript",
26399
27762
  content: transcriptContent,
26400
27763
  sizeBytes: utf8ByteLength(transcriptContent),
26401
- sha256: sha256Hex(transcriptContent)
27764
+ sha256: sha256Hex2(transcriptContent)
26402
27765
  }
26403
27766
  ];
26404
27767
  const detected = detectCurrentAgent();
@@ -26417,12 +27780,12 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26417
27780
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
26418
27781
  if (nativeFormat) {
26419
27782
  try {
26420
- const content = readFileSync48(nativeFilePath, "utf8");
27783
+ const content = readFileSync49(nativeFilePath, "utf8");
26421
27784
  bundles.push({
26422
27785
  format: nativeFormat,
26423
27786
  content,
26424
27787
  sizeBytes: utf8ByteLength(content),
26425
- sha256: sha256Hex(content)
27788
+ sha256: sha256Hex2(content)
26426
27789
  });
26427
27790
  } catch (err) {
26428
27791
  console.error(`Warning: could not read native file ${nativeFilePath}: ${err instanceof Error ? err.message : err}`);
@@ -26433,7 +27796,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26433
27796
  let metadata = {};
26434
27797
  if (opts.metadataFile) {
26435
27798
  try {
26436
- metadata = JSON.parse(readFileSync48(opts.metadataFile, "utf8"));
27799
+ metadata = JSON.parse(readFileSync49(opts.metadataFile, "utf8"));
26437
27800
  } catch (err) {
26438
27801
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
26439
27802
  process.exit(1);
@@ -26488,18 +27851,18 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
26488
27851
  process.exit(1);
26489
27852
  }
26490
27853
  }
26491
- 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));
26492
27855
 
26493
27856
  // src/commands/save-convo.ts
26494
- import { Command as Command36 } from "commander";
26495
- 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));
26496
27859
 
26497
27860
  // src/commands/inbox.ts
26498
27861
  init_store();
26499
27862
  init_client();
26500
27863
  init_resolve();
26501
- import { Command as Command37 } from "commander";
26502
- 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) => {
26503
27866
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26504
27867
  const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
26505
27868
  const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
@@ -26541,10 +27904,10 @@ init_store();
26541
27904
  init_client();
26542
27905
  init_resolve();
26543
27906
  init_registry_data();
26544
- import { Command as Command38 } from "commander";
26545
- import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync29, realpathSync } from "fs";
26546
- import { homedir as homedir34 } from "os";
26547
- 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";
26548
27911
  import { spawn as spawn5 } from "child_process";
26549
27912
  init_registry();
26550
27913
  init_which();
@@ -26583,10 +27946,10 @@ function extractCodexUuid(rolloutContent) {
26583
27946
  }
26584
27947
  function placeClaudeJsonl(uuid, content, recipientCwd) {
26585
27948
  const encoded = encodeClaudeCodeCwd(recipientCwd);
26586
- const projectDir = join57(homedir34(), ".claude", "projects", encoded);
26587
- mkdirSync29(projectDir, { recursive: true });
26588
- const placedAt = join57(projectDir, `${uuid}.jsonl`);
26589
- writeFileSync33(placedAt, content);
27949
+ const projectDir = join60(homedir36(), ".claude", "projects", encoded);
27950
+ mkdirSync30(projectDir, { recursive: true });
27951
+ const placedAt = join60(projectDir, `${uuid}.jsonl`);
27952
+ writeFileSync34(placedAt, content);
26590
27953
  return { placedAt, runFromCwd: recipientCwd };
26591
27954
  }
26592
27955
  function placeCodexRollout(uuid, content) {
@@ -26594,11 +27957,11 @@ function placeCodexRollout(uuid, content) {
26594
27957
  const yyyy = String(now.getUTCFullYear());
26595
27958
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
26596
27959
  const dd = String(now.getUTCDate()).padStart(2, "0");
26597
- const dir = join57(homedir34(), ".codex", "sessions", yyyy, mm, dd);
26598
- mkdirSync29(dir, { recursive: true });
27960
+ const dir = join60(homedir36(), ".codex", "sessions", yyyy, mm, dd);
27961
+ mkdirSync30(dir, { recursive: true });
26599
27962
  const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
26600
- const placedAt = join57(dir, `rollout-${ts}-${uuid}.jsonl`);
26601
- writeFileSync33(placedAt, content);
27963
+ const placedAt = join60(dir, `rollout-${ts}-${uuid}.jsonl`);
27964
+ writeFileSync34(placedAt, content);
26602
27965
  return { placedAt };
26603
27966
  }
26604
27967
  function pickTargetAgent(opts, sourceAgent) {
@@ -26617,7 +27980,15 @@ function isAgentInstalled(agent) {
26617
27980
  }
26618
27981
  return false;
26619
27982
  }
26620
- 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) => {
26621
27992
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26622
27993
  const credentials = requireAuth();
26623
27994
  const client = new ApiClient(credentials);
@@ -26711,12 +28082,8 @@ Bundle placed at: ${placement.placedAt}`);
26711
28082
  return;
26712
28083
  }
26713
28084
  if (cap2.mode === "cli-resume") {
26714
- let cmd = (cap2.cliResumeCommand ?? "").replace(/\{uuid\}/g, nativeUuid);
26715
- const resolvedCli = resolveAgentCliCommand(target.slug);
26716
- const cliName = getAgent(target.slug)?.launch?.cli;
26717
- if (resolvedCli && cliName && resolvedCli !== cliName && cmd.startsWith(`${cliName} `)) {
26718
- cmd = `"${resolvedCli}" ${cmd.slice(cliName.length + 1)}`;
26719
- }
28085
+ const { argv, display } = buildResumeCommand(cap2.cliResumeCommand ?? "", nativeUuid, resolveAgentCliCommand(target.slug), getAgent(target.slug)?.launch?.cli);
28086
+ const cmd = display;
26720
28087
  const detected = detectCurrentAgent();
26721
28088
  const insideSameAgent = detected && detected.slug === target.slug;
26722
28089
  const shouldPrintOnly = opts.dryRun || insideSameAgent;
@@ -26748,10 +28115,11 @@ Or ask the assistant to continue the conversation in THIS session by ` + `fetchi
26748
28115
  }
26749
28116
  return;
26750
28117
  }
26751
- const parts = cmd.split(" ");
26752
- 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, {
26753
28120
  cwd: placement.runFromCwd ?? recipientCwd,
26754
- stdio: "inherit"
28121
+ stdio: "inherit",
28122
+ ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
26755
28123
  });
26756
28124
  child.on("exit", (code) => {
26757
28125
  process.exit(code ?? 0);
@@ -26847,7 +28215,7 @@ process.on("uncaughtException", (err) => {
26847
28215
  console.error(`Uncaught exception: ${formatError(err)}`);
26848
28216
  process.exit(1);
26849
28217
  });
26850
- var program = new Command39;
28218
+ var program = new Command40;
26851
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)");
26852
28220
  program.addCommand(infoCommand);
26853
28221
  program.addCommand(loginCommand);
@@ -26880,6 +28248,7 @@ program.addCommand(appsCommand);
26880
28248
  program.addCommand(membersCommand);
26881
28249
  program.addCommand(apiCommand);
26882
28250
  program.addCommand(doctorCommand);
28251
+ program.addCommand(debugCommand);
26883
28252
  program.addCommand(shareConvoCommand);
26884
28253
  program.addCommand(saveConvoCommand);
26885
28254
  program.addCommand(inboxCommand);