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.
- package/dist/index.js +596 -187
- 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
|
-
|
|
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
|
|
4855
|
-
|
|
4856
|
-
|
|
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 (
|
|
4868
|
-
|
|
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
|
-
|
|
4902
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
5092
|
-
{ id: "fileStorage", singular: "file storage", plural: "File storage", tab: "
|
|
5093
|
-
{ id: "component", singular: "component", plural: "Components", tab: "
|
|
5094
|
-
{ id: "schedule", singular: "scheduled job", plural: "Scheduled work", tab: "
|
|
5095
|
-
{ id: "workflow", singular: "workflow", plural: "Workflows", tab: "
|
|
5096
|
-
{ id: "endpoint", singular: "public endpoint", plural: "Endpoints", tab: "
|
|
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),
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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.
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28094
|
-
|
|
28095
|
-
|
|
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
|
|
28100
|
-
if (
|
|
28508
|
+
const existing = readLock();
|
|
28509
|
+
if (!existing || !holderAlive(existing))
|
|
28101
28510
|
return false;
|
|
28102
|
-
|
|
28103
|
-
|
|
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({
|
|
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("
|
|
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 =
|
|
29571
|
-
if (
|
|
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
|
-
|
|
29584
|
-
|
|
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:
|
|
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(
|
|
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"
|
|
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
|
-
|
|
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
|
|
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").
|
|
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
|
|
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
|
-
[
|
|
30310
|
-
[CLAUDE_ACCOUNT_PROFILE_FIELDS.compact]: compact
|
|
30703
|
+
[CLAUDE_ACCOUNT_PROFILE_FIELD]: block
|
|
30311
30704
|
});
|
|
30312
30705
|
return;
|
|
30313
30706
|
}
|
|
30314
|
-
process.stdout.write(
|
|
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("
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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)) {
|