runwork 0.28.0 → 0.29.1

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.
Files changed (2) hide show
  1. package/dist/index.js +596 -187
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1793,6 +1793,20 @@ function isRelPathIgnored(relPath, sets) {
1793
1793
  }
1794
1794
  return isPathIgnored(path, sets);
1795
1795
  }
1796
+ function isIgnoredDirectory(relDir, sets) {
1797
+ const path = toPosixRelPath(relDir);
1798
+ if (!path || path === ".")
1799
+ return false;
1800
+ for (const ignored of sets.paths) {
1801
+ if (path === ignored || path.startsWith(`${ignored}/`))
1802
+ return true;
1803
+ }
1804
+ for (const segment of path.split("/")) {
1805
+ if (sets.dirs.has(segment))
1806
+ return true;
1807
+ }
1808
+ return false;
1809
+ }
1796
1810
  function toPosixRelPath(relPath) {
1797
1811
  return relPath.includes("\\") ? relPath.replace(/\\/g, "/") : relPath;
1798
1812
  }
@@ -2711,6 +2725,7 @@ var init_registry_data = __esm(() => {
2711
2725
  app: { macos: "Claude", windows: "Claude" },
2712
2726
  deepLink: "claude://claude.ai/new?q={prompt}",
2713
2727
  deepLinkInFolder: "claude://cowork/new?q={prompt}&folder={folder}",
2728
+ deepLinkInFolderSplit: { gapMs: 500 },
2714
2729
  openInFolder: {
2715
2730
  rank: 4,
2716
2731
  instruction: "Open Claude Desktop and add {dir} as a project folder",
@@ -4079,6 +4094,18 @@ var init_types = __esm(() => {
4079
4094
  toSlug = toSkillSlug;
4080
4095
  });
4081
4096
 
4097
+ // ../../shared/spaces/launch-preamble.ts
4098
+ function isRunworkLaunchPreamble(text2) {
4099
+ const trimmed = text2.trim();
4100
+ if (trimmed.startsWith(LAUNCH_IN_FOLDER)) {
4101
+ return trimmed.includes(LAUNCH_FOLDER_IS_SPACE) || trimmed.includes(LAUNCH_CONTINUE_CONVERSATION);
4102
+ }
4103
+ if (trimmed.startsWith(LAUNCH_NO_FOLDER))
4104
+ return trimmed.includes(LAUNCH_SPACE_IN_RUNWORK);
4105
+ return false;
4106
+ }
4107
+ var LAUNCH_IN_FOLDER = "Work in ", LAUNCH_FOLDER_IS_SPACE = ". That folder is ", LAUNCH_CONTINUE_CONVERSATION = ". Continue the conversation we had there", LAUNCH_NO_FOLDER = "Work on ", LAUNCH_SPACE_IN_RUNWORK = "space in Runwork";
4108
+
4082
4109
  // src/agents/utils/session-digest.ts
4083
4110
  import { posix, win32 } from "path";
4084
4111
  function pathFlavourOf(path) {
@@ -4253,6 +4280,12 @@ function buildAssetMarkers(acc, epochMs) {
4253
4280
  }
4254
4281
  return markers;
4255
4282
  }
4283
+ function isInjectedUserText(text2) {
4284
+ const trimmed = text2.trim();
4285
+ if (!trimmed)
4286
+ return true;
4287
+ return SKIP_PREFIXES.some((p) => trimmed.startsWith(p)) || isRunworkLaunchPreamble(trimmed);
4288
+ }
4256
4289
  function formatCombinedDigest(sessions, opts = { days: 7 }) {
4257
4290
  const sorted = [...sessions].sort((a, b) => (a.start ?? "").localeCompare(b.start ?? ""));
4258
4291
  const lines = [];
@@ -4767,6 +4800,7 @@ function parseMcpPrefixedToolName(name) {
4767
4800
 
4768
4801
  // src/agents/utils/session-listing.ts
4769
4802
  import { openSync, readSync, closeSync, fstatSync } from "fs";
4803
+ import { StringDecoder } from "string_decoder";
4770
4804
  function isReflectionOwnSession(titleOrFirstMessage) {
4771
4805
  if (!titleOrFirstMessage)
4772
4806
  return false;
@@ -4839,7 +4873,44 @@ function titleFromText(text2) {
4839
4873
  return null;
4840
4874
  return oneLine.length > MAX_TITLE_CHARS ? oneLine.slice(0, MAX_TITLE_CHARS - 3) + "..." : oneLine;
4841
4875
  }
4842
- var HEAD_BYTES, CONVERSATION_PROMPT_MARKER = "# Conversation reflection:", WEEKLY_PROMPT_MARKER = "# Weekly reflection:", TRIAGE_PROMPT_MARKER = "# Reflection triage:", REFLECTION_SESSION_MARKERS, MAX_TITLE_CHARS = 80, TAIL_BYTES;
4876
+ function readTranscriptRecords(path, onRecord, maxBytes = DEEP_SCAN_BYTES) {
4877
+ let fd = null;
4878
+ try {
4879
+ fd = openSync(path, "r");
4880
+ const buf = Buffer.alloc(Math.min(HEAD_BYTES, maxBytes));
4881
+ const decoder = new StringDecoder("utf-8");
4882
+ let position = 0;
4883
+ let carry = "";
4884
+ while (position < maxBytes) {
4885
+ const bytesRead = readSync(fd, buf, 0, Math.min(buf.length, maxBytes - position), position);
4886
+ if (bytesRead <= 0)
4887
+ break;
4888
+ position += bytesRead;
4889
+ const lines = (carry + decoder.write(buf.subarray(0, bytesRead))).split(`
4890
+ `);
4891
+ carry = lines.pop() ?? "";
4892
+ for (const raw of lines) {
4893
+ if (!raw.trim())
4894
+ continue;
4895
+ let record;
4896
+ try {
4897
+ record = JSON.parse(raw);
4898
+ } catch {
4899
+ continue;
4900
+ }
4901
+ if (onRecord(record))
4902
+ return;
4903
+ }
4904
+ }
4905
+ } catch {} finally {
4906
+ if (fd !== null) {
4907
+ try {
4908
+ closeSync(fd);
4909
+ } catch {}
4910
+ }
4911
+ }
4912
+ }
4913
+ var HEAD_BYTES, CONVERSATION_PROMPT_MARKER = "# Conversation reflection:", WEEKLY_PROMPT_MARKER = "# Weekly reflection:", TRIAGE_PROMPT_MARKER = "# Reflection triage:", REFLECTION_SESSION_MARKERS, MAX_TITLE_CHARS = 80, TAIL_BYTES, DEEP_SCAN_BYTES;
4843
4914
  var init_session_listing = __esm(() => {
4844
4915
  HEAD_BYTES = 64 * 1024;
4845
4916
  REFLECTION_SESSION_MARKERS = [
@@ -4848,12 +4919,60 @@ var init_session_listing = __esm(() => {
4848
4919
  TRIAGE_PROMPT_MARKER
4849
4920
  ];
4850
4921
  TAIL_BYTES = 16 * 1024;
4922
+ DEEP_SCAN_BYTES = 1536 * 1024;
4851
4923
  });
4852
4924
 
4853
4925
  // src/agents/claude/jsonl-listing.ts
4854
- function scanClaudeJsonlHead(head) {
4855
- const result = { firstTimestamp: null, title: null, cwd: null };
4856
- let firstUserText = null;
4926
+ function claudeTitleCandidate(record) {
4927
+ if (record.type === "custom-title" && typeof record.customTitle === "string" && record.customTitle.trim()) {
4928
+ return { source: "custom-title", text: record.customTitle };
4929
+ }
4930
+ if (record.type === "ai-title" && typeof record.aiTitle === "string" && record.aiTitle.trim()) {
4931
+ return { source: "ai-title", text: record.aiTitle };
4932
+ }
4933
+ if (record.type !== "user")
4934
+ return null;
4935
+ const message = record.message;
4936
+ if (!message || message.role !== "user")
4937
+ return null;
4938
+ const texts = [];
4939
+ if (typeof message.content === "string")
4940
+ texts.push(message.content);
4941
+ else if (Array.isArray(message.content)) {
4942
+ for (const item of message.content) {
4943
+ const it = item;
4944
+ if (it && it.type === "text" && typeof it.text === "string")
4945
+ texts.push(it.text);
4946
+ }
4947
+ }
4948
+ for (const text2 of texts) {
4949
+ const trimmed = text2.trim();
4950
+ if (isInjectedUserText(trimmed))
4951
+ continue;
4952
+ return { source: "user-text", text: trimmed };
4953
+ }
4954
+ return null;
4955
+ }
4956
+ function absorbClaudeRecord(state, o, rankTitles) {
4957
+ const { result } = state;
4958
+ if (!result.firstTimestamp && typeof o.timestamp === "string")
4959
+ result.firstTimestamp = o.timestamp;
4960
+ if (!result.cwd && typeof o.cwd === "string")
4961
+ result.cwd = o.cwd;
4962
+ if (!result.surface && typeof o.entrypoint === "string")
4963
+ result.surface = o.entrypoint;
4964
+ const candidate = claudeTitleCandidate(o);
4965
+ if (candidate && (rankTitles ? TITLE_RANK[candidate.source] < state.bestRank : result.title === null)) {
4966
+ state.bestRank = TITLE_RANK[candidate.source];
4967
+ result.title = titleFromText(candidate.text);
4968
+ }
4969
+ const complete = Boolean(result.firstTimestamp && result.cwd && result.surface);
4970
+ return rankTitles ? state.bestRank === 0 && complete : result.title !== null && complete;
4971
+ }
4972
+ function emptyState() {
4973
+ return { result: { firstTimestamp: null, title: null, cwd: null }, bestRank: Number.POSITIVE_INFINITY };
4974
+ }
4975
+ function scanHeadInto(state, head) {
4857
4976
  for (const line of head.split(`
4858
4977
  `)) {
4859
4978
  if (!line.trim())
@@ -4864,47 +4983,30 @@ function scanClaudeJsonlHead(head) {
4864
4983
  } catch {
4865
4984
  continue;
4866
4985
  }
4867
- if (!result.firstTimestamp && typeof o.timestamp === "string")
4868
- result.firstTimestamp = o.timestamp;
4869
- if (!result.cwd && typeof o.cwd === "string")
4870
- result.cwd = o.cwd;
4871
- if (!result.surface && typeof o.entrypoint === "string")
4872
- result.surface = o.entrypoint;
4873
- if (!result.title && o.type === "ai-title" && typeof o.aiTitle === "string") {
4874
- result.title = titleFromText(o.aiTitle);
4875
- }
4876
- if (firstUserText === null && o.type === "user") {
4877
- const message = o.message;
4878
- if (message && message.role === "user") {
4879
- const texts = [];
4880
- if (typeof message.content === "string")
4881
- texts.push(message.content);
4882
- else if (Array.isArray(message.content)) {
4883
- for (const item of message.content) {
4884
- const it = item;
4885
- if (it && it.type === "text" && typeof it.text === "string")
4886
- texts.push(it.text);
4887
- }
4888
- }
4889
- for (const text2 of texts) {
4890
- const trimmed = text2.trim();
4891
- if (!trimmed || SKIP_PREFIXES.some((p) => trimmed.startsWith(p)))
4892
- continue;
4893
- firstUserText = trimmed;
4894
- break;
4895
- }
4896
- }
4897
- }
4898
- if (result.title && result.firstTimestamp && result.cwd && result.surface)
4899
- break;
4986
+ if (absorbClaudeRecord(state, o, true))
4987
+ return true;
4900
4988
  }
4901
- if (!result.title && firstUserText)
4902
- result.title = titleFromText(firstUserText);
4989
+ return false;
4990
+ }
4991
+ function scanClaudeTranscript(path, head, maxBytes) {
4992
+ const state = emptyState();
4993
+ const { result } = state;
4994
+ if (scanHeadInto(state, head))
4995
+ return result;
4996
+ if (result.title && result.firstTimestamp && result.cwd && result.surface)
4997
+ return result;
4998
+ readTranscriptRecords(path, (o) => absorbClaudeRecord(state, o, false), maxBytes);
4903
4999
  return result;
4904
5000
  }
5001
+ var TITLE_RANK;
4905
5002
  var init_jsonl_listing = __esm(() => {
4906
5003
  init_session_listing();
4907
5004
  init_session_digest();
5005
+ TITLE_RANK = {
5006
+ "custom-title": 0,
5007
+ "ai-title": 1,
5008
+ "user-text": 2
5009
+ };
4908
5010
  });
4909
5011
 
4910
5012
  // src/agents/utils/skill-name.ts
@@ -5080,23 +5182,39 @@ function spaceKindsSentence() {
5080
5182
  function sentenceName(plural) {
5081
5183
  return /^[A-Z]{2,}\b/.test(plural) ? plural : plural.toLowerCase();
5082
5184
  }
5083
- var SPACE_KINDS, BY_ID;
5185
+ var SPACE_TABS, APPS_AND_AUTOMATIONS, SPACE_KINDS, TAB_BY_NAME, BY_ID;
5084
5186
  var init_kinds = __esm(() => {
5187
+ SPACE_TABS = [
5188
+ { id: "appsAuto", label: "Apps and automations" },
5189
+ { id: "skills", label: "Skills" },
5190
+ { id: "files", label: "Files" },
5191
+ { id: "integ", label: "Integrations" },
5192
+ { id: "conversations", label: "Chats" }
5193
+ ];
5194
+ APPS_AND_AUTOMATIONS = SPACE_TABS[0];
5085
5195
  SPACE_KINDS = [
5086
- { id: "app", singular: "app", plural: "Apps", tab: "apps" },
5196
+ { id: "app", singular: "app", plural: "Apps", tab: "appsAuto" },
5087
5197
  { id: "skill", singular: "skill", plural: "Skills", tab: "skills" },
5088
- { id: "agent", singular: "agent", plural: "Agents", tab: "agents" },
5198
+ { id: "agent", singular: "agent", plural: "Agents", tab: "appsAuto" },
5089
5199
  { id: "file", singular: "file", plural: "Files", tab: "files" },
5090
5200
  { id: "conversation", singular: "chat", plural: "Chats", tab: "conversations" },
5091
- { id: "data", singular: "entity", plural: "Data", tab: "data" },
5092
- { id: "fileStorage", singular: "file storage", plural: "File storage", tab: "data" },
5093
- { id: "component", singular: "component", plural: "Components", tab: "data" },
5094
- { id: "schedule", singular: "scheduled job", plural: "Scheduled work", tab: "auto" },
5095
- { id: "workflow", singular: "workflow", plural: "Workflows", tab: "auto" },
5096
- { id: "endpoint", singular: "public endpoint", plural: "Endpoints", tab: "auto" },
5201
+ { id: "data", singular: "entity", plural: "Data", tab: "appsAuto" },
5202
+ { id: "fileStorage", singular: "file storage", plural: "File storage", tab: "appsAuto" },
5203
+ { id: "component", singular: "component", plural: "Components", tab: "appsAuto" },
5204
+ { id: "schedule", singular: "scheduled job", plural: "Scheduled work", tab: "appsAuto" },
5205
+ { id: "workflow", singular: "workflow", plural: "Workflows", tab: "appsAuto" },
5206
+ { id: "endpoint", singular: "public endpoint", plural: "Endpoints", tab: "appsAuto" },
5097
5207
  { id: "integration", singular: "integration", plural: "Integrations", tab: "integ" },
5098
5208
  { id: "mcpServer", singular: "MCP server", plural: "MCP servers", tab: "integ" }
5099
5209
  ];
5210
+ TAB_BY_NAME = new Map([
5211
+ ...SPACE_TABS.map((tab) => [tab.id, tab.id]),
5212
+ ["chats", "conversations"],
5213
+ ["apps", "appsAuto"],
5214
+ ["auto", "appsAuto"],
5215
+ ["agents", "appsAuto"],
5216
+ ["data", "appsAuto"]
5217
+ ]);
5100
5218
  BY_ID = new Map(SPACE_KINDS.map((k) => [k.id, k]));
5101
5219
  });
5102
5220
 
@@ -5175,7 +5293,7 @@ function annotateSpacesMcp(tree) {
5175
5293
  visit(root);
5176
5294
  return tree;
5177
5295
  }
5178
- var SPACE_DEFINITION = 'A space is where one piece of work lives: its files, how your AI should handle it, the apps and automations around it, and the conversations. Start a chat from a space and the AI already knows the job. Share a file and a teammate picks it up where you left it. Think "Q3 board deck", "Customer onboarding", "Website redesign".', SPACE_API_PREFIX = "/api/workspaces/:id/spaces", SPACE_JOBS, SPACE_LAUNCH_SENTENCE = "Start a chat in a space with the `runwork://launch?space=<space id>` link on the person's computer, or ask the `space_launch_link` tool for that URL.", machineOnlySpaceJobs = () => SPACE_JOBS.filter((j) => j.mcp === null), SPACES_ROUTING_SENTENCE = "Keep a document the team should have in a space (`space_list`, then `space_write_file`; the CLI twins are `runwork spaces list` and `runwork spaces put`, and the route is `POST /api/workspaces/:id/spaces/:spaceId/files`), not in a local file or a storage bucket.", SPACES_GROUP_DESCRIPTION, HELP_FOOTER_DOORS, SPACES_SUBCOMMAND_MCP, TEAM_BLOCK_RUNWORK_LINE = "Runwork is connected here: the `runwork` block above says what it can do, and any folder mapped to a space carries its own `runwork-space` block in its AGENTS.md.";
5296
+ var SPACE_DEFINITION = 'A space is where one piece of work lives: its files, how your AI should handle it, the apps and automations around it, and the conversations. Start a chat from a space and the AI already knows the job. Share a file and a teammate picks it up where you left it. Think "Q3 board deck", "Customer onboarding", "Website redesign".', SPACE_API_PREFIX = "/api/workspaces/:id/spaces", SPACE_JOBS, SPACE_LAUNCH_SENTENCE = "Start a chat in a space with the `runwork://launch?space=<space id>` link on the person's computer, or ask the `space_launch_link` tool for that URL.", machineOnlySpaceJobs = () => SPACE_JOBS.filter((j) => j.mcp === null), SPACES_GROUP_DESCRIPTION, HELP_FOOTER_DOORS, SPACES_SUBCOMMAND_MCP, TEAM_BLOCK_RUNWORK_LINE = "Runwork is connected here: the `runwork` block above says what it can do, and any folder mapped to a space carries its own `runwork-space` block in its AGENTS.md.";
5179
5297
  var init_spaces = __esm(() => {
5180
5298
  init_kinds();
5181
5299
  SPACE_JOBS = [
@@ -6521,7 +6639,7 @@ ${instructions}`;
6521
6639
  const head = readFileHead(filePath);
6522
6640
  if (head === null)
6523
6641
  continue;
6524
- const scanned = scanClaudeJsonlHead(head);
6642
+ const scanned = scanClaudeTranscript(filePath, head);
6525
6643
  if (isReflectionOwnSession(scanned.title))
6526
6644
  continue;
6527
6645
  const sessionId = file.slice(0, -".jsonl".length);
@@ -10174,7 +10292,7 @@ function extractCodexRolloutSession(raw, agentSlug) {
10174
10292
  digest.assetMarkers = buildAssetMarkers(assetAcc, epochMs);
10175
10293
  return digest;
10176
10294
  }
10177
- var CODEX_SKIP_PREFIXES, CODEX_UNKNOWN_PROJECT = "codex";
10295
+ var CODEX_ASSESSOR_PROMPT_PREFIX = "The following is the Codex agent history", CODEX_SKIP_PREFIXES, CODEX_UNKNOWN_PROJECT = "codex";
10178
10296
  var init_rollout_digest = __esm(() => {
10179
10297
  init_session_digest();
10180
10298
  CODEX_SKIP_PREFIXES = [
@@ -10185,7 +10303,9 @@ var init_rollout_digest = __esm(() => {
10185
10303
  "<environment_context",
10186
10304
  "<system",
10187
10305
  "AGENTS.md instructions",
10188
- "<recommended_plugins"
10306
+ "<recommended_plugins",
10307
+ CODEX_ASSESSOR_PROMPT_PREFIX,
10308
+ ">>> TRANSCRIPT START"
10189
10309
  ];
10190
10310
  });
10191
10311
 
@@ -10379,6 +10499,50 @@ var init_analyst = __esm(() => {
10379
10499
  function codexUuidFromRolloutName(basenameNoExt) {
10380
10500
  return UUID_TAIL.exec(basenameNoExt)?.[1] ?? null;
10381
10501
  }
10502
+ function codexTitleCandidate(record) {
10503
+ const p = record.payload;
10504
+ if (!p || typeof p !== "object")
10505
+ return null;
10506
+ if (record.type !== "response_item" || p.type !== "message" || p.role !== "user" || !Array.isArray(p.content))
10507
+ return null;
10508
+ const isAssessorTurn = p.content.some((item) => {
10509
+ const it = item;
10510
+ return typeof it.text === "string" && it.text.trim().startsWith(CODEX_ASSESSOR_PROMPT_PREFIX);
10511
+ });
10512
+ if (isAssessorTurn)
10513
+ return null;
10514
+ for (const item of p.content) {
10515
+ const it = item;
10516
+ if (it.type !== "input_text" && it.type !== "text")
10517
+ continue;
10518
+ if (typeof it.text !== "string")
10519
+ continue;
10520
+ const trimmed = it.text.trim();
10521
+ if (CODEX_SKIP_PREFIXES.some((pre) => trimmed.startsWith(pre)) || isInjectedUserText(trimmed))
10522
+ continue;
10523
+ return { source: "user-text", text: trimmed };
10524
+ }
10525
+ return null;
10526
+ }
10527
+ function absorbCodexRecord(result, o) {
10528
+ if (!result.firstTimestamp && typeof o.timestamp === "string")
10529
+ result.firstTimestamp = o.timestamp;
10530
+ const p = o.payload;
10531
+ if (p && typeof p === "object" && o.type === "session_meta") {
10532
+ if (typeof p.cwd === "string" && !result.cwd)
10533
+ result.cwd = p.cwd;
10534
+ if (typeof p.id === "string" && !result.sessionId)
10535
+ result.sessionId = p.id;
10536
+ if (typeof p.originator === "string" && !result.surface)
10537
+ result.surface = p.originator;
10538
+ }
10539
+ if (!result.title) {
10540
+ const candidate = codexTitleCandidate(o);
10541
+ if (candidate)
10542
+ result.title = titleFromText(candidate.text);
10543
+ }
10544
+ return Boolean(result.title && result.firstTimestamp && result.cwd);
10545
+ }
10382
10546
  function scanCodexRolloutHead(head) {
10383
10547
  const result = { firstTimestamp: null, title: null, cwd: null };
10384
10548
  for (const line of head.split(`
@@ -10391,36 +10555,18 @@ function scanCodexRolloutHead(head) {
10391
10555
  } catch {
10392
10556
  continue;
10393
10557
  }
10394
- if (!result.firstTimestamp && typeof o.timestamp === "string")
10395
- result.firstTimestamp = o.timestamp;
10396
- const p = o.payload;
10397
- if (!p || typeof p !== "object")
10398
- continue;
10399
- if (o.type === "session_meta") {
10400
- if (typeof p.cwd === "string" && !result.cwd)
10401
- result.cwd = p.cwd;
10402
- if (typeof p.id === "string" && !result.sessionId)
10403
- result.sessionId = p.id;
10404
- if (typeof p.originator === "string" && !result.surface)
10405
- result.surface = p.originator;
10406
- }
10407
- if (!result.title && o.type === "response_item" && p.type === "message" && p.role === "user" && Array.isArray(p.content)) {
10408
- for (const item of p.content) {
10409
- const it = item;
10410
- if ((it.type === "input_text" || it.type === "text") && typeof it.text === "string") {
10411
- const trimmed = it.text.trim();
10412
- if (!trimmed || CODEX_SKIP_PREFIXES.some((pre) => trimmed.startsWith(pre)) || SKIP_PREFIXES.some((pre) => trimmed.startsWith(pre)))
10413
- continue;
10414
- result.title = titleFromText(trimmed);
10415
- break;
10416
- }
10417
- }
10418
- }
10419
- if (result.title && result.firstTimestamp && result.cwd)
10558
+ if (absorbCodexRecord(result, o))
10420
10559
  break;
10421
10560
  }
10422
10561
  return result;
10423
10562
  }
10563
+ function scanCodexRollout(path, head, maxBytes) {
10564
+ const result = scanCodexRolloutHead(head);
10565
+ if (result.title && result.firstTimestamp && result.cwd)
10566
+ return result;
10567
+ readTranscriptRecords(path, (o) => absorbCodexRecord(result, o), maxBytes);
10568
+ return result;
10569
+ }
10424
10570
  var CODEX_HEAD_BYTES, UUID_TAIL;
10425
10571
  var init_rollout_listing = __esm(() => {
10426
10572
  init_session_listing();
@@ -11145,7 +11291,7 @@ var init_adapter3 = __esm(async () => {
11145
11291
  const head = readFileHead(file, CODEX_HEAD_BYTES);
11146
11292
  if (head === null)
11147
11293
  continue;
11148
- const scanned = scanCodexRolloutHead(head);
11294
+ const scanned = scanCodexRollout(file, head);
11149
11295
  if (isReflectionOwnSession(scanned.title))
11150
11296
  continue;
11151
11297
  sessions.push({
@@ -12434,6 +12580,18 @@ function findAgentFiles(folder, env) {
12434
12580
  }
12435
12581
  return out;
12436
12582
  }
12583
+ function liveFolderActivity(conversations) {
12584
+ const live = [];
12585
+ for (const c of conversations) {
12586
+ if (c.status !== "live")
12587
+ continue;
12588
+ const folders = c.folders ?? [];
12589
+ if (folders.length === 0)
12590
+ continue;
12591
+ live.push({ agentSlug: c.agentSlug, folders, lastActivityAt: c.lastActivityAt });
12592
+ }
12593
+ return live;
12594
+ }
12437
12595
  function buildAssetCensus(input) {
12438
12596
  const env = input.env ?? defaultCensusEnvironment();
12439
12597
  const now = input.now ?? new Date;
@@ -12455,6 +12613,18 @@ function buildAssetCensus(input) {
12455
12613
  byFolder.set(f, cur);
12456
12614
  }
12457
12615
  }
12616
+ for (const live of input.liveActivity ?? []) {
12617
+ for (const folder of live.folders) {
12618
+ const cur = byFolder.get(resolve3(folder));
12619
+ if (!cur)
12620
+ continue;
12621
+ const agent = cur.agents.get(live.agentSlug);
12622
+ if (agent && live.lastActivityAt > agent.last)
12623
+ agent.last = live.lastActivityAt;
12624
+ if (live.lastActivityAt > cur.last)
12625
+ cur.last = live.lastActivityAt;
12626
+ }
12627
+ }
12458
12628
  const outerFolders = [...byFolder.keys()].filter((f) => spaceExclusionReason(f, env) === null).map((f) => resolve3(f));
12459
12629
  const isNested = (folder) => {
12460
12630
  const f = resolve3(folder);
@@ -18827,7 +18997,7 @@ function createKeyboardListener() {
18827
18997
  }
18828
18998
 
18829
18999
  // src/generated/version.ts
18830
- var VERSION = "0.28.0";
19000
+ var VERSION = "0.29.1";
18831
19001
 
18832
19002
  // src/commands/dev.ts
18833
19003
  var exports_dev = {};
@@ -21362,6 +21532,17 @@ function localAppDataRoot(input) {
21362
21532
  const join53 = joiner(input);
21363
21533
  return input.localAppData ?? join53(home(input), "AppData", "Local");
21364
21534
  }
21535
+ function desktopStateDirs(input) {
21536
+ const join53 = joiner(input);
21537
+ const named = (root) => join53(root, DESKTOP_APP_IDENTIFIER);
21538
+ if (input.platform === "win32") {
21539
+ return [named(appDataRoot(input)), named(localAppDataRoot(input))];
21540
+ }
21541
+ if (input.platform === "darwin")
21542
+ return [named(appDataRoot(input))];
21543
+ return [named(join53(home(input), ".local", "share")), named(appDataRoot(input))];
21544
+ }
21545
+ var DESKTOP_APP_IDENTIFIER = "ai.runwork.desktop";
21365
21546
 
21366
21547
  // src/agents/claude/declarations.ts
21367
21548
  function claudeEntrypoint(head) {
@@ -21393,7 +21574,7 @@ function attributeClaudeEntrypoint(marker) {
21393
21574
  return null;
21394
21575
  }
21395
21576
  }
21396
- var CLAUDE_HEAD, CLAUDE_PROJECTS_STORE, CLAUDE_COWORK_STORE, CLAUDE_CODE_CAPTURE_TARGETS, CLAUDE_DESKTOP_CAPTURE_TARGETS, CLAUDE_ACCOUNT_PROFILE_FIELDS;
21577
+ var CLAUDE_HEAD, CLAUDE_PROJECTS_STORE, CLAUDE_COWORK_STORE, CLAUDE_CODE_CAPTURE_TARGETS, CLAUDE_DESKTOP_CAPTURE_TARGETS, CLAUDE_ACCOUNT_PROFILE_FIELD = "conversation_preferences";
21397
21578
  var init_declarations = __esm(() => {
21398
21579
  CLAUDE_HEAD = 64 * 1024;
21399
21580
  CLAUDE_PROJECTS_STORE = {
@@ -21440,10 +21621,6 @@ var init_declarations = __esm(() => {
21440
21621
  note: "the MCP config we write for Claude Desktop, next to its session store"
21441
21622
  }
21442
21623
  ];
21443
- CLAUDE_ACCOUNT_PROFILE_FIELDS = {
21444
- full: "cowork_global_instructions",
21445
- compact: "conversation_preferences"
21446
- };
21447
21624
  });
21448
21625
 
21449
21626
  // src/agents/codex/declarations.ts
@@ -26188,6 +26365,7 @@ var SPACE_SIZE_LIMIT_BYTES = 300 * 1024 * 1024;
26188
26365
  var SPACE_PUSH_BATCH_BYTES = 75 * 1024 * 1024;
26189
26366
  var SPACE_FILE_SIZE_LIMIT_BYTES = SPACE_PUSH_BATCH_BYTES;
26190
26367
  var SPACE_SETTLE_MS = 90000;
26368
+ var SPACE_CLOCK_AHEAD_MS = 2000;
26191
26369
  var SPACE_THUMBNAIL_MAX_BYTES = 8 * 1024 * 1024;
26192
26370
  function formatBytes(bytes) {
26193
26371
  if (bytes < 1024)
@@ -26362,6 +26540,44 @@ function isExplicitlyIncluded(relPath, rules) {
26362
26540
  }
26363
26541
  return false;
26364
26542
  }
26543
+ function admitsNothingUnder(relDir, rules) {
26544
+ const dir = includeRulePath(relDir);
26545
+ if (!dir)
26546
+ return false;
26547
+ const lineage = pathAndAncestors(dir);
26548
+ for (const step of lineage) {
26549
+ if (rules.denies.has(step))
26550
+ return true;
26551
+ }
26552
+ for (const step of lineage) {
26553
+ if (rules.directories.has(step))
26554
+ return false;
26555
+ }
26556
+ const under2 = `${dir}/`;
26557
+ for (const path2 of rules.paths) {
26558
+ if (path2 === dir || path2.startsWith(under2))
26559
+ return false;
26560
+ }
26561
+ for (const directory of rules.directories) {
26562
+ if (directory.startsWith(under2))
26563
+ return false;
26564
+ }
26565
+ return true;
26566
+ }
26567
+ function sharesNothingUnder(relDir, rules) {
26568
+ const dir = includeRulePath(relDir);
26569
+ if (!dir)
26570
+ return false;
26571
+ for (const step of pathAndAncestors(dir)) {
26572
+ if (rules.denies.has(step))
26573
+ return true;
26574
+ }
26575
+ if (rules.names.size > 0)
26576
+ return false;
26577
+ if (rules.directories.size === 0 && rules.paths.size === 0)
26578
+ return false;
26579
+ return admitsNothingUnder(dir, rules);
26580
+ }
26365
26581
  function includeRulePath(relPath) {
26366
26582
  return relPath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
26367
26583
  }
@@ -26432,6 +26648,13 @@ function hiddenFromSpace(relPath, ignoreSets, rules, floor = buildSpaceFloorSets
26432
26648
  return true;
26433
26649
  return !isExplicitlyIncluded(relPath, rules);
26434
26650
  }
26651
+ function spaceSkipsDirectory(relDir, ignoreSets, rules, floor = buildSpaceFloorSets()) {
26652
+ if (sharesNothingUnder(relDir, rules))
26653
+ return true;
26654
+ if (!isIgnoredDirectory(relDir, ignoreSets))
26655
+ return false;
26656
+ return isIgnoredDirectory(relDir, floor) || admitsNothingUnder(relDir, rules);
26657
+ }
26435
26658
  function agentManagedInstructionPaths() {
26436
26659
  const paths = new Set;
26437
26660
  for (const adapter2 of getAllAdapters()) {
@@ -26462,6 +26685,10 @@ function readIncludeRules(folder) {
26462
26685
 
26463
26686
  // src/spaces/space-repo.ts
26464
26687
  init_instruction_hint();
26688
+ // ../../shared/spaces/refusal.ts
26689
+ var SPACE_RESET_COMMAND = "runwork spaces reset";
26690
+ var SPACE_EXCLUDE_COMMAND = "runwork spaces exclude";
26691
+
26465
26692
  // ../../shared/spaces/waiting-changes.ts
26466
26693
  var WAITING_SUMMARY_ENTRY_CAP = 200;
26467
26694
 
@@ -26633,6 +26860,7 @@ function normaliseMtime(ms) {
26633
26860
 
26634
26861
  // src/spaces/space-repo.ts
26635
26862
  init_atomic_json();
26863
+ init_trash();
26636
26864
  var SPACE_BRANCH = "main";
26637
26865
  var REMOTE_MAIN = `refs/remotes/${RUNWORK_REMOTE}/${SPACE_BRANCH}`;
26638
26866
  var LOCAL_MAIN = `refs/heads/${SPACE_BRANCH}`;
@@ -26667,7 +26895,13 @@ function spaceRemainingBytes(storage, fallbackLimit = SPACE_SIZE_LIMIT_BYTES) {
26667
26895
  function spaceGitdir(folder) {
26668
26896
  const normalized = resolve7(folder);
26669
26897
  const hash = createHash7("sha256").update(normalized).digest("hex").slice(0, 12);
26670
- return join59(homedir36(), ".runwork", "space-repos", `${basename9(normalized)}-${hash}`);
26898
+ return join59(spaceReposRoot(), `${basename9(normalized)}-${hash}`);
26899
+ }
26900
+ function spaceReposRoot() {
26901
+ return join59(homedir36(), ".runwork", "space-repos");
26902
+ }
26903
+ function isSpaceHistoryPath(gitdir) {
26904
+ return dirname17(gitdir) === spaceReposRoot();
26671
26905
  }
26672
26906
  function repoAt(folder) {
26673
26907
  return { fs: fs6, dir: folder, gitdir: spaceGitdir(folder) };
@@ -26678,8 +26912,80 @@ function normalizeUrl(url) {
26678
26912
  async function repoMappingRefusal(folder, spaceRemoteUrl) {
26679
26913
  return null;
26680
26914
  }
26915
+ function spaceIdFromRemoteUrl(url) {
26916
+ const match = /\/spaces\/(spc_[A-Za-z0-9_-]+)(?:[/?#]|$)/.exec(url);
26917
+ return match ? match[1] : null;
26918
+ }
26919
+ async function foreignSpaceHistory(folder, spaceRemoteUrl) {
26920
+ const gitdir = spaceGitdir(folder);
26921
+ if (!existsSync59(gitdir))
26922
+ return null;
26923
+ const now = spaceIdFromRemoteUrl(spaceRemoteUrl);
26924
+ if (!now)
26925
+ return null;
26926
+ let was = null;
26927
+ try {
26928
+ const url = (await git.listRemotes({ fs: fs6, gitdir })).find((r) => r.remote === RUNWORK_REMOTE)?.url;
26929
+ was = url ? spaceIdFromRemoteUrl(url) : null;
26930
+ } catch {
26931
+ return null;
26932
+ }
26933
+ return was && was !== now ? { was, now } : null;
26934
+ }
26935
+ function directoryWeight(dir) {
26936
+ let bytes = 0;
26937
+ let files = 0;
26938
+ const pending = [dir];
26939
+ while (pending.length > 0) {
26940
+ const current = pending.pop();
26941
+ let entries;
26942
+ try {
26943
+ entries = readdirSync24(current, { withFileTypes: true });
26944
+ } catch {
26945
+ continue;
26946
+ }
26947
+ for (const entry of entries) {
26948
+ const full = join59(current, entry.name);
26949
+ if (entry.isDirectory()) {
26950
+ pending.push(full);
26951
+ continue;
26952
+ }
26953
+ try {
26954
+ const stats = lstatSync2(full);
26955
+ if (stats.isFile()) {
26956
+ bytes += stats.size;
26957
+ files++;
26958
+ }
26959
+ } catch {}
26960
+ }
26961
+ }
26962
+ return { bytes, files };
26963
+ }
26964
+ function discardSpaceHistory(folder, reason) {
26965
+ const gitdir = spaceGitdir(folder);
26966
+ if (!isSpaceHistoryPath(gitdir))
26967
+ return { state: "outside", gitdir, root: spaceReposRoot() };
26968
+ if (!existsSync59(gitdir))
26969
+ return { state: "none", gitdir };
26970
+ const weight = directoryWeight(gitdir);
26971
+ const trash = moveToTrash(gitdir, reason);
26972
+ if (trash === null)
26973
+ return { state: "stuck", gitdir, trashRoot: trashRoot() };
26974
+ forgetWaitingCache(folder);
26975
+ return { state: "discarded", gitdir, trash, bytes: weight.bytes, files: weight.files };
26976
+ }
26681
26977
  async function ensureSpaceRepo(folder, spaceRemoteUrl) {
26682
26978
  const gitdir = spaceGitdir(folder);
26979
+ let setAside = null;
26980
+ const foreign = await foreignSpaceHistory(folder, spaceRemoteUrl);
26981
+ if (foreign) {
26982
+ const discarded = discardSpaceHistory(folder, `space history for ${foreign.was}: ${folder} is now mapped to ${foreign.now}`);
26983
+ if (discarded.state === "stuck") {
26984
+ throw new Error(`This folder's earlier history belonged to another space and could not be moved to ${discarded.trashRoot}, so nothing was synced. It is at ${discarded.gitdir}.`);
26985
+ }
26986
+ if (discarded.state === "discarded")
26987
+ setAside = { spaceId: foreign.was, trash: discarded.trash, bytes: discarded.bytes };
26988
+ }
26683
26989
  let created = false;
26684
26990
  if (!existsSync59(gitdir)) {
26685
26991
  migrateLegacyInFolderRepo(folder, gitdir);
@@ -26694,7 +27000,7 @@ async function ensureSpaceRepo(folder, spaceRemoteUrl) {
26694
27000
  if (!current || normalizeUrl(current.url) !== normalizeUrl(spaceRemoteUrl)) {
26695
27001
  await git.addRemote({ ...repoAt(folder), remote: RUNWORK_REMOTE, url: spaceRemoteUrl, force: true });
26696
27002
  }
26697
- return { created };
27003
+ return { created, setAside };
26698
27004
  }
26699
27005
  function migrateLegacyInFolderRepo(folder, gitdir) {
26700
27006
  const inFolder = join59(folder, ".git");
@@ -26931,9 +27237,29 @@ function unaddressableNamesIn(folder) {
26931
27237
  walk(folder, "");
26932
27238
  return found.sort();
26933
27239
  }
26934
- async function readStatusMatrix(folder) {
27240
+ function spaceVisibleFs(folder, skip) {
27241
+ const root = resolve7(folder).replace(/\\/g, "/").replace(/\/+$/, "");
27242
+ const relativeToFolder = (target) => {
27243
+ const path2 = String(target).replace(/\\/g, "/").replace(/\/+$/, "");
27244
+ if (path2 === root)
27245
+ return "";
27246
+ return path2.startsWith(`${root}/`) ? path2.slice(root.length + 1) : null;
27247
+ };
27248
+ return {
27249
+ promises: {
27250
+ ...fs6.promises,
27251
+ readdir: async (target) => {
27252
+ const rel = relativeToFolder(String(target));
27253
+ if (rel !== null && rel !== "" && skip(rel))
27254
+ return [];
27255
+ return fs6.promises.readdir(target);
27256
+ }
27257
+ }
27258
+ };
27259
+ }
27260
+ async function readStatusMatrix(folder, skip) {
26935
27261
  try {
26936
- return await git.statusMatrix({ ...repoAt(folder) });
27262
+ return await git.statusMatrix({ ...repoAt(folder), fs: spaceVisibleFs(folder, skip) });
26937
27263
  } catch (err) {
26938
27264
  const offenders = unaddressableNamesIn(folder);
26939
27265
  if (offenders.length === 0)
@@ -26970,14 +27296,14 @@ async function planFolderChanges(folder, opts = {}) {
26970
27296
  const ignoreSets = buildSpaceIgnoreSets(folder);
26971
27297
  const managedInstructionPaths = new Set(agentManagedInstructionPaths());
26972
27298
  const includeRules = readIncludeRules(folder);
26973
- const matrix = await readStatusMatrix(folder);
27299
+ const floor = buildSpaceFloorSets();
27300
+ const matrix = await readStatusMatrix(folder, (relDir) => spaceSkipsDirectory(relDir, ignoreSets, includeRules, floor));
26974
27301
  const changes = [];
26975
27302
  const skipped = [];
26976
27303
  let addedBytes = 0;
26977
27304
  const remove = (filepath) => {
26978
27305
  changes.push({ path: filepath, kind: "remove", bytes: 0 });
26979
27306
  };
26980
- const floor = buildSpaceFloorSets();
26981
27307
  for (const [filepath, head, workdir] of matrix) {
26982
27308
  if (hiddenFromSpace(filepath, ignoreSets, includeRules, floor))
26983
27309
  continue;
@@ -27101,8 +27427,8 @@ function splitPlannedChanges(changes, rules) {
27101
27427
  split.heldNewestMtimeMs = Math.max(split.heldNewestMtimeMs ?? 0, change.mtimeMs);
27102
27428
  continue;
27103
27429
  }
27104
- const age = nowMs - (change.mtimeMs ?? 0);
27105
- const unfinished = change.kind !== "remove" && rules.settle !== false && age >= 0 && age < SPACE_SETTLE_MS;
27430
+ const age = nowMs - Math.floor(change.mtimeMs ?? 0);
27431
+ const unfinished = change.kind !== "remove" && rules.settle !== false && age > -SPACE_CLOCK_AHEAD_MS && age < SPACE_SETTLE_MS;
27106
27432
  if (unfinished && !namedForSharing(change.path, shareNow)) {
27107
27433
  split.settling.push(change.path);
27108
27434
  continue;
@@ -27272,7 +27598,7 @@ function weightSentence(heaviest) {
27272
27598
  function ceilingMessage(refusal) {
27273
27599
  const sizes = `${formatBytes(refusal.bytesToPush)} and the space has ${formatBytes(refusal.remainingBytes)} left`;
27274
27600
  if (refusal.pendingCommits === 0) {
27275
- const nothingWritten = `Sharing this folder would send ${sizes}, so nothing was committed here and nothing went up.${weightSentence(refusal.heaviest)} Share less of it with \`runwork spaces exclude <path>\`, or remove the large files, then sync again.`;
27601
+ const nothingWritten = `Sharing this folder would send ${sizes}, so nothing was committed here and nothing went up.${weightSentence(refusal.heaviest)} Share less of it with \`${SPACE_EXCLUDE_COMMAND} <path>\`, or remove the large files, then sync again.`;
27276
27602
  if (!refusal.spaceHasWorkToGive)
27277
27603
  return nothingWritten;
27278
27604
  return `${nothingWritten} The space's own newer work is still waiting for this folder and arrives on the first sync that is not refused.`;
@@ -27280,7 +27606,7 @@ function ceilingMessage(refusal) {
27280
27606
  const commits = refusal.pendingCommits === 1 ? "Its commit is" : `Its ${refusal.pendingCommits} commits are`;
27281
27607
  const alreadyWritten = `This push carries ${sizes}. ${commits} already in this folder's space history, so removing files now adds another commit and takes nothing away. What has to start over is that history: it lives at ${refusal.historyPath}, outside your folder, and neither your files nor your own git repository are part of it.`;
27282
27608
  const bothWays = refusal.spaceHasWorkToGive ? " Until the push lands, this folder cannot take the space's newer work either, because the space combines the two sides when a push arrives." : "";
27283
- const exit = ` Start it over with \`runwork spaces reset ${refusal.folder}\`: this machine's copy of that history is discarded and the next sync rebuilds it from the space. Those commits never reached the space, so they go with it; your own files stay where they are.`;
27609
+ const exit = ` Start it over with \`${SPACE_RESET_COMMAND} ${refusal.folder}\`: this machine's copy of that history is discarded and the next sync rebuilds it from the space. Those commits never reached the space, so they go with it; your own files stay where they are.`;
27284
27610
  return `${alreadyWritten}${bothWays}${exit}`;
27285
27611
  }
27286
27612
  async function resolveOid(folder, ref) {
@@ -27388,7 +27714,7 @@ async function localVersionsToKeep(folder, commit) {
27388
27714
  }
27389
27715
  return keep;
27390
27716
  }
27391
- async function ceilingCheck(input, limits, plan) {
27717
+ async function ceilingCheck(input, limits, plan, now) {
27392
27718
  const local = await resolveOid(input.folder, LOCAL_MAIN);
27393
27719
  const remote = await resolveOid(input.folder, REMOTE_MAIN);
27394
27720
  const common = local && remote ? await mergeBase(input.folder, local, remote) : null;
@@ -27399,7 +27725,9 @@ async function ceilingCheck(input, limits, plan) {
27399
27725
  if (isUnreadableStorage(input.spaceStorage))
27400
27726
  return { decision, refusal: null, unmeasured: input.spaceStorage.unreadable };
27401
27727
  const pending = local && (decision === "push" || decision === "push-then-fast-forward") ? await unpushedCommits(input.folder, local, common) : [];
27402
- const bytesToPush = pending.reduce((sum, c) => sum + c.bytes, 0) + plan.addedBytes;
27728
+ const staging = splitPlannedChanges(plan.changes, { settle: input.settle, shareNow: input.shareNow, auto: input.auto, now }).staging;
27729
+ const addingBytes = staging.reduce((sum, change) => change.kind === "remove" ? sum : sum + change.bytes, 0);
27730
+ const bytesToPush = pending.reduce((sum, c) => sum + c.bytes, 0) + addingBytes;
27403
27731
  const remainingBytes = spaceRemainingBytes(input.spaceStorage, limits.spaceBytes);
27404
27732
  if (!pushWouldExceed(remainingBytes, bytesToPush))
27405
27733
  return nothingToSay;
@@ -27412,7 +27740,7 @@ async function ceilingCheck(input, limits, plan) {
27412
27740
  bytesToPush,
27413
27741
  pendingCommits: pending.length,
27414
27742
  historyPath: spaceGitdir(input.folder),
27415
- heaviest: pending.length === 0 ? heaviestShares(plan.changes) : [],
27743
+ heaviest: pending.length === 0 ? heaviestShares(staging) : [],
27416
27744
  spaceHasWorkToGive: decision === "fast-forward" || decision === "checkout-remote" || decision === "push-then-fast-forward"
27417
27745
  }
27418
27746
  };
@@ -27420,11 +27748,13 @@ async function ceilingCheck(input, limits, plan) {
27420
27748
  async function syncSpaceFolder(input) {
27421
27749
  const limits = input.limits ?? DEFAULT_SPACE_REPO_LIMITS;
27422
27750
  const platform9 = input.platform ?? hostPathPlatform();
27751
+ const now = input.now ?? new Date;
27423
27752
  const onAuth = spaceRepoAuth(input.apiKey);
27424
27753
  const base = {
27425
27754
  folder: input.folder,
27426
27755
  decision: "nothing",
27427
27756
  initialized: false,
27757
+ historySetAside: null,
27428
27758
  commit: { committed: false, commits: [], added: [], removed: [], skipped: [], settling: [], held: [], heldNewestMtimeMs: null, heldSummary: noWaiting(), addedBytes: 0 },
27429
27759
  pushed: false,
27430
27760
  batches: 0,
@@ -27439,7 +27769,9 @@ async function syncSpaceFolder(input) {
27439
27769
  };
27440
27770
  let measured = null;
27441
27771
  try {
27442
- base.initialized = (await ensureSpaceRepo(input.folder, input.spaceRemoteUrl)).created;
27772
+ const ensured = await ensureSpaceRepo(input.folder, input.spaceRemoteUrl);
27773
+ base.initialized = ensured.created;
27774
+ base.historySetAside = ensured.setAside;
27443
27775
  base.skippedForPlatform = [...platformSkippedPaths(input.folder, platform9)];
27444
27776
  let fetchFailure = null;
27445
27777
  try {
@@ -27452,7 +27784,7 @@ async function syncSpaceFolder(input) {
27452
27784
  const plan = firstFromSpace ? await dropAlreadyInSpace(input.folder, walked, REMOTE_MAIN) : walked;
27453
27785
  measured = plan;
27454
27786
  const holding = input.auto !== true && !askedToShare(input);
27455
- const ceiling = fetchFailure || holding ? { decision: "nothing", refusal: null, unmeasured: null } : await ceilingCheck(input, limits, plan);
27787
+ const ceiling = fetchFailure || holding ? { decision: "nothing", refusal: null, unmeasured: null } : await ceilingCheck(input, limits, plan, now);
27456
27788
  const refused = ceiling.unmeasured !== null ? unmeasuredMessage(ceiling.unmeasured) : ceiling.refusal ? ceilingMessage(ceiling.refusal) : null;
27457
27789
  if (refused) {
27458
27790
  base.commit = { committed: false, commits: [], added: [], removed: [], skipped: plan.skipped, settling: [], held: [], heldNewestMtimeMs: null, heldSummary: noWaiting(), addedBytes: plan.addedBytes };
@@ -27470,7 +27802,7 @@ async function syncSpaceFolder(input) {
27470
27802
  settle: input.settle,
27471
27803
  shareNow: input.shareNow,
27472
27804
  auto: input.auto,
27473
- now: input.now
27805
+ now
27474
27806
  });
27475
27807
  if (fetchFailure)
27476
27808
  throw fetchFailure;
@@ -27540,7 +27872,7 @@ async function syncSpaceFolder(input) {
27540
27872
  base.error = describeError(err);
27541
27873
  return base;
27542
27874
  } finally {
27543
- rememberWaitingChanges(input.folder, measured, base, { auto: input.auto, now: input.now });
27875
+ rememberWaitingChanges(input.folder, measured, base, { auto: input.auto, now });
27544
27876
  }
27545
27877
  }
27546
27878
  function rememberWaitingChanges(folder, plan, result, rules) {
@@ -27829,11 +28161,13 @@ function scopeOfMappedFolder(folder, opts = {}) {
27829
28161
  const ignoreSets = buildSpaceIgnoreSets(folder);
27830
28162
  const rules = readIncludeRules(folder);
27831
28163
  const shares = at ? () => true : (rel) => rel === SPACE_INCLUDE_FILE || isIncluded(rel, rules);
28164
+ const hidden = hiddenPredicate(ignoreSets, rules);
28165
+ const outOfSight = at ? hidden : (rel) => hidden(rel) || sharesNothingUnder(rel, rules);
27832
28166
  const groups = new Map;
27833
28167
  let files = 0;
27834
28168
  let bytes = 0;
27835
28169
  const groupPrefix = at ? `${at}/` : "";
27836
- countUnder(at ? join60(folder, at) : folder, folder, hiddenPredicate(ignoreSets, rules), shares, {
28170
+ countUnder(at ? join60(folder, at) : folder, folder, outOfSight, shares, {
27837
28171
  maxDepth: Number.POSITIVE_INFINITY,
27838
28172
  budget: { left: Number.POSITIVE_INFINITY },
27839
28173
  followLinks: false,
@@ -27852,6 +28186,32 @@ function scopeOfMappedFolder(folder, opts = {}) {
27852
28186
  const largest = [...groups.values()].sort((a, b) => b.files - a.files || a.name.localeCompare(b.name)).slice(0, limit);
27853
28187
  return { folder, files, bytes, largest };
27854
28188
  }
28189
+ function holdsMoreFilesThan(folder, max) {
28190
+ const hidden = hiddenPredicate(buildSpaceIgnoreSets(folder), readIncludeRules(folder));
28191
+ let found = 0;
28192
+ const walk = (dir) => {
28193
+ for (const name of safeReadDir(dir)) {
28194
+ const full = join60(dir, name);
28195
+ const rel = relative8(folder, full).split(sep9).join("/");
28196
+ if (hidden(rel))
28197
+ continue;
28198
+ if (weigh(full, false).directory) {
28199
+ if (walk(full))
28200
+ return true;
28201
+ continue;
28202
+ }
28203
+ found += 1;
28204
+ if (found > max)
28205
+ return true;
28206
+ }
28207
+ return false;
28208
+ };
28209
+ return walk(folder);
28210
+ }
28211
+ function topLevelSpaceEntries(folder) {
28212
+ const hidden = hiddenPredicate(buildSpaceIgnoreSets(folder), readIncludeRules(folder));
28213
+ return safeReadDir(folder).filter((name) => !hidden(name)).sort();
28214
+ }
27855
28215
  function safeReadDir(dir) {
27856
28216
  try {
27857
28217
  return readdirSync25(dir);
@@ -27881,6 +28241,10 @@ function measure(path2) {
27881
28241
  // ../../shared/spaces/map-scope.ts
27882
28242
  var MAP_SCOPE_MAX_FILES = 1e4;
27883
28243
  var MAP_SCOPE_MAX_BYTES = 500 * 1024 * 1024;
28244
+ var MAP_SEED_MAX_FILES = 5000;
28245
+ function mapSeededNothingSentence() {
28246
+ return `This folder holds more than ${MAP_SEED_MAX_FILES.toLocaleString("en-US")} files, so nothing is shared until you pick what to share.`;
28247
+ }
27884
28248
  function isOverMapScope(scope) {
27885
28249
  return scope.files > MAP_SCOPE_MAX_FILES || scope.bytes > MAP_SCOPE_MAX_BYTES;
27886
28250
  }
@@ -27926,6 +28290,10 @@ function mapScopeWarning(scope) {
27926
28290
  example: mapScopeIncludeExample(scope)
27927
28291
  };
27928
28292
  }
28293
+ function historySetAsideSentence(setAside) {
28294
+ const where = setAside.trash ? ` at ${setAside.trash}` : "";
28295
+ return `This folder's earlier history belonged to another space and was set aside${where} (${formatBytes(setAside.bytes)}). Your files were not touched.`;
28296
+ }
27929
28297
 
27930
28298
  // src/commands/spaces.ts
27931
28299
  init_remote();
@@ -28033,8 +28401,12 @@ function lockPath2() {
28033
28401
  return join61(runworkDir(), "sync.lock");
28034
28402
  }
28035
28403
  var STALE_LOCK_MS = 5 * 60 * 1000;
28036
- var DEFAULT_WAIT_MS = 30000;
28404
+ var HEARTBEAT_MS = 30000;
28405
+ var POLL_START_MS = 250;
28406
+ var POLL_MAX_MS = 5000;
28407
+ var WAIT_NOTICE_MS = 15000;
28037
28408
  var exitHandlerRegistered = false;
28409
+ var heartbeat = null;
28038
28410
  function ensureExitHandler() {
28039
28411
  if (exitHandlerRegistered)
28040
28412
  return;
@@ -28056,12 +28428,21 @@ function readLock() {
28056
28428
  return null;
28057
28429
  }
28058
28430
  }
28431
+ function heardFrom(existing) {
28432
+ return existing.refreshedAt ?? existing.startedAt;
28433
+ }
28434
+ function holderAlive(existing) {
28435
+ if (Date.now() - heardFrom(existing) > STALE_LOCK_MS)
28436
+ return false;
28437
+ return isProcessAlive2(existing.pid);
28438
+ }
28059
28439
  function writeLockExclusive() {
28060
28440
  try {
28061
28441
  if (!existsSync61(runworkDir())) {
28062
28442
  mkdirSync34(runworkDir(), { recursive: true });
28063
28443
  }
28064
- writeFileSync32(lockPath2(), JSON.stringify({ pid: process.pid, startedAt: Date.now() }), {
28444
+ const now = Date.now();
28445
+ writeFileSync32(lockPath2(), JSON.stringify({ pid: process.pid, startedAt: now, refreshedAt: now }), {
28065
28446
  flag: "wx"
28066
28447
  });
28067
28448
  return true;
@@ -28069,20 +28450,40 @@ function writeLockExclusive() {
28069
28450
  return false;
28070
28451
  }
28071
28452
  }
28453
+ function startHeartbeat() {
28454
+ if (heartbeat)
28455
+ return;
28456
+ heartbeat = setInterval(() => {
28457
+ const existing = readLock();
28458
+ if (existing?.pid !== process.pid)
28459
+ return;
28460
+ try {
28461
+ writeFileSync32(lockPath2(), JSON.stringify({ ...existing, refreshedAt: Date.now() }));
28462
+ } catch {}
28463
+ }, HEARTBEAT_MS);
28464
+ heartbeat.unref();
28465
+ }
28466
+ function stopHeartbeat() {
28467
+ if (!heartbeat)
28468
+ return;
28469
+ clearInterval(heartbeat);
28470
+ heartbeat = null;
28471
+ }
28072
28472
  function tryAcquireOnce() {
28073
28473
  if (writeLockExclusive()) {
28074
28474
  ensureExitHandler();
28475
+ startHeartbeat();
28075
28476
  return true;
28076
28477
  }
28077
28478
  const existing = readLock();
28078
- const stale = !existing || Date.now() - existing.startedAt > STALE_LOCK_MS || !isProcessAlive2(existing.pid);
28079
- if (!stale)
28479
+ if (existing && holderAlive(existing))
28080
28480
  return false;
28081
28481
  try {
28082
28482
  unlinkSync9(lockPath2());
28083
28483
  } catch {}
28084
28484
  if (writeLockExclusive()) {
28085
28485
  ensureExitHandler();
28486
+ startHeartbeat();
28086
28487
  return true;
28087
28488
  }
28088
28489
  return false;
@@ -28090,20 +28491,34 @@ function tryAcquireOnce() {
28090
28491
  function sleep(ms) {
28091
28492
  return new Promise((resolve8) => setTimeout(resolve8, ms));
28092
28493
  }
28093
- async function acquireSyncLock(waitMs = DEFAULT_WAIT_MS) {
28094
- const deadline = Date.now() + waitMs;
28095
- let delay = 250;
28494
+ function noticeWaiting(pid, waitedMs) {
28495
+ if (waitedMs < WAIT_NOTICE_MS) {
28496
+ console.error(`Another Runwork sync is running on this machine (pid ${pid}). Waiting for it to finish.`);
28497
+ return;
28498
+ }
28499
+ console.error(`Still waiting for that sync, ${Math.round(waitedMs / 1000)}s so far.`);
28500
+ }
28501
+ async function acquireSyncLock() {
28502
+ const startedWaiting = Date.now();
28503
+ let delay = POLL_START_MS;
28504
+ let notices = 0;
28096
28505
  for (;; ) {
28097
28506
  if (tryAcquireOnce())
28098
28507
  return true;
28099
- const remaining = deadline - Date.now();
28100
- if (remaining <= 0)
28508
+ const existing = readLock();
28509
+ if (!existing || !holderAlive(existing))
28101
28510
  return false;
28102
- await sleep(Math.min(delay, remaining));
28103
- delay = Math.min(delay * 2, 5000);
28511
+ const waitedMs = Date.now() - startedWaiting;
28512
+ if (waitedMs >= notices * WAIT_NOTICE_MS) {
28513
+ noticeWaiting(existing.pid, waitedMs);
28514
+ notices += 1;
28515
+ }
28516
+ await sleep(delay);
28517
+ delay = Math.min(delay * 2, POLL_MAX_MS);
28104
28518
  }
28105
28519
  }
28106
28520
  function releaseSyncLock() {
28521
+ stopHeartbeat();
28107
28522
  const existing = readLock();
28108
28523
  if (existing?.pid === process.pid) {
28109
28524
  try {
@@ -28353,6 +28768,7 @@ function failedFolder(folder, err) {
28353
28768
  folder,
28354
28769
  decision: "nothing",
28355
28770
  initialized: false,
28771
+ historySetAside: null,
28356
28772
  commit: { committed: false, commits: [], added: [], removed: [], skipped: [], settling: [], held: [], heldNewestMtimeMs: null, heldSummary: noWaiting(), addedBytes: 0 },
28357
28773
  pushed: false,
28358
28774
  batches: 0,
@@ -28434,7 +28850,12 @@ spacesCommand.command("suggest").description("Propose spaces and things to keep
28434
28850
  saveCensusStore(store);
28435
28851
  const records = Object.fromEntries([...candidates.keys()].map((key) => [key, store.sessions[key]]).filter(([, r]) => Boolean(r)));
28436
28852
  const knownSkills = opts.offline ? null : await fetchKnownSkills();
28437
- const census = buildAssetCensus({ records, knownSkills, mappedFolders: mappedFolderIndex(loadSpaceMappings().mappings) });
28853
+ const census = buildAssetCensus({
28854
+ records,
28855
+ knownSkills,
28856
+ mappedFolders: mappedFolderIndex(loadSpaceMappings().mappings),
28857
+ liveActivity: liveFolderActivity(listing.conversations)
28858
+ });
28438
28859
  if (json) {
28439
28860
  jsonOut({ captured, days, census });
28440
28861
  return;
@@ -28536,6 +28957,13 @@ spacesCommand.command("map <folder>").description("Keep a folder in a space: eve
28536
28957
  writeFileSync33(localList, fromSpace);
28537
28958
  }
28538
28959
  }
28960
+ const narrowed = !existsSync64(localList) && holdsMoreFilesThan(target, MAP_SEED_MAX_FILES) ? mapSeededNothingSentence() : null;
28961
+ if (narrowed) {
28962
+ let seeded = readIncludeRules(target);
28963
+ for (const entry of topLevelSpaceEntries(target))
28964
+ seeded = excludePath(seeded, entry);
28965
+ writeIncludeRules(target, seeded);
28966
+ }
28539
28967
  if (existsSync64(localList)) {
28540
28968
  excludeFromTheirGit(target, SPACE_INCLUDE_FILE);
28541
28969
  }
@@ -28574,7 +29002,7 @@ spacesCommand.command("map <folder>").description("Keep a folder in a space: eve
28574
29002
  writeSpaceMarker(mapping);
28575
29003
  excludeFromTheirGit(target, ".runwork/");
28576
29004
  if (!json) {
28577
- console.log(dim(mapScopeWarning(scope).count));
29005
+ console.log(dim(narrowed ?? mapScopeWarning(scope).count));
28578
29006
  if (overScope && !auto) {
28579
29007
  console.log(dim(`${waitingScopeSentence(scope)}, and none of it leaves this computer until you share it.`));
28580
29008
  }
@@ -28591,7 +29019,7 @@ spacesCommand.command("map <folder>").description("Keep a folder in a space: eve
28591
29019
  }
28592
29020
  const problem = firstSyncProblem(firstSync, firstSyncError);
28593
29021
  if (json) {
28594
- jsonOut({ mapped: mapping, path: spaceMappingsPath(), refused: false, scope, createdFolder, adoptedInstructions: adopted, firstSync, firstSyncError, firstSyncOk: problem === null, firstSyncProblem: problem });
29022
+ jsonOut({ mapped: mapping, path: spaceMappingsPath(), refused: false, scope, createdFolder, adoptedInstructions: adopted, narrowed, firstSync, firstSyncError, firstSyncOk: problem === null, firstSyncProblem: problem });
28595
29023
  if (problem)
28596
29024
  process.exitCode = MAP_FIRST_SYNC_FAILED;
28597
29025
  return;
@@ -28600,6 +29028,9 @@ spacesCommand.command("map <folder>").description("Keep a folder in a space: eve
28600
29028
  console.log(dim(`Created ${mapping.folder}.`));
28601
29029
  console.log(green(`Mapped ${mapping.folder} to ${spaceDisplayName(space)}.`));
28602
29030
  const outcome = firstSync?.results[0];
29031
+ if (outcome?.historySetAside) {
29032
+ console.log(dim(` ${historySetAsideSentence(outcome.historySetAside)}`));
29033
+ }
28603
29034
  if (outcome && !problem) {
28604
29035
  console.log(dim(` ${describeFolderSync(outcome)}`));
28605
29036
  const kept = outcome.keptLocalCopies;
@@ -28693,9 +29124,6 @@ function writeIncludeRules(folder, rules) {
28693
29124
  writeFileSync33(file, existing === null ? formatIncludeFile(rules) : updateIncludeFile(existing, rules));
28694
29125
  excludeFromTheirGit(folder, SPACE_INCLUDE_FILE);
28695
29126
  }
28696
- function spaceReposRoot() {
28697
- return join64(homedir39(), ".runwork", "space-repos");
28698
- }
28699
29127
  var LOCAL_MAIN_REF = `refs/heads/${SPACE_BRANCH}`;
28700
29128
  var REMOTE_MAIN_REF = `refs/remotes/${RUNWORK_REMOTE}/${SPACE_BRANCH}`;
28701
29129
  async function readSpaceRef(gitdir, ref) {
@@ -28780,35 +29208,6 @@ function describeOrphan(repo) {
28780
29208
  const state = repo.state === "unpushed" ? red(`holds ${repo.unpushedCommits || "some"} commit(s) the space does not have`) : repo.state === "in-sync" ? green("the space has all of it") : repo.state === "behind" ? green("the space has moved past it") : dim("never committed anything");
28781
29209
  return ` ${repo.name} ${dim(formatBytes(repo.bytes))} ${state}${repo.spaceId ? dim(` ${repo.spaceId}`) : ""}`;
28782
29210
  }
28783
- function directoryWeight(dir) {
28784
- let bytes = 0;
28785
- let files = 0;
28786
- const pending = [dir];
28787
- while (pending.length > 0) {
28788
- const current = pending.pop();
28789
- let entries;
28790
- try {
28791
- entries = readdirSync26(current, { withFileTypes: true });
28792
- } catch {
28793
- continue;
28794
- }
28795
- for (const entry of entries) {
28796
- const full = join64(current, entry.name);
28797
- if (entry.isDirectory()) {
28798
- pending.push(full);
28799
- continue;
28800
- }
28801
- try {
28802
- const stats = lstatSync4(full);
28803
- if (stats.isFile()) {
28804
- bytes += stats.size;
28805
- files++;
28806
- }
28807
- } catch {}
28808
- }
28809
- }
28810
- return { bytes, files };
28811
- }
28812
29211
  async function readStdinBytes() {
28813
29212
  if (process.stdin.isTTY)
28814
29213
  return null;
@@ -29356,7 +29755,7 @@ async function underSyncLock(json, run) {
29356
29755
  const acquired = await acquireSyncLock();
29357
29756
  if (!acquired) {
29358
29757
  if (!json)
29359
- console.log(" Another sync appears to be in progress on this machine; proceeding anyway.");
29758
+ console.log(" This machine's sync lock could not be taken, and nothing is holding it; proceeding anyway.");
29360
29759
  await run();
29361
29760
  return;
29362
29761
  }
@@ -29567,12 +29966,24 @@ spacesCommand.command("reset <folder>").description("Throw away this machine's c
29567
29966
  process.exit(1);
29568
29967
  }
29569
29968
  const gitdir = spaceGitdir(found.folder);
29570
- const root = join64(homedir39(), ".runwork", "space-repos");
29571
- if (dirname18(gitdir) !== root) {
29969
+ const root = spaceReposRoot();
29970
+ if (!isSpaceHistoryPath(gitdir)) {
29572
29971
  console.error(`Error: refusing to remove ${gitdir}. A space history is a folder directly under ${root}, and this is not one.`);
29573
29972
  process.exit(1);
29574
29973
  }
29575
- if (!existsSync64(gitdir)) {
29974
+ if (opts.yes !== true && !json && existsSync64(gitdir)) {
29975
+ const confirmed = await promptConfirm(`Throw away ${formatBytes(directoryWeight(gitdir).bytes)} of ${found.mapping.spaceName} history for ${found.folder}? Your files stay where they are.`);
29976
+ if (!confirmed) {
29977
+ console.log("Cancelled.");
29978
+ return;
29979
+ }
29980
+ }
29981
+ const discarded = discardSpaceHistory(found.folder, `runwork spaces reset ${found.folder}`);
29982
+ if (discarded.state === "outside") {
29983
+ console.error(`Error: refusing to remove ${discarded.gitdir}. A space history is a folder directly under ${discarded.root}, and this is not one.`);
29984
+ process.exit(1);
29985
+ }
29986
+ if (discarded.state === "none") {
29576
29987
  if (json) {
29577
29988
  jsonOut({ folder: found.folder, spaceId: found.mapping.spaceId, spaceName: found.mapping.spaceName, gitdir, removed: false, bytes: 0, files: 0, trash: null });
29578
29989
  return;
@@ -29580,25 +29991,15 @@ spacesCommand.command("reset <folder>").description("Throw away this machine's c
29580
29991
  console.log(dim(`${found.folder} has no space history on this machine. The next sync builds one: runwork spaces sync ${found.folder}`));
29581
29992
  return;
29582
29993
  }
29583
- const weight = directoryWeight(gitdir);
29584
- if (opts.yes !== true && !json) {
29585
- const confirmed = await promptConfirm(`Throw away ${formatBytes(weight.bytes)} of ${found.mapping.spaceName} history for ${found.folder}? Your files stay where they are.`);
29586
- if (!confirmed) {
29587
- console.log("Cancelled.");
29588
- return;
29589
- }
29590
- }
29591
- const trashed = moveToTrash(gitdir, `runwork spaces reset ${found.folder}`);
29592
- if (trashed === null) {
29593
- console.error(`Error: ${gitdir} could not be moved to ${join64(homedir39(), ".runwork", "trash")}, so nothing was removed.`);
29994
+ if (discarded.state === "stuck") {
29995
+ console.error(`Error: ${gitdir} could not be moved to ${discarded.trashRoot}, so nothing was removed.`);
29594
29996
  process.exit(1);
29595
29997
  }
29596
- forgetWaitingCache(found.folder);
29597
29998
  if (json) {
29598
- jsonOut({ folder: found.folder, spaceId: found.mapping.spaceId, spaceName: found.mapping.spaceName, gitdir, removed: true, bytes: weight.bytes, files: weight.files, trash: trashed });
29999
+ jsonOut({ folder: found.folder, spaceId: found.mapping.spaceId, spaceName: found.mapping.spaceName, gitdir, removed: true, bytes: discarded.bytes, files: discarded.files, trash: discarded.trash });
29599
30000
  return;
29600
30001
  }
29601
- console.log(green(`${found.folder} starts over with ${found.mapping.spaceName}.`) + dim(` ${formatBytes(weight.bytes)} of history moved to ${trashed}`));
30002
+ console.log(green(`${found.folder} starts over with ${found.mapping.spaceName}.`) + dim(` ${formatBytes(discarded.bytes)} of history moved to ${discarded.trash}`));
29602
30003
  console.log(dim(`Nothing in your folder was touched. Rebuild from the space with: runwork spaces sync ${found.folder}`));
29603
30004
  console.log(dim(`If the pushes were being refused for size, narrow what this folder shares first: runwork spaces files ${found.folder}`));
29604
30005
  });
@@ -29863,9 +30264,8 @@ function routingTable(surface) {
29863
30264
  ];
29864
30265
  }
29865
30266
  var PRODUCT_DEV_EXCEPTION = "**Exception -- product development:** Editing your current codebase, running tests, git operations, and building features in the product you're working on are NOT Runwork territory. Runwork is for infrastructure and team tooling, not the product itself.";
29866
- var ROUTING_PROSE = `Save or update a skill with \`save_skill\`, not local SKILL.md files. Store structured data with \`entity_{Name}\` tools, not SQLite or JSON files. Schedule recurring jobs as Runwork schedules (\`schedule_{name}\`), not OS cron. Run multi-step processes as Runwork workflows (\`workflow_{name}\`), not ad-hoc scripts. Call third-party services (Slack, HubSpot, Gmail, Google) through \`{id}_api\` tools, not raw \`fetch\`. ${SPACES_ROUTING_SENTENCE} Store other files in Runwork file storage, not the local filesystem. Expose public APIs as Runwork endpoints (\`endpoint_*\`), not standalone servers.`;
29867
30267
  function buildRunworkInstructions(ctx) {
29868
- const { workspaceName, workspaceId, workspaceSlug, inventory, surface = "machine", form = "full" } = ctx;
30268
+ const { workspaceName, workspaceId, workspaceSlug, inventory, surface = "machine" } = ctx;
29869
30269
  const dashboardUrl = workspaceSlug ? `https://${workspaceSlug}.runwork.ai` : "https://runwork.ai";
29870
30270
  const workspaceIdLine = `Workspace ID: \`${workspaceId}\`${workspaceSlug ? ` | Dashboard: ${dashboardUrl}` : ""}`;
29871
30271
  const connection = surface === "mcp" ? `You are connected to the **${workspaceName}** Runwork workspace` : `This machine is connected to the **${workspaceName}** Runwork workspace`;
@@ -29878,11 +30278,7 @@ function buildRunworkInstructions(ctx) {
29878
30278
  workspaceIdLine,
29879
30279
  ""
29880
30280
  ];
29881
- if (form === "compact") {
29882
- lines.push(`**Default to Runwork -- team sync beats local files.** ${ROUTING_PROSE} These sync across every teammate's AI agents; local alternatives only work for you.`, "", PRODUCT_DEV_EXCEPTION, "", `**Available now:** ${buildInventoryLine(inventory)}`, "", `Runwork MCP tools are always connected -- use them for data access, integration API calls, skill management, and resource discovery.${integrationToolHint} Web dashboard: ${dashboardUrl}. Invoke the \`runwork\` skill for the full capability reference, or run \`runwork doctor\` to check setup.`, ...buildPersonaBlock(ctx.persona));
29883
- } else {
29884
- lines.push("**Default to Runwork -- team sync beats local files.** When the user asks for any of the following, use the Runwork tool shown. These operations sync across every teammate's AI agents; local alternatives only work for the current user.", "", ...routingTable(surface), "", ...spacesInstructionBlock(surface), "", PRODUCT_DEV_EXCEPTION, "", `**Available now:** ${buildInventoryLine(inventory)}`, "", `Runwork MCP tools are always connected -- use them for data access, integration API calls, skill management, and resource discovery.${integrationToolHint} Use \`runwork\` CLI to create (\`runwork init\`), develop (\`runwork dev\`), and deploy (\`runwork deploy\`) apps. Install CLI: \`curl -fsSL https://runwork.ai/install.sh | sh\`. Web dashboard: ${dashboardUrl}. Invoke the \`runwork\` skill for full capability reference.`, ...buildPersonaBlock(ctx.persona));
29885
- }
30281
+ lines.push("**Default to Runwork -- team sync beats local files.** When the user asks for any of the following, use the Runwork tool shown. These operations sync across every teammate's AI agents; local alternatives only work for the current user.", "", ...routingTable(surface), "", ...spacesInstructionBlock(surface), "", PRODUCT_DEV_EXCEPTION, "", `**Available now:** ${buildInventoryLine(inventory)}`, "", `Runwork MCP tools are always connected -- use them for data access, integration API calls, skill management, and resource discovery.${integrationToolHint} Use \`runwork\` CLI to create (\`runwork init\`), develop (\`runwork dev\`), and deploy (\`runwork deploy\`) apps. Install CLI: \`curl -fsSL https://runwork.ai/install.sh | sh\`. Web dashboard: ${dashboardUrl}. Invoke the \`runwork\` skill for full capability reference, or run \`runwork doctor\` to check setup.`, ...buildPersonaBlock(ctx.persona));
29886
30282
  return lines.join(`
29887
30283
  `);
29888
30284
  }
@@ -30229,15 +30625,14 @@ function generateIntroSkill(ctx) {
30229
30625
  description: "TRIGGER when user asks to: save/create/share/update a skill, store or persist data, schedule a recurring job, run a workflow or pipeline, call Slack/HubSpot/Gmail/any third-party integration, upload or persist a file, share team knowledge or expertise, connect AI tools via MCP, expose a public API, or build a team app or internal tool. Runwork is the team's shared infrastructure platform -- these operations sync across every teammate's AI agents, while local files only work for the current user. Always invoke BEFORE reaching for local files, native cron, SQLite, raw fetch calls, or one-off scripts. Features: skills, MCP servers, entities, file storage, schedules, workflows, integrations, agents, public endpoints, components. Interact via MCP tools (always available), CLI (`runwork`), web dashboard, desktop app, or API."
30230
30626
  };
30231
30627
  }
30232
- function generateInstructionHint(ctx, form = "full") {
30628
+ function generateInstructionHint(ctx) {
30233
30629
  const body = buildRunworkInstructions({
30234
30630
  workspaceName: ctx.workspaceName,
30235
30631
  workspaceId: ctx.workspaceId,
30236
30632
  workspaceSlug: ctx.workspaceSlug,
30237
30633
  inventory: toRunworkInventory(ctx),
30238
30634
  persona: ctx.persona,
30239
- surface: "machine",
30240
- form
30635
+ surface: "machine"
30241
30636
  });
30242
30637
  return [
30243
30638
  "<!-- runwork:start -->",
@@ -30288,7 +30683,7 @@ function readSetupExtras(workspaceId) {
30288
30683
  }
30289
30684
  return {};
30290
30685
  }
30291
- var instructionsCommand = new Command17("instructions").description("Print the Runwork instruction block sync writes into each agent's config").option("--workspace <name-or-id>", "Workspace name or ID").option("--form <full|compact>", "Which rendering to print (default: full)", "full").action(async (opts, command) => {
30686
+ var instructionsCommand = new Command17("instructions").description("Print the Runwork instruction block sync writes into each agent's config").option("--workspace <name-or-id>", "Workspace name or ID").action(async (opts, command) => {
30292
30687
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
30293
30688
  const credentials = requireAuth();
30294
30689
  const client = new ApiClient(credentials);
@@ -30300,18 +30695,16 @@ var instructionsCommand = new Command17("instructions").description("Print the R
30300
30695
  workspaceSlug: extras.workspaceSlug,
30301
30696
  persona: extras.persona
30302
30697
  });
30303
- const full = generateInstructionHint(ctx, "full");
30304
- const compact = generateInstructionHint(ctx, "compact");
30698
+ const block = generateInstructionHint(ctx);
30305
30699
  if (useJson) {
30306
30700
  jsonOut({
30307
30701
  workspaceId,
30308
30702
  workspaceName: ctx.workspaceName,
30309
- [CLAUDE_ACCOUNT_PROFILE_FIELDS.full]: full,
30310
- [CLAUDE_ACCOUNT_PROFILE_FIELDS.compact]: compact
30703
+ [CLAUDE_ACCOUNT_PROFILE_FIELD]: block
30311
30704
  });
30312
30705
  return;
30313
30706
  }
30314
- process.stdout.write((opts.form === "compact" ? compact : full) + `
30707
+ process.stdout.write(block + `
30315
30708
  `);
30316
30709
  });
30317
30710
 
@@ -32978,7 +33371,7 @@ function ensureWorkspacePointer(state, statePath2, credentials) {
32978
33371
  async function syncFromState(state, statePath2, credentials, opts) {
32979
33372
  const acquired = await acquireSyncLock();
32980
33373
  if (!acquired) {
32981
- console.log(" Another sync appears to be in progress on this machine; proceeding anyway.");
33374
+ console.log(" This machine's sync lock could not be taken, and nothing is holding it; proceeding anyway.");
32982
33375
  await runSyncFromState(state, statePath2, credentials, opts);
32983
33376
  return;
32984
33377
  }
@@ -35798,6 +36191,7 @@ function buildCapturePlan(input) {
35798
36191
  const home2 = input.homeDir.replace(/[/\\]+$/, "");
35799
36192
  const rw = (...parts) => join75(home2, ".runwork", ...parts);
35800
36193
  const steps = [];
36194
+ const storePathsInput = { platform: input.platform, homeDir: home2, sep: sep12, appData: input.appData, localAppData: input.localAppData };
35801
36195
  steps.push({ id: "env", kind: "env", names: ENV_NAMES, note: "process environment (proxy credentials stripped)" });
35802
36196
  for (const [id, file] of [
35803
36197
  ["runwork.setup", "setup.json"],
@@ -35818,13 +36212,28 @@ function buildCapturePlan(input) {
35818
36212
  steps.push({ id, kind: "stat", path: rw(file), note: "size and mtime only: contains conversation text" });
35819
36213
  }
35820
36214
  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.waiting", kind: "census", path: rw("waiting"), depth: 1, maxEntries: CENSUS_MAX_ENTRIES, note: "size and mtime only: each holds a mapped folder's own file names" }, { id: "runwork.log", kind: "file", path: rw("desktop-launch.log"), maxBytes: LOG_TAIL_BYTES, tail: true, note: "tail: this log has no rotation" });
35821
- const storeInput = { platform: input.platform, homeDir: home2, sep: sep12, appData: input.appData, localAppData: input.localAppData };
36215
+ desktopStateDirs(storePathsInput).forEach((dir, i) => {
36216
+ steps.push({
36217
+ id: i === 0 ? "desktop.app-state" : `desktop.app-state.alt${i - 1}`,
36218
+ kind: "file",
36219
+ path: join75(dir, "app-state.json"),
36220
+ maxBytes: CONFIG_MAX_BYTES,
36221
+ note: "desktop state: onboarding, updater, the last vendor account-write refusal"
36222
+ });
36223
+ steps.push({
36224
+ id: i === 0 ? "desktop.window-state" : `desktop.window-state.alt${i - 1}`,
36225
+ kind: "file",
36226
+ path: join75(dir, ".window-state.json"),
36227
+ maxBytes: CONFIG_MAX_BYTES,
36228
+ note: "window geometry: size, position, maximized, fullscreen"
36229
+ });
36230
+ });
35822
36231
  for (const target of CAPTURE_TARGETS) {
35823
36232
  const note = target.note ? { note: target.note } : {};
35824
- steps.push(target.kind === "file" ? { id: target.id, kind: "file", path: target.path(storeInput), maxBytes: target.maxBytes ?? CONFIG_MAX_BYTES, ...note } : { id: target.id, kind: "census", path: target.path(storeInput), depth: 1, maxEntries: CENSUS_MAX_ENTRIES, ...note });
36233
+ steps.push(target.kind === "file" ? { id: target.id, kind: "file", path: target.path(storePathsInput), maxBytes: target.maxBytes ?? CONFIG_MAX_BYTES, ...note } : { id: target.id, kind: "census", path: target.path(storePathsInput), depth: 1, maxEntries: CENSUS_MAX_ENTRIES, ...note });
35825
36234
  }
35826
36235
  steps.push({ id: "agents.skills", kind: "census", path: join75(home2, ".agents", "skills"), depth: 1, maxEntries: CENSUS_MAX_ENTRIES });
35827
- for (const store of resolveTranscriptStores(storeInput)) {
36236
+ for (const store of resolveTranscriptStores(storePathsInput)) {
35828
36237
  const id = `stores.${store.def.id.replace(/\//g, ".")}`;
35829
36238
  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 } : {} });
35830
36239
  for (const parent of parentsWorthProbing(store.path, sep12)) {