runwork 0.25.3 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -62,7 +62,11 @@ import {
62
62
  createInflateRaw
63
63
  } from "node:zlib";
64
64
  function firstEnv(names) {
65
- for (const variable of names) {
65
+ const keys = Object.keys(process.env);
66
+ for (const candidate of names) {
67
+ const variable = keys.find((k) => k === candidate) ?? keys.find((k) => k.toLowerCase() === candidate.toLowerCase());
68
+ if (variable === undefined)
69
+ continue;
66
70
  const value = process.env[variable];
67
71
  if (value && value.trim() !== "")
68
72
  return { variable, value: value.trim() };
@@ -828,6 +832,10 @@ class ApiClient {
828
832
  async reportTelemetry(workspaceId, events) {
829
833
  await this.request(`/api/workspaces/${workspaceId}/team/telemetry`, { method: "POST", body: JSON.stringify({ events }) });
830
834
  }
835
+ async createDebugReport(workspaceId, body) {
836
+ const res = await this.request(`/api/workspaces/${workspaceId}/debug-reports`, { method: "POST", body: JSON.stringify(body) });
837
+ return res.data;
838
+ }
831
839
  async createSharedConversation(workspaceId, body) {
832
840
  const res = await this.request(`/api/workspaces/${workspaceId}/shared-conversations`, {
833
841
  method: "POST",
@@ -1038,10 +1046,13 @@ var init_workspace_state = __esm(() => {
1038
1046
  // src/utils/subprocess.ts
1039
1047
  import {
1040
1048
  execFileSync as cpExecFileSync,
1041
- spawn as cpSpawn
1049
+ spawn as cpSpawn,
1050
+ spawnSync as cpSpawnSync
1042
1051
  } from "child_process";
1043
1052
  var execFileSync = (file, args, options) => {
1044
1053
  return cpExecFileSync(file, args, { windowsHide: true, ...options ?? {} });
1054
+ }, spawnSync = (file, args, options) => {
1055
+ return cpSpawnSync(file, args, { windowsHide: true, ...options ?? {} });
1045
1056
  }, spawn2 = (command, args, options) => {
1046
1057
  return cpSpawn(command, args ?? [], { windowsHide: true, ...options ?? {} });
1047
1058
  };
@@ -3135,13 +3146,56 @@ function bufToStr2(val) {
3135
3146
  return val.toString("utf-8").trim();
3136
3147
  return "";
3137
3148
  }
3149
+ function pushSyncStash(cwd) {
3150
+ execFileSync("git", ["stash", "push", "-m", SYNC_STASH_TAG], { cwd, stdio: "pipe" });
3151
+ return findSyncStashSha(cwd);
3152
+ }
3153
+ function findSyncStashSha(cwd) {
3154
+ try {
3155
+ const line = execFileSync("git", ["stash", "list", "--format=%H %gs"], { cwd, encoding: "utf-8" }).split(`
3156
+ `).find((l) => l.includes(SYNC_STASH_TAG));
3157
+ return line ? line.split(" ")[0] : null;
3158
+ } catch {
3159
+ return null;
3160
+ }
3161
+ }
3162
+ function stashRefForSha(cwd, sha) {
3163
+ try {
3164
+ const line = execFileSync("git", ["stash", "list", "--format=%H %gd"], { cwd, encoding: "utf-8" }).split(`
3165
+ `).find((l) => l.startsWith(`${sha} `));
3166
+ return line ? line.slice(sha.length + 1).trim() : null;
3167
+ } catch {
3168
+ return null;
3169
+ }
3170
+ }
3171
+ function restoreSyncStash(cwd, sha) {
3172
+ const resolved = sha ?? findSyncStashSha(cwd);
3173
+ if (!resolved)
3174
+ return "missing";
3175
+ const ref = stashRefForSha(cwd, resolved);
3176
+ if (!ref)
3177
+ return "missing";
3178
+ try {
3179
+ execFileSync("git", ["stash", "apply", resolved], { cwd, stdio: "pipe" });
3180
+ } catch {
3181
+ return "conflict";
3182
+ }
3183
+ const dropRef = stashRefForSha(cwd, resolved);
3184
+ if (dropRef) {
3185
+ try {
3186
+ execFileSync("git", ["stash", "drop", dropRef], { cwd, stdio: "pipe" });
3187
+ } catch {}
3188
+ }
3189
+ return "restored";
3190
+ }
3138
3191
  function syncWithRemote(cwd) {
3139
3192
  if (!hasCommits(cwd)) {
3140
3193
  return { status: "skipped", pushed: false };
3141
3194
  }
3142
3195
  const dirty = hasTrackedChanges(cwd);
3196
+ let stashSha = null;
3143
3197
  if (dirty) {
3144
- execFileSync("git", ["stash", "push", "-m", "runwork-dev-sync"], { cwd, stdio: "pipe" });
3198
+ stashSha = pushSyncStash(cwd);
3145
3199
  }
3146
3200
  let status = "synced";
3147
3201
  let syncError;
@@ -3182,9 +3236,8 @@ function syncWithRemote(cwd) {
3182
3236
  syncError = extractGitError(fetchErr);
3183
3237
  }
3184
3238
  if (dirty) {
3185
- try {
3186
- execFileSync("git", ["stash", "pop"], { cwd, stdio: "pipe" });
3187
- } catch {
3239
+ const restored = restoreSyncStash(cwd, stashSha);
3240
+ if (restored !== "restored") {
3188
3241
  return { status, pushed: false, error: "stash-conflict", keptUntracked, remoteOverwrote };
3189
3242
  }
3190
3243
  }
@@ -3201,6 +3254,7 @@ function syncWithRemote(cwd) {
3201
3254
  }
3202
3255
  return { status, pushed, pushError, keptUntracked, remoteOverwrote };
3203
3256
  }
3257
+ var SYNC_STASH_TAG = "runwork-dev-sync";
3204
3258
  var init_sync = __esm(() => {
3205
3259
  init_subprocess();
3206
3260
  init_manifest();
@@ -5105,10 +5159,6 @@ export declare class FileStorageClient {
5105
5159
  */
5106
5160
  export declare function createFileStorageClient(env: Env): FileStorageClient;
5107
5161
  import type { Hono } from 'hono';
5108
- /**
5109
- * Mount file storage routes on the Hono app
5110
- * Provides REST API for R2 bucket operations
5111
- */
5112
5162
  export declare function fileStorageRoutes(app: Hono<{
5113
5163
  Bindings: Env;
5114
5164
  }>): void;
@@ -5285,6 +5335,46 @@ export declare function componentRoutes(app: Hono<{
5285
5335
  `,
5286
5336
  "workspace.d.ts": `export { WorkspaceContext, getWorkspaceContext, listWorkspaceEntity, getWorkspaceEntity, createWorkspaceEntity, updateWorkspaceEntity, deleteWorkspaceEntity, callAppEndpoint, initializeWorkspace, } from './core-workspace';
5287
5337
  export type { WorkspaceUser, ListUsersOptions, ListUsersResponse, NotifyUserParams, NotifyUserResult, ListEntityRequest, ListEntityResponse, GetEntityRequest, CreateEntityRequest, UpdateEntityRequest, DeleteEntityRequest, RegisterEntityRequest, RegisterAppRequest, RegisterComponentRequest, RegisterScheduleRequest, RegisterWorkflowRequest, RegisterEndpointRequest, RegisterIntegrationRequest, CallAppEndpointOptions, CallAppEndpointResult, RegisterAgentRequest as WorkspaceRegisterAgentRequest, } from './core-workspace';
5338
+ `,
5339
+ "client.d.ts": `/**
5340
+ * Framework client entry: browser-side helpers an app's frontend imports as
5341
+ * \`@runworkai/framework/client\`, so app code never carries this plumbing
5342
+ * itself.
5343
+ *
5344
+ * Analytics: what people do in the app, reported to the workspace.
5345
+ *
5346
+ * Envelope follows the Segment Spec (the de-facto standard Google Analytics,
5347
+ * Mixpanel, PostHog and friends all map to), so nothing here needs to change
5348
+ * if the workspace later forwards events to a real analytics tool.
5349
+ *
5350
+ * The app does not interpret events. \`/api/analytics\` stamps the app's
5351
+ * identity and forwards to the workspace, which decides what each one means.
5352
+ * Today a \`page\` counts as one USE of the app, the multiplier behind a
5353
+ * standalone app's value in the workspace's reports. \`track\` events are kept
5354
+ * for a later analytics surface.
5355
+ *
5356
+ * A page load is reported here because the document is served before the
5357
+ * app's Worker runs, leaving no server-side trace otherwise. Once per
5358
+ * document, not per route change: the workspace counts an opened tool, and a
5359
+ * person navigating inside it is still one use.
5360
+ *
5361
+ * Fire-and-forget. Nothing here may ever affect the person using the app.
5362
+ */
5363
+ /** Record a custom event. Use a flat Title Case name ("Report Exported") and put variables in properties. */
5364
+ export declare function track(event: string, properties?: Record<string, unknown>): void;
5365
+ /** Record a named screen or view inside the app. */
5366
+ export declare function screen(name: string, properties?: Record<string, unknown>): void;
5367
+ /**
5368
+ * Tell the workspace who this browser belongs to, in the APP's own terms.
5369
+ * A claim, recorded as data; the workspace never grants anything on it.
5370
+ */
5371
+ export declare function identify(userId: string, traits?: Record<string, unknown>): void;
5372
+ /** Attach this person to a company, account or team the app knows about. */
5373
+ export declare function group(groupId: string, traits?: Record<string, unknown>): void;
5374
+ /** Link a past anonymous identity to a known one. */
5375
+ export declare function alias(userId: string, previousId?: string): void;
5376
+ /** Record that this app was opened. Called once automatically on load. */
5377
+ export declare function page(): void;
5288
5378
  `,
5289
5379
  "core-scheduler.d.ts": `/**
5290
5380
  * Core Scheduled Jobs Framework
@@ -5936,8 +6026,6 @@ export type Doc<T> = {
5936
6026
  * - Security validation on all field names
5937
6027
  */
5938
6028
  export declare class EntityDO extends DurableObject<Env> {
5939
- ctx: DurableObjectState;
5940
- env: Env;
5941
6029
  private _tableReady;
5942
6030
  private _migrationDone;
5943
6031
  constructor(ctx: DurableObjectState, env: Env);
@@ -6942,13 +7030,6 @@ export declare function toChannelName(appName: string): string;
6942
7030
  * these entries in the observability timeline.
6943
7031
  */
6944
7032
  export declare function flog(level: 'info' | 'warn' | 'error', system: string, message: string, data?: Record<string, unknown>): void;
6945
- /**
6946
- * Emit an event to the workspace unified event stream.
6947
- * Fire-and-forget: uses ctx.waitUntil so it doesn't block the response.
6948
- * Silently skips if workspace env vars are not configured (standalone mode).
6949
- *
6950
- * Automatically routes events to a channel derived from APP_NAME when available.
6951
- */
6952
7033
  export declare function emitEvent(ctx: WaitUntilContext, env: EventEnv, event: EmitEventParams): void;
6953
7034
  export {};
6954
7035
  `,
@@ -7023,7 +7104,109 @@ export declare const isStr: (s: unknown) => s is string;
7023
7104
  * that would cause structured clone to fail in ctx.storage.put().
7024
7105
  */
7025
7106
  export declare function safeClone<T>(value: T, fallback?: T): T;
7026
- export declare function platformFetch(env: Env, url: string | URL, init?: RequestInit): Promise<Response>;
7107
+ /**
7108
+ * Platform fetch - routes requests through WorkspaceObject DO for production workers.
7109
+ *
7110
+ * Workers for Platforms (WfP) workers cannot reliably make HTTP requests back to their
7111
+ * parent platform worker (they get 522 timeouts). This utility routes platform API calls
7112
+ * through the WorkspaceObject Durable Object binding, which works across worker boundaries.
7113
+ *
7114
+ * Supported paths:
7115
+ * - /api/proxy/integrations/* - Integration proxy (Nango)
7116
+ * - /api/proxy/openai/* - AI Gateway proxy
7117
+ * - /api/storage/presign - Storage presigned URLs
7118
+ *
7119
+ * For preview containers (DEPLOYMENT_MODE !== 'production'), uses standard fetch.
7120
+ *
7121
+ * @example
7122
+ * \`\`\`typescript
7123
+ * // Instead of:
7124
+ * const response = await fetch('https://runwork.ai/api/proxy/integrations/proxy/contacts', options);
7125
+ *
7126
+ * // Use:
7127
+ * const response = await platformFetch(env, 'https://runwork.ai/api/proxy/integrations/proxy/contacts', options);
7128
+ * \`\`\`
7129
+ */
7130
+ /**
7131
+ * What the app was doing when it made a call.
7132
+ *
7133
+ * Internal plumbing, deliberately not exported from the package: app authors
7134
+ * never set this, the framework establishes it at each entry point (schedule
7135
+ * tick, workflow run, route handler, agent turn) and \`platformFetch\` below
7136
+ * reads it. It lives beside \`platformFetch\` because that is its only consumer.
7137
+ *
7138
+ * WHY IT EXISTS: usage rows record \`appId\` and \`userId\` and nothing about what
7139
+ * was executing, so a HubSpot call made by a nightly schedule cannot be told
7140
+ * apart from one a person made from their own agent. \`runId\` is the field that
7141
+ * cannot be reconstructed later: it links one run's AI calls and integration
7142
+ * calls together, which is what per-run cost is built from.
7143
+ *
7144
+ * Design: docs/plans/2026-08-28-baselines-capture-redesign.md section 4.1.
7145
+ */
7146
+ export interface RunContext {
7147
+ /**
7148
+ * WHAT was executing. Only constructs the framework itself runs, never
7149
+ * "where the caller was" (a browser, MCP, the CLI): that is a different
7150
+ * question and \`audit_logs.actor_type\` owns it.
7151
+ */
7152
+ kind: 'schedule' | 'workflow' | 'endpoint' | 'route' | 'agent';
7153
+ /**
7154
+ * Human-readable name, following the convention the audit rows already use:
7155
+ * a schedule/workflow/agent slug, or \`METHOD /path\` for endpoints and routes.
7156
+ *
7157
+ * OMITTED rather than defaulted when genuinely unknown. A placeholder string
7158
+ * like 'unknown' becomes a value every query has to filter out, and it is
7159
+ * indistinguishable from an app that named something 'unknown'.
7160
+ */
7161
+ name?: string;
7162
+ /**
7163
+ * App-local registry key, so a usage row joins back to a registration. The
7164
+ * app id is a column of its own, so this is the part after it: a slug for
7165
+ * schedules, workflows and agents, \`METHOD /path\` for endpoints.
7166
+ */
7167
+ resourceKey?: string;
7168
+ /** One id shared by everything emitted inside this execution. */
7169
+ runId: string;
7170
+ /** The enclosing run, for workflow steps, nested calls and sub-agents. */
7171
+ parentRunId?: string;
7172
+ trigger?: 'cron' | 'manual' | 'webhook' | 'chat' | 'api';
7173
+ /**
7174
+ * The person who started this run. Named to match the governance program's
7175
+ * \`DelegationChain.triggererUserId\` (\`worker/types/permissions.ts\`), which is
7176
+ * the same fact: one name for it across both programs.
7177
+ *
7178
+ * ATTRIBUTION, NEVER AUTHORITY. This travels on a header from the app, behind
7179
+ * the shared WORKSPACE_API_KEY, so an app can put any user id here. The
7180
+ * platform may RECORD it; nothing may ever AUTHORIZE on it. Whose authority a
7181
+ * run borrows is resolved platform-side by PermissionService, not stated by
7182
+ * the caller.
7183
+ */
7184
+ triggererUserId?: string;
7185
+ attempt?: number;
7186
+ }
7187
+ /** Header carrying the context to the platform side, which writes the audit row. */
7188
+ export declare const RUN_CONTEXT_HEADER = "x-runwork-run-context";
7189
+ /**
7190
+ * Run \`fn\` with \`ctx\` as the ambient execution context.
7191
+ *
7192
+ * Nesting is automatic: entering a context inside another records the outer
7193
+ * \`runId\` as \`parentRunId\` unless the caller set one. That is what makes a
7194
+ * sub-agent's calls point at the run that started it. Re-entering the SAME
7195
+ * run id is not nesting (a workflow resumes into its own run across wake-ups)
7196
+ * and must not make a run its own parent.
7197
+ */
7198
+ export declare function withRunContext<T>(ctx: RunContext, fn: () => T): T;
7199
+ /** The current execution context, or undefined outside any entry point. */
7200
+ export declare function getRunContext(): RunContext | undefined;
7201
+ /**
7202
+ * Merge the context header into request headers.
7203
+ *
7204
+ * One serialised header rather than several, so adding a field never needs a
7205
+ * matching change in the platform's parsing. Never overwrites a header the
7206
+ * caller set: a caller that knows its own context beats the ambient one.
7207
+ */
7208
+ export declare function withRunContextHeader(init?: HeadersInit): HeadersInit | undefined;
7209
+ export declare function platformFetch(env: Env, url: string | URL, rawInit?: RequestInit): Promise<Response>;
7027
7210
  /**
7028
7211
  * Workspace API fetch - routes workspace service requests through WorkspaceObject DO
7029
7212
  * for production WfP workers, avoiding 522 recursive invocation errors.
@@ -7036,7 +7219,7 @@ export declare function platformFetch(env: Env, url: string | URL, init?: Reques
7036
7219
  * @param path - Workspace API sub-path (e.g., '/ingest-event')
7037
7220
  * @param init - Standard fetch options
7038
7221
  */
7039
- export declare function workspaceApiFetch(env: Pick<Env, 'WORKSPACE_API_URL' | 'WORKSPACE_API_KEY' | 'WORKSPACE_ID' | 'DEPLOYMENT_MODE' | 'WorkspaceObject'>, path: string, init?: RequestInit): Promise<Response>;
7222
+ export declare function workspaceApiFetch(env: Pick<Env, 'WORKSPACE_API_URL' | 'WORKSPACE_API_KEY' | 'WORKSPACE_ID' | 'DEPLOYMENT_MODE' | 'WorkspaceObject'>, path: string, rawInit?: RequestInit): Promise<Response>;
7040
7223
  `,
7041
7224
  "workflows.d.ts": `import type { WorkflowDefinition } from './core-workflow-types';
7042
7225
  /**
@@ -7231,7 +7414,7 @@ export declare class BaseAgent extends AIChatAgent<Env> {
7231
7414
  * - Types are defined in core-workflow-types.ts
7232
7415
  * - Native DO-based implementation is in core-workflow-instance.ts and core-workflow-coordinator.ts
7233
7416
  */
7234
- import type { Env } from './core-utils';
7417
+ import { type Env } from './core-utils';
7235
7418
  export type { WorkflowResult, WorkflowStatus, WorkflowInstanceInfo, WorkflowDefinition, WorkflowContext, WorkflowStepUtilities, StepOptions, WaitEventOptions, WorkflowLogger, WorkflowState, WorkflowStatusResponse, } from './core-workflow-types';
7236
7419
  import type { WorkflowInstanceInfo } from './core-workflow-types';
7237
7420
  /**
@@ -7780,8 +7963,8 @@ export declare const integrationApiSchema: z.ZodObject<{
7780
7963
  endpoint: z.ZodString;
7781
7964
  data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7782
7965
  }, "strip", z.ZodTypeAny, {
7783
- method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7784
7966
  endpoint: string;
7967
+ method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE";
7785
7968
  data?: Record<string, unknown> | undefined;
7786
7969
  }, {
7787
7970
  endpoint: string;
@@ -8022,7 +8205,7 @@ function createKeyboardListener() {
8022
8205
  }
8023
8206
 
8024
8207
  // src/generated/version.ts
8025
- var VERSION = "0.25.3";
8208
+ var VERSION = "0.27.0";
8026
8209
 
8027
8210
  // src/commands/dev.ts
8028
8211
  var exports_dev = {};
@@ -8103,6 +8286,7 @@ async function execDev(options) {
8103
8286
  await ensureGitCredentialHelper(creds.baseUrl);
8104
8287
  }
8105
8288
  ensureRunworkRemote(cwd, client.getGitRemoteUrl(config.workspaceId, config.appId));
8289
+ let editsUnprotected = false;
8106
8290
  const oldManifest = await loadManifest(cwd);
8107
8291
  if (oldManifest) {
8108
8292
  const userEdits = await detectUserEdits(cwd, oldManifest);
@@ -8126,9 +8310,22 @@ async function execDev(options) {
8126
8310
  execFileSync("git", ["add", "--", ...userEdits], { stdio: "pipe" });
8127
8311
  execFileSync("git", ["commit", "-m", "sync: user edits"], { stdio: "pipe" });
8128
8312
  } catch {}
8313
+ editsUnprotected = hasTrackedChanges(cwd);
8314
+ if (editsUnprotected) {
8315
+ if (useJson) {
8316
+ jsonLine({
8317
+ event: "startup",
8318
+ phase: "user_edits_unprotected",
8319
+ timestamp: ts(),
8320
+ warning: "Could not commit your edits (a commit hook or lock may be interfering). Skipping the template update so they are not lost."
8321
+ });
8322
+ } else {
8323
+ console.warn(yellow("Could not commit your edits (a commit hook or lock may be interfering). Skipping the template update so they are not lost."));
8324
+ }
8325
+ }
8129
8326
  }
8130
8327
  }
8131
- if (!noSync) {
8328
+ if (!noSync && !editsUnprotected) {
8132
8329
  if (useJson) {
8133
8330
  jsonLine({ event: "startup", phase: "template_update", timestamp: ts() });
8134
8331
  } else {
@@ -8971,10 +9168,39 @@ function startAppProbeScript(pattern) {
8971
9168
  function isCommandNotFoundExit(code) {
8972
9169
  return typeof code === "number" && COMMAND_NOT_FOUND_EXIT_CODES.includes(code);
8973
9170
  }
8974
- var PATH_REFRESH_FAILED_MARKER = "__runwork_path_refresh_failed__", WINDOWS_PATH_REFRESH, COMMAND_NOT_FOUND_EXIT_CODES;
9171
+ function expandWindowsPathTemplate(path2, resolved) {
9172
+ for (const [placeholder, defaults] of Object.entries(WINDOWS_PATH_PLACEHOLDERS)) {
9173
+ if (!path2.startsWith(placeholder))
9174
+ continue;
9175
+ const rest = path2.slice(placeholder.length).replace(/^[/\\]/, "");
9176
+ const fromEnv = resolved[placeholder];
9177
+ if (fromEnv) {
9178
+ return { path: `${fromEnv.replace(/[/\\]+$/, "")}\\${rest.replace(/\//g, "\\")}`, needsHomeJoin: false };
9179
+ }
9180
+ if ("absoluteDefault" in defaults) {
9181
+ return { path: `${defaults.absoluteDefault}\\${rest.replace(/\//g, "\\")}`, needsHomeJoin: false };
9182
+ }
9183
+ return { path: `${defaults.homeRelativeDefault}/${rest}`, needsHomeJoin: true };
9184
+ }
9185
+ return { path: path2, needsHomeJoin: !/^([A-Za-z]:[\\/]|\\\\|\/)/.test(path2) };
9186
+ }
9187
+ function parseConfigDeclaredPath(text2, key) {
9188
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9189
+ const match = new RegExp(`^\\s*${escapedKey}\\s*=\\s*"((?:[^"\\\\]|\\\\.)*)"`, "m").exec(text2);
9190
+ if (!match)
9191
+ return null;
9192
+ const path2 = match[1].replace(/\\(.)/g, "$1");
9193
+ return path2 || null;
9194
+ }
9195
+ var PATH_REFRESH_FAILED_MARKER = "__runwork_path_refresh_failed__", WINDOWS_PATH_REFRESH, COMMAND_NOT_FOUND_EXIT_CODES, WINDOWS_PATH_PLACEHOLDERS;
8975
9196
  var init_detection_probes = __esm(() => {
8976
9197
  WINDOWS_PATH_REFRESH = "try { $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + " + "[Environment]::GetEnvironmentVariable('Path','User') + ';' + $env:Path } " + `catch { Write-Output '${PATH_REFRESH_FAILED_MARKER}' }; `;
8977
9198
  COMMAND_NOT_FOUND_EXIT_CODES = [127, 9009];
9199
+ WINDOWS_PATH_PLACEHOLDERS = {
9200
+ "%APPDATA%": { homeRelativeDefault: "AppData/Roaming" },
9201
+ "%LOCALAPPDATA%": { homeRelativeDefault: "AppData/Local" },
9202
+ "%ProgramFiles%": { absoluteDefault: "C:\\Program Files" }
9203
+ };
8978
9204
  });
8979
9205
 
8980
9206
  // src/utils/which.ts
@@ -9387,6 +9613,9 @@ function getAgent(slug) {
9387
9613
  function getAgents() {
9388
9614
  return AGENT_REGISTRY;
9389
9615
  }
9616
+ function getDetectableAgents() {
9617
+ return AGENT_REGISTRY.filter((a) => a.detection);
9618
+ }
9390
9619
  function isConnectOnlyAgent(agent) {
9391
9620
  if (!agent)
9392
9621
  return false;
@@ -9415,11 +9644,15 @@ function resolveAgentCli(agent) {
9415
9644
  return;
9416
9645
  return detectionBinaryTarget(agent.detection);
9417
9646
  }
9418
- var CLAUDE_CODE_NATIVE_INSTALL_PATHS, CHATGPT_SECURITY_SETTINGS_URL = "https://chatgpt.com/plugins#settings/Security", CHATGPT_CREATE_CONNECTOR_URL = "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins", AGENT_REGISTRY, slugIndex, nameIndex, CUSTOM_ADAPTER_SLUGS, getRegistryAgent, getRegistryAgents;
9647
+ var CLAUDE_CODE_NATIVE_INSTALL_PATHS, CODEX_CONFIG_FILE = ".codex/config.toml", CODEX_BUNDLED_CLI_PATHS, CHATGPT_SECURITY_SETTINGS_URL = "https://chatgpt.com/plugins#settings/Security", CHATGPT_CREATE_CONNECTOR_URL = "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins", AGENT_REGISTRY, slugIndex, nameIndex, CUSTOM_ADAPTER_SLUGS, getRegistryAgent, getRegistryAgents;
9419
9648
  var init_registry_data = __esm(() => {
9420
9649
  CLAUDE_CODE_NATIVE_INSTALL_PATHS = [
9421
9650
  { macos: ".local/bin/claude", linux: ".local/bin/claude", windows: ".local/bin/claude.exe" }
9422
9651
  ];
9652
+ CODEX_BUNDLED_CLI_PATHS = [
9653
+ { macos: "/Applications/ChatGPT.app/Contents/Resources/codex" },
9654
+ { macos: "/Applications/Codex.app/Contents/Resources/codex" }
9655
+ ];
9423
9656
  AGENT_REGISTRY = [
9424
9657
  {
9425
9658
  slug: "claude-code",
@@ -9477,7 +9710,7 @@ var init_registry_data = __esm(() => {
9477
9710
  {
9478
9711
  method: "path",
9479
9712
  target: {
9480
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9713
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9481
9714
  linux: ".config/Claude/claude_desktop_config.json"
9482
9715
  }
9483
9716
  }
@@ -9490,7 +9723,7 @@ var init_registry_data = __esm(() => {
9490
9723
  skillsPaths: { global: ".claude/skills", project: ".claude/skills" },
9491
9724
  mcpConfigPath: {
9492
9725
  macos: "Library/Application Support/Claude/claude_desktop_config.json",
9493
- windows: "AppData/Roaming/Claude/claude_desktop_config.json",
9726
+ windows: "%APPDATA%/Claude/claude_desktop_config.json",
9494
9727
  linux: ".config/Claude/claude_desktop_config.json"
9495
9728
  },
9496
9729
  mcpConfigKey: "mcpServers",
@@ -9651,8 +9884,18 @@ var init_registry_data = __esm(() => {
9651
9884
  name: "Codex CLI",
9652
9885
  description: "AI in your terminal by OpenAI, great for coding, debugging, and automating dev tasks",
9653
9886
  category: "cli",
9654
- detection: { method: "binary", target: "codex" },
9655
- launch: { cli: "codex", cliAcceptsPrompt: true },
9887
+ detection: {
9888
+ method: "any",
9889
+ target: [
9890
+ { method: "binary", target: "codex" },
9891
+ {
9892
+ method: "config-value",
9893
+ target: { file: CODEX_CONFIG_FILE, key: "CODEX_CLI_PATH" }
9894
+ },
9895
+ ...CODEX_BUNDLED_CLI_PATHS.map((target) => ({ method: "path", target }))
9896
+ ]
9897
+ },
9898
+ launch: { cli: "codex", cliAcceptsPrompt: true, cliWellKnownPaths: CODEX_BUNDLED_CLI_PATHS },
9656
9899
  logo: "codex",
9657
9900
  downloadUrl: "https://learn.chatgpt.com/docs/codex/cli",
9658
9901
  install: {
@@ -9689,6 +9932,7 @@ var init_registry_data = __esm(() => {
9689
9932
  launch: { url: "https://chatgpt.com/?prompt={prompt}" },
9690
9933
  logo: "openai",
9691
9934
  downloadUrl: "https://chatgpt.com",
9935
+ suggestsCli: "codex",
9692
9936
  firstClass: true,
9693
9937
  resumeCapability: {
9694
9938
  mode: "url-prompt",
@@ -9745,6 +9989,7 @@ var init_registry_data = __esm(() => {
9745
9989
  ]
9746
9990
  },
9747
9991
  launch: { app: { macos: "ChatGPT Classic", windows: "ChatGPT Classic" }, bundleId: { macos: "com.openai.chat" }, appxPackage: "OpenAI.ChatGPT", url: "https://chatgpt.com/?prompt={prompt}" },
9992
+ suggestsCli: "codex",
9748
9993
  logo: "openai",
9749
9994
  downloadUrl: "https://chatgpt.com/download",
9750
9995
  downloadUrls: {
@@ -9800,8 +10045,8 @@ var init_registry_data = __esm(() => {
9800
10045
  target: [
9801
10046
  { method: "binary", target: "windsurf" },
9802
10047
  { method: "path", target: { macos: "/Applications/Windsurf.app" } },
9803
- { method: "path", target: { windows: "AppData/Local/Programs/Windsurf" } },
9804
- { method: "path", target: { windows: "C:\\Program Files\\Windsurf" } }
10048
+ { method: "path", target: { windows: "%LOCALAPPDATA%/Programs/Windsurf" } },
10049
+ { method: "path", target: { windows: "%ProgramFiles%/Windsurf" } }
9805
10050
  ]
9806
10051
  },
9807
10052
  launch: { app: { macos: "Windsurf", windows: "Windsurf" } },
@@ -9881,7 +10126,14 @@ var init_registry_data = __esm(() => {
9881
10126
  name: "GitHub Copilot (VS Code)",
9882
10127
  description: "GitHub's AI pair programmer in VS Code",
9883
10128
  category: "extension",
9884
- detection: { method: "binary", target: "code" },
10129
+ detection: {
10130
+ method: "path",
10131
+ target: {
10132
+ macos: "Library/Application Support/Code/User/globalStorage/github.copilot",
10133
+ windows: "%APPDATA%/Code/User/globalStorage/github.copilot",
10134
+ linux: ".config/Code/User/globalStorage/github.copilot"
10135
+ }
10136
+ },
9885
10137
  launch: { app: { macos: "Visual Studio Code", windows: "Code" }, cli: "code" },
9886
10138
  logo: "vscode",
9887
10139
  downloadUrl: "https://marketplace.visualstudio.com/items?itemName=GitHub.copilot",
@@ -9983,12 +10235,145 @@ var init_registry_data = __esm(() => {
9983
10235
  getRegistryAgents = getAgents;
9984
10236
  });
9985
10237
 
10238
+ // src/agents/detection.ts
10239
+ import { execFile } from "child_process";
10240
+ import { existsSync as existsSync26, readFileSync as readFileSync23 } from "fs";
10241
+ import { homedir as homedir6, platform as platform2 } from "os";
10242
+ import { join as join22 } from "path";
10243
+ import { promisify } from "util";
10244
+ function isWindows() {
10245
+ return platform2() === "win32";
10246
+ }
10247
+ function toList(value) {
10248
+ return Array.isArray(value) ? value : [value];
10249
+ }
10250
+ async function runPowerShell(script) {
10251
+ const execFileAsync = promisify(execFile);
10252
+ try {
10253
+ await execFileAsync("powershell", ["-NoProfile", "-Command", script]);
10254
+ return true;
10255
+ } catch {
10256
+ return false;
10257
+ }
10258
+ }
10259
+ function resolveDetectionPath(target) {
10260
+ const resolved = resolvePlatformString(target);
10261
+ if (!resolved)
10262
+ return null;
10263
+ const expanded = expandWindowsPathTemplate(resolved, {
10264
+ "%APPDATA%": process.env.APPDATA,
10265
+ "%LOCALAPPDATA%": process.env.LOCALAPPDATA,
10266
+ "%ProgramFiles%": process.env.ProgramFiles
10267
+ });
10268
+ return expanded.needsHomeJoin ? join22(homedir6(), expanded.path) : expanded.path;
10269
+ }
10270
+ function checkPath(target) {
10271
+ const absolute = resolveDetectionPath(target);
10272
+ if (!absolute)
10273
+ return null;
10274
+ return existsSync26(absolute) ? absolute : null;
10275
+ }
10276
+ function checkConfigDeclaredPath(target) {
10277
+ const configPath = resolveDetectionPath(target.file);
10278
+ if (!configPath)
10279
+ return null;
10280
+ let text2;
10281
+ try {
10282
+ if (!existsSync26(configPath))
10283
+ return null;
10284
+ text2 = readFileSync23(configPath, "utf-8");
10285
+ } catch {
10286
+ return null;
10287
+ }
10288
+ const declared = parseConfigDeclaredPath(text2, target.key);
10289
+ return declared && existsSync26(declared) ? declared : null;
10290
+ }
10291
+ async function checkMacosBundleId(target) {
10292
+ if (platform2() !== "darwin")
10293
+ return false;
10294
+ const execFileAsync = promisify(execFile);
10295
+ for (const id of toList(target)) {
10296
+ if (!isValidBundleId(id))
10297
+ continue;
10298
+ try {
10299
+ await execFileAsync("sh", ["-c", macosBundleIdProbeScript(id)]);
10300
+ return true;
10301
+ } catch {}
10302
+ }
10303
+ return false;
10304
+ }
10305
+ async function checkWindowsAppxPackage(target) {
10306
+ if (!isWindows())
10307
+ return false;
10308
+ const probes = toList(target).map((pkg) => runPowerShell(appxPackageProbeScript(pkg)));
10309
+ const results = await Promise.all(probes);
10310
+ return results.some(Boolean);
10311
+ }
10312
+ async function checkWindowsStartApp(target) {
10313
+ if (!isWindows())
10314
+ return false;
10315
+ const probes = toList(target).map((pattern) => runPowerShell(startAppProbeScript(pattern)));
10316
+ const results = await Promise.all(probes);
10317
+ return results.some(Boolean);
10318
+ }
10319
+ async function runAgentDetectionDetailed(detection) {
10320
+ switch (detection.method) {
10321
+ case "binary": {
10322
+ const target = resolvePlatformString(detection.target);
10323
+ const resolved = target ? whichBinary(target) : null;
10324
+ return resolved ? { detected: true, via: "binary", resolvedPath: resolved } : NOT_DETECTED;
10325
+ }
10326
+ case "path": {
10327
+ const resolved = checkPath(detection.target);
10328
+ return resolved ? { detected: true, via: "path", resolvedPath: resolved } : NOT_DETECTED;
10329
+ }
10330
+ case "config-value": {
10331
+ const resolved = checkConfigDeclaredPath(detection.target);
10332
+ return resolved ? { detected: true, via: "config-value", resolvedPath: resolved } : NOT_DETECTED;
10333
+ }
10334
+ case "windows-appx":
10335
+ return await checkWindowsAppxPackage(detection.target) ? { detected: true, via: "windows-appx" } : NOT_DETECTED;
10336
+ case "windows-start-app":
10337
+ return await checkWindowsStartApp(detection.target) ? { detected: true, via: "windows-start-app" } : NOT_DETECTED;
10338
+ case "macos-bundle-id":
10339
+ return await checkMacosBundleId(detection.target) ? { detected: true, via: "macos-bundle-id" } : NOT_DETECTED;
10340
+ case "any": {
10341
+ const probes = await Promise.all(detection.target.map((p) => runAgentDetectionDetailed(p)));
10342
+ return probes.find((p) => p.detected) ?? NOT_DETECTED;
10343
+ }
10344
+ case "always":
10345
+ return { detected: true, via: "always" };
10346
+ default:
10347
+ return NOT_DETECTED;
10348
+ }
10349
+ }
10350
+ async function runAgentDetection(detection) {
10351
+ return (await runAgentDetectionDetailed(detection)).detected;
10352
+ }
10353
+
10354
+ class RegistryDetectedAdapter {
10355
+ async detect() {
10356
+ const def = getRegistryAgent(this.slug);
10357
+ if (!def)
10358
+ return false;
10359
+ return runAgentDetection(def.detection);
10360
+ }
10361
+ }
10362
+ var NOT_DETECTED;
10363
+ var init_detection = __esm(() => {
10364
+ init_which();
10365
+ init_registry_data();
10366
+ init_registry();
10367
+ init_detection_probes();
10368
+ NOT_DETECTED = { detected: false };
10369
+ });
10370
+
9986
10371
  // src/agents/registry.ts
9987
- import { platform as platform2, homedir as homedir6 } from "os";
9988
- import { isAbsolute as isAbsolute2, join as join22 } from "path";
9989
- import { existsSync as existsSync26 } from "fs";
10372
+ import { platform as platform3, homedir as homedir7 } from "os";
10373
+ import { isAbsolute as isAbsolute3, join as join23 } from "path";
10374
+ import { existsSync as existsSync27 } from "fs";
9990
10375
  function getNodePlatform() {
9991
- const p = platform2();
10376
+ const p = platform3();
9992
10377
  if (p === "darwin")
9993
10378
  return "macos";
9994
10379
  if (p === "win32")
@@ -10005,7 +10390,7 @@ function resolveToAbsolute(ps, scope) {
10005
10390
  const resolved = resolvePlatformString(ps);
10006
10391
  if (!resolved)
10007
10392
  return;
10008
- return scope === "global" ? join22(homedir6(), resolved) : join22(process.cwd(), resolved);
10393
+ return scope === "global" ? join23(homedir7(), resolved) : join23(process.cwd(), resolved);
10009
10394
  }
10010
10395
  function resolveAgentCliCommand(slug) {
10011
10396
  const agent = getAgent(slug);
@@ -10014,19 +10399,40 @@ function resolveAgentCliCommand(slug) {
10014
10399
  return null;
10015
10400
  if (whichBinary(cli))
10016
10401
  return cli;
10402
+ const declared = vendorDeclaredCliPath(slug);
10403
+ if (declared)
10404
+ return declared;
10017
10405
  for (const candidate of agent?.launch?.cliWellKnownPaths ?? []) {
10018
10406
  const resolved = resolvePlatformString(candidate);
10019
10407
  if (!resolved)
10020
10408
  continue;
10021
- const absolute = isAbsolute2(resolved) ? resolved : join22(homedir6(), resolved);
10022
- if (existsSync26(absolute))
10409
+ const absolute = isAbsolute3(resolved) ? resolved : join23(homedir7(), resolved);
10410
+ if (existsSync27(absolute))
10023
10411
  return absolute;
10024
10412
  }
10025
10413
  return null;
10026
10414
  }
10415
+ function configValueMatchers(detection) {
10416
+ if (!detection)
10417
+ return [];
10418
+ if (detection.method === "config-value")
10419
+ return [detection.target];
10420
+ if (detection.method === "any")
10421
+ return detection.target.flatMap(configValueMatchers);
10422
+ return [];
10423
+ }
10424
+ function vendorDeclaredCliPath(slug) {
10425
+ for (const matcher of configValueMatchers(getAgent(slug)?.detection)) {
10426
+ const resolved = checkConfigDeclaredPath(matcher);
10427
+ if (resolved)
10428
+ return resolved;
10429
+ }
10430
+ return null;
10431
+ }
10027
10432
  var init_registry = __esm(() => {
10028
10433
  init_which();
10029
10434
  init_registry_data();
10435
+ init_detection();
10030
10436
  init_registry_data();
10031
10437
  });
10032
10438
 
@@ -10867,6 +11273,8 @@ function scanClaudeJsonlHead(head) {
10867
11273
  result.firstTimestamp = o.timestamp;
10868
11274
  if (!result.cwd && typeof o.cwd === "string")
10869
11275
  result.cwd = o.cwd;
11276
+ if (!result.surface && typeof o.entrypoint === "string")
11277
+ result.surface = o.entrypoint;
10870
11278
  if (!result.title && o.type === "ai-title" && typeof o.aiTitle === "string") {
10871
11279
  result.title = titleFromText(o.aiTitle);
10872
11280
  }
@@ -10892,7 +11300,7 @@ function scanClaudeJsonlHead(head) {
10892
11300
  }
10893
11301
  }
10894
11302
  }
10895
- if (result.title && result.firstTimestamp && result.cwd)
11303
+ if (result.title && result.firstTimestamp && result.cwd && result.surface)
10896
11304
  break;
10897
11305
  }
10898
11306
  if (!result.title && firstUserText)
@@ -10921,6 +11329,8 @@ function scanCodexRolloutHead(head) {
10921
11329
  result.cwd = p.cwd;
10922
11330
  if (typeof p.id === "string" && !result.sessionId)
10923
11331
  result.sessionId = p.id;
11332
+ if (typeof p.originator === "string" && !result.surface)
11333
+ result.surface = p.originator;
10924
11334
  }
10925
11335
  if (!result.title && o.type === "response_item" && p.type === "message" && p.role === "user" && Array.isArray(p.content)) {
10926
11336
  for (const item of p.content) {
@@ -11020,23 +11430,23 @@ function skillNameFromPath(path2) {
11020
11430
  }
11021
11431
 
11022
11432
  // src/utils/trash.ts
11023
- import { cpSync, existsSync as existsSync27, mkdirSync as mkdirSync13, readdirSync as readdirSync5, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync3 } from "fs";
11024
- import { basename as basename2, dirname as dirname6, join as join23 } from "path";
11025
- import { homedir as homedir7 } from "os";
11433
+ import { cpSync, existsSync as existsSync28, mkdirSync as mkdirSync13, readdirSync as readdirSync5, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync3 } from "fs";
11434
+ import { basename as basename2, dirname as dirname6, join as join24 } from "path";
11435
+ import { homedir as homedir8 } from "os";
11026
11436
  function trashRoot() {
11027
- return join23(homedir7(), ".runwork", "trash");
11437
+ return join24(homedir8(), ".runwork", "trash");
11028
11438
  }
11029
11439
  function batchDir(now) {
11030
11440
  const stamp = now.toISOString().replace(/[:.]/g, "-");
11031
- return join23(trashRoot(), `${stamp}-${process.pid}`);
11441
+ return join24(trashRoot(), `${stamp}-${process.pid}`);
11032
11442
  }
11033
11443
  function pruneTrash(now = new Date) {
11034
11444
  const root = trashRoot();
11035
- if (!existsSync27(root))
11445
+ if (!existsSync28(root))
11036
11446
  return;
11037
11447
  const cutoff = now.getTime() - TRASH_RETENTION_DAYS * 24 * 60 * 60 * 1000;
11038
11448
  for (const entry of readdirSync5(root)) {
11039
- const dir = join23(root, entry);
11449
+ const dir = join24(root, entry);
11040
11450
  try {
11041
11451
  if (statSync3(dir).mtimeMs < cutoff)
11042
11452
  rmSync4(dir, { recursive: true, force: true });
@@ -11044,7 +11454,7 @@ function pruneTrash(now = new Date) {
11044
11454
  }
11045
11455
  }
11046
11456
  function moveToTrash(sourcePath, reason, now = new Date) {
11047
- if (!existsSync27(sourcePath))
11457
+ if (!existsSync28(sourcePath))
11048
11458
  return null;
11049
11459
  if (!activeBatch || !activeBatch.startsWith(trashRoot())) {
11050
11460
  activeBatch = batchDir(now);
@@ -11052,8 +11462,8 @@ function moveToTrash(sourcePath, reason, now = new Date) {
11052
11462
  }
11053
11463
  const parent = basename2(dirname6(sourcePath));
11054
11464
  const grandparent = basename2(dirname6(dirname6(sourcePath)));
11055
- const destDir = join23(activeBatch, `${grandparent}__${parent}`.replace(/[^a-zA-Z0-9._-]/g, "-"));
11056
- const dest = join23(destDir, basename2(sourcePath));
11465
+ const destDir = join24(activeBatch, `${grandparent}__${parent}`.replace(/[^a-zA-Z0-9._-]/g, "-"));
11466
+ const dest = join24(destDir, basename2(sourcePath));
11057
11467
  try {
11058
11468
  mkdirSync13(destDir, { recursive: true });
11059
11469
  try {
@@ -11065,7 +11475,7 @@ function moveToTrash(sourcePath, reason, now = new Date) {
11065
11475
  } catch {
11066
11476
  return null;
11067
11477
  }
11068
- const manifestPath = join23(activeBatch, "manifest.json");
11478
+ const manifestPath = join24(activeBatch, "manifest.json");
11069
11479
  const manifest = readJsonOrNull(manifestPath) ?? { entries: [] };
11070
11480
  manifest.entries.push({ from: sourcePath, to: dest, reason, at: now.toISOString() });
11071
11481
  try {
@@ -11108,27 +11518,27 @@ function configHash(value) {
11108
11518
  var init_hash = () => {};
11109
11519
 
11110
11520
  // src/agents/utils/json-config.ts
11111
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync28 } from "fs";
11521
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync29 } from "fs";
11112
11522
  import { dirname as dirname7 } from "path";
11113
11523
  function isRunworkManagedKey(key) {
11114
11524
  return key === RUNWORK_WORKSPACE_MCP_NAME || key.startsWith(RUNWORK_MCP_PREFIX) || key.startsWith(RUNWORK_MCP_PREFIX_LEGACY);
11115
11525
  }
11116
11526
  function readJsonConfig(filePath) {
11117
- if (!existsSync28(filePath))
11527
+ if (!existsSync29(filePath))
11118
11528
  return {};
11119
11529
  try {
11120
- return JSON.parse(readFileSync23(filePath, "utf-8"));
11530
+ return JSON.parse(readFileSync24(filePath, "utf-8"));
11121
11531
  } catch (err) {
11122
11532
  console.warn(` [config] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}`);
11123
11533
  return {};
11124
11534
  }
11125
11535
  }
11126
11536
  function existingContentIsUnparseable(filePath) {
11127
- if (!existsSync28(filePath))
11537
+ if (!existsSync29(filePath))
11128
11538
  return { bad: false };
11129
11539
  let raw;
11130
11540
  try {
11131
- raw = readFileSync23(filePath, "utf-8");
11541
+ raw = readFileSync24(filePath, "utf-8");
11132
11542
  } catch {
11133
11543
  return { bad: false };
11134
11544
  }
@@ -11150,7 +11560,7 @@ function writeJsonConfig(filePath, config) {
11150
11560
  `);
11151
11561
  }
11152
11562
  function removeRunworkMcpServers(filePath, topKey) {
11153
- if (!existsSync28(filePath))
11563
+ if (!existsSync29(filePath))
11154
11564
  return false;
11155
11565
  const config = readJsonConfig(filePath);
11156
11566
  const existing = config[topKey] || {};
@@ -11205,25 +11615,25 @@ var init_json_config = __esm(() => {
11205
11615
  });
11206
11616
 
11207
11617
  // src/agents/utils/skill-removal.ts
11208
- import { existsSync as existsSync29, readdirSync as readdirSync6 } from "fs";
11209
- import { join as join24 } from "path";
11618
+ import { existsSync as existsSync30, readdirSync as readdirSync6 } from "fs";
11619
+ import { join as join25 } from "path";
11210
11620
  function removeMatchingSkillDirs(dir, allowed, reason = "skill removed") {
11211
- if (!allowed.size || !existsSync29(dir))
11621
+ if (!allowed.size || !existsSync30(dir))
11212
11622
  return;
11213
11623
  for (const entry of readdirSync6(dir)) {
11214
11624
  if (!allowed.has(entry))
11215
11625
  continue;
11216
- moveToTrash(join24(dir, entry), reason);
11626
+ moveToTrash(join25(dir, entry), reason);
11217
11627
  }
11218
11628
  }
11219
11629
  function removeMatchingSkillFiles(dir, allowed, suffix, reason = "skill removed") {
11220
- if (!allowed.size || !existsSync29(dir))
11630
+ if (!allowed.size || !existsSync30(dir))
11221
11631
  return;
11222
11632
  const names = new Set([...allowed].map((slug) => `${slug}${suffix}`));
11223
11633
  for (const entry of readdirSync6(dir)) {
11224
11634
  if (!names.has(entry))
11225
11635
  continue;
11226
- moveToTrash(join24(dir, entry), reason);
11636
+ moveToTrash(join25(dir, entry), reason);
11227
11637
  }
11228
11638
  }
11229
11639
  var init_skill_removal = __esm(() => {
@@ -11231,7 +11641,7 @@ var init_skill_removal = __esm(() => {
11231
11641
  });
11232
11642
 
11233
11643
  // src/agents/utils/instruction-hint.ts
11234
- import { existsSync as existsSync30, readFileSync as readFileSync24, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "fs";
11644
+ import { existsSync as existsSync31, readFileSync as readFileSync25, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "fs";
11235
11645
  import { dirname as dirname8 } from "path";
11236
11646
  function writeHintToFile(filePath, hint) {
11237
11647
  mkdirSync15(dirname8(filePath), { recursive: true });
@@ -11241,8 +11651,8 @@ ${hint}
11241
11651
  ${END_MARKER}`;
11242
11652
  }
11243
11653
  let content = "";
11244
- if (existsSync30(filePath)) {
11245
- content = readFileSync24(filePath, "utf-8");
11654
+ if (existsSync31(filePath)) {
11655
+ content = readFileSync25(filePath, "utf-8");
11246
11656
  }
11247
11657
  const startIdx = content.indexOf(START_MARKER);
11248
11658
  const endIdx = content.indexOf(END_MARKER);
@@ -11264,9 +11674,9 @@ ${END_MARKER}`;
11264
11674
  writeFileSync15(filePath, content);
11265
11675
  }
11266
11676
  function removeHintFromFile(filePath) {
11267
- if (!existsSync30(filePath))
11677
+ if (!existsSync31(filePath))
11268
11678
  return false;
11269
- let content = readFileSync24(filePath, "utf-8");
11679
+ let content = readFileSync25(filePath, "utf-8");
11270
11680
  const startIdx = content.indexOf(START_MARKER);
11271
11681
  const endIdx = content.indexOf(END_MARKER);
11272
11682
  if (startIdx < 0 || endIdx < 0)
@@ -11284,9 +11694,9 @@ function removeHintFromFile(filePath) {
11284
11694
  return true;
11285
11695
  }
11286
11696
  function removeTeamInstructionsFromFile(filePath) {
11287
- if (!existsSync30(filePath))
11697
+ if (!existsSync31(filePath))
11288
11698
  return false;
11289
- let content = readFileSync24(filePath, "utf-8");
11699
+ let content = readFileSync25(filePath, "utf-8");
11290
11700
  const startIdx = content.indexOf(TEAM_START_MARKER);
11291
11701
  const endIdx = content.indexOf(TEAM_END_MARKER);
11292
11702
  if (startIdx < 0 || endIdx < 0)
@@ -11306,8 +11716,8 @@ function removeTeamInstructionsFromFile(filePath) {
11306
11716
  function writeTeamInstructionsToFile(filePath, instructions) {
11307
11717
  mkdirSync15(dirname8(filePath), { recursive: true });
11308
11718
  let content = "";
11309
- if (existsSync30(filePath)) {
11310
- content = readFileSync24(filePath, "utf-8");
11719
+ if (existsSync31(filePath)) {
11720
+ content = readFileSync25(filePath, "utf-8");
11311
11721
  }
11312
11722
  const block = `${TEAM_START_MARKER}
11313
11723
  ${instructions}
@@ -11756,117 +12166,70 @@ function vlog(...args) {
11756
12166
  }
11757
12167
  var verbose = false;
11758
12168
 
11759
- // src/agents/detection.ts
11760
- import { execFile } from "child_process";
11761
- import { existsSync as existsSync31 } from "fs";
11762
- import { homedir as homedir8, platform as platform3 } from "os";
11763
- import { isAbsolute as isAbsolute3, join as join25 } from "path";
11764
- import { promisify } from "util";
11765
- function isWindows() {
11766
- return platform3() === "win32";
12169
+ // src/agents/transcript-sources.ts
12170
+ import { accessSync, constants, statSync as statSync4 } from "fs";
12171
+ function classifyFsErrorCode(code) {
12172
+ if (code === "ENOENT" || code === "ENOTDIR")
12173
+ return "missing-dir";
12174
+ if (code === "EACCES" || code === "EPERM")
12175
+ return "permission-denied";
12176
+ return "error";
11767
12177
  }
11768
- function toList(value) {
11769
- return Array.isArray(value) ? value : [value];
12178
+ function errorCode(err) {
12179
+ const e = err;
12180
+ return e && typeof e === "object" && typeof e.code === "string" ? e.code : undefined;
11770
12181
  }
11771
- async function runPowerShell(script) {
11772
- const execFileAsync = promisify(execFile);
12182
+ function probeTranscriptSource(source) {
12183
+ if (source.available && !source.available()) {
12184
+ return { status: "missing-tool", path: source.path, detail: source.tool ?? "unknown tool" };
12185
+ }
11773
12186
  try {
11774
- await execFileAsync("powershell", ["-NoProfile", "-Command", script]);
11775
- return true;
11776
- } catch {
11777
- return false;
12187
+ statSync4(source.path);
12188
+ } catch (err) {
12189
+ return { status: classifyFsErrorCode(errorCode(err)), path: source.path, detail: errorCode(err) };
11778
12190
  }
12191
+ try {
12192
+ accessSync(source.path, source.kind === "dir" ? constants.R_OK | constants.X_OK : constants.R_OK);
12193
+ } catch (err) {
12194
+ return { status: classifyFsErrorCode(errorCode(err)), path: source.path, detail: errorCode(err) };
12195
+ }
12196
+ return { status: "ok", path: source.path };
11779
12197
  }
11780
- function resolveDetectionPath(target) {
11781
- const resolved = resolvePlatformString(target);
11782
- if (!resolved)
11783
- return null;
11784
- return isAbsolute3(resolved) ? resolved : join25(homedir8(), resolved);
12198
+ function diagnoseTranscriptRead(agent, thrown) {
12199
+ const probe = summarizeTranscriptSources(agent.transcriptSources?.() ?? []);
12200
+ const base = { agentSlug: agent.slug, sessions: null };
12201
+ if (probe.status !== "ok" && probe.status !== "no-reader") {
12202
+ return {
12203
+ ...base,
12204
+ status: probe.status,
12205
+ path: probe.path,
12206
+ ...probe.detail ? { detail: probe.detail } : {}
12207
+ };
12208
+ }
12209
+ if (thrown !== undefined) {
12210
+ return { ...base, status: "error", detail: thrown instanceof Error ? thrown.message : String(thrown) };
12211
+ }
12212
+ return probe.status === "ok" ? { ...base, status: "error", path: probe.path, detail: "read returned no result" } : { ...base, status: "no-reader" };
11785
12213
  }
11786
- function checkPath(target) {
11787
- const absolute = resolveDetectionPath(target);
11788
- if (!absolute)
11789
- return null;
11790
- return existsSync31(absolute) ? absolute : null;
11791
- }
11792
- async function checkMacosBundleId(target) {
11793
- if (platform3() !== "darwin")
11794
- return false;
11795
- const execFileAsync = promisify(execFile);
11796
- for (const id of toList(target)) {
11797
- if (!isValidBundleId(id))
11798
- continue;
11799
- try {
11800
- await execFileAsync("sh", ["-c", macosBundleIdProbeScript(id)]);
11801
- return true;
11802
- } catch {}
11803
- }
11804
- return false;
11805
- }
11806
- async function checkWindowsAppxPackage(target) {
11807
- if (!isWindows())
11808
- return false;
11809
- const probes = toList(target).map((pkg) => runPowerShell(appxPackageProbeScript(pkg)));
11810
- const results = await Promise.all(probes);
11811
- return results.some(Boolean);
11812
- }
11813
- async function checkWindowsStartApp(target) {
11814
- if (!isWindows())
11815
- return false;
11816
- const probes = toList(target).map((pattern) => runPowerShell(startAppProbeScript(pattern)));
11817
- const results = await Promise.all(probes);
11818
- return results.some(Boolean);
11819
- }
11820
- async function runAgentDetectionDetailed(detection) {
11821
- switch (detection.method) {
11822
- case "binary": {
11823
- const target = resolvePlatformString(detection.target);
11824
- const resolved = target ? whichBinary(target) : null;
11825
- return resolved ? { detected: true, via: "binary", resolvedPath: resolved } : NOT_DETECTED;
11826
- }
11827
- case "path": {
11828
- const resolved = checkPath(detection.target);
11829
- return resolved ? { detected: true, via: "path", resolvedPath: resolved } : NOT_DETECTED;
11830
- }
11831
- case "windows-appx":
11832
- return await checkWindowsAppxPackage(detection.target) ? { detected: true, via: "windows-appx" } : NOT_DETECTED;
11833
- case "windows-start-app":
11834
- return await checkWindowsStartApp(detection.target) ? { detected: true, via: "windows-start-app" } : NOT_DETECTED;
11835
- case "macos-bundle-id":
11836
- return await checkMacosBundleId(detection.target) ? { detected: true, via: "macos-bundle-id" } : NOT_DETECTED;
11837
- case "any": {
11838
- const probes = await Promise.all(detection.target.map((p) => runAgentDetectionDetailed(p)));
11839
- return probes.find((p) => p.detected) ?? NOT_DETECTED;
11840
- }
11841
- case "always":
11842
- return { detected: true, via: "always" };
11843
- default:
11844
- return NOT_DETECTED;
11845
- }
11846
- }
11847
- async function runAgentDetection(detection) {
11848
- return (await runAgentDetectionDetailed(detection)).detected;
12214
+ function transcriptSourcesReadable(sources) {
12215
+ return summarizeTranscriptSources(sources).status === "ok";
11849
12216
  }
11850
-
11851
- class RegistryDetectedAdapter {
11852
- async detect() {
11853
- const def = getRegistryAgent(this.slug);
11854
- if (!def)
11855
- return false;
11856
- return runAgentDetection(def.detection);
12217
+ function summarizeTranscriptSources(sources) {
12218
+ if (sources.length === 0)
12219
+ return { status: "no-reader", path: "" };
12220
+ const probes = sources.map(probeTranscriptSource);
12221
+ const rank = ["permission-denied", "missing-tool", "error", "ok", "missing-dir"];
12222
+ for (const status of rank) {
12223
+ const hit = probes.find((p) => p.status === status);
12224
+ if (hit)
12225
+ return hit;
11857
12226
  }
12227
+ return probes[0];
11858
12228
  }
11859
- var NOT_DETECTED;
11860
- var init_detection = __esm(() => {
11861
- init_which();
11862
- init_registry_data();
11863
- init_registry();
11864
- init_detection_probes();
11865
- NOT_DETECTED = { detected: false };
11866
- });
12229
+ var init_transcript_sources = () => {};
11867
12230
 
11868
12231
  // src/agents/claude-code.ts
11869
- import { chmodSync, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync25, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync16 } from "fs";
12232
+ import { chmodSync, existsSync as existsSync32, mkdirSync as mkdirSync16, readFileSync as readFileSync26, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
11870
12233
  import { join as join26 } from "path";
11871
12234
  import { homedir as homedir9 } from "os";
11872
12235
  function getPluginJson() {
@@ -11910,6 +12273,7 @@ var init_claude_code = __esm(() => {
11910
12273
  init_instruction_hint();
11911
12274
  init_session_start_hook();
11912
12275
  init_detection();
12276
+ init_transcript_sources();
11913
12277
  ClaudeCodeAdapter = class ClaudeCodeAdapter extends RegistryDetectedAdapter {
11914
12278
  name = "Claude Code";
11915
12279
  slug = "claude-code";
@@ -12056,7 +12420,7 @@ ${instructions}`;
12056
12420
  let settings = {};
12057
12421
  if (hadFile) {
12058
12422
  try {
12059
- settings = JSON.parse(readFileSync25(settingsPath, "utf-8"));
12423
+ settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
12060
12424
  } catch {}
12061
12425
  }
12062
12426
  if (settings.permissions && typeof settings.permissions === "object") {
@@ -12117,7 +12481,7 @@ ${instructions}`;
12117
12481
  const settingsPath = join26(homedir9(), ".claude", "settings.json");
12118
12482
  if (existsSync32(settingsPath)) {
12119
12483
  try {
12120
- const settings = JSON.parse(readFileSync25(settingsPath, "utf-8"));
12484
+ const settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
12121
12485
  if (settings.permissions) {
12122
12486
  for (const key of ["allow", "deny"]) {
12123
12487
  const arr = settings.permissions[key];
@@ -12207,7 +12571,7 @@ ${instructions}`;
12207
12571
  const filePath = join26(cwdPath, file);
12208
12572
  let stat;
12209
12573
  try {
12210
- stat = statSync4(filePath);
12574
+ stat = statSync5(filePath);
12211
12575
  } catch {
12212
12576
  continue;
12213
12577
  }
@@ -12215,7 +12579,7 @@ ${instructions}`;
12215
12579
  continue;
12216
12580
  let content;
12217
12581
  try {
12218
- content = readFileSync25(filePath, "utf-8");
12582
+ content = readFileSync26(filePath, "utf-8");
12219
12583
  } catch {
12220
12584
  continue;
12221
12585
  }
@@ -12299,9 +12663,15 @@ ${instructions}`;
12299
12663
  return null;
12300
12664
  }
12301
12665
  }
12666
+ transcriptRoot() {
12667
+ return join26(homedir9(), ".claude", "projects");
12668
+ }
12669
+ transcriptSources() {
12670
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
12671
+ }
12302
12672
  async readSessionDigests(sinceISO) {
12303
12673
  try {
12304
- const projectsDir = join26(homedir9(), ".claude", "projects");
12674
+ const projectsDir = this.transcriptRoot();
12305
12675
  if (!existsSync32(projectsDir))
12306
12676
  return null;
12307
12677
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -12326,7 +12696,7 @@ ${instructions}`;
12326
12696
  const filePath = join26(cwdPath, file);
12327
12697
  let stat;
12328
12698
  try {
12329
- stat = statSync4(filePath);
12699
+ stat = statSync5(filePath);
12330
12700
  } catch {
12331
12701
  continue;
12332
12702
  }
@@ -12334,7 +12704,7 @@ ${instructions}`;
12334
12704
  continue;
12335
12705
  let content;
12336
12706
  try {
12337
- content = readFileSync25(filePath, "utf-8");
12707
+ content = readFileSync26(filePath, "utf-8");
12338
12708
  } catch {
12339
12709
  continue;
12340
12710
  }
@@ -12350,8 +12720,8 @@ ${instructions}`;
12350
12720
  }
12351
12721
  async listSessions(sinceISO) {
12352
12722
  try {
12353
- const projectsDir = join26(homedir9(), ".claude", "projects");
12354
- if (!existsSync32(projectsDir))
12723
+ const projectsDir = this.transcriptRoot();
12724
+ if (!transcriptSourcesReadable(this.transcriptSources()))
12355
12725
  return null;
12356
12726
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
12357
12727
  let cwdEntries;
@@ -12375,7 +12745,7 @@ ${instructions}`;
12375
12745
  const filePath = join26(cwdPath, file);
12376
12746
  let stat;
12377
12747
  try {
12378
- stat = statSync4(filePath);
12748
+ stat = statSync5(filePath);
12379
12749
  } catch {
12380
12750
  continue;
12381
12751
  }
@@ -12390,7 +12760,7 @@ ${instructions}`;
12390
12760
  const sessionId = file.slice(0, -".jsonl".length);
12391
12761
  let endedAt = null;
12392
12762
  try {
12393
- const stamp = JSON.parse(readFileSync25(join26(homedir9(), ".runwork", "sessions", `${sessionId}.ended.json`), "utf-8"));
12763
+ const stamp = JSON.parse(readFileSync26(join26(homedir9(), ".runwork", "sessions", `${sessionId}.ended.json`), "utf-8"));
12394
12764
  if (stamp && typeof stamp.endedAt === "string")
12395
12765
  endedAt = stamp.endedAt;
12396
12766
  } catch {}
@@ -12402,7 +12772,8 @@ ${instructions}`;
12402
12772
  startedAt: scanned.firstTimestamp,
12403
12773
  lastActivityAt: resolveLastActivity(filePath, stat.mtimeMs),
12404
12774
  endedAt,
12405
- transcriptPath: filePath
12775
+ transcriptPath: filePath,
12776
+ surface: scanned.surface ?? null
12406
12777
  });
12407
12778
  }
12408
12779
  }
@@ -12413,7 +12784,7 @@ ${instructions}`;
12413
12784
  }
12414
12785
  async readSkillUsage(lastSyncAt) {
12415
12786
  try {
12416
- const projectsDir = join26(homedir9(), ".claude", "projects");
12787
+ const projectsDir = this.transcriptRoot();
12417
12788
  if (!existsSync32(projectsDir))
12418
12789
  return null;
12419
12790
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -12448,7 +12819,7 @@ ${instructions}`;
12448
12819
  const filePath = join26(cwdPath, file);
12449
12820
  let fileStat;
12450
12821
  try {
12451
- fileStat = statSync4(filePath);
12822
+ fileStat = statSync5(filePath);
12452
12823
  } catch {
12453
12824
  continue;
12454
12825
  }
@@ -12456,7 +12827,7 @@ ${instructions}`;
12456
12827
  continue;
12457
12828
  let content;
12458
12829
  try {
12459
- content = readFileSync25(filePath, "utf-8");
12830
+ content = readFileSync26(filePath, "utf-8");
12460
12831
  } catch {
12461
12832
  continue;
12462
12833
  }
@@ -12704,7 +13075,7 @@ var init_claude_desktop_plugin_tree = __esm(() => {
12704
13075
  });
12705
13076
 
12706
13077
  // src/agents/claude-desktop.ts
12707
- import { existsSync as existsSync34, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, readFileSync as readFileSync26, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "fs";
13078
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, readFileSync as readFileSync27, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "fs";
12708
13079
  import { dirname as dirname9, join as join28 } from "path";
12709
13080
  import { homedir as homedir10, platform as platform4, tmpdir as tmpdir3 } from "os";
12710
13081
  function isRunworkRpmPluginName(name) {
@@ -12823,7 +13194,7 @@ function findRpmPluginByName(pluginName) {
12823
13194
  if (!existsSync34(manifestPath))
12824
13195
  return null;
12825
13196
  try {
12826
- const manifest = JSON.parse(readFileSync26(manifestPath, "utf-8"));
13197
+ const manifest = JSON.parse(readFileSync27(manifestPath, "utf-8"));
12827
13198
  const entry = manifest.plugins?.find((p) => p.name === pluginName);
12828
13199
  if (!entry?.id)
12829
13200
  return null;
@@ -12863,6 +13234,7 @@ function setCoworkPluginEnabled(pluginsDir, enabled) {
12863
13234
  var PLUGIN_NAME2 = "runwork", PLUGIN_VERSION2 = "1.0.0", PLUGIN_DESCRIPTION = "Skills and tools from your Runwork workspace", PLUGIN_AUTHOR_NAME = "Runwork", ClaudeDesktopAdapter;
12864
13235
  var init_claude_desktop = __esm(() => {
12865
13236
  init_types();
13237
+ init_transcript_sources();
12866
13238
  init_session_digest();
12867
13239
  init_json_config();
12868
13240
  init_instruction_hint();
@@ -13003,7 +13375,7 @@ var init_claude_desktop = __esm(() => {
13003
13375
  let desktopConfig = {};
13004
13376
  if (existsSync34(configPath)) {
13005
13377
  try {
13006
- desktopConfig = JSON.parse(readFileSync26(configPath, "utf-8"));
13378
+ desktopConfig = JSON.parse(readFileSync27(configPath, "utf-8"));
13007
13379
  } catch {}
13008
13380
  }
13009
13381
  if (!desktopConfig.preferences)
@@ -13042,7 +13414,7 @@ var init_claude_desktop = __esm(() => {
13042
13414
  if (!existsSync34(manifestPath))
13043
13415
  continue;
13044
13416
  try {
13045
- const manifest = JSON.parse(readFileSync26(manifestPath, "utf-8"));
13417
+ const manifest = JSON.parse(readFileSync27(manifestPath, "utf-8"));
13046
13418
  if (!Array.isArray(manifest.plugins))
13047
13419
  continue;
13048
13420
  const ours = manifest.plugins.filter((p) => isRunworkRpmPluginName(p.name));
@@ -13136,7 +13508,7 @@ var init_claude_desktop = __esm(() => {
13136
13508
  const filePath = join28(projPath, file);
13137
13509
  let stat;
13138
13510
  try {
13139
- stat = statSync5(filePath);
13511
+ stat = statSync6(filePath);
13140
13512
  } catch {
13141
13513
  continue;
13142
13514
  }
@@ -13144,7 +13516,7 @@ var init_claude_desktop = __esm(() => {
13144
13516
  continue;
13145
13517
  let content;
13146
13518
  try {
13147
- content = readFileSync26(filePath, "utf-8");
13519
+ content = readFileSync27(filePath, "utf-8");
13148
13520
  } catch {
13149
13521
  continue;
13150
13522
  }
@@ -13161,10 +13533,13 @@ var init_claude_desktop = __esm(() => {
13161
13533
  return null;
13162
13534
  }
13163
13535
  }
13536
+ transcriptSources() {
13537
+ return [{ path: getCoworkBaseDir(), kind: "dir" }];
13538
+ }
13164
13539
  async listSessions(sinceISO) {
13165
13540
  try {
13166
13541
  const baseDir = getCoworkBaseDir();
13167
- if (!existsSync34(baseDir))
13542
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13168
13543
  return null;
13169
13544
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
13170
13545
  let accounts;
@@ -13181,7 +13556,7 @@ var init_claude_desktop = __esm(() => {
13181
13556
  const metaPath = join28(orgPath, entry);
13182
13557
  let meta;
13183
13558
  try {
13184
- meta = JSON.parse(readFileSync26(metaPath, "utf-8"));
13559
+ meta = JSON.parse(readFileSync27(metaPath, "utf-8"));
13185
13560
  } catch {
13186
13561
  continue;
13187
13562
  }
@@ -13200,7 +13575,7 @@ var init_claude_desktop = __esm(() => {
13200
13575
  const filePath = join28(projPath, file);
13201
13576
  let stat;
13202
13577
  try {
13203
- stat = statSync5(filePath);
13578
+ stat = statSync6(filePath);
13204
13579
  } catch {
13205
13580
  continue;
13206
13581
  }
@@ -13217,7 +13592,8 @@ var init_claude_desktop = __esm(() => {
13217
13592
  title: typeof meta.title === "string" && meta.title ? meta.title : null,
13218
13593
  startedAt: typeof meta.createdAt === "number" ? new Date(meta.createdAt).toISOString() : null,
13219
13594
  lastActivityAt: new Date(lastActivityMs).toISOString(),
13220
- transcriptPath
13595
+ transcriptPath,
13596
+ surface: "local-agent"
13221
13597
  });
13222
13598
  }
13223
13599
  }
@@ -13283,7 +13659,7 @@ var init_claude_desktop = __esm(() => {
13283
13659
  const scheduledTasksPath = join28(claudeAppDir, "scheduled-tasks.json");
13284
13660
  if (existsSync34(scheduledTasksPath)) {
13285
13661
  try {
13286
- const raw = readFileSync26(scheduledTasksPath, "utf-8");
13662
+ const raw = readFileSync27(scheduledTasksPath, "utf-8");
13287
13663
  const parsed = JSON.parse(raw);
13288
13664
  const tasks = Array.isArray(parsed) ? parsed : Object.values(parsed);
13289
13665
  for (const task of tasks) {
@@ -13327,7 +13703,7 @@ var init_claude_desktop = __esm(() => {
13327
13703
  }
13328
13704
  const versionPath = join28(claudeAppDir, "claude-code", "sdk-version");
13329
13705
  if (existsSync34(versionPath)) {
13330
- return readFileSync26(versionPath, "utf-8").trim();
13706
+ return readFileSync27(versionPath, "utf-8").trim();
13331
13707
  }
13332
13708
  return null;
13333
13709
  } catch {
@@ -13341,7 +13717,7 @@ var init_claude_desktop = __esm(() => {
13341
13717
  continue;
13342
13718
  const orgPath = join28(baseDir, orgDir);
13343
13719
  try {
13344
- if (!statSync5(orgPath).isDirectory())
13720
+ if (!statSync6(orgPath).isDirectory())
13345
13721
  continue;
13346
13722
  } catch {
13347
13723
  continue;
@@ -13351,7 +13727,7 @@ var init_claude_desktop = __esm(() => {
13351
13727
  continue;
13352
13728
  const userPath = join28(orgPath, userDir);
13353
13729
  try {
13354
- if (!statSync5(userPath).isDirectory())
13730
+ if (!statSync6(userPath).isDirectory())
13355
13731
  continue;
13356
13732
  } catch {
13357
13733
  continue;
@@ -13360,7 +13736,7 @@ var init_claude_desktop = __esm(() => {
13360
13736
  if (!file.endsWith(".json"))
13361
13737
  continue;
13362
13738
  try {
13363
- const session = JSON.parse(readFileSync26(join28(userPath, file), "utf-8"));
13739
+ const session = JSON.parse(readFileSync27(join28(userPath, file), "utf-8"));
13364
13740
  onSession(session);
13365
13741
  } catch {
13366
13742
  continue;
@@ -13479,6 +13855,7 @@ var init_cursor = __esm(async () => {
13479
13855
  init_skill_removal();
13480
13856
  init_json_config();
13481
13857
  init_detection();
13858
+ init_transcript_sources();
13482
13859
  await init_sqlite();
13483
13860
  CursorAdapter = class CursorAdapter extends RegistryDetectedAdapter {
13484
13861
  name = "Cursor";
@@ -13737,6 +14114,16 @@ ${instructions}`;
13737
14114
  return null;
13738
14115
  }
13739
14116
  }
14117
+ transcriptSources() {
14118
+ return [
14119
+ {
14120
+ path: this.globalStorageDbPath(),
14121
+ kind: "file",
14122
+ tool: "sqlite",
14123
+ available: sqliteAvailable
14124
+ }
14125
+ ];
14126
+ }
13740
14127
  globalStorageDbPath() {
13741
14128
  const os2 = platform5();
13742
14129
  if (os2 === "darwin") {
@@ -13764,7 +14151,7 @@ ${instructions}`;
13764
14151
  async listSessions(sinceISO) {
13765
14152
  try {
13766
14153
  const dbPath = this.globalStorageDbPath();
13767
- if (!existsSync35(dbPath) || !sqliteAvailable())
14154
+ if (!transcriptSourcesReadable(this.transcriptSources()))
13768
14155
  return null;
13769
14156
  const rows = queryReadonlySqlite(dbPath, `SELECT json_object('id', json_extract(value,'$.composerId'), 'name', json_extract(value,'$.name'), 'createdAt', json_extract(value,'$.createdAt'), 'lastUpdatedAt', json_extract(value,'$.lastUpdatedAt')) FROM cursorDiskKV WHERE key LIKE 'composerData:%'`);
13770
14157
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
@@ -13893,7 +14280,7 @@ ${hint}`;
13893
14280
  });
13894
14281
 
13895
14282
  // src/agents/codex.ts
13896
- import { existsSync as existsSync37, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync27, statSync as statSync6, writeFileSync as writeFileSync21 } from "fs";
14283
+ import { existsSync as existsSync37, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync28, statSync as statSync7, writeFileSync as writeFileSync21 } from "fs";
13897
14284
  import { basename as basename3, join as join31 } from "path";
13898
14285
  import { homedir as homedir13 } from "os";
13899
14286
  import { parse, stringify } from "smol-toml";
@@ -13903,6 +14290,7 @@ function isRunworkManagedCodexKey(key) {
13903
14290
  var CodexAdapter, CodexDesktopAdapter;
13904
14291
  var init_codex = __esm(async () => {
13905
14292
  init_types();
14293
+ init_transcript_sources();
13906
14294
  init_session_digest();
13907
14295
  init_session_listing();
13908
14296
  init_skill_removal();
@@ -13925,7 +14313,7 @@ var init_codex = __esm(async () => {
13925
14313
  const configPath = join31(homedir13(), ".codex", "config.toml");
13926
14314
  let parsed = {};
13927
14315
  if (existsSync37(configPath)) {
13928
- parsed = parse(readFileSync27(configPath, "utf-8"));
14316
+ parsed = parse(readFileSync28(configPath, "utf-8"));
13929
14317
  }
13930
14318
  if (!parsed.mcp_servers || typeof parsed.mcp_servers !== "object") {
13931
14319
  parsed.mcp_servers = {};
@@ -13980,7 +14368,7 @@ var init_codex = __esm(async () => {
13980
14368
  const configPath = scope === "project" ? join31(process.cwd(), ".codex", "config.toml") : join31(homedir13(), ".codex", "config.toml");
13981
14369
  let parsed = {};
13982
14370
  if (existsSync37(configPath)) {
13983
- parsed = parse(readFileSync27(configPath, "utf-8"));
14371
+ parsed = parse(readFileSync28(configPath, "utf-8"));
13984
14372
  }
13985
14373
  if (config.modelPreference) {
13986
14374
  parsed.model = config.modelPreference;
@@ -14039,7 +14427,7 @@ var init_codex = __esm(async () => {
14039
14427
  const configPath = join31(homedir13(), ".codex", "config.toml");
14040
14428
  if (existsSync37(configPath)) {
14041
14429
  try {
14042
- const parsed = parse(readFileSync27(configPath, "utf-8"));
14430
+ const parsed = parse(readFileSync28(configPath, "utf-8"));
14043
14431
  if (parsed.mcp_servers && typeof parsed.mcp_servers === "object") {
14044
14432
  const mcpServers = parsed.mcp_servers;
14045
14433
  for (const key of Object.keys(mcpServers)) {
@@ -14088,7 +14476,7 @@ var init_codex = __esm(async () => {
14088
14476
  if (messageCount === 0) {
14089
14477
  const historyPath = join31(codexDir, "history.jsonl");
14090
14478
  if (existsSync37(historyPath)) {
14091
- const content = readFileSync27(historyPath, "utf-8").trim();
14479
+ const content = readFileSync28(historyPath, "utf-8").trim();
14092
14480
  if (content) {
14093
14481
  for (const line of content.split(/[\r\n]+/)) {
14094
14482
  try {
@@ -14121,6 +14509,12 @@ var init_codex = __esm(async () => {
14121
14509
  return null;
14122
14510
  }
14123
14511
  }
14512
+ transcriptRoot() {
14513
+ return join31(homedir13(), ".codex", "sessions");
14514
+ }
14515
+ transcriptSources() {
14516
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
14517
+ }
14124
14518
  scanRolloutActivity(sinceMs) {
14125
14519
  const days = new Set;
14126
14520
  const result = {
@@ -14131,7 +14525,7 @@ var init_codex = __esm(async () => {
14131
14525
  latestMs: 0,
14132
14526
  activeDays: []
14133
14527
  };
14134
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14528
+ const sessionsDir = this.transcriptRoot();
14135
14529
  if (!existsSync37(sessionsDir))
14136
14530
  return result;
14137
14531
  const files = [];
@@ -14154,7 +14548,7 @@ var init_codex = __esm(async () => {
14154
14548
  for (const file of files) {
14155
14549
  let stat;
14156
14550
  try {
14157
- stat = statSync6(file);
14551
+ stat = statSync7(file);
14158
14552
  } catch {
14159
14553
  continue;
14160
14554
  }
@@ -14162,7 +14556,7 @@ var init_codex = __esm(async () => {
14162
14556
  continue;
14163
14557
  let content;
14164
14558
  try {
14165
- content = readFileSync27(file, "utf-8");
14559
+ content = readFileSync28(file, "utf-8");
14166
14560
  } catch {
14167
14561
  continue;
14168
14562
  }
@@ -14221,8 +14615,8 @@ var init_codex = __esm(async () => {
14221
14615
  }
14222
14616
  async readSessionDigests(sinceISO) {
14223
14617
  try {
14224
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14225
- if (!existsSync37(sessionsDir))
14618
+ const sessionsDir = this.transcriptRoot();
14619
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14226
14620
  return null;
14227
14621
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14228
14622
  const files = [];
@@ -14246,7 +14640,7 @@ var init_codex = __esm(async () => {
14246
14640
  for (const file of files) {
14247
14641
  let stat;
14248
14642
  try {
14249
- stat = statSync6(file);
14643
+ stat = statSync7(file);
14250
14644
  } catch {
14251
14645
  continue;
14252
14646
  }
@@ -14254,7 +14648,7 @@ var init_codex = __esm(async () => {
14254
14648
  continue;
14255
14649
  let content;
14256
14650
  try {
14257
- content = readFileSync27(file, "utf-8");
14651
+ content = readFileSync28(file, "utf-8");
14258
14652
  } catch {
14259
14653
  continue;
14260
14654
  }
@@ -14269,8 +14663,8 @@ var init_codex = __esm(async () => {
14269
14663
  }
14270
14664
  async listSessions(sinceISO) {
14271
14665
  try {
14272
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14273
- if (!existsSync37(sessionsDir))
14666
+ const sessionsDir = this.transcriptRoot();
14667
+ if (!transcriptSourcesReadable(this.transcriptSources()))
14274
14668
  return null;
14275
14669
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14276
14670
  const files = [];
@@ -14294,7 +14688,7 @@ var init_codex = __esm(async () => {
14294
14688
  for (const file of files) {
14295
14689
  let stat;
14296
14690
  try {
14297
- stat = statSync6(file);
14691
+ stat = statSync7(file);
14298
14692
  } catch {
14299
14693
  continue;
14300
14694
  }
@@ -14313,7 +14707,8 @@ var init_codex = __esm(async () => {
14313
14707
  title: scanned.title,
14314
14708
  startedAt: scanned.firstTimestamp,
14315
14709
  lastActivityAt: resolveLastActivity(file, stat.mtimeMs),
14316
- transcriptPath: file
14710
+ transcriptPath: file,
14711
+ surface: scanned.surface ?? null
14317
14712
  });
14318
14713
  }
14319
14714
  return sessions;
@@ -14325,7 +14720,7 @@ var init_codex = __esm(async () => {
14325
14720
  try {
14326
14721
  const versionPath = join31(homedir13(), ".codex", "version.json");
14327
14722
  if (existsSync37(versionPath)) {
14328
- const data = JSON.parse(readFileSync27(versionPath, "utf-8"));
14723
+ const data = JSON.parse(readFileSync28(versionPath, "utf-8"));
14329
14724
  return data.latest_version ?? null;
14330
14725
  }
14331
14726
  } catch {}
@@ -14333,7 +14728,7 @@ var init_codex = __esm(async () => {
14333
14728
  }
14334
14729
  async readSkillUsage(lastSyncAt) {
14335
14730
  try {
14336
- const sessionsDir = join31(homedir13(), ".codex", "sessions");
14731
+ const sessionsDir = this.transcriptRoot();
14337
14732
  if (!existsSync37(sessionsDir))
14338
14733
  return null;
14339
14734
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14350,7 +14745,7 @@ var init_codex = __esm(async () => {
14350
14745
  if (entry.endsWith(".jsonl")) {
14351
14746
  let fileStat;
14352
14747
  try {
14353
- fileStat = statSync6(fullPath);
14748
+ fileStat = statSync7(fullPath);
14354
14749
  } catch {
14355
14750
  continue;
14356
14751
  }
@@ -14359,7 +14754,7 @@ var init_codex = __esm(async () => {
14359
14754
  this.parseRolloutForSkills(fullPath, sinceMs, skillCounts);
14360
14755
  } else {
14361
14756
  try {
14362
- if (statSync6(fullPath).isDirectory())
14757
+ if (statSync7(fullPath).isDirectory())
14363
14758
  walkDir2(fullPath);
14364
14759
  } catch {
14365
14760
  continue;
@@ -14386,7 +14781,7 @@ var init_codex = __esm(async () => {
14386
14781
  parseRolloutForSkills(filePath, sinceMs, skillCounts) {
14387
14782
  let content;
14388
14783
  try {
14389
- content = readFileSync27(filePath, "utf-8");
14784
+ content = readFileSync28(filePath, "utf-8");
14390
14785
  } catch {
14391
14786
  return;
14392
14787
  }
@@ -14439,7 +14834,7 @@ var init_codex = __esm(async () => {
14439
14834
  let state = {};
14440
14835
  if (existsSync37(statePath)) {
14441
14836
  try {
14442
- state = JSON.parse(readFileSync27(statePath, "utf-8"));
14837
+ state = JSON.parse(readFileSync28(statePath, "utf-8"));
14443
14838
  } catch {
14444
14839
  return "app_running";
14445
14840
  }
@@ -14474,17 +14869,11 @@ var init_codex = __esm(async () => {
14474
14869
  CodexDesktopAdapter = class CodexDesktopAdapter extends CodexAdapter {
14475
14870
  name = "Codex";
14476
14871
  slug = "codex-app";
14477
- async readUsageStats() {
14478
- return null;
14479
- }
14480
- async listSessions() {
14481
- return null;
14482
- }
14483
14872
  };
14484
14873
  });
14485
14874
 
14486
14875
  // src/agents/cline.ts
14487
- import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync28, readdirSync as readdirSync12, rmSync as rmSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync22 } from "fs";
14876
+ import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync29, readdirSync as readdirSync12, rmSync as rmSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync22 } from "fs";
14488
14877
  import { join as join32 } from "path";
14489
14878
  import { homedir as homedir14 } from "os";
14490
14879
  var ClineAdapter;
@@ -14545,7 +14934,7 @@ var init_cline = __esm(() => {
14545
14934
  let state = {};
14546
14935
  if (existsSync38(globalStatePath)) {
14547
14936
  try {
14548
- state = JSON.parse(readFileSync28(globalStatePath, "utf-8"));
14937
+ state = JSON.parse(readFileSync29(globalStatePath, "utf-8"));
14549
14938
  } catch {}
14550
14939
  }
14551
14940
  if (config.modelPreference) {
@@ -14602,7 +14991,7 @@ var init_cline = __esm(() => {
14602
14991
  });
14603
14992
 
14604
14993
  // src/agents/gemini.ts
14605
- import { existsSync as existsSync39, mkdirSync as mkdirSync23, readdirSync as readdirSync13, readFileSync as readFileSync29, statSync as statSync7, writeFileSync as writeFileSync23 } from "fs";
14994
+ import { existsSync as existsSync39, mkdirSync as mkdirSync23, readdirSync as readdirSync13, readFileSync as readFileSync30, statSync as statSync8, writeFileSync as writeFileSync23 } from "fs";
14606
14995
  import { basename as basename4, join as join33 } from "path";
14607
14996
  import { homedir as homedir15 } from "os";
14608
14997
  var GeminiAdapter;
@@ -14612,6 +15001,7 @@ var init_gemini = __esm(() => {
14612
15001
  init_skill_removal();
14613
15002
  init_trash();
14614
15003
  init_types();
15004
+ init_transcript_sources();
14615
15005
  init_instruction_hint();
14616
15006
  init_json_config();
14617
15007
  init_detection();
@@ -14661,7 +15051,7 @@ var init_gemini = __esm(() => {
14661
15051
  let settings = {};
14662
15052
  if (existsSync39(settingsPath)) {
14663
15053
  try {
14664
- settings = JSON.parse(readFileSync29(settingsPath, "utf-8"));
15054
+ settings = JSON.parse(readFileSync30(settingsPath, "utf-8"));
14665
15055
  } catch {}
14666
15056
  }
14667
15057
  if (config.modelPreference) {
@@ -14684,7 +15074,7 @@ var init_gemini = __esm(() => {
14684
15074
  }
14685
15075
  async readUsageStats(lastSyncAt) {
14686
15076
  try {
14687
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15077
+ const tmpDir = this.transcriptRoot();
14688
15078
  if (!existsSync39(tmpDir))
14689
15079
  return null;
14690
15080
  const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
@@ -14714,7 +15104,7 @@ var init_gemini = __esm(() => {
14714
15104
  const filePath = join33(chatsDir, file.name);
14715
15105
  let stat;
14716
15106
  try {
14717
- stat = statSync7(filePath);
15107
+ stat = statSync8(filePath);
14718
15108
  } catch {
14719
15109
  continue;
14720
15110
  }
@@ -14722,7 +15112,7 @@ var init_gemini = __esm(() => {
14722
15112
  continue;
14723
15113
  let session;
14724
15114
  try {
14725
- session = JSON.parse(readFileSync29(filePath, "utf-8"));
15115
+ session = JSON.parse(readFileSync30(filePath, "utf-8"));
14726
15116
  } catch {
14727
15117
  continue;
14728
15118
  }
@@ -14762,8 +15152,14 @@ var init_gemini = __esm(() => {
14762
15152
  return null;
14763
15153
  }
14764
15154
  }
15155
+ transcriptRoot() {
15156
+ return join33(homedir15(), ".gemini", "tmp");
15157
+ }
15158
+ transcriptSources() {
15159
+ return [{ path: this.transcriptRoot(), kind: "dir" }];
15160
+ }
14765
15161
  *chatFiles(sinceMs) {
14766
- const tmpDir = join33(homedir15(), ".gemini", "tmp");
15162
+ const tmpDir = this.transcriptRoot();
14767
15163
  if (!existsSync39(tmpDir))
14768
15164
  return;
14769
15165
  let projects;
@@ -14788,7 +15184,7 @@ var init_gemini = __esm(() => {
14788
15184
  const filePath = join33(chatsDir, file);
14789
15185
  let stat;
14790
15186
  try {
14791
- stat = statSync7(filePath);
15187
+ stat = statSync8(filePath);
14792
15188
  } catch {
14793
15189
  continue;
14794
15190
  }
@@ -14800,12 +15196,14 @@ var init_gemini = __esm(() => {
14800
15196
  }
14801
15197
  async listSessions(sinceISO) {
14802
15198
  try {
15199
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15200
+ return null;
14803
15201
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14804
15202
  const sessions = [];
14805
15203
  for (const { filePath, project, mtimeMs } of this.chatFiles(sinceMs)) {
14806
15204
  let session;
14807
15205
  try {
14808
- session = JSON.parse(readFileSync29(filePath, "utf-8"));
15206
+ session = JSON.parse(readFileSync30(filePath, "utf-8"));
14809
15207
  } catch {
14810
15208
  continue;
14811
15209
  }
@@ -14839,12 +15237,14 @@ var init_gemini = __esm(() => {
14839
15237
  }
14840
15238
  async readSessionDigests(sinceISO) {
14841
15239
  try {
15240
+ if (!transcriptSourcesReadable(this.transcriptSources()))
15241
+ return null;
14842
15242
  const sinceMs = sinceISO ? new Date(sinceISO).getTime() : 0;
14843
15243
  const digests = [];
14844
15244
  for (const { filePath, project } of this.chatFiles(sinceMs)) {
14845
15245
  let content;
14846
15246
  try {
14847
- content = readFileSync29(filePath, "utf-8");
15247
+ content = readFileSync30(filePath, "utf-8");
14848
15248
  } catch {
14849
15249
  continue;
14850
15250
  }
@@ -14884,6 +15284,7 @@ var init_generic_adapter = __esm(() => {
14884
15284
  init_json_config();
14885
15285
  init_instruction_hint();
14886
15286
  init_registry();
15287
+ init_detection_probes();
14887
15288
  init_detection();
14888
15289
  init_trash();
14889
15290
  GenericAgentAdapter = class GenericAgentAdapter extends RegistryDetectedAdapter {
@@ -14908,7 +15309,13 @@ var init_generic_adapter = __esm(() => {
14908
15309
  async writeMcpServers(servers, _scope) {
14909
15310
  if (!this.def.mcpConfigPath)
14910
15311
  return;
14911
- const filePath = join34(homedir16(), resolvePlatformString(this.def.mcpConfigPath) || "");
15312
+ const resolved = resolvePlatformString(this.def.mcpConfigPath) || "";
15313
+ const expanded = expandWindowsPathTemplate(resolved, {
15314
+ "%APPDATA%": process.env.APPDATA,
15315
+ "%LOCALAPPDATA%": process.env.LOCALAPPDATA,
15316
+ "%ProgramFiles%": process.env.ProgramFiles
15317
+ });
15318
+ const filePath = expanded.needsHomeJoin ? join34(homedir16(), expanded.path) : expanded.path;
14912
15319
  if (!filePath)
14913
15320
  return;
14914
15321
  const entries = {};
@@ -15061,6 +15468,43 @@ var init_detect = __esm(async () => {
15061
15468
  ALL_ADAPTERS = buildAllAdapters();
15062
15469
  });
15063
15470
 
15471
+ // ../../shared/types/local-conversations.ts
15472
+ function describeScanOutcome(outcome) {
15473
+ switch (outcome.status) {
15474
+ case "ok":
15475
+ case "no-reader":
15476
+ return null;
15477
+ case "missing-dir":
15478
+ return `no conversation history found${outcome.path ? ` at ${outcome.path}` : ""}`;
15479
+ case "permission-denied":
15480
+ return `access denied${outcome.path ? ` to ${outcome.path}` : ""}`;
15481
+ case "missing-tool":
15482
+ return `needs ${outcome.detail ?? "a tool that is not installed"}`;
15483
+ case "error":
15484
+ return `could not be read${outcome.detail ? `: ${outcome.detail}` : ""}`;
15485
+ }
15486
+ }
15487
+ function scanOutcomeNeedsAttention(outcome) {
15488
+ return outcome.status === "permission-denied" || outcome.status === "error";
15489
+ }
15490
+ function describeStoreOutcome(outcome) {
15491
+ if (outcome.status === "orphaned") {
15492
+ return `not read: nothing on this machine was detected as ${outcome.members.join(" or ")}`;
15493
+ }
15494
+ if (outcome.status === "ok")
15495
+ return null;
15496
+ return describeScanOutcome({
15497
+ agentSlug: outcome.storeId,
15498
+ status: outcome.status,
15499
+ sessions: outcome.sessions,
15500
+ path: outcome.path,
15501
+ ...outcome.detail ? { detail: outcome.detail } : {}
15502
+ });
15503
+ }
15504
+ function storeOutcomeNeedsAttention(outcome) {
15505
+ return outcome.status === "orphaned" || outcome.status === "permission-denied" || outcome.status === "error";
15506
+ }
15507
+
15064
15508
  // src/utils/insight-id.ts
15065
15509
  import { createHash as createHash3 } from "node:crypto";
15066
15510
  function computeInsightId(userSeed, localKey) {
@@ -15072,7 +15516,7 @@ function insightLocalKey(teaches, slug) {
15072
15516
  var init_insight_id = () => {};
15073
15517
 
15074
15518
  // src/reflect/insight-store.ts
15075
- import { existsSync as existsSync41, readFileSync as readFileSync30 } from "fs";
15519
+ import { existsSync as existsSync41, readFileSync as readFileSync31 } from "fs";
15076
15520
  import { join as join35 } from "path";
15077
15521
  import { homedir as homedir17 } from "os";
15078
15522
  function storePath2() {
@@ -15083,7 +15527,7 @@ function readAll() {
15083
15527
  if (!existsSync41(path2))
15084
15528
  return {};
15085
15529
  try {
15086
- const parsed = JSON.parse(readFileSync30(path2, "utf-8"));
15530
+ const parsed = JSON.parse(readFileSync31(path2, "utf-8"));
15087
15531
  return parsed && typeof parsed === "object" ? parsed : {};
15088
15532
  } catch {
15089
15533
  return {};
@@ -15128,7 +15572,7 @@ var init_insight_store = __esm(() => {
15128
15572
  var DEFAULT_DAILY_TIER2_BUDGET = 3;
15129
15573
 
15130
15574
  // src/reflect/cadence.ts
15131
- import { existsSync as existsSync42, readFileSync as readFileSync31 } from "fs";
15575
+ import { existsSync as existsSync42, readFileSync as readFileSync32 } from "fs";
15132
15576
  import { join as join36 } from "path";
15133
15577
  import { homedir as homedir18 } from "os";
15134
15578
  function remainingDailyAnalyses(state, now = new Date, budget = DEFAULT_DAILY_TIER2_BUDGET) {
@@ -15150,7 +15594,7 @@ function loadCadenceState() {
15150
15594
  const p = statePath();
15151
15595
  if (!existsSync42(p))
15152
15596
  return { ...DEFAULT_STATE };
15153
- const parsed = JSON.parse(readFileSync31(p, "utf-8"));
15597
+ const parsed = JSON.parse(readFileSync32(p, "utf-8"));
15154
15598
  const state = { ...DEFAULT_STATE, ...parsed && typeof parsed === "object" ? parsed : {} };
15155
15599
  if (state.enabled === false && state.disabledByUser !== true) {
15156
15600
  state.enabled = true;
@@ -15211,7 +15655,7 @@ __export(exports_run_log, {
15211
15655
  RUN_LOG_CAP: () => RUN_LOG_CAP,
15212
15656
  ANALYST_ERROR_MAX_CHARS: () => ANALYST_ERROR_MAX_CHARS
15213
15657
  });
15214
- import { existsSync as existsSync43, mkdirSync as mkdirSync25, readFileSync as readFileSync32, readdirSync as readdirSync15, statSync as statSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync25 } from "fs";
15658
+ import { existsSync as existsSync43, mkdirSync as mkdirSync25, readFileSync as readFileSync33, readdirSync as readdirSync15, statSync as statSync9, unlinkSync as unlinkSync7, writeFileSync as writeFileSync25 } from "fs";
15215
15659
  import { dirname as dirname10, join as join37 } from "path";
15216
15660
  import { homedir as homedir19 } from "os";
15217
15661
  function runLogPath() {
@@ -15222,7 +15666,7 @@ function loadRunLog() {
15222
15666
  const p = runLogPath();
15223
15667
  if (!existsSync43(p))
15224
15668
  return [];
15225
- const parsed = JSON.parse(readFileSync32(p, "utf-8"));
15669
+ const parsed = JSON.parse(readFileSync33(p, "utf-8"));
15226
15670
  return Array.isArray(parsed) ? parsed : [];
15227
15671
  } catch {
15228
15672
  return [];
@@ -15355,7 +15799,7 @@ function acquireRunLock(nowMs = Date.now()) {
15355
15799
  return true;
15356
15800
  } catch {
15357
15801
  try {
15358
- if (nowMs - statSync8(p).mtimeMs < RUN_STALE_MS)
15802
+ if (nowMs - statSync9(p).mtimeMs < RUN_STALE_MS)
15359
15803
  return false;
15360
15804
  unlinkSync7(p);
15361
15805
  } catch {
@@ -15390,7 +15834,7 @@ var init_run_log = __esm(() => {
15390
15834
  });
15391
15835
 
15392
15836
  // src/reflect/conversation-queue.ts
15393
- import { existsSync as existsSync44, readFileSync as readFileSync33 } from "fs";
15837
+ import { existsSync as existsSync44, readFileSync as readFileSync34 } from "fs";
15394
15838
  import { join as join38 } from "path";
15395
15839
  import { homedir as homedir20 } from "os";
15396
15840
  function conversationKey(c) {
@@ -15404,7 +15848,7 @@ function loadQueueState() {
15404
15848
  const p = queuePath();
15405
15849
  if (!existsSync44(p))
15406
15850
  return { entries: {} };
15407
- const parsed = JSON.parse(readFileSync33(p, "utf-8"));
15851
+ const parsed = JSON.parse(readFileSync34(p, "utf-8"));
15408
15852
  if (parsed && typeof parsed === "object" && parsed.entries && typeof parsed.entries === "object") {
15409
15853
  return { entries: parsed.entries };
15410
15854
  }
@@ -15612,7 +16056,7 @@ function parseHandoffResult(kind, text2) {
15612
16056
  }
15613
16057
 
15614
16058
  // src/reflect/model-repair.ts
15615
- import { existsSync as existsSync45, readFileSync as readFileSync34, writeFileSync as writeFileSync26 } from "fs";
16059
+ import { existsSync as existsSync45, readFileSync as readFileSync35, writeFileSync as writeFileSync26 } from "fs";
15616
16060
  import { homedir as homedir21 } from "os";
15617
16061
  import { join as join39 } from "path";
15618
16062
  function codexConfigPath() {
@@ -15649,7 +16093,7 @@ function repairCodexModelPin() {
15649
16093
  const path2 = codexConfigPath();
15650
16094
  if (!existsSync45(path2))
15651
16095
  return null;
15652
- const current = readFileSync34(path2, "utf-8");
16096
+ const current = readFileSync35(path2, "utf-8");
15653
16097
  const stripped = stripModelPin(current);
15654
16098
  if (!stripped)
15655
16099
  return null;
@@ -16043,6 +16487,7 @@ var init_reflect = __esm(async () => {
16043
16487
  init_store();
16044
16488
  init_client();
16045
16489
  init_resolve();
16490
+ init_transcript_sources();
16046
16491
  init_session_digest();
16047
16492
  init_insight_id();
16048
16493
  init_insight_store();
@@ -16107,13 +16552,13 @@ var init_reflect = __esm(async () => {
16107
16552
  const seen = new Set;
16108
16553
  for (const adapter2 of adapters) {
16109
16554
  if (!adapter2.readSessionDigests) {
16110
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16555
+ perAgent.push({ agentSlug: adapter2.slug, status: "no-reader", sessions: null });
16111
16556
  continue;
16112
16557
  }
16113
16558
  try {
16114
16559
  const sessions = await adapter2.readSessionDigests(sinceISO);
16115
16560
  if (sessions === null) {
16116
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
16561
+ perAgent.push(diagnoseTranscriptRead(adapter2));
16117
16562
  continue;
16118
16563
  }
16119
16564
  let added = 0;
@@ -16125,20 +16570,26 @@ var init_reflect = __esm(async () => {
16125
16570
  digests.push(s);
16126
16571
  added++;
16127
16572
  }
16128
- perAgent.push({ slug: adapter2.slug, sessions: added });
16129
- } catch {
16130
- perAgent.push({ slug: adapter2.slug, sessions: "error" });
16573
+ perAgent.push({ agentSlug: adapter2.slug, status: "ok", sessions: added });
16574
+ } catch (err) {
16575
+ perAgent.push(diagnoseTranscriptRead(adapter2, err));
16131
16576
  }
16132
16577
  }
16133
16578
  if (!json) {
16134
16579
  console.error(bold(`
16135
16580
  Reflection over the last ${days} days`));
16136
- for (const a of perAgent)
16137
- console.error(` ${cyan(a.slug)}: ${a.sessions === "unsupported" ? gray("no transcript reader") : a.sessions === "error" ? red("read error") : `${a.sessions} session(s)`}`);
16581
+ for (const a of perAgent) {
16582
+ const reason = describeScanOutcome(a);
16583
+ if (reason === null) {
16584
+ console.error(` ${cyan(a.agentSlug)}: ${a.status === "no-reader" ? gray("no transcript reader") : `${a.sessions} session(s)`}`);
16585
+ } else {
16586
+ console.error(` ${cyan(a.agentSlug)}: ${scanOutcomeNeedsAttention(a) ? red(reason) : gray(reason)}`);
16587
+ }
16588
+ }
16138
16589
  }
16139
16590
  if (digests.length === 0) {
16140
16591
  if (json) {
16141
- jsonOut({ insights: [], reason: "no-sessions" });
16592
+ jsonOut({ insights: [], reason: "no-sessions", perAgent });
16142
16593
  return;
16143
16594
  }
16144
16595
  console.error(yellow(`
@@ -16465,7 +16916,214 @@ ${all.length} insight(s) from your recent work:
16465
16916
  });
16466
16917
  });
16467
16918
 
16919
+ // src/agents/transcript-stores.ts
16920
+ function joiner(input) {
16921
+ const sep4 = input.sep ?? (input.platform === "win32" ? "\\" : "/");
16922
+ return (...parts) => parts.join(sep4);
16923
+ }
16924
+ function home(input) {
16925
+ return input.homeDir.replace(/[/\\]+$/, "");
16926
+ }
16927
+ function appDataRoot(input) {
16928
+ const join41 = joiner(input);
16929
+ if (input.platform === "darwin")
16930
+ return join41(home(input), "Library", "Application Support");
16931
+ if (input.platform === "win32") {
16932
+ return input.appData ?? join41(home(input), "AppData", "Roaming");
16933
+ }
16934
+ return join41(home(input), ".config");
16935
+ }
16936
+ function localAppDataRoot(input) {
16937
+ const join41 = joiner(input);
16938
+ return input.localAppData ?? join41(home(input), "AppData", "Local");
16939
+ }
16940
+ function codexOriginator(head) {
16941
+ for (const line of head.split(`
16942
+ `)) {
16943
+ if (!line.trim())
16944
+ continue;
16945
+ let o;
16946
+ try {
16947
+ o = JSON.parse(line);
16948
+ } catch {
16949
+ continue;
16950
+ }
16951
+ if (o.type !== "session_meta")
16952
+ continue;
16953
+ const payload = o.payload;
16954
+ return typeof payload?.originator === "string" ? payload.originator : null;
16955
+ }
16956
+ return null;
16957
+ }
16958
+ function claudeEntrypoint(head) {
16959
+ for (const line of head.split(`
16960
+ `)) {
16961
+ if (!line.trim())
16962
+ continue;
16963
+ let o;
16964
+ try {
16965
+ o = JSON.parse(line);
16966
+ } catch {
16967
+ continue;
16968
+ }
16969
+ if (typeof o.entrypoint === "string")
16970
+ return o.entrypoint;
16971
+ }
16972
+ return null;
16973
+ }
16974
+ function attributeCodexOriginator(marker) {
16975
+ switch (marker) {
16976
+ case "Codex Desktop":
16977
+ case "codex_work_desktop":
16978
+ return "codex-app";
16979
+ case "codex-tui":
16980
+ case "codex_exec":
16981
+ case "Claude Code":
16982
+ return "codex";
16983
+ default:
16984
+ return null;
16985
+ }
16986
+ }
16987
+ function attributeClaudeEntrypoint(marker) {
16988
+ switch (marker) {
16989
+ case "cli":
16990
+ case "sdk-cli":
16991
+ return "claude-code";
16992
+ case "claude-desktop":
16993
+ case "local-agent":
16994
+ return "claude-desktop";
16995
+ default:
16996
+ return null;
16997
+ }
16998
+ }
16999
+ function resolveTranscriptStores(input) {
17000
+ const resolved = [];
17001
+ for (const def of TRANSCRIPT_STORES) {
17002
+ const path2 = def.path(input);
17003
+ if (path2 === null)
17004
+ continue;
17005
+ resolved.push({ def, path: path2, altPaths: def.altPaths?.(input) ?? [] });
17006
+ }
17007
+ return resolved;
17008
+ }
17009
+ function familyMembers(family, table = TRANSCRIPT_STORES) {
17010
+ const slugs = new Set;
17011
+ for (const store of table) {
17012
+ if (store.family !== family)
17013
+ continue;
17014
+ for (const member of store.members)
17015
+ slugs.add(member);
17016
+ }
17017
+ return [...slugs];
17018
+ }
17019
+ function isStoreUnlocked(def, detectedSlugs, table = TRANSCRIPT_STORES) {
17020
+ const detected = detectedSlugs instanceof Set ? detectedSlugs : new Set(detectedSlugs);
17021
+ return familyMembers(def.family, table).some((m) => detected.has(m));
17022
+ }
17023
+ function telemetryOwner(def, detectedSlugs) {
17024
+ const detected = detectedSlugs instanceof Set ? detectedSlugs : new Set(detectedSlugs);
17025
+ if (detected.has(def.readerSlug))
17026
+ return def.readerSlug;
17027
+ return def.members.find((m) => detected.has(m)) ?? null;
17028
+ }
17029
+ function suppressedStoreReaders(detectedSlugs, sharesImplementation, table = TRANSCRIPT_STORES) {
17030
+ const detected = detectedSlugs instanceof Set ? detectedSlugs : new Set(detectedSlugs);
17031
+ const suppressed = new Set;
17032
+ for (const store of table) {
17033
+ const owner = telemetryOwner(store, detected);
17034
+ if (owner === null)
17035
+ continue;
17036
+ for (const member of store.members) {
17037
+ if (member === owner || !detected.has(member))
17038
+ continue;
17039
+ if (sharesImplementation(member, owner))
17040
+ suppressed.add(member);
17041
+ }
17042
+ }
17043
+ return suppressed;
17044
+ }
17045
+ function attributeTranscript(def, head) {
17046
+ if (head === null || !def.marker) {
17047
+ return { agentSlug: def.defaultMember, marker: null, recognised: false };
17048
+ }
17049
+ const marker = def.marker(head);
17050
+ if (marker === null)
17051
+ return { agentSlug: def.defaultMember, marker: null, recognised: false };
17052
+ const slug = def.attribute?.(marker) ?? null;
17053
+ return slug === null ? { agentSlug: def.defaultMember, marker, recognised: false } : { agentSlug: slug, marker, recognised: true };
17054
+ }
17055
+ var CODEX_HEAD, CLAUDE_HEAD, TRANSCRIPT_STORES;
17056
+ var init_transcript_stores = __esm(() => {
17057
+ CODEX_HEAD = 256 * 1024;
17058
+ CLAUDE_HEAD = 64 * 1024;
17059
+ TRANSCRIPT_STORES = [
17060
+ {
17061
+ id: "codex/sessions",
17062
+ readerSlug: "codex",
17063
+ family: "codex",
17064
+ members: ["codex", "codex-app"],
17065
+ defaultMember: "codex",
17066
+ kind: "dir",
17067
+ path: (i) => joiner(i)(home(i), ".codex", "sessions"),
17068
+ matches: (n) => n.startsWith("rollout-") && n.endsWith(".jsonl"),
17069
+ headBytes: CODEX_HEAD,
17070
+ marker: codexOriginator,
17071
+ attribute: attributeCodexOriginator
17072
+ },
17073
+ {
17074
+ id: "claude/projects",
17075
+ readerSlug: "claude-code",
17076
+ family: "claude",
17077
+ members: ["claude-code", "claude-desktop"],
17078
+ defaultMember: "claude-code",
17079
+ kind: "dir",
17080
+ path: (i) => joiner(i)(home(i), ".claude", "projects"),
17081
+ matches: (n) => n.endsWith(".jsonl"),
17082
+ subordinate: (rel) => rel.split(/[\\/]/).length > 2,
17083
+ headBytes: CLAUDE_HEAD,
17084
+ marker: claudeEntrypoint,
17085
+ attribute: attributeClaudeEntrypoint
17086
+ },
17087
+ {
17088
+ id: "claude/cowork",
17089
+ readerSlug: "claude-desktop",
17090
+ family: "claude",
17091
+ members: ["claude-desktop"],
17092
+ defaultMember: "claude-desktop",
17093
+ kind: "dir",
17094
+ path: (i) => joiner(i)(appDataRoot(i), "Claude", "local-agent-mode-sessions"),
17095
+ altPaths: (i) => i.platform === "win32" ? [joiner(i)(localAppDataRoot(i), "Packages", "Claude_pzs8sxrjxfjjc", "LocalCache", "Roaming", "Claude", "local-agent-mode-sessions")] : [],
17096
+ matches: (n) => n.endsWith(".jsonl"),
17097
+ headBytes: CLAUDE_HEAD,
17098
+ marker: claudeEntrypoint,
17099
+ attribute: attributeClaudeEntrypoint,
17100
+ note: "macOS TCC can refuse this while it still exists"
17101
+ },
17102
+ {
17103
+ id: "gemini/tmp",
17104
+ readerSlug: "gemini",
17105
+ family: "gemini",
17106
+ members: ["gemini"],
17107
+ defaultMember: "gemini",
17108
+ kind: "dir",
17109
+ path: (i) => joiner(i)(home(i), ".gemini", "tmp"),
17110
+ matches: (n) => n.endsWith(".json")
17111
+ },
17112
+ {
17113
+ id: "cursor/state",
17114
+ readerSlug: "cursor",
17115
+ family: "cursor",
17116
+ members: ["cursor"],
17117
+ defaultMember: "cursor",
17118
+ kind: "file",
17119
+ path: (i) => joiner(i)(appDataRoot(i), "Cursor", "User", "globalStorage", "state.vscdb"),
17120
+ tool: "sqlite"
17121
+ }
17122
+ ];
17123
+ });
17124
+
16468
17125
  // src/reflect/conversation-registry.ts
17126
+ import { homedir as homedir22, platform as platform7 } from "os";
16469
17127
  function computeStatus(lastActivityAt, nowMs, idleMinutes = DEFAULT_IDLE_MINUTES) {
16470
17128
  const lastMs = new Date(lastActivityAt).getTime();
16471
17129
  if (Number.isNaN(lastMs))
@@ -16496,43 +17154,114 @@ function mergeSessionListings(listings, opts) {
16496
17154
  status: isFreshEndStamp(s.endedAt, s.lastActivityAt) ? "finished" : computeStatus(s.lastActivityAt, opts.nowMs, opts.idleMinutes)
16497
17155
  }));
16498
17156
  }
17157
+ function machineStores(opts) {
17158
+ return opts.stores ?? resolveTranscriptStores({
17159
+ platform: platform7(),
17160
+ homeDir: homedir22(),
17161
+ appData: process.env.APPDATA ?? null,
17162
+ localAppData: process.env.LOCALAPPDATA ?? null
17163
+ });
17164
+ }
17165
+ function attributeListing(store, sessions) {
17166
+ if (!store.def.attribute)
17167
+ return sessions;
17168
+ return sessions.map((s) => {
17169
+ const surface = s.surface ?? null;
17170
+ const slug = surface === null ? null : store.def.attribute?.(surface) ?? null;
17171
+ return {
17172
+ ...s,
17173
+ agentSlug: slug ?? store.def.defaultMember,
17174
+ surface
17175
+ };
17176
+ });
17177
+ }
16499
17178
  async function listLocalConversations(opts = {}) {
16500
- const adapters = await detectAgents();
17179
+ const detected = opts.detected ?? (await detectAgents()).map((a) => a.slug);
17180
+ const detectedSet = new Set(detected);
16501
17181
  const sinceISO = opts.sinceISO ?? null;
17182
+ const maxPerStore = opts.maxPerStore ?? DEFAULT_MAX_PER_STORE;
16502
17183
  const listings = [];
16503
- const perAgent = [];
16504
- for (const adapter2 of adapters) {
16505
- if (!adapter2.listSessions) {
16506
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
17184
+ const perStore = [];
17185
+ const stores = machineStores(opts);
17186
+ const table = stores.map((s) => s.def);
17187
+ for (const store of stores) {
17188
+ const base = {
17189
+ storeId: store.def.id,
17190
+ family: store.def.family,
17191
+ members: [...familyMembers(store.def.family, table)],
17192
+ path: store.path
17193
+ };
17194
+ if (!isStoreUnlocked(store.def, detectedSet, table)) {
17195
+ perStore.push({ ...base, status: "orphaned", sessions: null });
17196
+ continue;
17197
+ }
17198
+ const reader = (opts.readerFor ?? getAdapterBySlug)(store.def.readerSlug);
17199
+ if (!reader?.listSessions) {
17200
+ perStore.push({ ...base, status: "no-reader", sessions: null });
16507
17201
  continue;
16508
17202
  }
16509
17203
  try {
16510
- const sessions = await adapter2.listSessions(sinceISO);
17204
+ const sessions = await reader.listSessions(sinceISO);
16511
17205
  if (sessions === null) {
16512
- perAgent.push({ slug: adapter2.slug, sessions: "unsupported" });
17206
+ const outcome = diagnoseTranscriptRead(reader);
17207
+ perStore.push({ ...base, status: outcome.status, sessions: null, ...outcome.detail ? { detail: outcome.detail } : {} });
16513
17208
  continue;
16514
17209
  }
16515
- listings.push(sessions);
16516
- perAgent.push({ slug: adapter2.slug, sessions: sessions.length });
16517
- } catch {
16518
- perAgent.push({ slug: adapter2.slug, sessions: "error" });
17210
+ const attributed = attributeListing(store, sessions).sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt)).slice(0, maxPerStore);
17211
+ listings.push(attributed);
17212
+ perStore.push({
17213
+ ...base,
17214
+ status: "ok",
17215
+ sessions: attributed.length,
17216
+ ...sessions.length > attributed.length ? { truncated: sessions.length } : {}
17217
+ });
17218
+ } catch (err) {
17219
+ const outcome = diagnoseTranscriptRead(reader, err);
17220
+ perStore.push({ ...base, status: outcome.status, sessions: null, ...outcome.detail ? { detail: outcome.detail } : {} });
16519
17221
  }
16520
17222
  }
16521
17223
  const conversations = mergeSessionListings(listings, {
16522
17224
  nowMs: opts.nowMs ?? Date.now(),
16523
17225
  idleMinutes: opts.idleMinutes
16524
17226
  });
16525
- return { conversations, perAgent };
17227
+ return { conversations, perAgent: derivePerAgent(perStore, conversations), perStore };
17228
+ }
17229
+ function derivePerAgent(perStore, conversations) {
17230
+ const counts = new Map;
17231
+ for (const c of conversations)
17232
+ counts.set(c.agentSlug, (counts.get(c.agentSlug) ?? 0) + 1);
17233
+ const rows = new Map;
17234
+ for (const store of perStore) {
17235
+ for (const slug of store.members) {
17236
+ const existing = rows.get(slug);
17237
+ if (store.status === "ok") {
17238
+ rows.set(slug, { agentSlug: slug, status: "ok", sessions: counts.get(slug) ?? 0 });
17239
+ continue;
17240
+ }
17241
+ if (!existing || existing.status !== "ok") {
17242
+ rows.set(slug, {
17243
+ agentSlug: slug,
17244
+ status: store.status === "orphaned" ? "missing-dir" : store.status,
17245
+ sessions: null,
17246
+ ...store.path ? { path: store.path } : {},
17247
+ ...store.detail ? { detail: store.detail } : {}
17248
+ });
17249
+ }
17250
+ }
17251
+ }
17252
+ return [...rows.values()];
16526
17253
  }
16527
- var DEFAULT_IDLE_MINUTES = 10;
17254
+ var DEFAULT_IDLE_MINUTES = 10, DEFAULT_MAX_PER_STORE = 500;
16528
17255
  var init_conversation_registry = __esm(async () => {
17256
+ init_transcript_sources();
17257
+ init_transcript_stores();
16529
17258
  await init_detect();
16530
17259
  });
16531
17260
 
16532
17261
  // src/reflect/session-summary.ts
16533
17262
  import { createHash as createHash4 } from "crypto";
16534
17263
  import { execFileSync as execFileSync3 } from "child_process";
16535
- import { readFileSync as readFileSync35 } from "fs";
17264
+ import { readFileSync as readFileSync36 } from "fs";
16536
17265
  import { isAbsolute as isAbsolute4 } from "path";
16537
17266
  function validateAssetMarkers(markers, knownSkills) {
16538
17267
  if (!markers || markers.length === 0)
@@ -16634,7 +17363,7 @@ function digestForEntry(entry) {
16634
17363
  return null;
16635
17364
  let content;
16636
17365
  try {
16637
- content = readFileSync35(entry.transcriptPath, "utf-8");
17366
+ content = readFileSync36(entry.transcriptPath, "utf-8");
16638
17367
  } catch {
16639
17368
  return null;
16640
17369
  }
@@ -16762,18 +17491,18 @@ var init_session_summary = __esm(() => {
16762
17491
  });
16763
17492
 
16764
17493
  // src/reflect/telemetry-outbox.ts
16765
- import { existsSync as existsSync46, readFileSync as readFileSync36 } from "fs";
16766
- import { join as join41 } from "path";
16767
- import { homedir as homedir22 } from "os";
17494
+ import { existsSync as existsSync47, readFileSync as readFileSync37 } from "fs";
17495
+ import { join as join42 } from "path";
17496
+ import { homedir as homedir23 } from "os";
16768
17497
  function outboxPath() {
16769
- return join41(homedir22(), ".runwork", "telemetry-outbox.json");
17498
+ return join42(homedir23(), ".runwork", "telemetry-outbox.json");
16770
17499
  }
16771
17500
  function loadTelemetryOutbox() {
16772
17501
  try {
16773
17502
  const p = outboxPath();
16774
- if (!existsSync46(p))
17503
+ if (!existsSync47(p))
16775
17504
  return [];
16776
- const parsed = JSON.parse(readFileSync36(p, "utf-8"));
17505
+ const parsed = JSON.parse(readFileSync37(p, "utf-8"));
16777
17506
  if (!Array.isArray(parsed))
16778
17507
  return [];
16779
17508
  return parsed.filter((e) => !!e && typeof e === "object" && typeof e.dedupeKey === "string" && !!e.event && typeof e.event === "object");
@@ -16834,8 +17563,8 @@ var init_active_time = __esm(() => {
16834
17563
 
16835
17564
  // src/reflect/pattern-store.ts
16836
17565
  import { createHash as createHash5 } from "crypto";
16837
- import { join as join42 } from "path";
16838
- import { homedir as homedir23 } from "os";
17566
+ import { join as join43 } from "path";
17567
+ import { homedir as homedir24 } from "os";
16839
17568
  function addBuckets(a, b) {
16840
17569
  if (!a)
16841
17570
  return b ? { ...b } : null;
@@ -16996,7 +17725,7 @@ function buildPatternEvent(payload, nowISO) {
16996
17725
  };
16997
17726
  }
16998
17727
  function storePath3() {
16999
- return join42(homedir23(), ".runwork", "pattern-store.json");
17728
+ return join43(homedir24(), ".runwork", "pattern-store.json");
17000
17729
  }
17001
17730
  function loadPatternStore() {
17002
17731
  const parsed = readJsonOrNull(storePath3());
@@ -17206,7 +17935,7 @@ var init_triage = __esm(async () => {
17206
17935
  });
17207
17936
 
17208
17937
  // src/reflect/conversation-analysis.ts
17209
- import { existsSync as existsSync47 } from "fs";
17938
+ import { existsSync as existsSync48 } from "fs";
17210
17939
  import { basename as basename5 } from "path";
17211
17940
  function brokenAnalystBinaries() {
17212
17941
  return TIER2_ANALYSTS.filter((a) => {
@@ -17234,7 +17963,7 @@ function isPromptStillValid(key) {
17234
17963
  if (!standing?.promptPath || standing.promptConversationKey !== key)
17235
17964
  return false;
17236
17965
  try {
17237
- return existsSync47(standing.promptPath);
17966
+ return existsSync48(standing.promptPath);
17238
17967
  } catch {
17239
17968
  return false;
17240
17969
  }
@@ -17922,7 +18651,7 @@ var init_welcome = __esm(() => {
17922
18651
  });
17923
18652
 
17924
18653
  // src/index.ts
17925
- import { Command as Command39 } from "commander";
18654
+ import { Command as Command40 } from "commander";
17926
18655
 
17927
18656
  // src/commands/login.ts
17928
18657
  init_login_flow();
@@ -18231,7 +18960,18 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
18231
18960
  console.log("Syncing...");
18232
18961
  try {
18233
18962
  commitWorkingTree(cwd, `deploy: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`);
18234
- } catch {}
18963
+ } catch (err) {
18964
+ if (hasTrackedChanges(cwd)) {
18965
+ const detail = err instanceof Error ? err.message : String(err);
18966
+ if (useJson) {
18967
+ 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"]));
18968
+ process.exit(1);
18969
+ }
18970
+ console.error("Could not commit your changes, so nothing new would be deployed.");
18971
+ console.error("A pre-commit hook or a stale .git/index.lock may be interfering. Commit manually, then re-run `runwork deploy`.");
18972
+ process.exit(1);
18973
+ }
18974
+ }
18235
18975
  if (!hasCommits(cwd)) {
18236
18976
  if (useJson) {
18237
18977
  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"]));
@@ -20278,34 +21018,184 @@ var skillsCommand = new Command13("skills").description("Manage workspace skills
20278
21018
  await init_reflect();
20279
21019
 
20280
21020
  // src/commands/conversations.ts
20281
- init_conversation_queue();
20282
- init_session_summary();
20283
- init_telemetry_outbox();
20284
- init_insight_store();
20285
- init_atomic_json();
20286
- init_store();
20287
- init_client();
20288
- init_resolve();
20289
- init_workspace_state();
20290
- init_colors();
20291
- await __promiseAll([
20292
- init_conversation_registry(),
20293
- init_conversation_analysis()
20294
- ]);
21021
+ init_transcript_stores();
21022
+ await init_detect();
20295
21023
  import { Command as Command15 } from "commander";
20296
- import { join as join43 } from "path";
20297
- import { homedir as homedir24 } from "os";
20298
- var DEFAULT_LOOKBACK_DAYS = 30;
20299
- function sinceFromDays(days) {
20300
- if (days <= 0)
21024
+ import { join as join44 } from "path";
21025
+ import { homedir as homedir25, platform as platform8 } from "os";
21026
+
21027
+ // src/agents/store-census.ts
21028
+ init_transcript_stores();
21029
+ import { readdirSync as readdirSync16, statSync as statSync10, existsSync as existsSync46, openSync as openSync5, readSync as readSync3, closeSync as closeSync5 } from "fs";
21030
+ import { join as join41 } from "path";
21031
+ function sanitizeMarker(raw) {
21032
+ const cleaned = raw.replace(/[^\x20-\x7E]/g, "").trim();
21033
+ return cleaned.length > 64 ? `${cleaned.slice(0, 63)}…` : cleaned;
21034
+ }
21035
+ function readHead(path2, n) {
21036
+ let fd;
21037
+ try {
21038
+ fd = openSync5(path2, "r");
21039
+ const buf = Buffer.alloc(n);
21040
+ const read = readSync3(fd, buf, 0, n, 0);
21041
+ return buf.subarray(0, read).toString("utf-8");
21042
+ } catch {
20301
21043
  return null;
20302
- return new Date(Date.now() - days * 24 * 3600 * 1000).toISOString();
21044
+ } finally {
21045
+ if (fd !== undefined) {
21046
+ try {
21047
+ closeSync5(fd);
21048
+ } catch {}
21049
+ }
21050
+ }
20303
21051
  }
20304
- function parseDays(raw) {
20305
- const n = Number(raw);
20306
- if (!Number.isFinite(n) || n < 0)
20307
- return DEFAULT_LOOKBACK_DAYS;
20308
- return Math.floor(n);
21052
+ function isPermissionError(err) {
21053
+ const code = err?.code;
21054
+ return code === "EACCES" || code === "EPERM";
21055
+ }
21056
+ function isMissingError(err) {
21057
+ const code = err?.code;
21058
+ return code === "ENOENT" || code === "ENOTDIR";
21059
+ }
21060
+ function collect(dir, def, out, budget) {
21061
+ if (budget.left <= 0)
21062
+ return;
21063
+ const entries = readdirSync16(dir, { withFileTypes: true });
21064
+ for (const e of entries) {
21065
+ if (budget.left <= 0)
21066
+ return;
21067
+ const full = join41(dir, e.name);
21068
+ if (e.isDirectory()) {
21069
+ try {
21070
+ collect(full, def, out, budget);
21071
+ } catch (err) {
21072
+ if (isPermissionError(err))
21073
+ throw err;
21074
+ }
21075
+ } else if (!def.matches || def.matches(e.name)) {
21076
+ out.push(full);
21077
+ budget.left--;
21078
+ }
21079
+ }
21080
+ }
21081
+ var DEFAULT_MAX_FILES = 2000;
21082
+ function censusStore(store, unlocked, opts = {}) {
21083
+ const { def, path: path2, altPaths } = store;
21084
+ const base = {
21085
+ storeId: def.id,
21086
+ family: def.family,
21087
+ members: def.members,
21088
+ path: path2,
21089
+ unlocked,
21090
+ status: "ok"
21091
+ };
21092
+ const altHits = altPaths.filter((p) => {
21093
+ try {
21094
+ return existsSync46(p);
21095
+ } catch {
21096
+ return false;
21097
+ }
21098
+ });
21099
+ if (altHits.length > 0)
21100
+ base.altHits = altHits;
21101
+ if (!existsSync46(path2))
21102
+ return { ...base, status: "missing" };
21103
+ if (def.kind === "file") {
21104
+ try {
21105
+ const s = statSync10(path2);
21106
+ return { ...base, files: 1, bytes: s.size, newestAt: new Date(s.mtimeMs).toISOString() };
21107
+ } catch (err) {
21108
+ return { ...base, status: isPermissionError(err) ? "permission-denied" : "error", detail: String(err) };
21109
+ }
21110
+ }
21111
+ const all = [];
21112
+ const budget = { left: opts.maxFiles ?? DEFAULT_MAX_FILES };
21113
+ try {
21114
+ collect(path2, def, all, budget);
21115
+ } catch (err) {
21116
+ if (isPermissionError(err))
21117
+ return { ...base, status: "permission-denied", detail: "EACCES" };
21118
+ if (isMissingError(err))
21119
+ return { ...base, status: "missing" };
21120
+ return { ...base, status: "error", detail: err instanceof Error ? err.message : String(err) };
21121
+ }
21122
+ const files = [];
21123
+ let nested = 0;
21124
+ for (const f of all) {
21125
+ const rel = f.slice(path2.length).replace(/^[\\/]+/, "");
21126
+ if (def.subordinate?.(rel))
21127
+ nested++;
21128
+ else
21129
+ files.push(f);
21130
+ }
21131
+ let bytes = 0;
21132
+ let newest = 0;
21133
+ for (const f of all) {
21134
+ try {
21135
+ const s = statSync10(f);
21136
+ bytes += s.size;
21137
+ if (s.mtimeMs > newest)
21138
+ newest = s.mtimeMs;
21139
+ } catch {}
21140
+ }
21141
+ const result = {
21142
+ ...base,
21143
+ files: files.length,
21144
+ ...nested > 0 ? { nested } : {},
21145
+ bytes,
21146
+ newestAt: newest > 0 ? new Date(newest).toISOString() : null
21147
+ };
21148
+ if (opts.skipMarkers || !def.marker)
21149
+ return result;
21150
+ const counts = new Map;
21151
+ for (const f of files) {
21152
+ const head = readHead(f, def.headBytes ?? 64 * 1024);
21153
+ const { agentSlug, marker, recognised } = attributeTranscript(def, head);
21154
+ const clean = marker === null ? null : sanitizeMarker(marker);
21155
+ const key = clean ?? "\x00none";
21156
+ const existing = counts.get(key);
21157
+ if (existing)
21158
+ existing.count++;
21159
+ else
21160
+ counts.set(key, { marker: clean, count: 1, agentSlug, recognised });
21161
+ }
21162
+ result.markers = [...counts.values()].sort((a, b) => b.count - a.count);
21163
+ return result;
21164
+ }
21165
+ function censusStores(stores, detectedSlugs, opts = {}) {
21166
+ const detected = new Set(detectedSlugs);
21167
+ return stores.map((store) => censusStore(store, isStoreUnlocked(store.def, detected), opts));
21168
+ }
21169
+ function orphanedStoresWithContent(censuses) {
21170
+ return censuses.filter((c) => !c.unlocked && c.status === "ok" && (c.files ?? 0) > 0);
21171
+ }
21172
+
21173
+ // src/commands/conversations.ts
21174
+ init_conversation_queue();
21175
+ init_session_summary();
21176
+ init_telemetry_outbox();
21177
+ init_insight_store();
21178
+ init_atomic_json();
21179
+ init_store();
21180
+ init_client();
21181
+ init_resolve();
21182
+ init_workspace_state();
21183
+ init_colors();
21184
+ await __promiseAll([
21185
+ init_conversation_registry(),
21186
+ init_conversation_analysis()
21187
+ ]);
21188
+ var DEFAULT_LOOKBACK_DAYS = 0;
21189
+ function sinceFromDays(days) {
21190
+ if (days <= 0)
21191
+ return null;
21192
+ return new Date(Date.now() - days * 24 * 3600 * 1000).toISOString();
21193
+ }
21194
+ function parseDays(raw) {
21195
+ const n = Number(raw);
21196
+ if (!Number.isFinite(n) || n < 0)
21197
+ return DEFAULT_LOOKBACK_DAYS;
21198
+ return Math.floor(n);
20309
21199
  }
20310
21200
  function bareRegistrySkillId(skill) {
20311
21201
  if (skill.type !== "external" || typeof skill.id !== "string")
@@ -20359,22 +21249,34 @@ function fit(text2, width) {
20359
21249
  return text2.length > width ? text2.slice(0, width - 1) + "…" : text2;
20360
21250
  }
20361
21251
  var conversationsCommand = new Command15("conversations").description("Local AI conversations across your agents (registry, end detection)");
20362
- conversationsCommand.command("list").description("List local conversations across all detected agents (metadata only)").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) => {
21252
+ conversationsCommand.command("list").description("List local conversations across all detected agents (metadata only)").option("--days <n>", "Narrow to this many days (0 = no window, the default)", String(DEFAULT_LOOKBACK_DAYS)).option("--idle-minutes <n>", "Idle threshold that marks a conversation finished", String(DEFAULT_IDLE_MINUTES)).action(async (opts, command) => {
20363
21253
  const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
20364
21254
  const days = parseDays(opts.days);
20365
21255
  const idleMinutes = Math.max(1, Number(opts.idleMinutes) || DEFAULT_IDLE_MINUTES);
20366
21256
  const result = await listLocalConversations({ sinceISO: sinceFromDays(days), idleMinutes });
20367
21257
  if (json) {
20368
- jsonOut({ conversations: result.conversations, perAgent: result.perAgent });
21258
+ jsonOut({ conversations: result.conversations, perAgent: result.perAgent, perStore: result.perStore });
20369
21259
  return;
20370
21260
  }
20371
21261
  console.log(bold(days > 0 ? `
20372
21262
  Local conversations, last ${days} day(s)` : `
20373
21263
  Local conversations (all)`));
21264
+ for (const s of result.perStore) {
21265
+ const reason = describeStoreOutcome(s);
21266
+ if (reason === null)
21267
+ continue;
21268
+ const line = ` ${s.storeId}: ${reason}`;
21269
+ console.log(storeOutcomeNeedsAttention(s) ? yellow(line) : dim(line));
21270
+ }
20374
21271
  for (const a of result.perAgent) {
20375
- if (a.sessions === "unsupported")
21272
+ if (a.status === "no-reader")
20376
21273
  continue;
20377
- console.log(dim(` ${a.slug}: ${a.sessions === "error" ? "read error" : `${a.sessions} session(s)`}`));
21274
+ const reason = describeScanOutcome(a);
21275
+ if (reason === null) {
21276
+ console.log(dim(` ${a.agentSlug}: ${a.sessions} session(s)`));
21277
+ } else {
21278
+ console.log(` ${a.agentSlug}: ${scanOutcomeNeedsAttention(a) ? yellow(reason) : dim(reason)}`);
21279
+ }
20378
21280
  }
20379
21281
  console.log("");
20380
21282
  const width = process.stdout.columns || 120;
@@ -20391,7 +21293,36 @@ Local conversations (all)`));
20391
21293
  if (result.conversations.length === 0)
20392
21294
  console.log(dim(" none"));
20393
21295
  });
20394
- 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) => {
21296
+ conversationsCommand.command("status").description("Why each agent did or did not contribute conversations (no conversation data)").option("--days <n>", "Narrow to this many days (0 = no window, the default)", String(DEFAULT_LOOKBACK_DAYS)).action(async (opts, command) => {
21297
+ const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
21298
+ const days = parseDays(opts.days);
21299
+ const { perAgent, perStore } = await listLocalConversations({ sinceISO: sinceFromDays(days) });
21300
+ if (json) {
21301
+ jsonOut({ perAgent, perStore });
21302
+ return;
21303
+ }
21304
+ console.log(bold(days > 0 ? `
21305
+ Conversation sources, last ${days} day(s)` : `
21306
+ Conversation sources`));
21307
+ for (const s of perStore) {
21308
+ const reason = describeStoreOutcome(s);
21309
+ if (reason === null) {
21310
+ console.log(` ${cyan(s.storeId)}: ${s.sessions} conversation(s)`);
21311
+ } else {
21312
+ console.log(` ${cyan(s.storeId)}: ${storeOutcomeNeedsAttention(s) ? yellow(reason) : dim(reason)}`);
21313
+ }
21314
+ }
21315
+ console.log("");
21316
+ for (const a of perAgent) {
21317
+ const reason = describeScanOutcome(a);
21318
+ if (reason === null) {
21319
+ console.log(` ${cyan(a.agentSlug)}: ${a.status === "no-reader" ? dim("no transcript reader") : `${a.sessions} conversation(s)`}`);
21320
+ } else {
21321
+ console.log(` ${cyan(a.agentSlug)}: ${scanOutcomeNeedsAttention(a) ? yellow(reason) : dim(reason)}`);
21322
+ }
21323
+ }
21324
+ });
21325
+ conversationsCommand.command("scan").description("Detect finished conversations (idle threshold) and queue them for reflection").option("--days <n>", "Narrow to this many days (0 = no window, the default)", String(DEFAULT_LOOKBACK_DAYS)).option("--idle-minutes <n>", "Idle threshold that marks a conversation finished", String(DEFAULT_IDLE_MINUTES)).action(async (opts, command) => {
20395
21326
  const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
20396
21327
  const days = parseDays(opts.days);
20397
21328
  const idleMinutes = Math.max(1, Number(opts.idleMinutes) || DEFAULT_IDLE_MINUTES);
@@ -20454,11 +21385,13 @@ conversationsCommand.command("scan").description("Detect finished conversations
20454
21385
  const snapshot = {
20455
21386
  generatedAt: new Date().toISOString(),
20456
21387
  idleMinutes,
20457
- conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt))
21388
+ conversations: [...listed, ...rescued].sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt)),
21389
+ perAgent: result.perAgent,
21390
+ perStore: result.perStore
20458
21391
  };
20459
- writeJsonAtomic(join43(homedir24(), ".runwork", "conversations.json"), snapshot);
21392
+ writeJsonAtomic(join44(homedir25(), ".runwork", "conversations.json"), snapshot);
20460
21393
  if (json) {
20461
- jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length });
21394
+ jsonOut({ queued: scan.queued, reopened: scan.reopened, pruned: scan.pruned, pending, finished: finished.length, perAgent: result.perAgent });
20462
21395
  return;
20463
21396
  }
20464
21397
  console.log(green(`Scanned ${result.conversations.length} conversation(s): ${scan.queued} newly queued, ${scan.reopened} reopened, ${pending} pending analysis.`));
@@ -20479,15 +21412,71 @@ conversationsCommand.command("analyze").description("Analyze queued finished con
20479
21412
  }
20480
21413
  console.log(outcome.reason ? dim(describeAnalysisOutcome(outcome)) : green(describeAnalysisOutcome(outcome)));
20481
21414
  });
21415
+ conversationsCommand.command("stores").description("Every local transcript store: what is in it, who wrote it, and whether anything reads it").option("--max-files <n>", "Cap transcripts inspected per store", String(DEFAULT_MAX_FILES)).option("--no-markers", "Skip provenance reads; counts, sizes and mtimes only").action(async (opts, command) => {
21416
+ const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
21417
+ const maxFiles = Math.max(1, Number(opts.maxFiles) || DEFAULT_MAX_FILES);
21418
+ const detected = (await detectAgents()).map((a) => a.slug);
21419
+ const stores = resolveTranscriptStores({
21420
+ platform: platform8(),
21421
+ homeDir: homedir25(),
21422
+ appData: process.env.APPDATA ?? null,
21423
+ localAppData: process.env.LOCALAPPDATA ?? null
21424
+ });
21425
+ const censuses = censusStores(stores, detected, {
21426
+ maxFiles,
21427
+ skipMarkers: opts.markers === false
21428
+ });
21429
+ if (json) {
21430
+ jsonOut({ detected, stores: censuses });
21431
+ return;
21432
+ }
21433
+ console.log(bold(`
21434
+ Transcript stores`));
21435
+ console.log(dim(` detected agents: ${detected.length > 0 ? detected.join(", ") : "none"}
21436
+ `));
21437
+ for (const c of censuses) {
21438
+ const owner = c.unlocked ? green("read") : yellow("ORPHANED");
21439
+ console.log(`${bold(c.storeId)} ${owner} ${dim(`family ${c.family}: ${c.members.join(", ")}`)}`);
21440
+ console.log(dim(` ${c.path}`));
21441
+ if (c.status !== "ok") {
21442
+ console.log(` ${yellow(c.status)}${c.detail ? dim(` (${c.detail})`) : ""}`);
21443
+ } else {
21444
+ const mb = ((c.bytes ?? 0) / 1e6).toFixed(1);
21445
+ const newest = c.newestAt ? c.newestAt.slice(0, 16).replace("T", " ") : "-";
21446
+ const nested = c.nested ? `, plus ${c.nested} subagent transcript(s)` : "";
21447
+ console.log(dim(` ${c.files} conversation(s)${nested}, ${mb} MB, newest ${newest}`));
21448
+ for (const m of c.markers ?? []) {
21449
+ if (m.marker === null) {
21450
+ console.log(` ${String(m.count).padStart(5)} ${dim(`(no marker, pre-dates the field) -> ${m.agentSlug}`)}`);
21451
+ continue;
21452
+ }
21453
+ const flag = m.recognised ? dim(`-> ${m.agentSlug}`) : yellow(`-> ${m.agentSlug} (UNRECOGNISED surface)`);
21454
+ console.log(` ${String(m.count).padStart(5)} ${cyan(m.marker)} ${flag}`);
21455
+ }
21456
+ }
21457
+ for (const alt of c.altHits ?? []) {
21458
+ console.log(` ${yellow("also present:")} ${dim(alt)}`);
21459
+ }
21460
+ console.log("");
21461
+ }
21462
+ const orphans = orphanedStoresWithContent(censuses);
21463
+ if (orphans.length > 0) {
21464
+ console.log(yellow(`${orphans.length} store(s) hold transcripts that no detected agent reads:`));
21465
+ for (const o of orphans) {
21466
+ console.log(` ${o.path} ${dim(`(${o.files} transcripts; needs one of: ${o.members.join(", ")})`)}`);
21467
+ }
21468
+ console.log("");
21469
+ }
21470
+ });
20482
21471
 
20483
21472
  // src/commands/instructions.ts
20484
21473
  init_store();
20485
21474
  init_client();
20486
21475
  init_resolve();
20487
21476
  import { Command as Command16 } from "commander";
20488
- import { readFileSync as readFileSync37, existsSync as existsSync48 } from "fs";
20489
- import { join as join44 } from "path";
20490
- import { homedir as homedir25 } from "os";
21477
+ import { readFileSync as readFileSync38, existsSync as existsSync49 } from "fs";
21478
+ import { join as join45 } from "path";
21479
+ import { homedir as homedir26 } from "os";
20491
21480
 
20492
21481
  // ../../shared/agent-instructions/runwork-instructions.ts
20493
21482
  function formatList(items, max = 8) {
@@ -20967,13 +21956,13 @@ async function buildInstructionContext(client, workspace) {
20967
21956
  // src/commands/instructions.ts
20968
21957
  function readSetupExtras(workspaceId) {
20969
21958
  for (const path2 of [
20970
- join44(process.cwd(), ".runwork", "setup.json"),
20971
- join44(homedir25(), ".runwork", "setup.json")
21959
+ join45(process.cwd(), ".runwork", "setup.json"),
21960
+ join45(homedir26(), ".runwork", "setup.json")
20972
21961
  ]) {
20973
- if (!existsSync48(path2))
21962
+ if (!existsSync49(path2))
20974
21963
  continue;
20975
21964
  try {
20976
- const state = JSON.parse(readFileSync37(path2, "utf-8"));
21965
+ const state = JSON.parse(readFileSync38(path2, "utf-8"));
20977
21966
  if (state.workspaceId === workspaceId) {
20978
21967
  return { workspaceSlug: state.workspaceSlug, persona: state.persona };
20979
21968
  }
@@ -21017,16 +22006,16 @@ import { Command as Command17 } from "commander";
21017
22006
  import * as path3 from "node:path";
21018
22007
 
21019
22008
  // src/utils/data-input.ts
21020
- import { readFileSync as readFileSync38, existsSync as existsSync49 } from "fs";
22009
+ import { readFileSync as readFileSync39, existsSync as existsSync50 } from "fs";
21021
22010
  async function parseDataInput(dataFlag) {
21022
22011
  if (dataFlag) {
21023
22012
  if (dataFlag.startsWith("@")) {
21024
22013
  const filePath = dataFlag.slice(1);
21025
- if (!existsSync49(filePath)) {
22014
+ if (!existsSync50(filePath)) {
21026
22015
  console.error(`File not found: ${filePath}`);
21027
22016
  process.exit(1);
21028
22017
  }
21029
- const content = readFileSync38(filePath, "utf-8");
22018
+ const content = readFileSync39(filePath, "utf-8");
21030
22019
  return parseJson(content, filePath);
21031
22020
  }
21032
22021
  return parseJson(dataFlag, "--data");
@@ -22080,7 +23069,7 @@ init_resolve();
22080
23069
  init_prompt();
22081
23070
  init_http();
22082
23071
  import { Command as Command21 } from "commander";
22083
- import { writeFileSync as writeFileSync29, readFileSync as readFileSync39 } from "fs";
23072
+ import { writeFileSync as writeFileSync29, readFileSync as readFileSync40 } from "fs";
22084
23073
  import { basename as basename6 } from "path";
22085
23074
  function formatSize(bytes) {
22086
23075
  if (bytes === undefined)
@@ -22190,7 +23179,7 @@ var uploadCommand = new Command21("upload").description("Upload a local file to
22190
23179
  const { workspaceId } = await resolveWorkspace2(client, opts);
22191
23180
  const objectKey = key || basename6(localPath);
22192
23181
  try {
22193
- const fileBuffer = readFileSync39(localPath);
23182
+ const fileBuffer = readFileSync40(localPath);
22194
23183
  const { url } = await client.getPresignedUrl(workspaceId, bucket, { action: "write", key: objectKey });
22195
23184
  const response = await httpFetch(url, { method: "PUT", body: fileBuffer });
22196
23185
  if (!response.ok) {
@@ -22707,8 +23696,8 @@ init_resolve();
22707
23696
  init_prompt();
22708
23697
  await init_detect();
22709
23698
  import { Command as Command27 } from "commander";
22710
- import { join as join49 } from "path";
22711
- import { homedir as homedir28 } from "os";
23699
+ import { join as join50 } from "path";
23700
+ import { homedir as homedir29 } from "os";
22712
23701
 
22713
23702
  // src/commands/sync.ts
22714
23703
  init_store();
@@ -22719,9 +23708,9 @@ await __promiseAll([
22719
23708
  init_codex()
22720
23709
  ]);
22721
23710
  import { Command as Command26 } from "commander";
22722
- import { readFileSync as readFileSync41, existsSync as existsSync52 } from "fs";
22723
- import { join as join48 } from "path";
22724
- import { homedir as homedir27 } from "os";
23711
+ import { readFileSync as readFileSync42, existsSync as existsSync53 } from "fs";
23712
+ import { join as join49 } from "path";
23713
+ import { homedir as homedir28 } from "os";
22725
23714
 
22726
23715
  // src/commands/mcp-entries.ts
22727
23716
  init_types();
@@ -22785,6 +23774,7 @@ var RUNWORK_AGENT_DEFAULTS = {
22785
23774
  };
22786
23775
  var AGENT_DEFAULTS_SCHEMA_VERSION = 1;
22787
23776
  // src/commands/sync-telemetry.ts
23777
+ init_transcript_stores();
22788
23778
  var TELEMETRY_BACKFILL_MS = 30 * 24 * 60 * 60 * 1000;
22789
23779
  function resolveTelemetrySince(state, nowMs = Date.now()) {
22790
23780
  if (state.lastTelemetryAt)
@@ -22795,11 +23785,20 @@ async function collectTelemetryEvents(params) {
22795
23785
  const now = params.now ?? new Date().toISOString();
22796
23786
  const events = [];
22797
23787
  const adapterResults = [];
23788
+ const bySlug = new Map(params.adapters.map((a) => [a.slug, a]));
23789
+ const suppressed = suppressedStoreReaders(params.adapters.map((a) => a.slug), (a, b) => {
23790
+ const left = bySlug.get(a);
23791
+ const right = bySlug.get(b);
23792
+ if (!left || !right)
23793
+ return false;
23794
+ return left.readUsageStats !== undefined && left.readUsageStats === right.readUsageStats || left.readSkillUsage !== undefined && left.readSkillUsage === right.readSkillUsage;
23795
+ });
22798
23796
  for (const adapter2 of params.adapters) {
22799
23797
  let stats = "unsupported";
22800
23798
  let skills = "unsupported";
22801
23799
  let error;
22802
- if (adapter2.readUsageStats) {
23800
+ const readsStore = !suppressed.has(adapter2.slug);
23801
+ if (adapter2.readUsageStats && readsStore) {
22803
23802
  try {
22804
23803
  stats = await adapter2.readUsageStats(params.since);
22805
23804
  if (stats && stats.hasNewActivity) {
@@ -22827,7 +23826,7 @@ async function collectTelemetryEvents(params) {
22827
23826
  stats = null;
22828
23827
  }
22829
23828
  }
22830
- if (adapter2.readSkillUsage) {
23829
+ if (adapter2.readSkillUsage && readsStore) {
22831
23830
  try {
22832
23831
  skills = await adapter2.readSkillUsage(params.since);
22833
23832
  if (skills && skills.length > 0) {
@@ -23433,7 +24432,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23433
24432
  if (!action.remoteContent)
23434
24433
  continue;
23435
24434
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23436
- await writeSkillToAgents(skillFile, action.source, ctx);
24435
+ const outcome = await writeSkillToAgents(skillFile, action.source, ctx);
24436
+ if (!writeLanded(outcome)) {
24437
+ vlog(` Pull FAILED for ${action.name} (${outcome.succeeded}/${outcome.attempted} agent writes); will retry next sync`);
24438
+ continue;
24439
+ }
23437
24440
  newHashes[action.name] = {
23438
24441
  localHash: contentHash(buildSkillMd2(skillFile)),
23439
24442
  remoteHash: contentHash(action.remoteContent),
@@ -23475,7 +24478,11 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23475
24478
  vlog(` Pushed (conflict resolved): ${action.name}`);
23476
24479
  } else if (resolution === "remote" && action.remoteContent) {
23477
24480
  const skillFile = makeSkillFile(action.name, action.remoteContent);
23478
- await writeSkillToAgents(skillFile, action.source, ctx);
24481
+ const outcome = await writeSkillToAgents(skillFile, action.source, ctx);
24482
+ if (!writeLanded(outcome)) {
24483
+ vlog(` Conflict pull FAILED for ${action.name} (${outcome.succeeded}/${outcome.attempted} agent writes); will retry next sync`);
24484
+ continue;
24485
+ }
23479
24486
  newHashes[action.name] = {
23480
24487
  localHash: contentHash(buildSkillMd2(skillFile)),
23481
24488
  remoteHash: contentHash(action.remoteContent),
@@ -23487,6 +24494,7 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23487
24494
  }
23488
24495
  for (const _action of plan.skips) {}
23489
24496
  const deletionSlugs = plan.deletions.map((action) => toSlug(action.name));
24497
+ let removalsFailed = false;
23490
24498
  if (deletionSlugs.length) {
23491
24499
  for (const adapter2 of ctx.adapters) {
23492
24500
  if (!adapter2.supportsSkills() || !adapter2.removeSkills)
@@ -23494,14 +24502,19 @@ async function executeSyncPlan(plan, resolvedConflicts, ctx) {
23494
24502
  for (const scope of ctx.scopes) {
23495
24503
  try {
23496
24504
  await adapter2.removeSkills(deletionSlugs, scope);
23497
- } catch {}
24505
+ } catch {
24506
+ removalsFailed = true;
24507
+ }
23498
24508
  }
23499
24509
  }
23500
24510
  for (const action of plan.deletions) {
23501
- vlog(` Deleted locally: ${action.name}`);
24511
+ vlog(removalsFailed ? ` Deletion of ${action.name} incomplete; will retry next sync` : ` Deleted locally: ${action.name}`);
23502
24512
  }
23503
24513
  }
23504
- return newHashes;
24514
+ return {
24515
+ newHashes,
24516
+ failedDeletions: removalsFailed ? plan.deletions.map((action) => action.name) : []
24517
+ };
23505
24518
  }
23506
24519
  function makeSkillFile(name, content) {
23507
24520
  return {
@@ -23512,17 +24525,25 @@ function makeSkillFile(name, content) {
23512
24525
  };
23513
24526
  }
23514
24527
  async function writeSkillToAgents(skillFile, source, ctx) {
24528
+ let attempted = 0;
24529
+ let succeeded = 0;
23515
24530
  for (const adapter2 of ctx.adapters) {
23516
24531
  if (source === "app" && ctx.hasMcp && adapter2.mcpProvidesSkills)
23517
24532
  continue;
23518
24533
  for (const scope of ctx.scopes) {
23519
24534
  if (!adapter2.supportsSkills())
23520
24535
  continue;
24536
+ attempted++;
23521
24537
  try {
23522
24538
  await adapter2.writeSkills([skillFile], scope);
24539
+ succeeded++;
23523
24540
  } catch {}
23524
24541
  }
23525
24542
  }
24543
+ return { attempted, succeeded };
24544
+ }
24545
+ function writeLanded(outcome) {
24546
+ return outcome.attempted === 0 || outcome.succeeded > 0;
23526
24547
  }
23527
24548
  function extractDescription(content) {
23528
24549
  const match = content.match(/^---\n[\s\S]*?description:\s*(.+)\n[\s\S]*?---/);
@@ -23581,10 +24602,10 @@ function sameStringSet(a, b) {
23581
24602
  }
23582
24603
 
23583
24604
  // src/utils/sync-lock.ts
23584
- import { existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync40, unlinkSync as unlinkSync8, writeFileSync as writeFileSync30 } from "fs";
23585
- import { join as join47 } from "path";
23586
- import { homedir as homedir26 } from "os";
23587
- var LOCK_PATH = join47(homedir26(), ".runwork", "sync.lock");
24605
+ import { existsSync as existsSync52, mkdirSync as mkdirSync28, readFileSync as readFileSync41, unlinkSync as unlinkSync8, writeFileSync as writeFileSync30 } from "fs";
24606
+ import { join as join48 } from "path";
24607
+ import { homedir as homedir27 } from "os";
24608
+ var LOCK_PATH = join48(homedir27(), ".runwork", "sync.lock");
23588
24609
  var STALE_LOCK_MS = 5 * 60 * 1000;
23589
24610
  var DEFAULT_WAIT_MS = 30000;
23590
24611
  var exitHandlerRegistered = false;
@@ -23604,15 +24625,15 @@ function isProcessAlive(pid) {
23604
24625
  }
23605
24626
  function readLock() {
23606
24627
  try {
23607
- return JSON.parse(readFileSync40(LOCK_PATH, "utf-8"));
24628
+ return JSON.parse(readFileSync41(LOCK_PATH, "utf-8"));
23608
24629
  } catch {
23609
24630
  return null;
23610
24631
  }
23611
24632
  }
23612
24633
  function writeLockExclusive() {
23613
24634
  try {
23614
- if (!existsSync51(join47(homedir26(), ".runwork"))) {
23615
- mkdirSync28(join47(homedir26(), ".runwork"), { recursive: true });
24635
+ if (!existsSync52(join48(homedir27(), ".runwork"))) {
24636
+ mkdirSync28(join48(homedir27(), ".runwork"), { recursive: true });
23616
24637
  }
23617
24638
  writeFileSync30(LOCK_PATH, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), {
23618
24639
  flag: "wx"
@@ -23681,10 +24702,10 @@ Tip: ${hint.title}`);
23681
24702
  } catch {}
23682
24703
  }
23683
24704
  function loadSetupState(filePath) {
23684
- if (!existsSync52(filePath))
24705
+ if (!existsSync53(filePath))
23685
24706
  return null;
23686
24707
  try {
23687
- return JSON.parse(readFileSync41(filePath, "utf-8"));
24708
+ return JSON.parse(readFileSync42(filePath, "utf-8"));
23688
24709
  } catch {
23689
24710
  return null;
23690
24711
  }
@@ -23705,15 +24726,15 @@ function readLocalSkills(state) {
23705
24726
  if (!baseDir)
23706
24727
  continue;
23707
24728
  for (const skillName of state.skills) {
23708
- const skillMdPath = join48(baseDir, skillName, "SKILL.md");
23709
- if (existsSync52(skillMdPath)) {
23710
- results.push({ name: skillName, content: readFileSync41(skillMdPath, "utf-8") });
24729
+ const skillMdPath = join49(baseDir, skillName, "SKILL.md");
24730
+ if (existsSync53(skillMdPath)) {
24731
+ results.push({ name: skillName, content: readFileSync42(skillMdPath, "utf-8") });
23711
24732
  continue;
23712
24733
  }
23713
24734
  const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23714
- const flatPath = join48(baseDir, `${filename}.md`);
23715
- if (existsSync52(flatPath)) {
23716
- results.push({ name: skillName, content: readFileSync41(flatPath, "utf-8") });
24735
+ const flatPath = join49(baseDir, `${filename}.md`);
24736
+ if (existsSync53(flatPath)) {
24737
+ results.push({ name: skillName, content: readFileSync42(flatPath, "utf-8") });
23717
24738
  }
23718
24739
  }
23719
24740
  if (results.length > 0)
@@ -23937,7 +24958,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23937
24958
  if (adapters.length > 0) {
23938
24959
  console.log(` Syncing to: ${adapters.map((a) => a.name).join(", ")}`);
23939
24960
  }
23940
- const newHashes = await executeSyncPlan(plan, resolvedConflicts, {
24961
+ const { newHashes, failedDeletions } = await executeSyncPlan(plan, resolvedConflicts, {
23941
24962
  client,
23942
24963
  workspaceId: state.workspaceId,
23943
24964
  adapters,
@@ -23967,9 +24988,9 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23967
24988
  persona: state.persona
23968
24989
  });
23969
24990
  let projectAppSkillFilter = null;
23970
- if (existsSync52(".runwork.json")) {
24991
+ if (existsSync53(".runwork.json")) {
23971
24992
  try {
23972
- const config = JSON.parse(readFileSync41(".runwork.json", "utf-8"));
24993
+ const config = JSON.parse(readFileSync42(".runwork.json", "utf-8"));
23973
24994
  if (config.appName) {
23974
24995
  projectAppSkillFilter = config.appName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23975
24996
  }
@@ -24238,7 +25259,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24238
25259
  }
24239
25260
  for (const adapter2 of adapters) {
24240
25261
  if (adapter2 instanceof CodexAdapter) {
24241
- const runworkDir = join48(homedir27(), ".runwork");
25262
+ const runworkDir = join49(homedir28(), ".runwork");
24242
25263
  const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
24243
25264
  if (result === "written") {
24244
25265
  vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
@@ -24324,7 +25345,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
24324
25345
  for (const [name, hash] of Object.entries(newHashes)) {
24325
25346
  mergedHashes[name] = hash;
24326
25347
  }
25348
+ const retryDeletions = new Set(failedDeletions);
24327
25349
  for (const del of plan.deletions) {
25350
+ if (retryDeletions.has(del.name))
25351
+ continue;
24328
25352
  delete mergedHashes[del.name];
24329
25353
  }
24330
25354
  state.skillHashes = mergedHashes;
@@ -24395,8 +25419,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
24395
25419
  verbose: !!opts.verbose,
24396
25420
  redetect: !!opts.redetect
24397
25421
  };
24398
- const projectStatePath = join48(process.cwd(), ".runwork", "setup.json");
24399
- const userStatePath = join48(homedir27(), ".runwork", "setup.json");
25422
+ const projectStatePath = join49(process.cwd(), ".runwork", "setup.json");
25423
+ const userStatePath = join49(homedir28(), ".runwork", "setup.json");
24400
25424
  const projectState = loadSetupState(projectStatePath);
24401
25425
  const userState = loadSetupState(userStatePath);
24402
25426
  if (!projectState && !userState) {
@@ -24465,7 +25489,7 @@ function toSkillFilename(name) {
24465
25489
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
24466
25490
  }
24467
25491
  function loadSetupStateForScope(scope) {
24468
- const path4 = scope === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
25492
+ const path4 = scope === "project" ? join50(process.cwd(), ".runwork", "setup.json") : join50(homedir29(), ".runwork", "setup.json");
24469
25493
  return readJsonOrNull(path4);
24470
25494
  }
24471
25495
  async function parkAndTeardownWorkspace(previous, scopes) {
@@ -24634,8 +25658,8 @@ Re-run without --dry-run to sync workspace data.`);
24634
25658
  }
24635
25659
  persistDefaultWorkspace(workspaceId, workspaceName);
24636
25660
  for (const s of scopes) {
24637
- const dir = s === "project" ? ".runwork" : join49(homedir28(), ".runwork");
24638
- writeJsonAtomic(join49(dir, "setup.json"), state);
25661
+ const dir = s === "project" ? ".runwork" : join50(homedir29(), ".runwork");
25662
+ writeJsonAtomic(join50(dir, "setup.json"), state);
24639
25663
  }
24640
25664
  if (restored)
24641
25665
  clearParkedState(workspaceId);
@@ -24643,7 +25667,7 @@ Re-run without --dry-run to sync workspace data.`);
24643
25667
  Syncing workspace data...
24644
25668
  `);
24645
25669
  for (const s of scopes) {
24646
- const statePath2 = s === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
25670
+ const statePath2 = s === "project" ? join50(process.cwd(), ".runwork", "setup.json") : join50(homedir29(), ".runwork", "setup.json");
24647
25671
  await syncFromState(state, statePath2, credentials, {
24648
25672
  dryRun: false,
24649
25673
  pullOnly: true,
@@ -24663,16 +25687,16 @@ init_client();
24663
25687
  import { Command as Command28 } from "commander";
24664
25688
 
24665
25689
  // src/utils/setup-state.ts
24666
- import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
24667
- import { join as join50 } from "path";
24668
- import { homedir as homedir29 } from "os";
25690
+ import { existsSync as existsSync54, readFileSync as readFileSync43 } from "fs";
25691
+ import { join as join51 } from "path";
25692
+ import { homedir as homedir30 } from "os";
24669
25693
  function loadSetupState2() {
24670
- const projectPath = join50(process.cwd(), ".runwork", "setup.json");
24671
- const userPath = join50(homedir29(), ".runwork", "setup.json");
25694
+ const projectPath = join51(process.cwd(), ".runwork", "setup.json");
25695
+ const userPath = join51(homedir30(), ".runwork", "setup.json");
24672
25696
  for (const p of [projectPath, userPath]) {
24673
- if (existsSync53(p)) {
25697
+ if (existsSync54(p)) {
24674
25698
  try {
24675
- return JSON.parse(readFileSync42(p, "utf-8"));
25699
+ return JSON.parse(readFileSync43(p, "utf-8"));
24676
25700
  } catch {
24677
25701
  continue;
24678
25702
  }
@@ -24731,14 +25755,14 @@ init_client();
24731
25755
  init_types();
24732
25756
  await init_detect();
24733
25757
  import { Command as Command29 } from "commander";
24734
- import { existsSync as existsSync54, readFileSync as readFileSync43 } from "fs";
24735
- import { resolve as resolve3, join as join51 } from "path";
24736
- import { homedir as homedir30 } from "os";
25758
+ import { existsSync as existsSync55, readFileSync as readFileSync44 } from "fs";
25759
+ import { resolve as resolve3, join as join52 } from "path";
25760
+ import { homedir as homedir31 } from "os";
24737
25761
  function loadSetupState3(filePath) {
24738
- if (!existsSync54(filePath))
25762
+ if (!existsSync55(filePath))
24739
25763
  return null;
24740
25764
  try {
24741
- return JSON.parse(readFileSync43(filePath, "utf-8"));
25765
+ return JSON.parse(readFileSync44(filePath, "utf-8"));
24742
25766
  } catch {
24743
25767
  return null;
24744
25768
  }
@@ -24754,8 +25778,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24754
25778
  process.exit(1);
24755
25779
  }
24756
25780
  const credentials = requireAuth();
24757
- const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
24758
- const userStatePath = join51(homedir30(), ".runwork", "setup.json");
25781
+ const projectStatePath = join52(process.cwd(), ".runwork", "setup.json");
25782
+ const userStatePath = join52(homedir31(), ".runwork", "setup.json");
24759
25783
  const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
24760
25784
  if (!state) {
24761
25785
  console.error("No setup state found. Run `runwork setup` first.");
@@ -24848,14 +25872,14 @@ init_prompt();
24848
25872
  init_subprocess();
24849
25873
  await init_detect();
24850
25874
  import { Command as Command30 } from "commander";
24851
- import { existsSync as existsSync55, readFileSync as readFileSync44, writeFileSync as writeFileSync32, readdirSync as readdirSync16, rmSync as rmSync12, unlinkSync as unlinkSync9, lstatSync, readlinkSync } from "fs";
24852
- import { join as join52, resolve as resolve4, relative as relative5, isAbsolute as isAbsolute5 } from "path";
24853
- import { homedir as homedir31 } from "os";
25875
+ import { existsSync as existsSync56, readFileSync as readFileSync45, writeFileSync as writeFileSync32, readdirSync as readdirSync17, rmSync as rmSync12, unlinkSync as unlinkSync9, lstatSync, readlinkSync } from "fs";
25876
+ import { join as join53, resolve as resolve4, relative as relative5, isAbsolute as isAbsolute5 } from "path";
25877
+ import { homedir as homedir32 } from "os";
24854
25878
  function loadSetupState4(filePath) {
24855
- if (!existsSync55(filePath))
25879
+ if (!existsSync56(filePath))
24856
25880
  return null;
24857
25881
  try {
24858
- return JSON.parse(readFileSync44(filePath, "utf-8"));
25882
+ return JSON.parse(readFileSync45(filePath, "utf-8"));
24859
25883
  } catch {
24860
25884
  return null;
24861
25885
  }
@@ -24863,22 +25887,22 @@ function loadSetupState4(filePath) {
24863
25887
  var PRESERVED_ENTRIES = ["bin", "apps", "trash"];
24864
25888
  function removeRunworkState(stateDir, opts) {
24865
25889
  const result = { removed: [], preserved: [], errors: [] };
24866
- if (!existsSync55(stateDir))
25890
+ if (!existsSync56(stateDir))
24867
25891
  return result;
24868
25892
  const preserve = new Set(PRESERVED_ENTRIES);
24869
25893
  if (opts.keepAuth)
24870
25894
  preserve.add(".credentials");
24871
25895
  let entries;
24872
25896
  try {
24873
- entries = readdirSync16(stateDir);
25897
+ entries = readdirSync17(stateDir);
24874
25898
  } catch (err) {
24875
25899
  result.errors.push(`${stateDir}: ${err instanceof Error ? err.message : err}`);
24876
25900
  return result;
24877
25901
  }
24878
25902
  for (const entry of entries) {
24879
- const target = join52(stateDir, entry);
25903
+ const target = join53(stateDir, entry);
24880
25904
  if (preserve.has(entry)) {
24881
- if (existsSync55(target))
25905
+ if (existsSync56(target))
24882
25906
  result.preserved.push(target);
24883
25907
  continue;
24884
25908
  }
@@ -24894,11 +25918,11 @@ function removeRunworkState(stateDir, opts) {
24894
25918
  var SHELL_PROFILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"];
24895
25919
  var BIN_DIR_PATTERN = /\.runwork[\\/]bin/;
24896
25920
  function stripRunworkPathLines(file) {
24897
- if (!existsSync55(file))
25921
+ if (!existsSync56(file))
24898
25922
  return false;
24899
25923
  let content;
24900
25924
  try {
24901
- content = readFileSync44(file, "utf-8");
25925
+ content = readFileSync45(file, "utf-8");
24902
25926
  } catch {
24903
25927
  return false;
24904
25928
  }
@@ -24927,7 +25951,7 @@ function stripRunworkPathLines(file) {
24927
25951
  }
24928
25952
  }
24929
25953
  function cleanShellProfilePathEntries() {
24930
- return SHELL_PROFILES.map((name) => join52(homedir31(), name)).filter(stripRunworkPathLines);
25954
+ return SHELL_PROFILES.map((name) => join53(homedir32(), name)).filter(stripRunworkPathLines);
24931
25955
  }
24932
25956
  function cleanPowerShellProfilePathEntries() {
24933
25957
  if (process.platform !== "win32")
@@ -24967,7 +25991,7 @@ function removeRunworkSymlink(linkPath) {
24967
25991
  } catch {
24968
25992
  return false;
24969
25993
  }
24970
- const ours = resolve4(join52(homedir31(), ".runwork", "bin"));
25994
+ const ours = resolve4(join53(homedir32(), ".runwork", "bin"));
24971
25995
  const rel = relative5(ours, resolve4(target));
24972
25996
  const insideOurs = rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
24973
25997
  if (!insideOurs)
@@ -24980,8 +26004,8 @@ function removeRunworkSymlink(linkPath) {
24980
26004
  }
24981
26005
  }
24982
26006
  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) => {
24983
- const projectStatePath = join52(process.cwd(), ".runwork", "setup.json");
24984
- const userStatePath = join52(homedir31(), ".runwork", "setup.json");
26007
+ const projectStatePath = join53(process.cwd(), ".runwork", "setup.json");
26008
+ const userStatePath = join53(homedir32(), ".runwork", "setup.json");
24985
26009
  const projectState = loadSetupState4(projectStatePath);
24986
26010
  const userState = loadSetupState4(userStatePath);
24987
26011
  if (!projectState && !userState) {
@@ -25068,8 +26092,8 @@ This will remove all Runwork configuration from your local agents:
25068
26092
  }
25069
26093
  }
25070
26094
  }
25071
- const stateDir = label === "project" ? join52(process.cwd(), ".runwork") : join52(homedir31(), ".runwork");
25072
- if (existsSync55(stateDir)) {
26095
+ const stateDir = label === "project" ? join53(process.cwd(), ".runwork") : join53(homedir32(), ".runwork");
26096
+ if (existsSync56(stateDir)) {
25073
26097
  const outcome = removeRunworkState(stateDir, {
25074
26098
  keepAuth: Boolean(opts.keepAuth) && label === "user"
25075
26099
  });
@@ -25099,7 +26123,7 @@ This will remove all Runwork configuration from your local agents:
25099
26123
  console.log(` - ${file}`);
25100
26124
  }
25101
26125
  const removedLinks = [
25102
- join52(homedir31(), ".local", "bin", "runwork"),
26126
+ join53(homedir32(), ".local", "bin", "runwork"),
25103
26127
  "/usr/local/bin/runwork"
25104
26128
  ].filter(removeRunworkSymlink);
25105
26129
  if (removedLinks.length > 0) {
@@ -25111,7 +26135,7 @@ This will remove all Runwork configuration from your local agents:
25111
26135
  if (process.platform === "win32") {
25112
26136
  console.log("");
25113
26137
  console.log(" Still on your PATH (remove by hand if you want it gone):");
25114
- console.log(` ${join52(homedir31(), ".runwork", "bin")} in your user PATH`);
26138
+ console.log(` ${join53(homedir32(), ".runwork", "bin")} in your user PATH`);
25115
26139
  }
25116
26140
  console.log("");
25117
26141
  if (errors > 0) {
@@ -25237,7 +26261,7 @@ var membersCommand = new Command32("members").description("List workspace member
25237
26261
  init_store();
25238
26262
  init_client();
25239
26263
  import { Command as Command33 } from "commander";
25240
- import { readFileSync as readFileSync45 } from "fs";
26264
+ import { readFileSync as readFileSync46 } from "fs";
25241
26265
  function normalizeApiPath(rawPath, baseUrl) {
25242
26266
  if (/^https?:\/\//i.test(rawPath)) {
25243
26267
  const target = new URL(rawPath);
@@ -25270,7 +26294,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
25270
26294
  let curlStr = opts.curl;
25271
26295
  if (opts.curlFile) {
25272
26296
  try {
25273
- curlStr = readFileSync45(opts.curlFile, "utf-8");
26297
+ curlStr = readFileSync46(opts.curlFile, "utf-8");
25274
26298
  } catch (err) {
25275
26299
  console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
25276
26300
  process.exit(1);
@@ -25290,7 +26314,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
25290
26314
  let raw = opts.body;
25291
26315
  if (raw.startsWith("@")) {
25292
26316
  try {
25293
- raw = readFileSync45(raw.slice(1), "utf-8");
26317
+ raw = readFileSync46(raw.slice(1), "utf-8");
25294
26318
  } catch (err) {
25295
26319
  console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
25296
26320
  process.exit(1);
@@ -25344,9 +26368,9 @@ init_preflight();
25344
26368
  init_credentials();
25345
26369
  await init_detect();
25346
26370
  import { parse as parse2 } from "smol-toml";
25347
- import { existsSync as existsSync56, readFileSync as readFileSync46 } from "fs";
25348
- import { join as join53, sep as sep4 } from "path";
25349
- import { homedir as homedir32, platform as osPlatform2, arch as osArch } from "os";
26371
+ import { existsSync as existsSync57, readFileSync as readFileSync47 } from "fs";
26372
+ import { join as join54, sep as sep4 } from "path";
26373
+ import { homedir as homedir33, platform as osPlatform2, arch as osArch } from "os";
25350
26374
  var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
25351
26375
  var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
25352
26376
  function detectPlatform() {
@@ -25374,10 +26398,10 @@ function buildContext() {
25374
26398
  const credentials = getCredentials();
25375
26399
  const client = credentials ? new ApiClient(credentials) : null;
25376
26400
  let config = null;
25377
- const configPath = join53(process.cwd(), ".runwork.json");
25378
- if (existsSync56(configPath)) {
26401
+ const configPath = join54(process.cwd(), ".runwork.json");
26402
+ if (existsSync57(configPath)) {
25379
26403
  try {
25380
- config = JSON.parse(readFileSync46(configPath, "utf-8"));
26404
+ config = JSON.parse(readFileSync47(configPath, "utf-8"));
25381
26405
  } catch {}
25382
26406
  }
25383
26407
  return { credentials, client, config, cwd: process.cwd() };
@@ -25431,8 +26455,8 @@ async function checkCliVersion() {
25431
26455
  };
25432
26456
  }
25433
26457
  async function checkCliArtifactReachable() {
25434
- const platform7 = detectPlatform();
25435
- if (!platform7.key) {
26458
+ const platform9 = detectPlatform();
26459
+ if (!platform9.key) {
25436
26460
  return {
25437
26461
  name: "cli-artifact",
25438
26462
  status: "skip",
@@ -25448,12 +26472,12 @@ async function checkCliArtifactReachable() {
25448
26472
  fix: "check network / outbound access to runwork.ai"
25449
26473
  };
25450
26474
  }
25451
- const entry = manifest.artifacts?.[platform7.key];
26475
+ const entry = manifest.artifacts?.[platform9.key];
25452
26476
  if (!entry?.path) {
25453
26477
  return {
25454
26478
  name: "cli-artifact",
25455
26479
  status: "fail",
25456
- message: `manifest has no artifact for ${platform7.key}`
26480
+ message: `manifest has no artifact for ${platform9.key}`
25457
26481
  };
25458
26482
  }
25459
26483
  const artifactUrl = `${BASE_URL2}${entry.path}`;
@@ -25466,7 +26490,7 @@ async function checkCliArtifactReachable() {
25466
26490
  message: `${artifactUrl} returned HTTP ${response.status}`
25467
26491
  };
25468
26492
  }
25469
- return { name: "cli-artifact", status: "pass", message: `${platform7.key} reachable` };
26493
+ return { name: "cli-artifact", status: "pass", message: `${platform9.key} reachable` };
25470
26494
  } catch (err) {
25471
26495
  const msg = err instanceof Error ? err.message : String(err);
25472
26496
  return {
@@ -25478,9 +26502,9 @@ async function checkCliArtifactReachable() {
25478
26502
  }
25479
26503
  async function checkCliInstallLocation() {
25480
26504
  const isWindows2 = osPlatform2() === "win32";
25481
- const home = homedir32();
25482
- const canonicalDir = join53(home, ".runwork", "bin");
25483
- const canonicalBinary = isWindows2 ? join53(canonicalDir, "runwork.exe") : join53(canonicalDir, "runwork");
26505
+ const home2 = homedir33();
26506
+ const canonicalDir = join54(home2, ".runwork", "bin");
26507
+ const canonicalBinary = isWindows2 ? join54(canonicalDir, "runwork.exe") : join54(canonicalDir, "runwork");
25484
26508
  const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
25485
26509
  const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
25486
26510
  if (runsFromCanonical) {
@@ -25490,7 +26514,7 @@ async function checkCliInstallLocation() {
25490
26514
  message: `canonical (${canonicalBinary})`
25491
26515
  };
25492
26516
  }
25493
- if (existsSync56(canonicalBinary)) {
26517
+ if (existsSync57(canonicalBinary)) {
25494
26518
  return {
25495
26519
  name: "cli-install-location",
25496
26520
  status: "warn",
@@ -25623,8 +26647,8 @@ async function checkGitCredentialHelper(ctx) {
25623
26647
  };
25624
26648
  }
25625
26649
  async function checkProjectConfig(ctx) {
25626
- const configPath = join53(ctx.cwd, ".runwork.json");
25627
- if (!existsSync56(configPath)) {
26650
+ const configPath = join54(ctx.cwd, ".runwork.json");
26651
+ if (!existsSync57(configPath)) {
25628
26652
  if (!ctx.credentials) {
25629
26653
  return { name: "project-config", status: "skip", message: "no project (not logged in)" };
25630
26654
  }
@@ -25686,7 +26710,7 @@ async function checkGitRemote(ctx) {
25686
26710
  if (!ctx.config) {
25687
26711
  return { name: "git-remote", status: "skip", message: "skipped (no project)" };
25688
26712
  }
25689
- if (!existsSync56(join53(ctx.cwd, ".git"))) {
26713
+ if (!existsSync57(join54(ctx.cwd, ".git"))) {
25690
26714
  return {
25691
26715
  name: "git-remote",
25692
26716
  status: "fail",
@@ -25740,12 +26764,12 @@ async function checkDeployFreshness(ctx) {
25740
26764
  return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
25741
26765
  }
25742
26766
  function loadSetupState5() {
25743
- const projectPath = join53(process.cwd(), ".runwork", "setup.json");
25744
- const userPath = join53(homedir32(), ".runwork", "setup.json");
26767
+ const projectPath = join54(process.cwd(), ".runwork", "setup.json");
26768
+ const userPath = join54(homedir33(), ".runwork", "setup.json");
25745
26769
  for (const p of [projectPath, userPath]) {
25746
- if (existsSync56(p)) {
26770
+ if (existsSync57(p)) {
25747
26771
  try {
25748
- return JSON.parse(readFileSync46(p, "utf-8"));
26772
+ return JSON.parse(readFileSync47(p, "utf-8"));
25749
26773
  } catch {
25750
26774
  continue;
25751
26775
  }
@@ -25760,13 +26784,13 @@ async function checkCodexNetwork() {
25760
26784
  if (!state || !state.configuredAgents.includes("codex")) {
25761
26785
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25762
26786
  }
25763
- const configPath = join53(homedir32(), ".codex", "config.toml");
25764
- if (!existsSync56(configPath)) {
26787
+ const configPath = join54(homedir33(), ".codex", "config.toml");
26788
+ if (!existsSync57(configPath)) {
25765
26789
  return { name, status: "skip", message: "no Codex config found" };
25766
26790
  }
25767
26791
  let parsed;
25768
26792
  try {
25769
- parsed = parse2(readFileSync46(configPath, "utf-8"));
26793
+ parsed = parse2(readFileSync47(configPath, "utf-8"));
25770
26794
  } catch {
25771
26795
  return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
25772
26796
  }
@@ -25819,19 +26843,19 @@ async function checkCodexDesktopProject() {
25819
26843
  if (!usesCodex) {
25820
26844
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25821
26845
  }
25822
- const statePath2 = join53(homedir32(), ".codex", ".codex-global-state.json");
25823
- if (!existsSync56(statePath2)) {
26846
+ const statePath2 = join54(homedir33(), ".codex", ".codex-global-state.json");
26847
+ if (!existsSync57(statePath2)) {
25824
26848
  return { name, status: "skip", message: "Codex desktop app not detected" };
25825
26849
  }
25826
26850
  let savedRoots = [];
25827
26851
  try {
25828
- const parsed = JSON.parse(readFileSync46(statePath2, "utf-8"));
26852
+ const parsed = JSON.parse(readFileSync47(statePath2, "utf-8"));
25829
26853
  const roots = parsed["electron-saved-workspace-roots"];
25830
26854
  savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
25831
26855
  } catch {
25832
26856
  return { name, status: "warn", message: "could not read Codex desktop state" };
25833
26857
  }
25834
- const runworkDir = join53(homedir32(), ".runwork");
26858
+ const runworkDir = join54(homedir33(), ".runwork");
25835
26859
  if (savedRoots.includes(runworkDir)) {
25836
26860
  return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
25837
26861
  }
@@ -25879,31 +26903,35 @@ async function checkAgentSetup() {
25879
26903
  details.push(`${state.configuredAgents.length} agent(s) configured`);
25880
26904
  }
25881
26905
  let mcpChecked = false;
26906
+ let mcpAllHealthy = true;
25882
26907
  for (const slug of state.configuredAgents) {
25883
26908
  const adapter2 = getAdapterBySlug(slug);
25884
26909
  if (!adapter2 || !adapter2.supportsMcpScope("user"))
25885
26910
  continue;
25886
26911
  const mcpConfigPath = getMcpConfigPath2(slug, "user");
25887
- if (mcpConfigPath && existsSync56(mcpConfigPath)) {
26912
+ if (mcpConfigPath && existsSync57(mcpConfigPath)) {
25888
26913
  try {
25889
- const content = readFileSync46(mcpConfigPath, "utf-8");
26914
+ const content = readFileSync47(mcpConfigPath, "utf-8");
25890
26915
  const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
25891
26916
  if (missingMcp.length > 0) {
25892
26917
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
25893
26918
  upgrade("warn");
25894
- } else {
25895
- details.push(`${state.mcpServers.length} MCP server(s) configured`);
26919
+ mcpAllHealthy = false;
25896
26920
  }
25897
26921
  mcpChecked = true;
25898
- break;
25899
26922
  } catch {}
25900
26923
  }
25901
26924
  }
26925
+ if (mcpChecked && mcpAllHealthy && state.mcpServers.length > 0) {
26926
+ details.push(`${state.mcpServers.length} MCP server(s) configured`);
26927
+ }
25902
26928
  if (!mcpChecked && state.mcpServers.length > 0) {
25903
26929
  details.push("could not verify MCP servers");
25904
26930
  upgrade("warn");
25905
26931
  }
25906
26932
  let skillsChecked = false;
26933
+ let skillsAllHealthy = true;
26934
+ let skillsSummaryLine = null;
25907
26935
  for (const slug of state.configuredAgents) {
25908
26936
  const skillsDir = getSkillsDir(slug, "user");
25909
26937
  if (!skillsDir)
@@ -25914,19 +26942,22 @@ async function checkAgentSetup() {
25914
26942
  const missingSkills = state.skills.filter((name) => {
25915
26943
  if (isCoveredByMcp(name))
25916
26944
  return false;
25917
- const skillPath = join53(skillsDir, name, "SKILL.md");
25918
- return !existsSync56(skillPath);
26945
+ const skillPath = join54(skillsDir, name, "SKILL.md");
26946
+ return !existsSync57(skillPath);
25919
26947
  });
25920
26948
  if (missingSkills.length > 0) {
25921
26949
  details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
25922
26950
  upgrade("warn");
25923
- } else if (state.skills.length > 0) {
26951
+ skillsAllHealthy = false;
26952
+ } else if (state.skills.length > 0 && !skillsSummaryLine) {
25924
26953
  const mcpCoveredCount = state.skills.filter(isCoveredByMcp).length;
25925
26954
  const onDiskCount = state.skills.length - mcpCoveredCount;
25926
- details.push(mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`);
26955
+ skillsSummaryLine = mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`;
25927
26956
  }
25928
26957
  skillsChecked = true;
25929
- break;
26958
+ }
26959
+ if (skillsChecked && skillsAllHealthy && skillsSummaryLine) {
26960
+ details.push(skillsSummaryLine);
25930
26961
  }
25931
26962
  if (!skillsChecked && state.skills.length > 0) {
25932
26963
  details.push("could not verify skills");
@@ -25942,28 +26973,28 @@ async function checkAgentSetup() {
25942
26973
  };
25943
26974
  }
25944
26975
  function getMcpConfigPath2(slug, scope) {
25945
- const home = homedir32();
26976
+ const home2 = homedir33();
25946
26977
  switch (slug) {
25947
26978
  case "claude-code":
25948
- return scope === "project" ? join53(process.cwd(), ".mcp.json") : join53(home, ".claude", "settings.json");
26979
+ return scope === "project" ? join54(process.cwd(), ".mcp.json") : join54(home2, ".claude", "settings.json");
25949
26980
  case "cursor":
25950
- return scope === "project" ? join53(process.cwd(), ".cursor", "mcp.json") : join53(home, ".cursor", "mcp.json");
26981
+ return scope === "project" ? join54(process.cwd(), ".cursor", "mcp.json") : join54(home2, ".cursor", "mcp.json");
25951
26982
  case "windsurf":
25952
- return scope === "project" ? join53(process.cwd(), ".windsurf", "mcp.json") : join53(home, ".windsurf", "mcp.json");
26983
+ return scope === "project" ? join54(process.cwd(), ".windsurf", "mcp.json") : join54(home2, ".windsurf", "mcp.json");
25953
26984
  case "codex":
25954
26985
  case "codex-app":
25955
- return scope === "user" ? join53(home, ".codex", "config.toml") : null;
26986
+ return scope === "user" ? join54(home2, ".codex", "config.toml") : null;
25956
26987
  case "gemini":
25957
- return scope === "user" ? join53(home, ".gemini", "settings.json") : null;
26988
+ return scope === "user" ? join54(home2, ".gemini", "settings.json") : null;
25958
26989
  default:
25959
26990
  return null;
25960
26991
  }
25961
26992
  }
25962
26993
  async function checkWorkspacePointers() {
25963
- const userStatePath = join53(homedir32(), ".runwork", "setup.json");
25964
- const state = existsSync56(userStatePath) ? (() => {
26994
+ const userStatePath = join54(homedir33(), ".runwork", "setup.json");
26995
+ const state = existsSync57(userStatePath) ? (() => {
25965
26996
  try {
25966
- return JSON.parse(readFileSync46(userStatePath, "utf-8"));
26997
+ return JSON.parse(readFileSync47(userStatePath, "utf-8"));
25967
26998
  } catch {
25968
26999
  return null;
25969
27000
  }
@@ -25993,15 +27024,15 @@ async function checkWorkspacePointers() {
25993
27024
  };
25994
27025
  }
25995
27026
  function getSkillsDir(slug, scope) {
25996
- const home = homedir32();
27027
+ const home2 = homedir33();
25997
27028
  switch (slug) {
25998
27029
  case "claude-code":
25999
- return scope === "project" ? join53(process.cwd(), ".claude", "skills") : join53(home, ".claude", "skills");
27030
+ return scope === "project" ? join54(process.cwd(), ".claude", "skills") : join54(home2, ".claude", "skills");
26000
27031
  case "codex":
26001
27032
  case "codex-app":
26002
- return scope === "project" ? join53(process.cwd(), ".agents", "skills") : join53(home, ".agents", "skills");
27033
+ return scope === "project" ? join54(process.cwd(), ".agents", "skills") : join54(home2, ".agents", "skills");
26003
27034
  case "gemini":
26004
- return scope === "project" ? join53(process.cwd(), ".gemini", "skills") : join53(home, ".gemini", "skills");
27035
+ return scope === "project" ? join54(process.cwd(), ".gemini", "skills") : join54(home2, ".gemini", "skills");
26005
27036
  default:
26006
27037
  return null;
26007
27038
  }
@@ -26054,8 +27085,8 @@ async function runAllChecks(options) {
26054
27085
  // src/health/fix.ts
26055
27086
  init_credentials();
26056
27087
  init_remote();
26057
- import { existsSync as existsSync57 } from "fs";
26058
- import { join as join54 } from "path";
27088
+ import { existsSync as existsSync58 } from "fs";
27089
+ import { join as join55 } from "path";
26059
27090
  async function applyDoctorFixes(ctx, failingNames) {
26060
27091
  const failing = new Set(failingNames);
26061
27092
  const outcomes = [];
@@ -26082,7 +27113,7 @@ async function applyDoctorFixes(ctx, failingNames) {
26082
27113
  applied: false,
26083
27114
  message: "no project config -- run inside an app directory"
26084
27115
  });
26085
- } else if (!existsSync57(join54(ctx.cwd, ".git"))) {
27116
+ } else if (!existsSync58(join55(ctx.cwd, ".git"))) {
26086
27117
  outcomes.push({
26087
27118
  name: "git-remote",
26088
27119
  applied: false,
@@ -26101,10 +27132,10 @@ async function applyDoctorFixes(ctx, failingNames) {
26101
27132
  }
26102
27133
 
26103
27134
  // src/agents/runtime-detection.ts
26104
- import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync10, readdirSync as readdirSync17 } from "fs";
26105
- import { homedir as homedir33 } from "os";
26106
- import { join as join55 } from "path";
26107
- var RUNWORK_SESSIONS_DIR = join55(homedir33(), ".runwork", "sessions");
27135
+ import { existsSync as existsSync59, readFileSync as readFileSync48, statSync as statSync12, readdirSync as readdirSync18 } from "fs";
27136
+ import { homedir as homedir34 } from "os";
27137
+ import { join as join56 } from "path";
27138
+ var RUNWORK_SESSIONS_DIR = join56(homedir34(), ".runwork", "sessions");
26108
27139
  function detectCurrentAgent() {
26109
27140
  const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
26110
27141
  if (claudeCodeSessionId) {
@@ -26167,11 +27198,11 @@ function detectCurrentAgent() {
26167
27198
  return null;
26168
27199
  }
26169
27200
  function readHookSessionInfo(sessionId) {
26170
- const path4 = join55(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
26171
- if (!existsSync58(path4))
27201
+ const path4 = join56(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
27202
+ if (!existsSync59(path4))
26172
27203
  return null;
26173
27204
  try {
26174
- const raw = readFileSync47(path4, "utf8");
27205
+ const raw = readFileSync48(path4, "utf8");
26175
27206
  const parsed = JSON.parse(raw);
26176
27207
  return parsed;
26177
27208
  } catch {
@@ -26179,40 +27210,40 @@ function readHookSessionInfo(sessionId) {
26179
27210
  }
26180
27211
  }
26181
27212
  function findClaudeCodeSessionFile(sessionId) {
26182
- const root = join55(homedir33(), ".claude", "projects");
26183
- if (!existsSync58(root))
27213
+ const root = join56(homedir34(), ".claude", "projects");
27214
+ if (!existsSync59(root))
26184
27215
  return null;
26185
27216
  let projectDirs;
26186
27217
  try {
26187
- projectDirs = readdirSync17(root);
27218
+ projectDirs = readdirSync18(root);
26188
27219
  } catch {
26189
27220
  return null;
26190
27221
  }
26191
27222
  for (const dir of projectDirs) {
26192
- const candidate = join55(root, dir, `${sessionId}.jsonl`);
26193
- if (existsSync58(candidate))
27223
+ const candidate = join56(root, dir, `${sessionId}.jsonl`);
27224
+ if (existsSync59(candidate))
26194
27225
  return candidate;
26195
27226
  }
26196
27227
  return null;
26197
27228
  }
26198
27229
  function findCodexRolloutFile(threadId) {
26199
- const root = join55(homedir33(), ".codex", "sessions");
26200
- if (!existsSync58(root))
27230
+ const root = join56(homedir34(), ".codex", "sessions");
27231
+ if (!existsSync59(root))
26201
27232
  return null;
26202
27233
  const stack = [root];
26203
27234
  while (stack.length > 0) {
26204
27235
  const dir = stack.pop();
26205
27236
  let entries;
26206
27237
  try {
26207
- entries = readdirSync17(dir);
27238
+ entries = readdirSync18(dir);
26208
27239
  } catch {
26209
27240
  continue;
26210
27241
  }
26211
27242
  for (const entry of entries) {
26212
- const full = join55(dir, entry);
27243
+ const full = join56(dir, entry);
26213
27244
  let s;
26214
27245
  try {
26215
- s = statSync10(full);
27246
+ s = statSync12(full);
26216
27247
  } catch {
26217
27248
  continue;
26218
27249
  }
@@ -26226,30 +27257,30 @@ function findCodexRolloutFile(threadId) {
26226
27257
  return null;
26227
27258
  }
26228
27259
  function findNewestClaudeCodeSession() {
26229
- const root = join55(homedir33(), ".claude", "projects");
26230
- if (!existsSync58(root))
27260
+ const root = join56(homedir34(), ".claude", "projects");
27261
+ if (!existsSync59(root))
26231
27262
  return null;
26232
27263
  let projectDirs;
26233
27264
  try {
26234
- projectDirs = readdirSync17(root);
27265
+ projectDirs = readdirSync18(root);
26235
27266
  } catch {
26236
27267
  return null;
26237
27268
  }
26238
27269
  let best = null;
26239
27270
  for (const dir of projectDirs) {
26240
- const projectPath = join55(root, dir);
27271
+ const projectPath = join56(root, dir);
26241
27272
  let files;
26242
27273
  try {
26243
- files = readdirSync17(projectPath);
27274
+ files = readdirSync18(projectPath);
26244
27275
  } catch {
26245
27276
  continue;
26246
27277
  }
26247
27278
  for (const file of files) {
26248
27279
  if (!file.endsWith(".jsonl"))
26249
27280
  continue;
26250
- const full = join55(projectPath, file);
27281
+ const full = join56(projectPath, file);
26251
27282
  try {
26252
- const s = statSync10(full);
27283
+ const s = statSync12(full);
26253
27284
  if (!best || s.mtimeMs > best.mtime) {
26254
27285
  best = {
26255
27286
  sessionId: file.replace(/\.jsonl$/, ""),
@@ -26265,8 +27296,8 @@ function findNewestClaudeCodeSession() {
26265
27296
  return best ? { sessionId: best.sessionId, path: best.path } : null;
26266
27297
  }
26267
27298
  function findNewestCodexRollout() {
26268
- const root = join55(homedir33(), ".codex", "sessions");
26269
- if (!existsSync58(root))
27299
+ const root = join56(homedir34(), ".codex", "sessions");
27300
+ if (!existsSync59(root))
26270
27301
  return null;
26271
27302
  const stack = [root];
26272
27303
  let best = null;
@@ -26274,15 +27305,15 @@ function findNewestCodexRollout() {
26274
27305
  const dir = stack.pop();
26275
27306
  let entries;
26276
27307
  try {
26277
- entries = readdirSync17(dir);
27308
+ entries = readdirSync18(dir);
26278
27309
  } catch {
26279
27310
  continue;
26280
27311
  }
26281
27312
  for (const entry of entries) {
26282
- const full = join55(dir, entry);
27313
+ const full = join56(dir, entry);
26283
27314
  let s;
26284
27315
  try {
26285
- s = statSync10(full);
27316
+ s = statSync12(full);
26286
27317
  } catch {
26287
27318
  continue;
26288
27319
  }
@@ -26506,13 +27537,697 @@ var doctorCommand = new Command34("doctor").description("Check system health: au
26506
27537
  }
26507
27538
  });
26508
27539
 
27540
+ // src/commands/debug.ts
27541
+ init_colors();
27542
+ import { Command as Command35 } from "commander";
27543
+ import { readFileSync as readFileSync49 } from "fs";
27544
+ import { homedir as homedir36, platform as platform9, release, arch, type as osType } from "os";
27545
+ import { join as join59 } from "path";
27546
+
27547
+ // src/debug/capture-plan.ts
27548
+ init_transcript_stores();
27549
+ init_registry_data();
27550
+ var CONFIG_MAX_BYTES = 64 * 1024;
27551
+ var LOG_TAIL_BYTES = 64 * 1024;
27552
+ var CENSUS_MAX_ENTRIES = 200;
27553
+ var ENV_NAMES = [
27554
+ "PATH",
27555
+ "SHELL",
27556
+ "HOME",
27557
+ "USERPROFILE",
27558
+ "APPDATA",
27559
+ "LOCALAPPDATA",
27560
+ "XDG_CONFIG_HOME",
27561
+ "HTTP_PROXY",
27562
+ "HTTPS_PROXY",
27563
+ "http_proxy",
27564
+ "https_proxy",
27565
+ "NO_PROXY",
27566
+ "no_proxy",
27567
+ "NODE_EXTRA_CA_CERTS",
27568
+ "RUNWORK_HTTP_TRANSPORT",
27569
+ "RUNWORK_API_URL",
27570
+ "TERM_PROGRAM",
27571
+ "LANG"
27572
+ ];
27573
+ function buildCapturePlan(input) {
27574
+ const sep5 = input.sep ?? (input.platform === "win32" ? "\\" : "/");
27575
+ const join57 = (...parts) => parts.join(sep5);
27576
+ const home2 = input.homeDir.replace(/[/\\]+$/, "");
27577
+ const rw = (...parts) => join57(home2, ".runwork", ...parts);
27578
+ const steps = [];
27579
+ steps.push({ id: "env", kind: "env", names: ENV_NAMES, note: "process environment (proxy credentials stripped)" });
27580
+ for (const [id, file] of [
27581
+ ["runwork.setup", "setup.json"],
27582
+ ["runwork.workspaces", "workspaces.json"],
27583
+ ["runwork.reflect-state", "reflect-state.json"],
27584
+ ["runwork.reflect-runs", "reflect-runs.json"],
27585
+ ["runwork.telemetry-outbox", "telemetry-outbox.json"],
27586
+ ["runwork.session-summaries-outbox", "session-summaries-outbox.json"]
27587
+ ]) {
27588
+ steps.push({ id, kind: "file", path: rw(file), maxBytes: CONFIG_MAX_BYTES });
27589
+ }
27590
+ for (const [id, file] of [
27591
+ ["runwork.conversations", "conversations.json"],
27592
+ ["runwork.reflect-queue", "reflect-queue.json"],
27593
+ ["runwork.insights", "insights.json"],
27594
+ ["runwork.pattern-store", "pattern-store.json"]
27595
+ ]) {
27596
+ steps.push({ id, kind: "stat", path: rw(file), note: "size and mtime only: contains conversation text" });
27597
+ }
27598
+ 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" });
27599
+ for (const [id, ...parts] of [
27600
+ ["claude.settings", ".claude", "settings.json"],
27601
+ ["claude.md", ".claude", "CLAUDE.md"],
27602
+ ["claude.mcp", ".claude", ".mcp.json"],
27603
+ ["codex.config", ".codex", "config.toml"],
27604
+ ["codex.agents-md", ".codex", "AGENTS.md"],
27605
+ ["gemini.settings", ".gemini", "settings.json"]
27606
+ ]) {
27607
+ steps.push({ id, kind: "file", path: join57(home2, ...parts), maxBytes: CONFIG_MAX_BYTES });
27608
+ }
27609
+ steps.push({
27610
+ id: "claude.desktop-config",
27611
+ kind: "file",
27612
+ path: join57(appDataRootFor(input, join57), "Claude", "claude_desktop_config.json"),
27613
+ maxBytes: CONFIG_MAX_BYTES,
27614
+ note: "the MCP config we write for Claude Desktop, next to its session store"
27615
+ });
27616
+ steps.push({
27617
+ id: "codex.version",
27618
+ kind: "file",
27619
+ path: join57(home2, ".codex", "version.json"),
27620
+ maxBytes: 4096,
27621
+ note: "Codex build, for placing an unrecognised originator value"
27622
+ });
27623
+ for (const [id, ...parts] of [
27624
+ ["claude.skills", ".claude", "skills"],
27625
+ ["claude.plugins", ".claude", "plugins"],
27626
+ ["agents.skills", ".agents", "skills"],
27627
+ ["codex.skills", ".codex", "skills"]
27628
+ ]) {
27629
+ steps.push({ id, kind: "census", path: join57(home2, ...parts), depth: 1, maxEntries: CENSUS_MAX_ENTRIES });
27630
+ }
27631
+ for (const store of resolveTranscriptStores({
27632
+ platform: input.platform,
27633
+ homeDir: home2,
27634
+ sep: sep5,
27635
+ appData: input.appData,
27636
+ localAppData: input.localAppData
27637
+ })) {
27638
+ const id = `stores.${store.def.id.replace(/\//g, ".")}`;
27639
+ steps.push(store.def.kind === "file" ? { id, kind: "stat", path: store.path, ...store.def.note ? { note: store.def.note } : {} } : { id, kind: "census", path: store.path, depth: 2, maxEntries: CENSUS_MAX_ENTRIES, ...store.def.note ? { note: store.def.note } : {} });
27640
+ for (const parent of parentsWorthProbing(store.path, sep5)) {
27641
+ steps.push({ id: `${id}.parent`, kind: "census", path: parent, depth: 1, maxEntries: 50, note: "is the vendor directory there at all" });
27642
+ }
27643
+ store.altPaths.forEach((alt, i) => {
27644
+ steps.push({ id: `${id}.alt${i}`, kind: "stat", path: alt, note: "alternate location; a hit means the canonical path is wrong here" });
27645
+ });
27646
+ }
27647
+ if (input.platform === "win32") {
27648
+ steps.push({
27649
+ id: "windows.appx-packages",
27650
+ kind: "census",
27651
+ path: join57(input.localAppData ?? join57(home2, "AppData", "Local"), "Packages"),
27652
+ depth: 1,
27653
+ maxEntries: CENSUS_MAX_ENTRIES,
27654
+ note: "AppX package identities: an MSIX agent can virtualize its writes in here"
27655
+ });
27656
+ }
27657
+ for (const profile of shellProfiles(input.platform)) {
27658
+ steps.push({
27659
+ id: `shell${profile}`,
27660
+ kind: "stat",
27661
+ path: join57(home2, profile),
27662
+ note: "presence only: profile bodies can contain the user's own secrets"
27663
+ });
27664
+ }
27665
+ steps.push(...binaryResolutionSteps(input));
27666
+ steps.push(...agentBinaryProbes(input));
27667
+ if (input.cliPath) {
27668
+ 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" }, { id: "cli.stores", kind: "exec", command: input.cliPath, args: ["--json", "conversations", "stores"], note: "per-store contents and provenance, no conversation text" });
27669
+ }
27670
+ return steps;
27671
+ }
27672
+ function parentsWorthProbing(storePath4, sep5) {
27673
+ const idx = storePath4.lastIndexOf(sep5);
27674
+ if (idx <= 0)
27675
+ return [];
27676
+ const parent = storePath4.slice(0, idx);
27677
+ const parentIdx = parent.lastIndexOf(sep5);
27678
+ if (parentIdx <= 0)
27679
+ return [];
27680
+ return [parent];
27681
+ }
27682
+ function agentBinaryProbes(input) {
27683
+ const finder = input.platform === "win32" ? "where.exe" : "which";
27684
+ const args = (binary) => input.platform === "win32" ? [binary] : ["-a", binary];
27685
+ const seen = new Set;
27686
+ const steps = [];
27687
+ for (const agent of getDetectableAgents()) {
27688
+ const binary = agent.launch?.cli;
27689
+ if (!binary || seen.has(binary))
27690
+ continue;
27691
+ seen.add(binary);
27692
+ steps.push({
27693
+ id: `agent-binary.${agent.slug}`,
27694
+ kind: "exec",
27695
+ command: finder,
27696
+ args: args(binary),
27697
+ note: `is ${agent.name}'s CLI on PATH, and where`
27698
+ });
27699
+ }
27700
+ return steps;
27701
+ }
27702
+ function appDataRootFor(input, join57) {
27703
+ const home2 = input.homeDir.replace(/[/\\]+$/, "");
27704
+ if (input.platform === "darwin")
27705
+ return join57(home2, "Library", "Application Support");
27706
+ if (input.platform === "win32")
27707
+ return input.appData ?? join57(home2, "AppData", "Roaming");
27708
+ return join57(home2, ".config");
27709
+ }
27710
+ function shellProfiles(platform9) {
27711
+ if (platform9 === "win32")
27712
+ return [];
27713
+ return [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"];
27714
+ }
27715
+ function binaryResolutionSteps(input) {
27716
+ const sep5 = input.sep ?? (input.platform === "win32" ? "\\" : "/");
27717
+ const join57 = (...parts) => parts.join(sep5);
27718
+ const home2 = input.homeDir.replace(/[/\\]+$/, "");
27719
+ if (input.platform === "win32") {
27720
+ return [
27721
+ { id: "binaries.where", kind: "exec", command: "where.exe", args: ["runwork"], note: "every match, not just the first" },
27722
+ { id: "binaries.runwork-bin", kind: "stat", path: join57(home2, ".runwork", "bin", "runwork.exe") },
27723
+ { 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" }
27724
+ ];
27725
+ }
27726
+ return [
27727
+ { id: "binaries.which", kind: "exec", command: "which", args: ["-a", "runwork"], note: "every match, not just the first" },
27728
+ { id: "binaries.runwork-bin", kind: "stat", path: join57(home2, ".runwork", "bin", "runwork") },
27729
+ { id: "binaries.local-bin", kind: "stat", path: join57(home2, ".local", "bin", "runwork") },
27730
+ ...input.platform === "darwin" ? [{ id: "binaries.usr-local-bin", kind: "stat", path: "/usr/local/bin/runwork" }] : []
27731
+ ];
27732
+ }
27733
+
27734
+ // src/debug/capture.ts
27735
+ var CAPTURE_FORMAT = 1;
27736
+ function summarizeBundle(jsonl) {
27737
+ const summary = {
27738
+ formatVersion: CAPTURE_FORMAT,
27739
+ producer: null,
27740
+ stepsPlanned: 0,
27741
+ stepsRun: 0,
27742
+ redactions: 0,
27743
+ withheld: 0,
27744
+ complete: false
27745
+ };
27746
+ for (const line of jsonl.split(`
27747
+ `)) {
27748
+ if (!line.trim())
27749
+ continue;
27750
+ let record;
27751
+ try {
27752
+ record = JSON.parse(line);
27753
+ } catch {
27754
+ continue;
27755
+ }
27756
+ if (record.type === "plan") {
27757
+ summary.formatVersion = record.format;
27758
+ summary.producer = record.producer;
27759
+ summary.stepsPlanned = record.steps.length;
27760
+ } else if (record.type === "step") {
27761
+ summary.stepsRun++;
27762
+ summary.redactions += record.redactions ?? 0;
27763
+ summary.withheld += record.withheld ?? 0;
27764
+ } else if (record.type === "complete") {
27765
+ summary.complete = true;
27766
+ }
27767
+ }
27768
+ return summary;
27769
+ }
27770
+ async function sha256Hex(text2) {
27771
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text2));
27772
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
27773
+ }
27774
+ var NEVER_READ = [".credentials", "credentials.json", "auth.json", ".netrc", "id_rsa"];
27775
+ function isForbiddenPath(path4) {
27776
+ const name = path4.split(/[/\\]/).pop()?.toLowerCase() ?? "";
27777
+ return NEVER_READ.some((deny) => name === deny.toLowerCase());
27778
+ }
27779
+ var SECRET_KEY_NAMES = "token|secret|password|passwd|api[-_]?key|apikey|authorization|access[-_]?token|refresh[-_]?token|client[-_]?secret|private[-_]?key|access[-_]?key|credentials?";
27780
+ var SECRET_KEY_PATTERN = `[A-Za-z0-9_-]*?(?:${SECRET_KEY_NAMES})`;
27781
+ var QUOTED_VALUE = String.raw`"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'`;
27782
+ var SECRET_QUERY_PARAMS = "token|key|secret|password|api[-_]?key|apikey|access[-_]?token|sig|signature";
27783
+ function redactSecrets(text2) {
27784
+ let redactions = 0;
27785
+ const mark = () => {
27786
+ redactions++;
27787
+ return "<redacted>";
27788
+ };
27789
+ 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) => {
27790
+ if (value.includes("<redacted>"))
27791
+ return match;
27792
+ const quote = value.startsWith('"') ? '"' : value.startsWith("'") ? "'" : "";
27793
+ return `${prefix}${quote}${mark()}${quote}`;
27794
+ });
27795
+ return { text: out, redactions };
27796
+ }
27797
+ var CONTENT_KEY_NAMES = "currentTitle|title|prompt|userMessages|summary|body|snippet";
27798
+ function redactUserContent(text2) {
27799
+ let withheld = 0;
27800
+ const out = text2.replace(new RegExp(`("?\\b(?:${CONTENT_KEY_NAMES})"?\\s*[:=]\\s*)(${QUOTED_VALUE})`, "gi"), (match, prefix, value) => {
27801
+ if (value === '""' || value === "''")
27802
+ return match;
27803
+ withheld++;
27804
+ const quote = value[0];
27805
+ return `${prefix}${quote}<withheld>${quote}`;
27806
+ });
27807
+ return { text: out, withheld };
27808
+ }
27809
+ function redactEnvValue(value) {
27810
+ if (value === null)
27811
+ return null;
27812
+ return value.replace(/(\w+:\/\/)[^/@\s]*@/g, "$1<redacted>@");
27813
+ }
27814
+ function classifyCaptureError(err) {
27815
+ const e = err;
27816
+ const code = e && typeof e === "object" && typeof e.code === "string" ? e.code : undefined;
27817
+ const message = e instanceof Error ? e.message : String(err);
27818
+ if (code === "ENOENT" || code === "ENOTDIR")
27819
+ return { status: "missing", detail: code };
27820
+ if (code === "EACCES" || code === "EPERM")
27821
+ return { status: "denied", detail: code };
27822
+ if (code === "ETIMEDOUT" || code === "TIMEOUT")
27823
+ return { status: "timeout", detail: code };
27824
+ if (code === "ESCOPE")
27825
+ return { status: "skipped", detail: message };
27826
+ return { status: "error", detail: code ? `${code}: ${message}` : message };
27827
+ }
27828
+ function stepTarget(step) {
27829
+ switch (step.kind) {
27830
+ case "file":
27831
+ case "census":
27832
+ case "stat":
27833
+ return step.path;
27834
+ case "env":
27835
+ return step.names.join(",");
27836
+ case "exec":
27837
+ return [step.command, ...step.args].join(" ");
27838
+ }
27839
+ }
27840
+ function withDeadline(work, ms) {
27841
+ return new Promise((resolve5, reject) => {
27842
+ const timer = setTimeout(() => reject(Object.assign(new Error("step deadline"), { code: "TIMEOUT" })), ms);
27843
+ work.then((value) => {
27844
+ clearTimeout(timer);
27845
+ resolve5(value);
27846
+ }, (err) => {
27847
+ clearTimeout(timer);
27848
+ reject(err);
27849
+ });
27850
+ });
27851
+ }
27852
+ async function runStep(port, step, deadlineMs) {
27853
+ const startedMs = port.now();
27854
+ const base = { type: "step", id: step.id, kind: step.kind, target: stepTarget(step) };
27855
+ const done = (extra) => ({ ...base, status: "ok", durationMs: port.now() - startedMs, ...extra });
27856
+ if ((step.kind === "file" || step.kind === "stat") && isForbiddenPath(step.path)) {
27857
+ return { ...base, status: "skipped", detail: "excluded by policy", durationMs: 0 };
27858
+ }
27859
+ try {
27860
+ switch (step.kind) {
27861
+ case "file": {
27862
+ const raw = await withDeadline(port.readText(step.path, step.maxBytes, step.tail === true), deadlineMs);
27863
+ const secrets = redactSecrets(raw.text);
27864
+ const content = redactUserContent(secrets.text);
27865
+ return done({
27866
+ text: content.text,
27867
+ truncated: raw.truncated,
27868
+ ...secrets.redactions > 0 ? { redactions: secrets.redactions } : {},
27869
+ ...content.withheld > 0 ? { withheld: content.withheld } : {}
27870
+ });
27871
+ }
27872
+ case "census": {
27873
+ const entries = await withDeadline(port.listDir(step.path, step.depth, step.maxEntries), deadlineMs);
27874
+ return done({ entries, entryCount: entries.length });
27875
+ }
27876
+ case "stat": {
27877
+ const info = await withDeadline(port.stat(step.path), deadlineMs);
27878
+ return done({ bytes: info.bytes, modifiedAt: info.modifiedAt });
27879
+ }
27880
+ case "env": {
27881
+ const raw = port.env(step.names);
27882
+ return done({
27883
+ env: Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, redactEnvValue(v)]))
27884
+ });
27885
+ }
27886
+ case "exec": {
27887
+ const result = await withDeadline(port.exec(step.command, step.args), deadlineMs);
27888
+ const out = redactSecrets(result.stdout);
27889
+ const err = redactSecrets(result.stderr);
27890
+ const redactions = out.redactions + err.redactions;
27891
+ return done({
27892
+ text: out.text,
27893
+ exitCode: result.exitCode,
27894
+ stderr: err.text,
27895
+ ...redactions > 0 ? { redactions } : {}
27896
+ });
27897
+ }
27898
+ }
27899
+ } catch (err) {
27900
+ const { status, detail } = classifyCaptureError(err);
27901
+ return { ...base, status, detail, durationMs: port.now() - startedMs };
27902
+ }
27903
+ }
27904
+ async function runCapture(port, producer, steps, opts = {}) {
27905
+ const deadlineMs = opts.stepDeadlineMs ?? 1e4;
27906
+ const startedMs = port.now();
27907
+ await port.append({
27908
+ type: "plan",
27909
+ format: CAPTURE_FORMAT,
27910
+ producer,
27911
+ startedAt: new Date(startedMs).toISOString(),
27912
+ steps: steps.map((s) => ({ id: s.id, kind: s.kind, ...s.note ? { note: s.note } : {} }))
27913
+ });
27914
+ let stepsRun = 0;
27915
+ for (const step of steps) {
27916
+ await port.append(await runStep(port, step, deadlineMs));
27917
+ stepsRun++;
27918
+ }
27919
+ if (opts.claims) {
27920
+ try {
27921
+ for (const claim of await opts.claims())
27922
+ await port.append(claim);
27923
+ } catch (err) {
27924
+ await port.append({
27925
+ type: "claim",
27926
+ id: "claims-failed",
27927
+ producer,
27928
+ value: { error: err instanceof Error ? err.message : String(err) }
27929
+ });
27930
+ }
27931
+ }
27932
+ await port.append({
27933
+ type: "complete",
27934
+ finishedAt: new Date(port.now()).toISOString(),
27935
+ stepsPlanned: steps.length,
27936
+ stepsRun,
27937
+ durationMs: port.now() - startedMs
27938
+ });
27939
+ return { stepsRun };
27940
+ }
27941
+
27942
+ // src/utils/machine-id.ts
27943
+ init_atomic_json();
27944
+ import { randomUUID } from "crypto";
27945
+ import { homedir as homedir35 } from "os";
27946
+ import { join as join57 } from "path";
27947
+ function machineIdPath() {
27948
+ return join57(homedir35(), ".runwork", "machine.json");
27949
+ }
27950
+ function isValidId(value) {
27951
+ 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);
27952
+ }
27953
+ function getMachineId() {
27954
+ const path4 = machineIdPath();
27955
+ const existing = readJsonOrNull(path4);
27956
+ if (existing && isValidId(existing.id))
27957
+ return existing.id;
27958
+ const record = { id: randomUUID(), createdAt: new Date().toISOString() };
27959
+ try {
27960
+ writeJsonAtomic(path4, record);
27961
+ } catch {
27962
+ return null;
27963
+ }
27964
+ const settled = readJsonOrNull(path4);
27965
+ return settled && isValidId(settled.id) ? settled.id : record.id;
27966
+ }
27967
+
27968
+ // src/commands/debug.ts
27969
+ init_store();
27970
+ init_client();
27971
+ init_resolve();
27972
+
27973
+ // src/debug/node-port.ts
27974
+ init_subprocess();
27975
+ init_which();
27976
+ import { closeSync as closeSync6, fsyncSync, mkdirSync as mkdirSync29, openSync as openSync6, readSync as readSync4, readdirSync as readdirSync19, statSync as statSync13, writeSync } from "fs";
27977
+ import { dirname as dirname12, join as join58 } from "path";
27978
+ var EXEC_OUTPUT_MAX = 256 * 1024;
27979
+ var EXEC_TIMEOUT_MS = 20000;
27980
+ function truncate5(text2, maxBytes) {
27981
+ return text2.length > maxBytes ? { text: text2.slice(0, maxBytes), truncated: true } : { text: text2, truncated: false };
27982
+ }
27983
+ function entryKind(path4) {
27984
+ try {
27985
+ const s = statSync13(path4);
27986
+ return s.isDirectory() ? "dir" : s.isFile() ? "file" : "other";
27987
+ } catch {
27988
+ return "other";
27989
+ }
27990
+ }
27991
+ function createNodeCapturePort(outputPath) {
27992
+ mkdirSync29(dirname12(outputPath), { recursive: true });
27993
+ const fd = openSync6(outputPath, "w");
27994
+ return {
27995
+ close: () => closeSync6(fd),
27996
+ async append(record) {
27997
+ writeSync(fd, JSON.stringify(record) + `
27998
+ `);
27999
+ try {
28000
+ fsyncSync(fd);
28001
+ } catch {}
28002
+ },
28003
+ async listDir(path4, depth, maxEntries) {
28004
+ const out = [];
28005
+ const queue = [{ dir: path4, prefix: "", level: 0 }];
28006
+ let first = true;
28007
+ while (queue.length > 0 && out.length < maxEntries) {
28008
+ const { dir, prefix, level } = queue.shift();
28009
+ let names;
28010
+ try {
28011
+ names = readdirSync19(dir);
28012
+ } catch (err) {
28013
+ if (first)
28014
+ throw err;
28015
+ continue;
28016
+ } finally {
28017
+ first = false;
28018
+ }
28019
+ for (const name of names) {
28020
+ if (out.length >= maxEntries)
28021
+ break;
28022
+ const full = join58(dir, name);
28023
+ const kind = entryKind(full);
28024
+ let bytes;
28025
+ let modifiedAt;
28026
+ try {
28027
+ const s = statSync13(full);
28028
+ bytes = s.size;
28029
+ modifiedAt = new Date(s.mtimeMs).toISOString();
28030
+ } catch {}
28031
+ out.push({ name: prefix ? `${prefix}/${name}` : name, kind, ...bytes !== undefined ? { bytes } : {}, ...modifiedAt ? { modifiedAt } : {} });
28032
+ if (kind === "dir" && level + 1 < depth) {
28033
+ queue.push({ dir: full, prefix: prefix ? `${prefix}/${name}` : name, level: level + 1 });
28034
+ }
28035
+ }
28036
+ }
28037
+ return out;
28038
+ },
28039
+ async stat(path4) {
28040
+ const s = statSync13(path4);
28041
+ return {
28042
+ bytes: s.size,
28043
+ modifiedAt: new Date(s.mtimeMs).toISOString(),
28044
+ kind: s.isDirectory() ? "dir" : s.isFile() ? "file" : "other"
28045
+ };
28046
+ },
28047
+ async readText(path4, maxBytes, tail) {
28048
+ const s = statSync13(path4);
28049
+ const size = s.size;
28050
+ const length = Math.min(size, maxBytes);
28051
+ const start = tail && size > maxBytes ? size - maxBytes : 0;
28052
+ const buffer = Buffer.alloc(length);
28053
+ const handle = openSync6(path4, "r");
28054
+ try {
28055
+ readSync4(handle, buffer, 0, length, start);
28056
+ } finally {
28057
+ closeSync6(handle);
28058
+ }
28059
+ return { text: buffer.toString("utf-8"), truncated: size > length };
28060
+ },
28061
+ env(names) {
28062
+ return Object.fromEntries(names.map((n) => [n, process.env[n] ?? null]));
28063
+ },
28064
+ async exec(command, args) {
28065
+ const spec = toSpawnSpec(command, args);
28066
+ const result = spawnSync(spec.command, spec.args, {
28067
+ encoding: "utf-8",
28068
+ timeout: EXEC_TIMEOUT_MS,
28069
+ maxBuffer: EXEC_OUTPUT_MAX,
28070
+ ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
28071
+ });
28072
+ if (result.error)
28073
+ throw result.error;
28074
+ return {
28075
+ exitCode: result.status ?? -1,
28076
+ stdout: truncate5(result.stdout ?? "", EXEC_OUTPUT_MAX).text,
28077
+ stderr: truncate5(result.stderr ?? "", 8 * 1024).text
28078
+ };
28079
+ },
28080
+ now: () => Date.now()
28081
+ };
28082
+ }
28083
+
28084
+ // src/commands/debug.ts
28085
+ init_transcript_sources();
28086
+ init_which();
28087
+ await init_detect();
28088
+ function defaultOutputPath(now) {
28089
+ const stamp = now.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
28090
+ return join59(homedir36(), ".runwork", "debug", `runwork-debug-${stamp}.jsonl`);
28091
+ }
28092
+ async function cliClaims() {
28093
+ const records = [];
28094
+ records.push({
28095
+ type: "claim",
28096
+ id: "producer.process",
28097
+ producer: "cli",
28098
+ value: {
28099
+ cliVersion: VERSION,
28100
+ execPath: process.execPath,
28101
+ argv0: process.argv[1] ?? null,
28102
+ nodeVersion: process.version,
28103
+ platform: platform9(),
28104
+ osType: osType(),
28105
+ release: release(),
28106
+ arch: arch(),
28107
+ cwd: process.cwd()
28108
+ }
28109
+ });
28110
+ records.push({
28111
+ type: "claim",
28112
+ id: "cli.path-resolution",
28113
+ producer: "cli",
28114
+ derivedFrom: ["binaries.which", "binaries.where", "env"],
28115
+ value: { pathResolved: whichBinary("runwork") }
28116
+ });
28117
+ const adapters = await detectAgents();
28118
+ records.push({
28119
+ type: "claim",
28120
+ id: "agents.detected",
28121
+ producer: "cli",
28122
+ derivedFrom: ["transcripts.claude-code", "transcripts.codex", "transcripts.gemini", "transcripts.cowork"],
28123
+ value: adapters.map((adapter2) => {
28124
+ const sources = adapter2.transcriptSources?.() ?? [];
28125
+ return {
28126
+ slug: adapter2.slug,
28127
+ name: adapter2.name,
28128
+ hasListing: typeof adapter2.listSessions === "function",
28129
+ hasDigests: typeof adapter2.readSessionDigests === "function",
28130
+ transcriptSources: sources.map((s) => s.path),
28131
+ readable: sources.length > 0 ? summarizeTranscriptSources(sources) : null
28132
+ };
28133
+ })
28134
+ });
28135
+ return records;
28136
+ }
28137
+ var debugCommand = new Command35("debug").description("Diagnostics for support");
28138
+ 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) => {
28139
+ const json = command.optsWithGlobals().json === true || !process.stdout.isTTY;
28140
+ const outputPath = typeof opts.out === "string" && opts.out ? opts.out : defaultOutputPath(new Date);
28141
+ const cliPath = whichBinary("runwork");
28142
+ const plan = buildCapturePlan({
28143
+ platform: platform9(),
28144
+ homeDir: homedir36(),
28145
+ appData: process.env.APPDATA ?? null,
28146
+ localAppData: process.env.LOCALAPPDATA ?? null,
28147
+ cliPath
28148
+ });
28149
+ if (!json) {
28150
+ console.log(bold(`
28151
+ Collecting diagnostics`));
28152
+ console.log(dim(` ${plan.length} checks, writing to ${outputPath}`));
28153
+ }
28154
+ const port = createNodeCapturePort(outputPath);
28155
+ let stepsRun = 0;
28156
+ try {
28157
+ ({ stepsRun } = await runCapture(port, "cli", plan, { claims: cliClaims }));
28158
+ } finally {
28159
+ port.close();
28160
+ }
28161
+ const sent = opts.send === true ? await sendBundle(outputPath, typeof opts.note === "string" ? opts.note : undefined) : null;
28162
+ if (json) {
28163
+ jsonOut({ path: outputPath, stepsPlanned: plan.length, stepsRun, ...sent ? { sent } : {} });
28164
+ return;
28165
+ }
28166
+ const counted = stepsRun === plan.length ? `${stepsRun}` : `${stepsRun} of ${plan.length}`;
28167
+ console.log(green(`
28168
+ Done. ${counted} checks recorded.`));
28169
+ console.log(`
28170
+ ${cyan(outputPath)}
28171
+ `);
28172
+ if (!cliPath) {
28173
+ console.log(yellow("Note: no `runwork` was found on PATH, so the doctor report is not included.\n"));
28174
+ }
28175
+ if (!sent) {
28176
+ console.log(dim("Send that file to support. It contains no credentials and no conversation text."));
28177
+ console.log(dim("Or re-run with --send to upload it."));
28178
+ return;
28179
+ }
28180
+ if (sent.ok) {
28181
+ console.log(green(`Sent to Runwork support (report ${sent.id}).`));
28182
+ console.log(dim(`It is kept until ${sent.expiresAt?.slice(0, 10)} and then deleted.`));
28183
+ } else {
28184
+ console.log(yellow(`Could not send: ${sent.error}`));
28185
+ console.log(dim("The file above is saved and can be sent to support by hand."));
28186
+ }
28187
+ });
28188
+ async function sendBundle(path4, note) {
28189
+ try {
28190
+ const content = readFileSync49(path4, "utf-8");
28191
+ const summary = summarizeBundle(content);
28192
+ const machineId = getMachineId();
28193
+ if (!machineId)
28194
+ return { ok: false, error: "could not identify this machine" };
28195
+ const credentials = getCredentials();
28196
+ if (!credentials)
28197
+ return { ok: false, error: "not signed in; run `runwork login`" };
28198
+ const api = new ApiClient(credentials);
28199
+ const workspace = await resolveWorkspace2(api);
28200
+ const result = await api.createDebugReport(workspace.workspaceId, {
28201
+ machineId,
28202
+ producer: "cli",
28203
+ content,
28204
+ sha256: await sha256Hex(content),
28205
+ formatVersion: summary.formatVersion,
28206
+ ...note ? { note } : {},
28207
+ context: {
28208
+ cliVersion: VERSION,
28209
+ platform: platform9(),
28210
+ release: release(),
28211
+ stepsPlanned: summary.stepsPlanned,
28212
+ stepsRun: summary.stepsRun,
28213
+ redactions: summary.redactions,
28214
+ withheld: summary.withheld,
28215
+ complete: summary.complete
28216
+ }
28217
+ });
28218
+ return { ok: true, id: result.id, expiresAt: result.expiresAt };
28219
+ } catch (err) {
28220
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
28221
+ }
28222
+ }
28223
+
26509
28224
  // src/commands/share-convo.ts
26510
28225
  init_store();
26511
28226
  init_client();
26512
28227
  init_resolve();
26513
- import { Command as Command35 } from "commander";
26514
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync33, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
26515
- import { join as join56 } from "path";
28228
+ import { Command as Command36 } from "commander";
28229
+ import { readFileSync as readFileSync50, writeFileSync as writeFileSync33, existsSync as existsSync60, mkdtempSync as mkdtempSync4 } from "fs";
28230
+ import { join as join60 } from "path";
26516
28231
  import { tmpdir as tmpdir4 } from "os";
26517
28232
  import { createHash as createHash6 } from "crypto";
26518
28233
 
@@ -26634,13 +28349,13 @@ function resolveLocalSessionShare(opts, conversation) {
26634
28349
  process.exit(1);
26635
28350
  }
26636
28351
  const title = opts.title ?? conversation.title ?? conversation.project;
26637
- const markdown = renderTranscriptMarkdown(readFileSync48(conversation.transcriptPath, "utf8"), family, title);
28352
+ const markdown = renderTranscriptMarkdown(readFileSync50(conversation.transcriptPath, "utf8"), family, title);
26638
28353
  if (!markdown) {
26639
28354
  console.error("Error: this conversation has no shareable content.");
26640
28355
  process.exit(1);
26641
28356
  }
26642
- const tempDir = mkdtempSync4(join56(tmpdir4(), "runwork-share-"));
26643
- const transcriptFile = join56(tempDir, "transcript.md");
28357
+ const tempDir = mkdtempSync4(join60(tmpdir4(), "runwork-share-"));
28358
+ const transcriptFile = join60(tempDir, "transcript.md");
26644
28359
  writeFileSync33(transcriptFile, markdown);
26645
28360
  opts.transcriptFile = transcriptFile;
26646
28361
  opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
@@ -26657,7 +28372,7 @@ function nativeBundleFormatForAgent(slug) {
26657
28372
  return "codex-rollout";
26658
28373
  return null;
26659
28374
  }
26660
- function sha256Hex(content) {
28375
+ function sha256Hex2(content) {
26661
28376
  return createHash6("sha256").update(content, "utf8").digest("hex");
26662
28377
  }
26663
28378
  function utf8ByteLength(content) {
@@ -26678,7 +28393,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26678
28393
  console.error("Error: --transcript-file is required. Pass the path to the LLM-emitted markdown transcript.");
26679
28394
  process.exit(1);
26680
28395
  }
26681
- if (!existsSync59(opts.transcriptFile)) {
28396
+ if (!existsSync60(opts.transcriptFile)) {
26682
28397
  console.error(`Error: transcript file does not exist: ${opts.transcriptFile}`);
26683
28398
  process.exit(1);
26684
28399
  }
@@ -26699,37 +28414,37 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26699
28414
  const credentials = requireAuth();
26700
28415
  const client = new ApiClient(credentials);
26701
28416
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
26702
- const transcriptContent = readFileSync48(opts.transcriptFile, "utf8");
28417
+ const transcriptContent = readFileSync50(opts.transcriptFile, "utf8");
26703
28418
  const bundles = [
26704
28419
  {
26705
28420
  format: "transcript",
26706
28421
  content: transcriptContent,
26707
28422
  sizeBytes: utf8ByteLength(transcriptContent),
26708
- sha256: sha256Hex(transcriptContent)
28423
+ sha256: sha256Hex2(transcriptContent)
26709
28424
  }
26710
28425
  ];
26711
28426
  const detected = detectCurrentAgent();
26712
28427
  const sourceAgent = opts.sourceAgent ?? detected?.slug ?? "generic";
26713
28428
  let nativeFilePath = null;
26714
28429
  if (opts.nativeFile) {
26715
- if (!existsSync59(opts.nativeFile)) {
28430
+ if (!existsSync60(opts.nativeFile)) {
26716
28431
  console.error(`Error: --native-file path does not exist: ${opts.nativeFile}`);
26717
28432
  process.exit(1);
26718
28433
  }
26719
28434
  nativeFilePath = opts.nativeFile;
26720
- } else if (detected?.sessionFilePath && existsSync59(detected.sessionFilePath)) {
28435
+ } else if (detected?.sessionFilePath && existsSync60(detected.sessionFilePath)) {
26721
28436
  nativeFilePath = detected.sessionFilePath;
26722
28437
  }
26723
28438
  if (nativeFilePath) {
26724
28439
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
26725
28440
  if (nativeFormat) {
26726
28441
  try {
26727
- const content = readFileSync48(nativeFilePath, "utf8");
28442
+ const content = readFileSync50(nativeFilePath, "utf8");
26728
28443
  bundles.push({
26729
28444
  format: nativeFormat,
26730
28445
  content,
26731
28446
  sizeBytes: utf8ByteLength(content),
26732
- sha256: sha256Hex(content)
28447
+ sha256: sha256Hex2(content)
26733
28448
  });
26734
28449
  } catch (err) {
26735
28450
  console.error(`Warning: could not read native file ${nativeFilePath}: ${err instanceof Error ? err.message : err}`);
@@ -26740,7 +28455,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26740
28455
  let metadata = {};
26741
28456
  if (opts.metadataFile) {
26742
28457
  try {
26743
- metadata = JSON.parse(readFileSync48(opts.metadataFile, "utf8"));
28458
+ metadata = JSON.parse(readFileSync50(opts.metadataFile, "utf8"));
26744
28459
  } catch (err) {
26745
28460
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
26746
28461
  process.exit(1);
@@ -26795,18 +28510,18 @@ Skipped: ${result.skipped.map((s) => `${s.identifier} (${s.reason})`).join(", ")
26795
28510
  process.exit(1);
26796
28511
  }
26797
28512
  }
26798
- var shareConvoCommand = new Command35("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required unless --session)").option("--session <key>", "Share a LOCAL conversation by registry key (transcript rendered mechanically)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo(opts, command, false));
28513
+ var shareConvoCommand = new Command36("share-convo").description("Share the current AI conversation with a teammate").option("--to <email>", "Recipient email (repeatable)", (value, prev = []) => [...prev, value], []).option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required unless --session)").option("--session <key>", "Share a LOCAL conversation by registry key (transcript rendered mechanically)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars otherwise)").option("--source-agent <slug>", "Override host-agent detection (e.g. claude-code, codex, claude-desktop)").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional personal note to recipients").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, suggestedNextStep, etc.").option("--metadata-file <path>", "Path to a JSON file with the same metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo(opts, command, false));
26799
28514
 
26800
28515
  // src/commands/save-convo.ts
26801
- import { Command as Command36 } from "commander";
26802
- var saveConvoCommand = new Command36("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
28516
+ import { Command as Command37 } from "commander";
28517
+ var saveConvoCommand = new Command37("save-convo").description("Save the current AI conversation as a personal checkpoint").option("--transcript-file <path>", "Path to the LLM-emitted markdown transcript (required)").option("--native-file <path>", "Path to the native session file (optional; auto-detected from env vars)").option("--source-agent <slug>", "Override host-agent detection").option("--title <string>", "Short title for the conversation (required)").option("--note <string>", "Optional note to your future self").option("--ttl-days <n>", "Days until expiration (1-30, default 7)").option("--metadata-json <json>", "Inline JSON object with workMode, openQuestions, etc.").option("--metadata-file <path>", "Path to a JSON file with metadata fields").option("--workspace <name-or-id>", "Workspace name or ID").action((opts, command) => runShareConvo({ ...opts, personal: true }, command, true));
26803
28518
 
26804
28519
  // src/commands/inbox.ts
26805
28520
  init_store();
26806
28521
  init_client();
26807
28522
  init_resolve();
26808
- import { Command as Command37 } from "commander";
26809
- var inboxCommand = new Command37("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
28523
+ import { Command as Command38 } from "commander";
28524
+ var inboxCommand = new Command38("inbox").description("List shared conversations visible to you").option("--filter <scope>", "Filter: all | received | sent | saved", "all").option("--limit <n>", "Max rows to return", "50").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
26810
28525
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26811
28526
  const scope = opts.filter === "received" || opts.filter === "sent" || opts.filter === "saved" ? opts.filter : "all";
26812
28527
  const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
@@ -26848,10 +28563,10 @@ init_store();
26848
28563
  init_client();
26849
28564
  init_resolve();
26850
28565
  init_registry_data();
26851
- import { Command as Command38 } from "commander";
26852
- import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync29, realpathSync } from "fs";
26853
- import { homedir as homedir34 } from "os";
26854
- import { join as join57 } from "path";
28566
+ import { Command as Command39 } from "commander";
28567
+ import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync30, realpathSync } from "fs";
28568
+ import { homedir as homedir37 } from "os";
28569
+ import { join as join61 } from "path";
26855
28570
  import { spawn as spawn5 } from "child_process";
26856
28571
  init_registry();
26857
28572
  init_which();
@@ -26890,9 +28605,9 @@ function extractCodexUuid(rolloutContent) {
26890
28605
  }
26891
28606
  function placeClaudeJsonl(uuid, content, recipientCwd) {
26892
28607
  const encoded = encodeClaudeCodeCwd(recipientCwd);
26893
- const projectDir = join57(homedir34(), ".claude", "projects", encoded);
26894
- mkdirSync29(projectDir, { recursive: true });
26895
- const placedAt = join57(projectDir, `${uuid}.jsonl`);
28608
+ const projectDir = join61(homedir37(), ".claude", "projects", encoded);
28609
+ mkdirSync30(projectDir, { recursive: true });
28610
+ const placedAt = join61(projectDir, `${uuid}.jsonl`);
26896
28611
  writeFileSync34(placedAt, content);
26897
28612
  return { placedAt, runFromCwd: recipientCwd };
26898
28613
  }
@@ -26901,10 +28616,10 @@ function placeCodexRollout(uuid, content) {
26901
28616
  const yyyy = String(now.getUTCFullYear());
26902
28617
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
26903
28618
  const dd = String(now.getUTCDate()).padStart(2, "0");
26904
- const dir = join57(homedir34(), ".codex", "sessions", yyyy, mm, dd);
26905
- mkdirSync29(dir, { recursive: true });
28619
+ const dir = join61(homedir37(), ".codex", "sessions", yyyy, mm, dd);
28620
+ mkdirSync30(dir, { recursive: true });
26906
28621
  const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
26907
- const placedAt = join57(dir, `rollout-${ts}-${uuid}.jsonl`);
28622
+ const placedAt = join61(dir, `rollout-${ts}-${uuid}.jsonl`);
26908
28623
  writeFileSync34(placedAt, content);
26909
28624
  return { placedAt };
26910
28625
  }
@@ -26924,7 +28639,15 @@ function isAgentInstalled(agent) {
26924
28639
  }
26925
28640
  return false;
26926
28641
  }
26927
- var resumeCommand2 = new Command38("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <name-or-id>", "Workspace name or ID").action(async (shareId, opts, command) => {
28642
+ function buildResumeCommand(template, uuid, resolvedCli, cliName) {
28643
+ const argv = template.replace(/\{uuid\}/g, uuid).split(" ").filter(Boolean);
28644
+ if (resolvedCli && cliName && argv[0] === cliName) {
28645
+ argv[0] = resolvedCli;
28646
+ }
28647
+ const display = [argv[0]?.includes(" ") ? `"${argv[0]}"` : argv[0], ...argv.slice(1)].filter((part) => Boolean(part)).join(" ");
28648
+ return { argv, display };
28649
+ }
28650
+ var resumeCommand2 = new Command39("resume").description("Resume a shared conversation locally in your agent of choice").argument("<share-id>", "The share ID (sc_*) returned by share-convo or save-convo").option("--agent <slug>", "Override target agent (e.g. claude-code, codex)").option("--into <path>", "Override target cwd (defaults to current $PWD)").option("--dry-run", "Print the resume command instead of executing it").option("--pick", "Show interactive picker (requires TTY) - not yet implemented").option("--workspace <name-or-id>", "Workspace name or ID").action(async (shareId, opts, command) => {
26928
28651
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
26929
28652
  const credentials = requireAuth();
26930
28653
  const client = new ApiClient(credentials);
@@ -27018,12 +28741,8 @@ Bundle placed at: ${placement.placedAt}`);
27018
28741
  return;
27019
28742
  }
27020
28743
  if (cap2.mode === "cli-resume") {
27021
- let cmd = (cap2.cliResumeCommand ?? "").replace(/\{uuid\}/g, nativeUuid);
27022
- const resolvedCli = resolveAgentCliCommand(target.slug);
27023
- const cliName = getAgent(target.slug)?.launch?.cli;
27024
- if (resolvedCli && cliName && resolvedCli !== cliName && cmd.startsWith(`${cliName} `)) {
27025
- cmd = `"${resolvedCli}" ${cmd.slice(cliName.length + 1)}`;
27026
- }
28744
+ const { argv, display } = buildResumeCommand(cap2.cliResumeCommand ?? "", nativeUuid, resolveAgentCliCommand(target.slug), getAgent(target.slug)?.launch?.cli);
28745
+ const cmd = display;
27027
28746
  const detected = detectCurrentAgent();
27028
28747
  const insideSameAgent = detected && detected.slug === target.slug;
27029
28748
  const shouldPrintOnly = opts.dryRun || insideSameAgent;
@@ -27055,10 +28774,11 @@ Or ask the assistant to continue the conversation in THIS session by ` + `fetchi
27055
28774
  }
27056
28775
  return;
27057
28776
  }
27058
- const parts = cmd.split(" ");
27059
- const child = spawn5(parts[0], parts.slice(1), {
28777
+ const spec = toSpawnSpec(argv[0], argv.slice(1));
28778
+ const child = spawn5(spec.command, spec.args, {
27060
28779
  cwd: placement.runFromCwd ?? recipientCwd,
27061
- stdio: "inherit"
28780
+ stdio: "inherit",
28781
+ ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
27062
28782
  });
27063
28783
  child.on("exit", (code) => {
27064
28784
  process.exit(code ?? 0);
@@ -27154,7 +28874,7 @@ process.on("uncaughtException", (err) => {
27154
28874
  console.error(`Uncaught exception: ${formatError(err)}`);
27155
28875
  process.exit(1);
27156
28876
  });
27157
- var program = new Command39;
28877
+ var program = new Command40;
27158
28878
  program.name("runwork").description("Runwork CLI - local development for Runwork apps").version(VERSION).option("--json", "Output as JSON (auto-enabled when stdout is not a TTY)");
27159
28879
  program.addCommand(infoCommand);
27160
28880
  program.addCommand(loginCommand);
@@ -27187,6 +28907,7 @@ program.addCommand(appsCommand);
27187
28907
  program.addCommand(membersCommand);
27188
28908
  program.addCommand(apiCommand);
27189
28909
  program.addCommand(doctorCommand);
28910
+ program.addCommand(debugCommand);
27190
28911
  program.addCommand(shareConvoCommand);
27191
28912
  program.addCommand(saveConvoCommand);
27192
28913
  program.addCommand(inboxCommand);