@krapht/ork 1.0.0-alpha.15 → 1.0.0-alpha.16
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/README.md +10 -4
- package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-DcRdK0Du.mjs} +25 -6
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CPespxlQ.mjs → attach-7lB5_jpr.mjs} +5 -5
- package/dist/{attach-BHO3X3cf.mjs → attach-B-FeE1dJ.mjs} +46 -8
- package/dist/bin.mjs +238 -369
- package/dist/{brief-za0ozN_E.mjs → brief-BvMtIFio.mjs} +4 -4
- package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-BRSOObAv.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion-CQ2UYKuJ.mjs → completion-Cgb2qPPh.mjs} +45 -6
- package/dist/{context-BNqDw9z1.mjs → context-BN9A2Nnu.mjs} +3 -3
- package/dist/{find-BYLlszqj.mjs → find-mLYsR0uC.mjs} +2 -2
- package/dist/flags-CdgIH1L6.mjs +230 -0
- package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DxCsOby1.mjs} +59 -24
- package/dist/{format-C8MsFj8_.mjs → format-hiDvtNGM.mjs} +17 -4
- package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
- package/dist/{in-a-run-Bp0k0J09.mjs → in-a-run-a447hxq8.mjs} +1 -1
- package/dist/{install-DWtXTq4y.mjs → install-23g_1ngs.mjs} +1 -1
- package/dist/{limits-B1qzS4cX.mjs → limits-C_Xz_t3C.mjs} +1 -1
- package/dist/{mcp-BOrVkyhN.mjs → mcp-CWng9I9n.mjs} +6 -6
- package/dist/{once-C1fL3GVo.mjs → once-lvwcWfVf.mjs} +3 -3
- package/dist/{pause-DOMK-R8V.mjs → pause-Cy11KrXK.mjs} +3 -3
- package/dist/{post-BzA6Qobq.mjs → post-C9soCx7i.mjs} +5 -3
- package/dist/print-BaWiWf4v.mjs +19 -0
- package/dist/project-records-BRiyQzUo.mjs +230 -0
- package/dist/{relay-OWuu4fib.mjs → relay-C0M1TWMB.mjs} +1 -1
- package/dist/{run--4-1xAF2.mjs → run-B-j3Qxqc.mjs} +26 -13
- package/dist/{run-CiPsW9Kf.mjs → run-D_u9V_Ve.mjs} +1 -1
- package/dist/{send-CnNNW-ot.mjs → send-qBxj0JbJ.mjs} +2 -2
- package/dist/{src-B9zWIVqo.mjs → src-CXBmaGwS.mjs} +93 -72
- package/dist/{src-DcrhgipG.mjs → src-DHDQ0itC.mjs} +91 -42
- package/dist/{src-Dr_fTSu-.mjs → src-DN-RPsil.mjs} +781 -502
- package/dist/{src-DQKz2eOH.mjs → src-nXLIwDrt.mjs} +9 -12
- package/dist/{store-oKJ-cCf3.mjs → store-Dyr1mP8K.mjs} +1 -1
- package/dist/ui/assets/app-BPSCxb_l.css +1 -0
- package/dist/ui/assets/{app-Drp8Dm9y.js → app-CnNRowFn.js} +40 -40
- package/dist/ui/assets/{index-BtwkftKe.js → index-CVzwYlz3.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CzKlsCnm.mjs → ui-DLrSt5ny.mjs} +3 -3
- package/dist/{usage-DHGNk2JL.mjs → usage-CxmpYYO4.mjs} +61 -107
- package/dist/{validate-Bs_gg22g.mjs → validate-BVrT43GO.mjs} +35 -8
- package/dist/{watch-CCCfw3n7.mjs → watch-mkLm4t9D.mjs} +5 -5
- package/dist/{workflows-CW28ZU-z.mjs → workflows-Bvl2Fquu.mjs} +2 -2
- package/dist/{worktrees-CyE8aF6j.mjs → worktrees-DOOCoS2w.mjs} +49 -20
- package/package.json +8 -4
- package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
|
@@ -66,7 +66,10 @@ const experimentsOn = ({ env = process.env, project, where, registry = EXPERIMEN
|
|
|
66
66
|
/** Unknown names as errors, for the checks every workflow goes through. */
|
|
67
67
|
const experimentDiagnostics = (options) => {
|
|
68
68
|
const state = experimentsOn(options);
|
|
69
|
-
|
|
69
|
+
if (state.ok) return [];
|
|
70
|
+
const at = options?.at;
|
|
71
|
+
const inProject = options?.project?.experimental ?? [];
|
|
72
|
+
return state.unknown.map(({ name, source, message }) => source === "project" && at !== void 0 ? Object.assign(error("experiment.unknown", `names an unknown experiment \`${name}\`; known: ${knownExperiments(options?.registry)}`, []), at(["experimental", inProject.indexOf(name)])) : error("experiment.unknown", message, []));
|
|
70
73
|
};
|
|
71
74
|
// ../integrations/src/integration.ts
|
|
72
75
|
/** Declares an integration with its option types checked. */
|
|
@@ -563,9 +566,11 @@ const IsolationSchema = z.enum([
|
|
|
563
566
|
"shared",
|
|
564
567
|
"readonly"
|
|
565
568
|
]);
|
|
566
|
-
const
|
|
569
|
+
const MONEY_MESSAGE = "must be a number above 0";
|
|
570
|
+
const TOKENS_MESSAGE = "must be a whole number above 0";
|
|
571
|
+
const BudgetSchema = z.number(MONEY_MESSAGE).positive(MONEY_MESSAGE);
|
|
567
572
|
/** A cap in tokens: input, output and cache-write; cache reads don't count. */
|
|
568
|
-
const TokenBudgetSchema = z.number().int().positive();
|
|
573
|
+
const TokenBudgetSchema = z.number(TOKENS_MESSAGE).int(TOKENS_MESSAGE).positive(TOKENS_MESSAGE);
|
|
569
574
|
/** Caps across runs (RFC-007), in Claude Code's estimated dollars. */
|
|
570
575
|
const QuotasSchema = z.strictObject({
|
|
571
576
|
dayUsd: BudgetSchema.optional().describe("Most to spend in a calendar day."),
|
|
@@ -573,7 +578,7 @@ const QuotasSchema = z.strictObject({
|
|
|
573
578
|
weekUsd: BudgetSchema.optional().describe("Most to spend in a calendar week, from Monday."),
|
|
574
579
|
monthUsd: BudgetSchema.optional().describe("Most to spend in a calendar month.")
|
|
575
580
|
});
|
|
576
|
-
const UNITS = {
|
|
581
|
+
const UNITS$1 = {
|
|
577
582
|
s: 1e3,
|
|
578
583
|
m: 6e4,
|
|
579
584
|
h: 36e5
|
|
@@ -583,7 +588,7 @@ const timeoutMs = (timeout) => {
|
|
|
583
588
|
const match = /^(\d+)([smh])$/u.exec(timeout ?? "");
|
|
584
589
|
if (match === null) return void 0;
|
|
585
590
|
const [, amount = "0", unit = "s"] = match;
|
|
586
|
-
return Number(amount) * (UNITS[unit] ?? 1e3);
|
|
591
|
+
return Number(amount) * (UNITS$1[unit] ?? 1e3);
|
|
587
592
|
};
|
|
588
593
|
/** The most a timer can hold; a longer one fires at once. */
|
|
589
594
|
const LONGEST_MS = 2 ** 31 - 1;
|
|
@@ -1282,6 +1287,8 @@ const newerVersion = (issues, raw) => {
|
|
|
1282
1287
|
* @file Workflow and project YAML: parsing with source positions for
|
|
1283
1288
|
* diagnostics, and the workflow's byte-stable canonical serializer.
|
|
1284
1289
|
*/
|
|
1290
|
+
/** The message, led by `file:line:col: ` when it is in another file. */
|
|
1291
|
+
const diagnosticText = (d) => d.file === void 0 ? d.message : `${d.file}:${d.line ?? 1}:${d.column ?? 1}: ${d.message}`;
|
|
1285
1292
|
const SCHEMA_URL = "https://raw.githubusercontent.com/mimshins/ork/main/packages/schema/workflow.schema.json";
|
|
1286
1293
|
const createLocate = (doc, counter) => (path) => {
|
|
1287
1294
|
for (let depth = path.length; depth >= 0; depth -= 1) {
|
|
@@ -3133,6 +3140,12 @@ const BUILTIN_AGENTS = {
|
|
|
3133
3140
|
* reached through several folders counts once, under its real path. Reads,
|
|
3134
3141
|
* never writes.
|
|
3135
3142
|
*/
|
|
3143
|
+
/** Where an agent was found: lowest to highest precedence. */
|
|
3144
|
+
const AGENT_SOURCES = [
|
|
3145
|
+
"builtin",
|
|
3146
|
+
"user",
|
|
3147
|
+
"project"
|
|
3148
|
+
];
|
|
3136
3149
|
const hashOf = (...parts) => createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 12);
|
|
3137
3150
|
const isDir = (target) => {
|
|
3138
3151
|
try {
|
|
@@ -4959,6 +4972,8 @@ const RETRY = "This may pass: `ork resume` tries again.";
|
|
|
4959
4972
|
const usd$1 = (n) => `$${Number.isInteger(n) ? String(n) : n.toFixed(2)}`;
|
|
4960
4973
|
const paren = (value) => value === void 0 ? "" : ` (${value})`;
|
|
4961
4974
|
const colon = (reason) => reason === void 0 ? "" : `: ${reason}`;
|
|
4975
|
+
/** A cap as `used N of M`, or `M` alone when the spend is not known. */
|
|
4976
|
+
const usedOf = (cap, used) => used === void 0 ? cap : `used ${used} of ${cap}`;
|
|
4962
4977
|
const info = (label, actor, message, hint, flags = {}) => ({
|
|
4963
4978
|
label,
|
|
4964
4979
|
message,
|
|
@@ -4975,7 +4990,7 @@ const END_KINDS = {
|
|
|
4975
4990
|
no_report: info("No report", "agent", () => "ended without handing in a report", "Check that its brief says what to report; its session is in the Logs panel."),
|
|
4976
4991
|
invalid_report: info("Invalid report", "agent", (f) => `handed in a report that breaks its declared output${colon(f.reason)}`, "Check that its brief asks for every field its `output.schema` declares."),
|
|
4977
4992
|
turn_limit: info("Turn limit", "workflow", (f) => `reached its turn limit${paren(f.maxTurns === void 0 ? void 0 : String(f.maxTurns))}`, "Raise its `maxTurns`, or give it a smaller task."),
|
|
4978
|
-
budget_limit: info("Budget limit", "workflow", (f) => `reached its budget${paren([...f.budgetUsd === void 0 ? [] : [usd$1(f.budgetUsd)], ...f.budgetTokens === void 0 ? [] : [`${String(f.budgetTokens)} tokens`]].join(", ") || void 0)}`, "Raise `budgetUsd` or `budgetTokens` on the node, or the same in the workflow's `limits`, then `ork resume`."),
|
|
4993
|
+
budget_limit: info("Budget limit", "workflow", (f) => `reached its budget${paren([...f.budgetUsd === void 0 ? [] : [usedOf(usd$1(f.budgetUsd), f.usedUsd === void 0 ? void 0 : usd$1(f.usedUsd))], ...f.budgetTokens === void 0 ? [] : [`${usedOf(String(f.budgetTokens), f.usedTokens === void 0 ? void 0 : String(f.usedTokens))} tokens`]].join(", ") || void 0)}`, "Raise `budgetUsd` or `budgetTokens` on the node, or the same in the workflow's `limits`, then `ork resume`."),
|
|
4979
4994
|
timed_out: info("Timed out", "workflow", (f) => `reached its timeout${paren(f.timeout)}`, "Raise its `timeout`, or give it a smaller task."),
|
|
4980
4995
|
context_too_long: info("Context too long", "workflow", () => "outgrew its model's context", "Give it a smaller task or less context, or a model with a larger context."),
|
|
4981
4996
|
plan_limit: info("Plan limit", "account", (f) => `ran into a usage limit${paren(f.window?.replaceAll("_", " "))}`, `Wait until the limit resets. ${RETRY}`, {
|
|
@@ -5386,18 +5401,25 @@ const loadProject = (projectRoot, file = path.join(projectRoot, PROJECT_FILE)) =
|
|
|
5386
5401
|
if (!existsSync(file)) return { diagnostics: [] };
|
|
5387
5402
|
const parsed = parseProject(readFileSync(file, "utf8"));
|
|
5388
5403
|
const label = shownPath(file, projectRoot);
|
|
5389
|
-
if (parsed.ok)
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5404
|
+
if (parsed.ok) {
|
|
5405
|
+
const at = (key) => ({
|
|
5406
|
+
file: label,
|
|
5407
|
+
...parsed.locate(key)
|
|
5408
|
+
});
|
|
5409
|
+
return {
|
|
5410
|
+
project: parsed.project,
|
|
5411
|
+
diagnostics: [],
|
|
5412
|
+
where: (key) => {
|
|
5413
|
+
const position = parsed.locate(key);
|
|
5414
|
+
return position === null ? label : `${label}:${position.line}:${position.column}`;
|
|
5415
|
+
},
|
|
5416
|
+
at
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
return { diagnostics: parsed.diagnostics.map((d) => Object.assign(error(d.code, d.message, []), { file: label }, d.line === void 0 ? {} : {
|
|
5420
|
+
line: d.line,
|
|
5421
|
+
column: d.column ?? 1
|
|
5422
|
+
})) };
|
|
5401
5423
|
};
|
|
5402
5424
|
/** Where the user's own settings live. */
|
|
5403
5425
|
const USER_SETTINGS_FILE = path.join(".ork", "settings.yaml");
|
|
@@ -5813,6 +5835,7 @@ const runTree = (events) => {
|
|
|
5813
5835
|
}
|
|
5814
5836
|
node.state = "running";
|
|
5815
5837
|
node.runs += 1;
|
|
5838
|
+
if (event.description !== void 0) node.description = event.description;
|
|
5816
5839
|
} else if (event.type === "node.held") {
|
|
5817
5840
|
const node = nodes.get(event.id);
|
|
5818
5841
|
if (node !== void 0) node.held = true;
|
|
@@ -6426,6 +6449,7 @@ const followUpProblem = (input) => {
|
|
|
6426
6449
|
"after-deploy"
|
|
6427
6450
|
].includes(input.due) && !date) return "`due` is now, after-merge, after-deploy or a YYYY-MM-DD date";
|
|
6428
6451
|
if (input.kind === "message" && input.draft === void 0) return "a message needs its `draft` (to, channel, text), which the maintainer sends";
|
|
6452
|
+
if (input.steps?.some((step) => step.text.trim() === "") === true) return "each step needs its text";
|
|
6429
6453
|
return null;
|
|
6430
6454
|
};
|
|
6431
6455
|
/** The project a `project:<name>` follow-up is for, or `undefined`. */
|
|
@@ -6757,6 +6781,19 @@ port: (node, cwd) => {
|
|
|
6757
6781
|
return result;
|
|
6758
6782
|
} };
|
|
6759
6783
|
} });
|
|
6784
|
+
// ../core/src/follow-ups/types.ts
|
|
6785
|
+
const FOLLOW_UP_KINDS = [
|
|
6786
|
+
"change",
|
|
6787
|
+
"message",
|
|
6788
|
+
"task",
|
|
6789
|
+
"check"
|
|
6790
|
+
];
|
|
6791
|
+
const FOLLOW_UP_STATUSES = [
|
|
6792
|
+
"waiting",
|
|
6793
|
+
"due",
|
|
6794
|
+
"done",
|
|
6795
|
+
"dropped"
|
|
6796
|
+
];
|
|
6760
6797
|
// ../core/src/project/mcp-file.ts
|
|
6761
6798
|
/**
|
|
6762
6799
|
* @file The project's `.mcp.json`, the file Claude Code reads too, so a server
|
|
@@ -7006,7 +7043,17 @@ const itemNodeOf = (node) => {
|
|
|
7006
7043
|
const { forEach, ...item } = node;
|
|
7007
7044
|
return item;
|
|
7008
7045
|
};
|
|
7046
|
+
/**
|
|
7047
|
+
* What any node reports when its `timeout` expires; known on every type, since
|
|
7048
|
+
* the timeout may come from `defaults` or the project.
|
|
7049
|
+
*/
|
|
7050
|
+
const TIMED_OUT_FIELD = "timedOut";
|
|
7051
|
+
/** The fields a node reports, or null when they can't be known ahead. */
|
|
7009
7052
|
const outputFieldsOf = (node) => {
|
|
7053
|
+
const fields = typeFieldsOf(node);
|
|
7054
|
+
return fields === null ? null : [...fields, TIMED_OUT_FIELD];
|
|
7055
|
+
};
|
|
7056
|
+
const typeFieldsOf = (node) => {
|
|
7010
7057
|
if (forEachOf$1(node) !== void 0) return GATHERED_FIELDS;
|
|
7011
7058
|
if (isToolNode(node)) return [
|
|
7012
7059
|
...TOOL_OUTPUT_FIELDS,
|
|
@@ -7984,6 +8031,21 @@ const checkItemSettings = (workflow, edges) => {
|
|
|
7984
8031
|
const nodes = Object.entries(workflow.nodes).flatMap(([id, node]) => isClaudeNode(node) ? FIELDS$1.flatMap((field) => checkNodeField(workflow, says, id, field)) : []);
|
|
7985
8032
|
return [...defaults, ...nodes];
|
|
7986
8033
|
};
|
|
8034
|
+
// ../core/src/graph/leader-budget.ts
|
|
8035
|
+
/**
|
|
8036
|
+
* @file The `budget.leader` warning: a leader with nodes to delegate to and no
|
|
8037
|
+
* cap of its own under a run budget. Its session sets aside what is left of
|
|
8038
|
+
* the run's budget in that unit, and a leader's reserve is never waited on
|
|
8039
|
+
* (ADR-077), so the nodes it owns are refused. A compiler warning, like
|
|
8040
|
+
* `loop.uncapped`, on the effective workflow: it never stops a run.
|
|
8041
|
+
*/
|
|
8042
|
+
const UNITS = ["budgetUsd", "budgetTokens"];
|
|
8043
|
+
/** One warning per leader and run budget its own cap leaves open. */
|
|
8044
|
+
const leaderBudgetWarnings = (workflow) => Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
8045
|
+
if (node.type !== "agent.leader" || !isClaudeNode(node)) return [];
|
|
8046
|
+
if (!Object.values(workflow.nodes).some((child) => child.parent === id && (child.type === "agent.leader" || child.type === "agent.worker"))) return [];
|
|
8047
|
+
return UNITS.filter((unit) => workflow.limits?.[unit] !== void 0 && (node[unit] ?? workflow.defaults?.[unit]) === void 0).map((unit) => warning("budget.leader", `leader \`${id}\` has no ${unit} of its own, so its session sets aside all that is left of limits.${unit} and the nodes it owns are refused; give it a ${unit}, or set one in defaults`, ["nodes", id]));
|
|
8048
|
+
});
|
|
7987
8049
|
// ../core/src/graph/script.ts
|
|
7988
8050
|
/**
|
|
7989
8051
|
* @file Checks on the programs a workflow declares (RFC-006): a tool node's and
|
|
@@ -8062,7 +8124,11 @@ const modelDriverProblems = (id, node, workflow) => {
|
|
|
8062
8124
|
};
|
|
8063
8125
|
/** Per-type checks that need the rest of the graph. */
|
|
8064
8126
|
const checkNodes = (workflow, edges) => {
|
|
8065
|
-
const diagnostics = [
|
|
8127
|
+
const diagnostics = [
|
|
8128
|
+
...checkScripts(workflow),
|
|
8129
|
+
...checkItemSettings(workflow, edges),
|
|
8130
|
+
...leaderBudgetWarnings(workflow)
|
|
8131
|
+
];
|
|
8066
8132
|
for (const [id, node] of Object.entries(workflow.nodes)) {
|
|
8067
8133
|
const path = ["nodes", id];
|
|
8068
8134
|
const incoming = edges.filter((e) => e.to === id);
|
|
@@ -8702,73 +8768,590 @@ const gitOk = async (cwd, args, env = {}) => {
|
|
|
8702
8768
|
};
|
|
8703
8769
|
/** Output lines without the trailing empty one. */
|
|
8704
8770
|
const lines = (text) => text.split("\n").filter((line) => line !== "");
|
|
8705
|
-
|
|
8771
|
+
/** Gitignored files a session needs as much in a worktree as in the clone. */
|
|
8772
|
+
const LOCAL_AGENT_FILES = ["CLAUDE.local.md", ".claude/settings.local.json"];
|
|
8773
|
+
/** Where the project's instructions, skills and agents live. */
|
|
8774
|
+
const AGENT_PATHSPECS = [
|
|
8775
|
+
":(glob)**/CLAUDE.md",
|
|
8776
|
+
":(glob)**/AGENTS.md",
|
|
8777
|
+
".claude",
|
|
8778
|
+
".agents"
|
|
8779
|
+
];
|
|
8780
|
+
/** Symlinked agent folders (e.g. `.claude/skills -> ../.ai/skills`), as targets. */
|
|
8781
|
+
const symlinkTargets = (root) => {
|
|
8782
|
+
const targets = [];
|
|
8783
|
+
for (const dir of [".claude", ".agents"]) {
|
|
8784
|
+
const base = path.join(root, dir);
|
|
8785
|
+
if (!existsSync(base) || !lstatSync(base).isDirectory()) continue;
|
|
8786
|
+
for (const name of [
|
|
8787
|
+
"skills",
|
|
8788
|
+
"agents",
|
|
8789
|
+
"commands",
|
|
8790
|
+
"rules"
|
|
8791
|
+
]) {
|
|
8792
|
+
const link = path.join(base, name);
|
|
8793
|
+
if (!existsSync(link) || !lstatSync(link).isSymbolicLink()) continue;
|
|
8794
|
+
const target = path.resolve(base, readlinkSync(link));
|
|
8795
|
+
const relative = path.relative(root, target);
|
|
8796
|
+
if (!relative.startsWith("..") && !path.isAbsolute(relative)) targets.push(relative);
|
|
8797
|
+
}
|
|
8798
|
+
}
|
|
8799
|
+
return [...new Set(targets)];
|
|
8800
|
+
};
|
|
8801
|
+
const statusPaths = async (root, pathspecs) => {
|
|
8802
|
+
const out = await gitOk(root, [
|
|
8803
|
+
"status",
|
|
8804
|
+
"--porcelain=v1",
|
|
8805
|
+
"--untracked-files=all",
|
|
8806
|
+
"--",
|
|
8807
|
+
...pathspecs
|
|
8808
|
+
]);
|
|
8809
|
+
return lines(out).map((line) => line.slice(3));
|
|
8810
|
+
};
|
|
8811
|
+
/** The project's git root, head, and what a worktree would be missing. */
|
|
8812
|
+
const inspectProject = async (cwd) => {
|
|
8813
|
+
const top = await git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
8814
|
+
if (top.code !== 0) return null;
|
|
8815
|
+
const root = top.stdout.trim();
|
|
8816
|
+
const head = await git(root, ["rev-parse", "HEAD"]);
|
|
8817
|
+
if (head.code !== 0) return null;
|
|
8818
|
+
const agent = await statusPaths(root, [...AGENT_PATHSPECS, ...symlinkTargets(root)]);
|
|
8819
|
+
const all = await statusPaths(root, ["."]);
|
|
8820
|
+
return {
|
|
8821
|
+
root,
|
|
8822
|
+
head: head.stdout.trim(),
|
|
8823
|
+
dirtyAgentFiles: agent,
|
|
8824
|
+
dirtyOther: all.length - agent.length
|
|
8825
|
+
};
|
|
8826
|
+
};
|
|
8706
8827
|
/**
|
|
8707
|
-
*
|
|
8708
|
-
*
|
|
8709
|
-
*
|
|
8710
|
-
* it. Only children: the `ork` process itself stays at normal priority.
|
|
8828
|
+
* The gitignored files to copy into a new worktree: every local agent file, and
|
|
8829
|
+
* what the repo's `.worktreeinclude` lists (Claude Code's convention). Only
|
|
8830
|
+
* files that are also gitignored qualify, so tracked files are never copied.
|
|
8711
8831
|
*/
|
|
8712
|
-
|
|
8713
|
-
const
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8832
|
+
const localFiles = async (root) => {
|
|
8833
|
+
const include = path.join(root, ".worktreeinclude");
|
|
8834
|
+
const candidates = lines(await gitOk(root, [
|
|
8835
|
+
"ls-files",
|
|
8836
|
+
"--others",
|
|
8837
|
+
"--ignored",
|
|
8838
|
+
...LOCAL_AGENT_FILES.map((f) => `--exclude=${f}`),
|
|
8839
|
+
...existsSync(include) ? [`--exclude-from=${include}`] : []
|
|
8840
|
+
]));
|
|
8841
|
+
if (candidates.length === 0) return [];
|
|
8842
|
+
const ignored = await git(root, [
|
|
8843
|
+
"check-ignore",
|
|
8844
|
+
"--",
|
|
8845
|
+
...candidates
|
|
8846
|
+
]);
|
|
8847
|
+
return lines(ignored.stdout);
|
|
8848
|
+
};
|
|
8849
|
+
const copyInto = (from, to, files) => {
|
|
8850
|
+
const copied = [];
|
|
8851
|
+
for (const file of files) {
|
|
8852
|
+
const target = path.join(to, file);
|
|
8853
|
+
if (existsSync(target) || !existsSync(path.join(from, file))) continue;
|
|
8854
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
8855
|
+
cpSync(path.join(from, file), target, { verbatimSymlinks: true });
|
|
8856
|
+
copied.push(file);
|
|
8857
|
+
}
|
|
8858
|
+
return copied;
|
|
8859
|
+
};
|
|
8718
8860
|
/**
|
|
8719
|
-
*
|
|
8720
|
-
*
|
|
8861
|
+
* Creates a worktree at `path` from `base`, with the local files: on a new
|
|
8862
|
+
* `branch`, or detached when there is none (`commit: never`). An existing
|
|
8863
|
+
* branch is checked out again only when this run created it (`known`): a
|
|
8864
|
+
* resumed run's branch outlives its removed worktree. Any other existing branch
|
|
8865
|
+
* stops it: Ork never takes over a branch it did not create.
|
|
8721
8866
|
*/
|
|
8722
|
-
const
|
|
8867
|
+
const createWorktree = async (options) => {
|
|
8868
|
+
const { root, branch, base } = options;
|
|
8869
|
+
const target = options.path;
|
|
8870
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
8871
|
+
if (branch === void 0) {
|
|
8872
|
+
await gitOk(root, [
|
|
8873
|
+
"worktree",
|
|
8874
|
+
"add",
|
|
8875
|
+
"--detach",
|
|
8876
|
+
target,
|
|
8877
|
+
base
|
|
8878
|
+
]);
|
|
8879
|
+
return {
|
|
8880
|
+
path: target,
|
|
8881
|
+
copied: copyInto(root, target, await localFiles(root)),
|
|
8882
|
+
reused: false
|
|
8883
|
+
};
|
|
8884
|
+
}
|
|
8885
|
+
const exists = (await git(root, [
|
|
8886
|
+
"rev-parse",
|
|
8887
|
+
"--verify",
|
|
8888
|
+
"--quiet",
|
|
8889
|
+
`refs/heads/${branch}`
|
|
8890
|
+
])).code === 0;
|
|
8891
|
+
if (exists && options.known !== true) throw new Error(`branch ${branch} already exists, and this run did not create it; Ork never takes over a branch it did not create`);
|
|
8892
|
+
await gitOk(root, exists ? [
|
|
8893
|
+
"worktree",
|
|
8894
|
+
"add",
|
|
8895
|
+
target,
|
|
8896
|
+
branch
|
|
8897
|
+
] : [
|
|
8898
|
+
"worktree",
|
|
8899
|
+
"add",
|
|
8900
|
+
"-b",
|
|
8901
|
+
branch,
|
|
8902
|
+
target,
|
|
8903
|
+
base
|
|
8904
|
+
]);
|
|
8905
|
+
return {
|
|
8906
|
+
path: target,
|
|
8907
|
+
branch,
|
|
8908
|
+
copied: copyInto(root, target, await localFiles(root)),
|
|
8909
|
+
reused: exists
|
|
8910
|
+
};
|
|
8911
|
+
};
|
|
8912
|
+
/** Copies files that exist in `from` and are missing in the worktree. */
|
|
8913
|
+
const carryFiles = (from, worktree, files) => copyInto(from, worktree, files.filter((f) => !path.isAbsolute(f) && !f.split(/[\\/]/u).includes("..")));
|
|
8914
|
+
/** What git keeps in a worktree's git dir while a rebase, merge or pick runs. */
|
|
8915
|
+
const UNDERWAY = [
|
|
8916
|
+
"rebase-merge",
|
|
8917
|
+
"rebase-apply",
|
|
8918
|
+
"MERGE_HEAD",
|
|
8919
|
+
"CHERRY_PICK_HEAD",
|
|
8920
|
+
"REVERT_HEAD"
|
|
8921
|
+
];
|
|
8922
|
+
/** Whether a rebase, merge, cherry-pick or revert is underway in `worktree`. */
|
|
8923
|
+
const gitOperationUnderway = async (worktree) => {
|
|
8924
|
+
const paths = await gitOk(worktree, ["rev-parse", ...UNDERWAY.flatMap((name) => ["--git-path", name])]);
|
|
8925
|
+
return lines(paths).some((file) => existsSync(path.resolve(worktree, file)));
|
|
8926
|
+
};
|
|
8723
8927
|
/**
|
|
8724
|
-
*
|
|
8725
|
-
*
|
|
8726
|
-
*
|
|
8727
|
-
*
|
|
8928
|
+
* Commits what a session left uncommitted. The repo's hooks run; when they
|
|
8929
|
+
* refuse, the error says why and the worktree keeps the changes. A worktree
|
|
8930
|
+
* mid-rebase (or merge, cherry-pick, revert) is left exactly as it is: its
|
|
8931
|
+
* changes are the operation's to finish, as a conflict resolver's are.
|
|
8728
8932
|
*/
|
|
8729
|
-
const
|
|
8730
|
-
if (
|
|
8731
|
-
const
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
"utility",
|
|
8741
|
-
...niced
|
|
8742
|
-
] : niced;
|
|
8743
|
-
return [first, [...rest, ...args]];
|
|
8933
|
+
const commitLeftovers = async (worktree, message) => {
|
|
8934
|
+
if (await gitOperationUnderway(worktree)) return false;
|
|
8935
|
+
const status = await gitOk(worktree, ["status", "--porcelain"]);
|
|
8936
|
+
if (lines(status).length === 0) return false;
|
|
8937
|
+
await gitOk(worktree, ["add", "-A"]);
|
|
8938
|
+
await gitOk(worktree, [
|
|
8939
|
+
"commit",
|
|
8940
|
+
"-m",
|
|
8941
|
+
message
|
|
8942
|
+
]);
|
|
8943
|
+
return true;
|
|
8744
8944
|
};
|
|
8745
8945
|
/**
|
|
8746
|
-
*
|
|
8747
|
-
*
|
|
8748
|
-
* inherits it. Under `nice` it does nothing: `nice` lowers relative to the
|
|
8749
|
-
* priority it finds, so lowering here too could land first and the two add up.
|
|
8946
|
+
* Moves a worktree's branch up to `branch` when it is behind and has nothing of
|
|
8947
|
+
* its own, e.g. a child's branch after its owner merged it and moved on.
|
|
8750
8948
|
*/
|
|
8751
|
-
const
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
// ../core/src/run/machine.ts
|
|
8949
|
+
const fastForward = async (worktree, branch) => (await git(worktree, [
|
|
8950
|
+
"merge",
|
|
8951
|
+
"--ff-only",
|
|
8952
|
+
"-q",
|
|
8953
|
+
branch
|
|
8954
|
+
])).code === 0;
|
|
8758
8955
|
/**
|
|
8759
|
-
*
|
|
8760
|
-
*
|
|
8761
|
-
* heavy one. A wait is never silent: `slot.waiting` names who holds the
|
|
8762
|
-
* slots, `slot.taken` ends it, and the node's clock (its owners' and the
|
|
8763
|
-
* run's too) stands still meanwhile, so a timeout counts from the slot.
|
|
8956
|
+
* Merges `branch` into the checkout at `into` with `--no-ff`; aborts on
|
|
8957
|
+
* conflict.
|
|
8764
8958
|
*/
|
|
8765
|
-
|
|
8766
|
-
const
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8959
|
+
const mergeBranch = async (into, branch, message) => {
|
|
8960
|
+
const merged = await git(into, [
|
|
8961
|
+
"merge",
|
|
8962
|
+
"--no-ff",
|
|
8963
|
+
"--no-edit",
|
|
8964
|
+
"-m",
|
|
8965
|
+
message,
|
|
8966
|
+
branch
|
|
8967
|
+
]);
|
|
8968
|
+
if (merged.code === 0) return { ok: true };
|
|
8969
|
+
const conflicts = lines((await git(into, [
|
|
8970
|
+
"diff",
|
|
8971
|
+
"--name-only",
|
|
8972
|
+
"--diff-filter=U"
|
|
8973
|
+
])).stdout);
|
|
8974
|
+
await git(into, ["merge", "--abort"]);
|
|
8975
|
+
return {
|
|
8976
|
+
ok: false,
|
|
8977
|
+
conflicts,
|
|
8978
|
+
error: (merged.stdout + merged.stderr).trim().split("\n").at(-1) ?? "the merge failed"
|
|
8979
|
+
};
|
|
8980
|
+
};
|
|
8981
|
+
const removeWorktree = async (root, worktree, force = false) => {
|
|
8982
|
+
await gitOk(root, [
|
|
8983
|
+
"worktree",
|
|
8984
|
+
"remove",
|
|
8985
|
+
...force ? ["--force"] : [],
|
|
8986
|
+
worktree
|
|
8987
|
+
]);
|
|
8988
|
+
};
|
|
8989
|
+
// ../core/src/worktree/clean.ts
|
|
8990
|
+
/**
|
|
8991
|
+
* @file What `ork worktrees clean` and `ork worktrees remove` do with what
|
|
8992
|
+
* list.ts reads: the plan (what goes, what's kept and why), by one rule for
|
|
8993
|
+
* every caller, and carrying it out. The CLI and the Worktrees panel both
|
|
8994
|
+
* plan here, so neither removes what the other would keep.
|
|
8995
|
+
*/
|
|
8996
|
+
/** What names an item for `remove`: its branch, or its worktree's path. */
|
|
8997
|
+
const keyOf = (item) => item.branch ?? item.path ?? `${item.runId}/${item.nodeId}`;
|
|
8998
|
+
/** The reasons no `force` overrides: removing would break something. */
|
|
8999
|
+
const MAIN_CHECKOUT = "checked out in the main checkout";
|
|
9000
|
+
const RUN_GOING = "its run is still going";
|
|
9001
|
+
const NOT_ORKS = "not created by Ork";
|
|
9002
|
+
const kept = (item, skip) => ({
|
|
9003
|
+
item,
|
|
9004
|
+
removeWorktree: false,
|
|
9005
|
+
deleteBranch: false,
|
|
9006
|
+
skip
|
|
9007
|
+
});
|
|
9008
|
+
/** What clean does with one item. */
|
|
9009
|
+
const cleanStep = (item, options) => {
|
|
9010
|
+
if (!item.recorded) return kept(item, NOT_ORKS);
|
|
9011
|
+
if (item.inMainCheckout) return kept(item, MAIN_CHECKOUT);
|
|
9012
|
+
if (item.heldBy !== void 0) return kept(item, `checked out in ${item.heldBy}`);
|
|
9013
|
+
if (options.live?.has(item.runId) === true) return kept(item, RUN_GOING);
|
|
9014
|
+
if (item.dirty && !options.force) return kept(item, "uncommitted changes");
|
|
9015
|
+
if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return kept(item, "commits on no branch");
|
|
9016
|
+
const removable = item.path !== null && !item.missing;
|
|
9017
|
+
if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
|
|
9018
|
+
item,
|
|
9019
|
+
removeWorktree: removable,
|
|
9020
|
+
deleteBranch: false,
|
|
9021
|
+
skip: "branch not merged"
|
|
9022
|
+
};
|
|
9023
|
+
return {
|
|
9024
|
+
item,
|
|
9025
|
+
removeWorktree: removable,
|
|
9026
|
+
deleteBranch: options.branches && item.branch !== void 0
|
|
9027
|
+
};
|
|
9028
|
+
};
|
|
9029
|
+
/** Whether a step drops a worktree whose folder is gone, by its own path. */
|
|
9030
|
+
const stepPrunes = (step) => step.item.missing && step.item.path !== null && step.skip === void 0;
|
|
9031
|
+
/** Whether a step does anything. */
|
|
9032
|
+
const stepActs = (step) => step.removeWorktree || step.deleteBranch || stepPrunes(step);
|
|
9033
|
+
/**
|
|
9034
|
+
* What `ork worktrees clean` would do. A worktree with uncommitted changes, an
|
|
9035
|
+
* unmerged branch, or a detached worktree holding commits no branch has, is
|
|
9036
|
+
* kept unless `force`; one checked out in the main checkout or in a worktree
|
|
9037
|
+
* Ork didn't create, whose run is still going, or an `ork/` branch no run
|
|
9038
|
+
* recorded creating, is kept even then.
|
|
9039
|
+
*/
|
|
9040
|
+
const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => filter.key === void 0 || keyOf(item) === filter.key).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
|
|
9041
|
+
/**
|
|
9042
|
+
* Carries out one step; what it did goes into `done` as it goes. It touches
|
|
9043
|
+
* only its own item: a missing worktree is dropped by its own path (`git
|
|
9044
|
+
* worktree remove`), never by `git worktree prune`, which would drop every
|
|
9045
|
+
* missing worktree in the repository, the user's own included.
|
|
9046
|
+
*/
|
|
9047
|
+
const applyStep = async (root, step, force, done) => {
|
|
9048
|
+
const { item } = step;
|
|
9049
|
+
if (stepPrunes(step) && !step.removeWorktree && item.path !== null) {
|
|
9050
|
+
await gitOk(root, [
|
|
9051
|
+
"worktree",
|
|
9052
|
+
"remove",
|
|
9053
|
+
item.path
|
|
9054
|
+
]);
|
|
9055
|
+
done.push(`pruned ${item.path}`);
|
|
9056
|
+
}
|
|
9057
|
+
if (step.removeWorktree && item.path !== null) {
|
|
9058
|
+
await removeWorktree(root, item.path, force);
|
|
9059
|
+
done.push(`removed ${item.path}`);
|
|
9060
|
+
}
|
|
9061
|
+
if (step.deleteBranch && item.branch !== void 0) {
|
|
9062
|
+
await gitOk(root, [
|
|
9063
|
+
"branch",
|
|
9064
|
+
force ? "-D" : "-d",
|
|
9065
|
+
item.branch
|
|
9066
|
+
]);
|
|
9067
|
+
done.push(`deleted ${item.branch}`);
|
|
9068
|
+
}
|
|
9069
|
+
};
|
|
9070
|
+
/**
|
|
9071
|
+
* Carries out a clean plan, step by step. What it did goes into `done` as it
|
|
9072
|
+
* goes, so a caller still has it when git fails halfway.
|
|
9073
|
+
*/
|
|
9074
|
+
const applyClean = async (root, steps, force, done = []) => {
|
|
9075
|
+
for (const step of steps) await applyStep(root, step, force, done);
|
|
9076
|
+
return done;
|
|
9077
|
+
};
|
|
9078
|
+
// ../core/src/list-filters/tables.ts
|
|
9079
|
+
/**
|
|
9080
|
+
* @file The field tables of the lists whose rows live in core (RFC-017): `ork
|
|
9081
|
+
* ps`, `feedback list`, `follow-ups list`, `decide --list`, `worktrees list`
|
|
9082
|
+
* and `usage`. Each says which flags filter it and what `--ids` prints; each
|
|
9083
|
+
* list's default (follow-ups due and waiting, feedback without the dismissed,
|
|
9084
|
+
* `ps`'s 20 of this project) stays its command's.
|
|
9085
|
+
*/
|
|
9086
|
+
/**
|
|
9087
|
+
* A scope-only field: a registered project's id or root, or `all`; the caller
|
|
9088
|
+
* reads the store for it, and refuses one not registered.
|
|
9089
|
+
*/
|
|
9090
|
+
const projectField = () => ({
|
|
9091
|
+
name: "project",
|
|
9092
|
+
description: "A registered project's id or root, or all (default: the one you are in)",
|
|
9093
|
+
type: "text",
|
|
9094
|
+
all: true
|
|
9095
|
+
});
|
|
9096
|
+
/** What `--status` matches on `ork ps`: the stored status, or the live one. */
|
|
9097
|
+
const RUN_LIST_STATUSES = [
|
|
9098
|
+
"running",
|
|
9099
|
+
"paused",
|
|
9100
|
+
"crashed",
|
|
9101
|
+
"succeeded",
|
|
9102
|
+
"failed",
|
|
9103
|
+
"budget_exhausted",
|
|
9104
|
+
"quota_exhausted",
|
|
9105
|
+
"timed_out",
|
|
9106
|
+
"interrupted"
|
|
9107
|
+
];
|
|
9108
|
+
const runStatusOf = (run) => run.listed ?? run.status;
|
|
9109
|
+
/** `ork ps`. */
|
|
9110
|
+
const runListTable = {
|
|
9111
|
+
fields: [
|
|
9112
|
+
{
|
|
9113
|
+
name: "status",
|
|
9114
|
+
description: `Only runs in this status, or all: ${RUN_LIST_STATUSES.join(", ")}`,
|
|
9115
|
+
type: "enum",
|
|
9116
|
+
values: RUN_LIST_STATUSES,
|
|
9117
|
+
all: true,
|
|
9118
|
+
get: runStatusOf
|
|
9119
|
+
},
|
|
9120
|
+
{
|
|
9121
|
+
name: "workflow",
|
|
9122
|
+
description: "Only this workflow's runs",
|
|
9123
|
+
type: "text",
|
|
9124
|
+
get: (run) => run.workflow
|
|
9125
|
+
},
|
|
9126
|
+
projectField(),
|
|
9127
|
+
{
|
|
9128
|
+
name: "since",
|
|
9129
|
+
description: "Only runs started since an age (24h, 30d, 12w), a day (2026-11-01), or all",
|
|
9130
|
+
type: "since",
|
|
9131
|
+
all: true,
|
|
9132
|
+
get: (run) => run.startedAt
|
|
9133
|
+
},
|
|
9134
|
+
{
|
|
9135
|
+
name: "until",
|
|
9136
|
+
description: "Only runs started on or before this day (2026-11-01)",
|
|
9137
|
+
type: "until",
|
|
9138
|
+
get: (run) => run.startedAt
|
|
9139
|
+
}
|
|
9140
|
+
],
|
|
9141
|
+
id: (run) => run.id
|
|
9142
|
+
};
|
|
9143
|
+
/** A feedback item's statuses, in triage's order. */
|
|
9144
|
+
const FEEDBACK_STATUSES = [
|
|
9145
|
+
"new",
|
|
9146
|
+
"triaged",
|
|
9147
|
+
"accepted",
|
|
9148
|
+
"dismissed",
|
|
9149
|
+
"published",
|
|
9150
|
+
"resolved"
|
|
9151
|
+
];
|
|
9152
|
+
const feedbackStatusOf = (item) => item.status;
|
|
9153
|
+
/** `ork feedback list`. */
|
|
9154
|
+
const feedbackListTable = {
|
|
9155
|
+
fields: [
|
|
9156
|
+
{
|
|
9157
|
+
name: "status",
|
|
9158
|
+
description: `Only items in this status, or all (dismissed too): ${FEEDBACK_STATUSES.join(", ")}`,
|
|
9159
|
+
type: "enum",
|
|
9160
|
+
values: FEEDBACK_STATUSES,
|
|
9161
|
+
all: true,
|
|
9162
|
+
get: feedbackStatusOf
|
|
9163
|
+
},
|
|
9164
|
+
{
|
|
9165
|
+
name: "kind",
|
|
9166
|
+
description: `Only items of this kind: ${FEEDBACK_KINDS.join(", ")}`,
|
|
9167
|
+
type: "enum",
|
|
9168
|
+
values: FEEDBACK_KINDS,
|
|
9169
|
+
get: (item) => item.kind
|
|
9170
|
+
},
|
|
9171
|
+
{
|
|
9172
|
+
name: "severity",
|
|
9173
|
+
description: `Only items of this severity: ${FEEDBACK_SEVERITIES.join(", ")}`,
|
|
9174
|
+
type: "enum",
|
|
9175
|
+
values: FEEDBACK_SEVERITIES,
|
|
9176
|
+
get: (item) => item.severity
|
|
9177
|
+
},
|
|
9178
|
+
{
|
|
9179
|
+
name: "workflow",
|
|
9180
|
+
description: "Only items this workflow's runs reported",
|
|
9181
|
+
type: "text",
|
|
9182
|
+
get: (item) => item.workflows
|
|
9183
|
+
},
|
|
9184
|
+
{
|
|
9185
|
+
name: "run",
|
|
9186
|
+
description: "Only items this run reported",
|
|
9187
|
+
type: "text",
|
|
9188
|
+
get: (item) => item.runs
|
|
9189
|
+
}
|
|
9190
|
+
],
|
|
9191
|
+
id: (item) => item.id
|
|
9192
|
+
};
|
|
9193
|
+
/**
|
|
9194
|
+
* What `--due` matches: when a follow-up comes due; `date` is any with a
|
|
9195
|
+
* `YYYY-MM-DD` date.
|
|
9196
|
+
*/
|
|
9197
|
+
const FOLLOW_UP_DUES = [
|
|
9198
|
+
"now",
|
|
9199
|
+
"after-merge",
|
|
9200
|
+
"after-deploy",
|
|
9201
|
+
"date"
|
|
9202
|
+
];
|
|
9203
|
+
/** `ork follow-ups list`. */
|
|
9204
|
+
const followUpListTable = {
|
|
9205
|
+
fields: [
|
|
9206
|
+
{
|
|
9207
|
+
name: "status",
|
|
9208
|
+
description: `Only follow-ups in this status, or all (default: due and waiting): ${FOLLOW_UP_STATUSES.join(", ")}`,
|
|
9209
|
+
type: "enum",
|
|
9210
|
+
values: FOLLOW_UP_STATUSES,
|
|
9211
|
+
all: true,
|
|
9212
|
+
get: (f) => f.status
|
|
9213
|
+
},
|
|
9214
|
+
{
|
|
9215
|
+
name: "for",
|
|
9216
|
+
description: "Only those for agent, me, project:<name> or person:<name>",
|
|
9217
|
+
type: "for",
|
|
9218
|
+
get: (f) => f.for
|
|
9219
|
+
},
|
|
9220
|
+
{
|
|
9221
|
+
name: "kind",
|
|
9222
|
+
description: `Only follow-ups of this kind: ${FOLLOW_UP_KINDS.join(", ")}`,
|
|
9223
|
+
type: "enum",
|
|
9224
|
+
values: FOLLOW_UP_KINDS,
|
|
9225
|
+
get: (f) => f.kind
|
|
9226
|
+
},
|
|
9227
|
+
{
|
|
9228
|
+
name: "due",
|
|
9229
|
+
description: `Only those due this way: ${FOLLOW_UP_DUES.join(", ")} (any dated one)`,
|
|
9230
|
+
type: "enum",
|
|
9231
|
+
values: FOLLOW_UP_DUES,
|
|
9232
|
+
get: (f) => /^\d{4}-\d{2}-\d{2}$/u.test(f.due) ? "date" : f.due
|
|
9233
|
+
},
|
|
9234
|
+
projectField()
|
|
9235
|
+
],
|
|
9236
|
+
id: (f) => f.id
|
|
9237
|
+
};
|
|
9238
|
+
/** `ork decide --list`: every field scopes the store read. */
|
|
9239
|
+
const decisionListTable = {
|
|
9240
|
+
fields: [
|
|
9241
|
+
{
|
|
9242
|
+
name: "workflow",
|
|
9243
|
+
description: "Only the decisions of this workflow's runs",
|
|
9244
|
+
type: "text"
|
|
9245
|
+
},
|
|
9246
|
+
{
|
|
9247
|
+
name: "run",
|
|
9248
|
+
description: "Only this run's decisions (a run id or a prefix of one)",
|
|
9249
|
+
type: "text"
|
|
9250
|
+
},
|
|
9251
|
+
projectField()
|
|
9252
|
+
],
|
|
9253
|
+
id: (decision) => decision.id
|
|
9254
|
+
};
|
|
9255
|
+
/** `ork worktrees list`; the id is what `ork worktrees remove` takes. */
|
|
9256
|
+
const worktreeListTable = {
|
|
9257
|
+
fields: [{
|
|
9258
|
+
name: "run",
|
|
9259
|
+
description: "Only this run's worktrees",
|
|
9260
|
+
type: "text",
|
|
9261
|
+
get: (item) => item.runId
|
|
9262
|
+
}, projectField()],
|
|
9263
|
+
id: keyOf
|
|
9264
|
+
};
|
|
9265
|
+
/**
|
|
9266
|
+
* `ork usage`: parse only, its values go straight into `store.usage`'s
|
|
9267
|
+
* `UsageFilter`, which filters; its rows have no id.
|
|
9268
|
+
*/
|
|
9269
|
+
const usageListTable = { fields: [
|
|
9270
|
+
{
|
|
9271
|
+
name: "workflow",
|
|
9272
|
+
description: "Only this workflow",
|
|
9273
|
+
type: "text"
|
|
9274
|
+
},
|
|
9275
|
+
projectField(),
|
|
9276
|
+
{
|
|
9277
|
+
name: "since",
|
|
9278
|
+
description: "Whole days counting today (30d, 12w), exact hours (24h), a day (2026-11-01), or all",
|
|
9279
|
+
type: "since",
|
|
9280
|
+
all: true
|
|
9281
|
+
},
|
|
9282
|
+
{
|
|
9283
|
+
name: "until",
|
|
9284
|
+
description: "The last day to include",
|
|
9285
|
+
type: "until"
|
|
9286
|
+
}
|
|
9287
|
+
] };
|
|
9288
|
+
// ../core/src/machine/priority.ts
|
|
9289
|
+
/**
|
|
9290
|
+
* @file Starting a child at the project's priority (`priority:`): `low`, the
|
|
9291
|
+
* default, starts it below Ork's own (under `nice`; on macOS also utility QoS
|
|
9292
|
+
* through `taskpolicy`), so the user's own apps come first; `normal` leaves
|
|
9293
|
+
* it. Only children: the `ork` process itself stays at normal priority.
|
|
9294
|
+
*/
|
|
9295
|
+
/** MacOS's tool for a program's QoS. */
|
|
9296
|
+
const TASKPOLICY = "/usr/sbin/taskpolicy";
|
|
9297
|
+
/** The POSIX tool that starts a program at a lower priority. */
|
|
9298
|
+
const NICE = "/usr/bin/nice";
|
|
9299
|
+
/** How far `nice` lowers a `low` child: the same as `PRIORITY_BELOW_NORMAL`. */
|
|
9300
|
+
const NICE_STEP = String(constants$1.priority.PRIORITY_BELOW_NORMAL);
|
|
9301
|
+
/**
|
|
9302
|
+
* Whether `bin` is a program `spawn` would find, so a wrapper can't hide its
|
|
9303
|
+
* absence.
|
|
9304
|
+
*/
|
|
9305
|
+
const programFound = (bin, env) => bin.includes("/") ? existsSync(bin) : (env["PATH"] ?? "").split(path.delimiter).some((dir) => dir !== "" && existsSync(path.join(dir, bin)));
|
|
9306
|
+
/**
|
|
9307
|
+
* What to spawn for `bin args` at `priority`. For `low`, when the program is
|
|
9308
|
+
* there: under `nice`, so it and what it starts run lower from the first
|
|
9309
|
+
* instruction, and on macOS under utility QoS as well; else the program itself,
|
|
9310
|
+
* which {@link lowerChild} lowers once it has started.
|
|
9311
|
+
*/
|
|
9312
|
+
const startAt = (priority, bin, args, env = process.env) => {
|
|
9313
|
+
if (priority !== "low" || !programFound(bin, env)) return [bin, [...args]];
|
|
9314
|
+
const niced = existsSync(NICE) ? [
|
|
9315
|
+
NICE,
|
|
9316
|
+
"-n",
|
|
9317
|
+
NICE_STEP,
|
|
9318
|
+
bin
|
|
9319
|
+
] : [bin];
|
|
9320
|
+
const [first = bin, ...rest] = process.platform === "darwin" && existsSync(TASKPOLICY) ? [
|
|
9321
|
+
TASKPOLICY,
|
|
9322
|
+
"-c",
|
|
9323
|
+
"utility",
|
|
9324
|
+
...niced
|
|
9325
|
+
] : niced;
|
|
9326
|
+
return [first, [...rest, ...args]];
|
|
9327
|
+
};
|
|
9328
|
+
/**
|
|
9329
|
+
* Lowers a started child's priority for `low` where {@link startAt} had no
|
|
9330
|
+
* `nice` to start it under (Windows), best effort; what it starts later
|
|
9331
|
+
* inherits it. Under `nice` it does nothing: `nice` lowers relative to the
|
|
9332
|
+
* priority it finds, so lowering here too could land first and the two add up.
|
|
9333
|
+
*/
|
|
9334
|
+
const lowerChild = (priority, pid) => {
|
|
9335
|
+
if (priority !== "low" || pid === void 0 || existsSync(NICE)) return;
|
|
9336
|
+
try {
|
|
9337
|
+
setPriority(pid, constants$1.priority.PRIORITY_BELOW_NORMAL);
|
|
9338
|
+
} catch {}
|
|
9339
|
+
};
|
|
9340
|
+
// ../core/src/run/machine.ts
|
|
9341
|
+
/**
|
|
9342
|
+
* @file A node's share of the machine (the `machine` of its context): the
|
|
9343
|
+
* project's priority for its commands, and a machine-wide slot around each
|
|
9344
|
+
* heavy one. A wait is never silent: `slot.waiting` names who holds the
|
|
9345
|
+
* slots, `slot.taken` ends it, and the node's clock (its owners' and the
|
|
9346
|
+
* run's too) stands still meanwhile, so a timeout counts from the slot.
|
|
9347
|
+
*/
|
|
9348
|
+
/** A wait for a slot, as `ork logs`, `ork tree` and the UI say it. */
|
|
9349
|
+
const slotWaitLine = (holders) => holders.length === 0 ? "waiting for a check slot" : `waiting for a check slot, held by ${holders.join(", ")}`;
|
|
9350
|
+
const none = () => {};
|
|
9351
|
+
/** The port for one activation; none for a run that shares nothing. */
|
|
9352
|
+
const machinePort = (options, runId, id, clock) => {
|
|
9353
|
+
const { machine } = options;
|
|
9354
|
+
if (machine === void 0) return void 0;
|
|
8772
9355
|
const { rules, ledger } = machine;
|
|
8773
9356
|
return {
|
|
8774
9357
|
priority: rules.priority,
|
|
@@ -9815,6 +10398,17 @@ const reportOf = (child, result) => {
|
|
|
9815
10398
|
if (result.error !== void 0) report.error = result.error;
|
|
9816
10399
|
return report;
|
|
9817
10400
|
};
|
|
10401
|
+
/** A job with the task's description clipped once; blank is none. */
|
|
10402
|
+
const jobOf = (id, task, template) => {
|
|
10403
|
+
const job = {
|
|
10404
|
+
id,
|
|
10405
|
+
brief: task.brief
|
|
10406
|
+
};
|
|
10407
|
+
if (template !== void 0) job.template = template;
|
|
10408
|
+
const description = task.description?.replaceAll(/[\s\p{Cc}]+/gu, " ").trim().slice(0, 80);
|
|
10409
|
+
if (description !== void 0 && description !== "") job.description = description;
|
|
10410
|
+
return job;
|
|
10411
|
+
};
|
|
9818
10412
|
const createOwnership = (hooks) => {
|
|
9819
10413
|
const { graph, leader } = hooks;
|
|
9820
10414
|
const owner = graph.nodes.get(leader);
|
|
@@ -9848,11 +10442,10 @@ const createOwnership = (hooks) => {
|
|
|
9848
10442
|
return {
|
|
9849
10443
|
reports: await Promise.all(jobs.map(async (job) => {
|
|
9850
10444
|
if (!("brief" in job)) return job;
|
|
9851
|
-
const child =
|
|
9852
|
-
if (job.template !== void 0) child.template = job.template;
|
|
10445
|
+
const { brief, ...child } = job;
|
|
9853
10446
|
const result = await hooks.runChild(child, {
|
|
9854
10447
|
from: leader,
|
|
9855
|
-
text:
|
|
10448
|
+
text: brief
|
|
9856
10449
|
});
|
|
9857
10450
|
return reportOf(job.id, result);
|
|
9858
10451
|
})),
|
|
@@ -9871,10 +10464,7 @@ const createOwnership = (hooks) => {
|
|
|
9871
10464
|
}
|
|
9872
10465
|
if (seen.has(task.child) || hooks.isRunning(task.child)) return rejected(task.child, templates.includes(task.child) && maxWorkers > 0 ? "already running; use spawn_worker for another instance" : "already running");
|
|
9873
10466
|
seen.add(task.child);
|
|
9874
|
-
return
|
|
9875
|
-
id: task.child,
|
|
9876
|
-
brief: task.brief
|
|
9877
|
-
};
|
|
10467
|
+
return jobOf(task.child, task);
|
|
9878
10468
|
});
|
|
9879
10469
|
return runJobs(jobs);
|
|
9880
10470
|
};
|
|
@@ -9893,11 +10483,7 @@ const createOwnership = (hooks) => {
|
|
|
9893
10483
|
if (spawned.length >= maxWorkers) return rejected(task.template, `no workers left to spawn (maxWorkers: ${String(maxWorkers)})`);
|
|
9894
10484
|
const id = nextInstance(task.template);
|
|
9895
10485
|
spawned.push(id);
|
|
9896
|
-
return
|
|
9897
|
-
id,
|
|
9898
|
-
template: task.template,
|
|
9899
|
-
brief: task.brief
|
|
9900
|
-
};
|
|
10486
|
+
return jobOf(id, task, task.template);
|
|
9901
10487
|
});
|
|
9902
10488
|
return runJobs(jobs);
|
|
9903
10489
|
};
|
|
@@ -10350,6 +10936,7 @@ const startedEvent = (activation, nodeType, round, agent) => {
|
|
|
10350
10936
|
round
|
|
10351
10937
|
};
|
|
10352
10938
|
if (activation.brief !== void 0) event.owner = activation.brief.from;
|
|
10939
|
+
if (activation.description !== void 0) event.description = activation.description;
|
|
10353
10940
|
if (agent !== void 0) event.agent = agent;
|
|
10354
10941
|
const { item } = activation;
|
|
10355
10942
|
if (item !== void 0) event.item = {
|
|
@@ -11802,7 +12389,7 @@ const startRun = (options) => {
|
|
|
11802
12389
|
if (stopped !== null) return;
|
|
11803
12390
|
stopped = status;
|
|
11804
12391
|
reasons.push(reason);
|
|
11805
|
-
queue.
|
|
12392
|
+
for (const { id } of queue.splice(0)) summary(id);
|
|
11806
12393
|
};
|
|
11807
12394
|
const enqueue = (id, input) => {
|
|
11808
12395
|
if (stopped !== null) return;
|
|
@@ -12888,7 +13475,8 @@ const checkWorkflowText = (text, { place, workflowDir, registered, home }) => {
|
|
|
12888
13475
|
...loaded.diagnostics,
|
|
12889
13476
|
...experimentDiagnostics({
|
|
12890
13477
|
project: loaded.project,
|
|
12891
|
-
where: loaded.where
|
|
13478
|
+
where: loaded.where,
|
|
13479
|
+
at: loaded.at
|
|
12892
13480
|
}),
|
|
12893
13481
|
...compiled.diagnostics,
|
|
12894
13482
|
...checkCallTargets(parsed.workflow, place, registered),
|
|
@@ -12897,7 +13485,7 @@ const checkWorkflowText = (text, { place, workflowDir, registered, home }) => {
|
|
|
12897
13485
|
projectRoot
|
|
12898
13486
|
})
|
|
12899
13487
|
]) {
|
|
12900
|
-
const position = parsed.locate(diagnostic.path);
|
|
13488
|
+
const position = diagnostic.file === void 0 ? parsed.locate(diagnostic.path) : null;
|
|
12901
13489
|
diagnostics.push(position === null ? diagnostic : {
|
|
12902
13490
|
...diagnostic,
|
|
12903
13491
|
...position
|
|
@@ -13505,7 +14093,7 @@ const explainWorkflowText = (text, file, nodeId, where) => {
|
|
|
13505
14093
|
const first = diagnostics.find((d) => d.severity === "error");
|
|
13506
14094
|
return {
|
|
13507
14095
|
ok: false,
|
|
13508
|
-
error: `The workflow has errors, so it can't be planned${first === void 0 ? "." : `: ${first
|
|
14096
|
+
error: `The workflow has errors, so it can't be planned${first === void 0 ? "." : `: ${diagnosticText(first)}`}`
|
|
13509
14097
|
};
|
|
13510
14098
|
}
|
|
13511
14099
|
const result = explainSession(workflow, graph, nodeId, {
|
|
@@ -15379,333 +15967,115 @@ const parseInputPairs = (pairs) => {
|
|
|
15379
15967
|
// ../core/src/run/replay.ts
|
|
15380
15968
|
const NO_FAN_OUTS = {
|
|
15381
15969
|
lists: /* @__PURE__ */ new Map(),
|
|
15382
|
-
items: /* @__PURE__ */ new Map()
|
|
15383
|
-
};
|
|
15384
|
-
/** Whether an activation's input carries a result this run made anew. */
|
|
15385
|
-
const carries = (fresh, input) => {
|
|
15386
|
-
if (input === void 0) return false;
|
|
15387
|
-
if (fresh.has(input)) return true;
|
|
15388
|
-
const results = input["results"];
|
|
15389
|
-
if (results === null || typeof results !== "object") return false;
|
|
15390
|
-
return Object.values(results).some((value) => value !== null && typeof value === "object" && fresh.has(value));
|
|
15391
|
-
};
|
|
15392
|
-
/**
|
|
15393
|
-
* Hands each node's recorded results back in order, until the node runs anew
|
|
15394
|
-
* once (it failed or never finished last time, or its input is new); from then
|
|
15395
|
-
* on it always runs. A node cut off mid-session continues that session, once.
|
|
15396
|
-
*/
|
|
15397
|
-
const createReplay = (history, cutOff = /* @__PURE__ */ new Map(), mail, earlier, fanOuts = NO_FAN_OUTS) => {
|
|
15398
|
-
const sessions = new Map(cutOff);
|
|
15399
|
-
const used = /* @__PURE__ */ new Map();
|
|
15400
|
-
const anew = /* @__PURE__ */ new Set();
|
|
15401
|
-
const fresh = /* @__PURE__ */ new WeakSet();
|
|
15402
|
-
/** Fanned nodes whose list was resolved anew: their items can't be replayed. */
|
|
15403
|
-
const renewed = /* @__PURE__ */ new Set();
|
|
15404
|
-
const takeItem = (activation, of) => {
|
|
15405
|
-
const recorded = fanOuts.items.get(activation.id)?.get(activation.round ?? 0);
|
|
15406
|
-
if (recorded !== void 0 && !renewed.has(of) && !carries(fresh, activation.input)) return recorded;
|
|
15407
|
-
anew.add(activation.id);
|
|
15408
|
-
return null;
|
|
15409
|
-
};
|
|
15410
|
-
return {
|
|
15411
|
-
take: (activation) => {
|
|
15412
|
-
const { id } = activation;
|
|
15413
|
-
if (activation.brief !== void 0 || anew.has(id)) return null;
|
|
15414
|
-
if (activation.item !== void 0) return takeItem(activation, activation.item.of);
|
|
15415
|
-
const index = used.get(id) ?? 0;
|
|
15416
|
-
const recorded = history.get(id)?.[index];
|
|
15417
|
-
if (recorded === void 0 || carries(fresh, activation.input)) {
|
|
15418
|
-
anew.add(id);
|
|
15419
|
-
return null;
|
|
15420
|
-
}
|
|
15421
|
-
used.set(id, index + 1);
|
|
15422
|
-
return recorded;
|
|
15423
|
-
},
|
|
15424
|
-
ran: (result) => {
|
|
15425
|
-
fresh.add(result.output);
|
|
15426
|
-
},
|
|
15427
|
-
cutOff: (activation) => {
|
|
15428
|
-
const session = activation.brief === void 0 && (activation.item === void 0 || !renewed.has(activation.item.of)) ? sessions.get(activation.id) : void 0;
|
|
15429
|
-
sessions.delete(activation.id);
|
|
15430
|
-
return session;
|
|
15431
|
-
},
|
|
15432
|
-
listed: (id, round, source) => {
|
|
15433
|
-
const list = fanOuts.lists.get(listKey(id, round));
|
|
15434
|
-
const ranAnew = typeof source === "object" && source !== null && fresh.has(source);
|
|
15435
|
-
if (list !== void 0 && !ranAnew) return list;
|
|
15436
|
-
renewed.add(id);
|
|
15437
|
-
return null;
|
|
15438
|
-
},
|
|
15439
|
-
previousSession: ({ id, item, round = 0 }) => item === void 0 || renewed.has(item.of) ? void 0 : [...fanOuts.items.get(id) ?? []].findLast(([before]) => before < round)?.[1].sessionId,
|
|
15440
|
-
lastOutput: (id) => history.get(id)?.at(-1)?.output,
|
|
15441
|
-
kept: () => [...history.keys(), ...fanOuts.items.keys()],
|
|
15442
|
-
...mail === void 0 ? {} : { mail },
|
|
15443
|
-
...earlier === void 0 ? {} : { earlier }
|
|
15444
|
-
};
|
|
15445
|
-
};
|
|
15446
|
-
/**
|
|
15447
|
-
* The run's `HumanChannel`, answering questions about loops out of rounds as
|
|
15448
|
-
* they were answered last time, in order; anything else goes to `human`.
|
|
15449
|
-
*/
|
|
15450
|
-
const replayLoopAnswers = (human, answers) => {
|
|
15451
|
-
const left = [...answers];
|
|
15452
|
-
return {
|
|
15453
|
-
permit: (request) => human.permit(request),
|
|
15454
|
-
...human.settled === void 0 ? {} : { settled: human.settled },
|
|
15455
|
-
ask: (question) => {
|
|
15456
|
-
const index = left.findIndex((a) => a.question === question.question);
|
|
15457
|
-
const [given] = index === -1 ? [] : left.splice(index, 1);
|
|
15458
|
-
return given === void 0 ? human.ask(question) : Promise.resolve(given.answer);
|
|
15459
|
-
}
|
|
15460
|
-
};
|
|
15461
|
-
};
|
|
15462
|
-
// ../core/src/store/drafts.ts
|
|
15463
|
-
const rowOf = (row) => {
|
|
15464
|
-
const baseHash = text(row, "base_hash");
|
|
15465
|
-
return {
|
|
15466
|
-
project: text(row, "project"),
|
|
15467
|
-
workflow: text(row, "workflow"),
|
|
15468
|
-
text: text(row, "text"),
|
|
15469
|
-
baseHash: baseHash === "" ? null : baseHash,
|
|
15470
|
-
at: text(row, "at")
|
|
15471
|
-
};
|
|
15472
|
-
};
|
|
15473
|
-
const draftQueries = (db) => {
|
|
15474
|
-
const put = db.prepare("INSERT OR REPLACE INTO workflow_drafts (project, workflow, text, base_hash, at) VALUES (?, ?, ?, ?, ?)");
|
|
15475
|
-
const one = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? AND workflow = ?");
|
|
15476
|
-
const all = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? ORDER BY workflow");
|
|
15477
|
-
const drop = db.prepare("DELETE FROM workflow_drafts WHERE project = ? AND workflow = ?");
|
|
15478
|
-
return {
|
|
15479
|
-
putDraft: (draft) => {
|
|
15480
|
-
put.run(draft.project, draft.workflow, draft.text, draft.baseHash, draft.at);
|
|
15481
|
-
},
|
|
15482
|
-
draft: (project, workflow) => {
|
|
15483
|
-
const row = one.get(project, workflow);
|
|
15484
|
-
return row === void 0 ? void 0 : rowOf(row);
|
|
15485
|
-
},
|
|
15486
|
-
drafts: (project) => all.all(project).map((row) => rowOf(row)),
|
|
15487
|
-
dropDraft: (project, workflow) => {
|
|
15488
|
-
drop.run(project, workflow);
|
|
15489
|
-
}
|
|
15490
|
-
};
|
|
15491
|
-
};
|
|
15492
|
-
/** Gitignored files a session needs as much in a worktree as in the clone. */
|
|
15493
|
-
const LOCAL_AGENT_FILES = ["CLAUDE.local.md", ".claude/settings.local.json"];
|
|
15494
|
-
/** Where the project's instructions, skills and agents live. */
|
|
15495
|
-
const AGENT_PATHSPECS = [
|
|
15496
|
-
":(glob)**/CLAUDE.md",
|
|
15497
|
-
":(glob)**/AGENTS.md",
|
|
15498
|
-
".claude",
|
|
15499
|
-
".agents"
|
|
15500
|
-
];
|
|
15501
|
-
/** Symlinked agent folders (e.g. `.claude/skills -> ../.ai/skills`), as targets. */
|
|
15502
|
-
const symlinkTargets = (root) => {
|
|
15503
|
-
const targets = [];
|
|
15504
|
-
for (const dir of [".claude", ".agents"]) {
|
|
15505
|
-
const base = path.join(root, dir);
|
|
15506
|
-
if (!existsSync(base) || !lstatSync(base).isDirectory()) continue;
|
|
15507
|
-
for (const name of [
|
|
15508
|
-
"skills",
|
|
15509
|
-
"agents",
|
|
15510
|
-
"commands",
|
|
15511
|
-
"rules"
|
|
15512
|
-
]) {
|
|
15513
|
-
const link = path.join(base, name);
|
|
15514
|
-
if (!existsSync(link) || !lstatSync(link).isSymbolicLink()) continue;
|
|
15515
|
-
const target = path.resolve(base, readlinkSync(link));
|
|
15516
|
-
const relative = path.relative(root, target);
|
|
15517
|
-
if (!relative.startsWith("..") && !path.isAbsolute(relative)) targets.push(relative);
|
|
15518
|
-
}
|
|
15519
|
-
}
|
|
15520
|
-
return [...new Set(targets)];
|
|
15521
|
-
};
|
|
15522
|
-
const statusPaths = async (root, pathspecs) => {
|
|
15523
|
-
const out = await gitOk(root, [
|
|
15524
|
-
"status",
|
|
15525
|
-
"--porcelain=v1",
|
|
15526
|
-
"--untracked-files=all",
|
|
15527
|
-
"--",
|
|
15528
|
-
...pathspecs
|
|
15529
|
-
]);
|
|
15530
|
-
return lines(out).map((line) => line.slice(3));
|
|
15531
|
-
};
|
|
15532
|
-
/** The project's git root, head, and what a worktree would be missing. */
|
|
15533
|
-
const inspectProject = async (cwd) => {
|
|
15534
|
-
const top = await git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
15535
|
-
if (top.code !== 0) return null;
|
|
15536
|
-
const root = top.stdout.trim();
|
|
15537
|
-
const head = await git(root, ["rev-parse", "HEAD"]);
|
|
15538
|
-
if (head.code !== 0) return null;
|
|
15539
|
-
const agent = await statusPaths(root, [...AGENT_PATHSPECS, ...symlinkTargets(root)]);
|
|
15540
|
-
const all = await statusPaths(root, ["."]);
|
|
15541
|
-
return {
|
|
15542
|
-
root,
|
|
15543
|
-
head: head.stdout.trim(),
|
|
15544
|
-
dirtyAgentFiles: agent,
|
|
15545
|
-
dirtyOther: all.length - agent.length
|
|
15546
|
-
};
|
|
15547
|
-
};
|
|
15548
|
-
/**
|
|
15549
|
-
* The gitignored files to copy into a new worktree: every local agent file, and
|
|
15550
|
-
* what the repo's `.worktreeinclude` lists (Claude Code's convention). Only
|
|
15551
|
-
* files that are also gitignored qualify, so tracked files are never copied.
|
|
15552
|
-
*/
|
|
15553
|
-
const localFiles = async (root) => {
|
|
15554
|
-
const include = path.join(root, ".worktreeinclude");
|
|
15555
|
-
const candidates = lines(await gitOk(root, [
|
|
15556
|
-
"ls-files",
|
|
15557
|
-
"--others",
|
|
15558
|
-
"--ignored",
|
|
15559
|
-
...LOCAL_AGENT_FILES.map((f) => `--exclude=${f}`),
|
|
15560
|
-
...existsSync(include) ? [`--exclude-from=${include}`] : []
|
|
15561
|
-
]));
|
|
15562
|
-
if (candidates.length === 0) return [];
|
|
15563
|
-
const ignored = await git(root, [
|
|
15564
|
-
"check-ignore",
|
|
15565
|
-
"--",
|
|
15566
|
-
...candidates
|
|
15567
|
-
]);
|
|
15568
|
-
return lines(ignored.stdout);
|
|
15569
|
-
};
|
|
15570
|
-
const copyInto = (from, to, files) => {
|
|
15571
|
-
const copied = [];
|
|
15572
|
-
for (const file of files) {
|
|
15573
|
-
const target = path.join(to, file);
|
|
15574
|
-
if (existsSync(target) || !existsSync(path.join(from, file))) continue;
|
|
15575
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
15576
|
-
cpSync(path.join(from, file), target, { verbatimSymlinks: true });
|
|
15577
|
-
copied.push(file);
|
|
15578
|
-
}
|
|
15579
|
-
return copied;
|
|
15580
|
-
};
|
|
15581
|
-
/**
|
|
15582
|
-
* Creates a worktree at `path` from `base`, with the local files: on a new
|
|
15583
|
-
* `branch`, or detached when there is none (`commit: never`). An existing
|
|
15584
|
-
* branch is checked out again only when this run created it (`known`): a
|
|
15585
|
-
* resumed run's branch outlives its removed worktree. Any other existing branch
|
|
15586
|
-
* stops it: Ork never takes over a branch it did not create.
|
|
15587
|
-
*/
|
|
15588
|
-
const createWorktree = async (options) => {
|
|
15589
|
-
const { root, branch, base } = options;
|
|
15590
|
-
const target = options.path;
|
|
15591
|
-
mkdirSync(path.dirname(target), { recursive: true });
|
|
15592
|
-
if (branch === void 0) {
|
|
15593
|
-
await gitOk(root, [
|
|
15594
|
-
"worktree",
|
|
15595
|
-
"add",
|
|
15596
|
-
"--detach",
|
|
15597
|
-
target,
|
|
15598
|
-
base
|
|
15599
|
-
]);
|
|
15600
|
-
return {
|
|
15601
|
-
path: target,
|
|
15602
|
-
copied: copyInto(root, target, await localFiles(root)),
|
|
15603
|
-
reused: false
|
|
15604
|
-
};
|
|
15605
|
-
}
|
|
15606
|
-
const exists = (await git(root, [
|
|
15607
|
-
"rev-parse",
|
|
15608
|
-
"--verify",
|
|
15609
|
-
"--quiet",
|
|
15610
|
-
`refs/heads/${branch}`
|
|
15611
|
-
])).code === 0;
|
|
15612
|
-
if (exists && options.known !== true) throw new Error(`branch ${branch} already exists, and this run did not create it; Ork never takes over a branch it did not create`);
|
|
15613
|
-
await gitOk(root, exists ? [
|
|
15614
|
-
"worktree",
|
|
15615
|
-
"add",
|
|
15616
|
-
target,
|
|
15617
|
-
branch
|
|
15618
|
-
] : [
|
|
15619
|
-
"worktree",
|
|
15620
|
-
"add",
|
|
15621
|
-
"-b",
|
|
15622
|
-
branch,
|
|
15623
|
-
target,
|
|
15624
|
-
base
|
|
15625
|
-
]);
|
|
15626
|
-
return {
|
|
15627
|
-
path: target,
|
|
15628
|
-
branch,
|
|
15629
|
-
copied: copyInto(root, target, await localFiles(root)),
|
|
15630
|
-
reused: exists
|
|
15631
|
-
};
|
|
15970
|
+
items: /* @__PURE__ */ new Map()
|
|
15632
15971
|
};
|
|
15633
|
-
/**
|
|
15634
|
-
const
|
|
15635
|
-
|
|
15636
|
-
|
|
15637
|
-
"
|
|
15638
|
-
"
|
|
15639
|
-
"
|
|
15640
|
-
"CHERRY_PICK_HEAD",
|
|
15641
|
-
"REVERT_HEAD"
|
|
15642
|
-
];
|
|
15643
|
-
/** Whether a rebase, merge, cherry-pick or revert is underway in `worktree`. */
|
|
15644
|
-
const gitOperationUnderway = async (worktree) => {
|
|
15645
|
-
const paths = await gitOk(worktree, ["rev-parse", ...UNDERWAY.flatMap((name) => ["--git-path", name])]);
|
|
15646
|
-
return lines(paths).some((file) => existsSync(path.resolve(worktree, file)));
|
|
15972
|
+
/** Whether an activation's input carries a result this run made anew. */
|
|
15973
|
+
const carries = (fresh, input) => {
|
|
15974
|
+
if (input === void 0) return false;
|
|
15975
|
+
if (fresh.has(input)) return true;
|
|
15976
|
+
const results = input["results"];
|
|
15977
|
+
if (results === null || typeof results !== "object") return false;
|
|
15978
|
+
return Object.values(results).some((value) => value !== null && typeof value === "object" && fresh.has(value));
|
|
15647
15979
|
};
|
|
15648
15980
|
/**
|
|
15649
|
-
*
|
|
15650
|
-
*
|
|
15651
|
-
*
|
|
15652
|
-
* changes are the operation's to finish, as a conflict resolver's are.
|
|
15981
|
+
* Hands each node's recorded results back in order, until the node runs anew
|
|
15982
|
+
* once (it failed or never finished last time, or its input is new); from then
|
|
15983
|
+
* on it always runs. A node cut off mid-session continues that session, once.
|
|
15653
15984
|
*/
|
|
15654
|
-
const
|
|
15655
|
-
|
|
15656
|
-
const
|
|
15657
|
-
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
|
|
15985
|
+
const createReplay = (history, cutOff = /* @__PURE__ */ new Map(), mail, earlier, fanOuts = NO_FAN_OUTS) => {
|
|
15986
|
+
const sessions = new Map(cutOff);
|
|
15987
|
+
const used = /* @__PURE__ */ new Map();
|
|
15988
|
+
const anew = /* @__PURE__ */ new Set();
|
|
15989
|
+
const fresh = /* @__PURE__ */ new WeakSet();
|
|
15990
|
+
/** Fanned nodes whose list was resolved anew: their items can't be replayed. */
|
|
15991
|
+
const renewed = /* @__PURE__ */ new Set();
|
|
15992
|
+
const takeItem = (activation, of) => {
|
|
15993
|
+
const recorded = fanOuts.items.get(activation.id)?.get(activation.round ?? 0);
|
|
15994
|
+
if (recorded !== void 0 && !renewed.has(of) && !carries(fresh, activation.input)) return recorded;
|
|
15995
|
+
anew.add(activation.id);
|
|
15996
|
+
return null;
|
|
15997
|
+
};
|
|
15998
|
+
return {
|
|
15999
|
+
take: (activation) => {
|
|
16000
|
+
const { id } = activation;
|
|
16001
|
+
if (activation.brief !== void 0 || anew.has(id)) return null;
|
|
16002
|
+
if (activation.item !== void 0) return takeItem(activation, activation.item.of);
|
|
16003
|
+
const index = used.get(id) ?? 0;
|
|
16004
|
+
const recorded = history.get(id)?.[index];
|
|
16005
|
+
if (recorded === void 0 || carries(fresh, activation.input)) {
|
|
16006
|
+
anew.add(id);
|
|
16007
|
+
return null;
|
|
16008
|
+
}
|
|
16009
|
+
used.set(id, index + 1);
|
|
16010
|
+
return recorded;
|
|
16011
|
+
},
|
|
16012
|
+
ran: (result) => {
|
|
16013
|
+
fresh.add(result.output);
|
|
16014
|
+
},
|
|
16015
|
+
cutOff: (activation) => {
|
|
16016
|
+
const session = activation.brief === void 0 && (activation.item === void 0 || !renewed.has(activation.item.of)) ? sessions.get(activation.id) : void 0;
|
|
16017
|
+
sessions.delete(activation.id);
|
|
16018
|
+
return session;
|
|
16019
|
+
},
|
|
16020
|
+
listed: (id, round, source) => {
|
|
16021
|
+
const list = fanOuts.lists.get(listKey(id, round));
|
|
16022
|
+
const ranAnew = typeof source === "object" && source !== null && fresh.has(source);
|
|
16023
|
+
if (list !== void 0 && !ranAnew) return list;
|
|
16024
|
+
renewed.add(id);
|
|
16025
|
+
return null;
|
|
16026
|
+
},
|
|
16027
|
+
previousSession: ({ id, item, round = 0 }) => item === void 0 || renewed.has(item.of) ? void 0 : [...fanOuts.items.get(id) ?? []].findLast(([before]) => before < round)?.[1].sessionId,
|
|
16028
|
+
lastOutput: (id) => history.get(id)?.at(-1)?.output,
|
|
16029
|
+
kept: () => [...history.keys(), ...fanOuts.items.keys()],
|
|
16030
|
+
...mail === void 0 ? {} : { mail },
|
|
16031
|
+
...earlier === void 0 ? {} : { earlier }
|
|
16032
|
+
};
|
|
15665
16033
|
};
|
|
15666
16034
|
/**
|
|
15667
|
-
*
|
|
15668
|
-
*
|
|
15669
|
-
*/
|
|
15670
|
-
const fastForward = async (worktree, branch) => (await git(worktree, [
|
|
15671
|
-
"merge",
|
|
15672
|
-
"--ff-only",
|
|
15673
|
-
"-q",
|
|
15674
|
-
branch
|
|
15675
|
-
])).code === 0;
|
|
15676
|
-
/**
|
|
15677
|
-
* Merges `branch` into the checkout at `into` with `--no-ff`; aborts on
|
|
15678
|
-
* conflict.
|
|
16035
|
+
* The run's `HumanChannel`, answering questions about loops out of rounds as
|
|
16036
|
+
* they were answered last time, in order; anything else goes to `human`.
|
|
15679
16037
|
*/
|
|
15680
|
-
const
|
|
15681
|
-
const
|
|
15682
|
-
"merge",
|
|
15683
|
-
"--no-ff",
|
|
15684
|
-
"--no-edit",
|
|
15685
|
-
"-m",
|
|
15686
|
-
message,
|
|
15687
|
-
branch
|
|
15688
|
-
]);
|
|
15689
|
-
if (merged.code === 0) return { ok: true };
|
|
15690
|
-
const conflicts = lines((await git(into, [
|
|
15691
|
-
"diff",
|
|
15692
|
-
"--name-only",
|
|
15693
|
-
"--diff-filter=U"
|
|
15694
|
-
])).stdout);
|
|
15695
|
-
await git(into, ["merge", "--abort"]);
|
|
16038
|
+
const replayLoopAnswers = (human, answers) => {
|
|
16039
|
+
const left = [...answers];
|
|
15696
16040
|
return {
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
16041
|
+
permit: (request) => human.permit(request),
|
|
16042
|
+
...human.settled === void 0 ? {} : { settled: human.settled },
|
|
16043
|
+
ask: (question) => {
|
|
16044
|
+
const index = left.findIndex((a) => a.question === question.question);
|
|
16045
|
+
const [given] = index === -1 ? [] : left.splice(index, 1);
|
|
16046
|
+
return given === void 0 ? human.ask(question) : Promise.resolve(given.answer);
|
|
16047
|
+
}
|
|
15700
16048
|
};
|
|
15701
16049
|
};
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
16050
|
+
// ../core/src/store/drafts.ts
|
|
16051
|
+
const rowOf = (row) => {
|
|
16052
|
+
const baseHash = text(row, "base_hash");
|
|
16053
|
+
return {
|
|
16054
|
+
project: text(row, "project"),
|
|
16055
|
+
workflow: text(row, "workflow"),
|
|
16056
|
+
text: text(row, "text"),
|
|
16057
|
+
baseHash: baseHash === "" ? null : baseHash,
|
|
16058
|
+
at: text(row, "at")
|
|
16059
|
+
};
|
|
16060
|
+
};
|
|
16061
|
+
const draftQueries = (db) => {
|
|
16062
|
+
const put = db.prepare("INSERT OR REPLACE INTO workflow_drafts (project, workflow, text, base_hash, at) VALUES (?, ?, ?, ?, ?)");
|
|
16063
|
+
const one = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? AND workflow = ?");
|
|
16064
|
+
const all = db.prepare("SELECT project, workflow, text, base_hash, at FROM workflow_drafts WHERE project = ? ORDER BY workflow");
|
|
16065
|
+
const drop = db.prepare("DELETE FROM workflow_drafts WHERE project = ? AND workflow = ?");
|
|
16066
|
+
return {
|
|
16067
|
+
putDraft: (draft) => {
|
|
16068
|
+
put.run(draft.project, draft.workflow, draft.text, draft.baseHash, draft.at);
|
|
16069
|
+
},
|
|
16070
|
+
draft: (project, workflow) => {
|
|
16071
|
+
const row = one.get(project, workflow);
|
|
16072
|
+
return row === void 0 ? void 0 : rowOf(row);
|
|
16073
|
+
},
|
|
16074
|
+
drafts: (project) => all.all(project).map((row) => rowOf(row)),
|
|
16075
|
+
dropDraft: (project, workflow) => {
|
|
16076
|
+
drop.run(project, workflow);
|
|
16077
|
+
}
|
|
16078
|
+
};
|
|
15709
16079
|
};
|
|
15710
16080
|
// ../core/src/worktree/list.ts
|
|
15711
16081
|
/**
|
|
@@ -16275,9 +16645,7 @@ const planLimitQueries = (db) => {
|
|
|
16275
16645
|
const all = db.prepare("SELECT name, status, resets_at, at FROM plan_limits ORDER BY name");
|
|
16276
16646
|
const clear = db.prepare("DELETE FROM plan_limits");
|
|
16277
16647
|
return {
|
|
16278
|
-
clearPlanLimits: () =>
|
|
16279
|
-
clear.run();
|
|
16280
|
-
},
|
|
16648
|
+
clearPlanLimits: () => Number(clear.run().changes),
|
|
16281
16649
|
recordPlanLimit: ({ window, status, resetsAt }, at) => {
|
|
16282
16650
|
put.run(window, status, resetsAt ?? null, at);
|
|
16283
16651
|
},
|
|
@@ -17690,7 +18058,7 @@ const capsHere = (home, place) => {
|
|
|
17690
18058
|
try {
|
|
17691
18059
|
const rules = place === void 0 ? void 0 : loadProject(place.root, placeFiles(place).rules);
|
|
17692
18060
|
return {
|
|
17693
|
-
problems: [...user.diagnostics, ...rules?.diagnostics ?? []].map((found) => found
|
|
18061
|
+
problems: [...user.diagnostics, ...rules?.diagnostics ?? []].map((found) => diagnosticText(found)),
|
|
17694
18062
|
caps: capsOf(user.settings?.quotas, rules?.project?.quotas, place?.root)
|
|
17695
18063
|
};
|
|
17696
18064
|
} catch (error) {
|
|
@@ -17843,95 +18211,6 @@ const usageReport = (rows, cpu = []) => {
|
|
|
17843
18211
|
hints: hintsOf(totals, groups.node)
|
|
17844
18212
|
};
|
|
17845
18213
|
};
|
|
17846
|
-
// ../core/src/worktree/clean.ts
|
|
17847
|
-
/**
|
|
17848
|
-
* @file What `ork worktrees clean` and `ork worktrees remove` do with what
|
|
17849
|
-
* list.ts reads: the plan (what goes, what's kept and why), by one rule for
|
|
17850
|
-
* every caller, and carrying it out. The CLI and the Worktrees panel both
|
|
17851
|
-
* plan here, so neither removes what the other would keep.
|
|
17852
|
-
*/
|
|
17853
|
-
/** What names an item for `remove`: its branch, or its worktree's path. */
|
|
17854
|
-
const keyOf = (item) => item.branch ?? item.path ?? `${item.runId}/${item.nodeId}`;
|
|
17855
|
-
/** The reasons no `force` overrides: removing would break something. */
|
|
17856
|
-
const MAIN_CHECKOUT = "checked out in the main checkout";
|
|
17857
|
-
const RUN_GOING = "its run is still going";
|
|
17858
|
-
const NOT_ORKS = "not created by Ork";
|
|
17859
|
-
const kept = (item, skip) => ({
|
|
17860
|
-
item,
|
|
17861
|
-
removeWorktree: false,
|
|
17862
|
-
deleteBranch: false,
|
|
17863
|
-
skip
|
|
17864
|
-
});
|
|
17865
|
-
/** What clean does with one item. */
|
|
17866
|
-
const cleanStep = (item, options) => {
|
|
17867
|
-
if (!item.recorded) return kept(item, NOT_ORKS);
|
|
17868
|
-
if (item.inMainCheckout) return kept(item, MAIN_CHECKOUT);
|
|
17869
|
-
if (item.heldBy !== void 0) return kept(item, `checked out in ${item.heldBy}`);
|
|
17870
|
-
if (options.live?.has(item.runId) === true) return kept(item, RUN_GOING);
|
|
17871
|
-
if (item.dirty && !options.force) return kept(item, "uncommitted changes");
|
|
17872
|
-
if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return kept(item, "commits on no branch");
|
|
17873
|
-
const removable = item.path !== null && !item.missing;
|
|
17874
|
-
if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
|
|
17875
|
-
item,
|
|
17876
|
-
removeWorktree: removable,
|
|
17877
|
-
deleteBranch: false,
|
|
17878
|
-
skip: "branch not merged"
|
|
17879
|
-
};
|
|
17880
|
-
return {
|
|
17881
|
-
item,
|
|
17882
|
-
removeWorktree: removable,
|
|
17883
|
-
deleteBranch: options.branches && item.branch !== void 0
|
|
17884
|
-
};
|
|
17885
|
-
};
|
|
17886
|
-
/** Whether a step drops a worktree whose folder is gone, by its own path. */
|
|
17887
|
-
const stepPrunes = (step) => step.item.missing && step.item.path !== null && step.skip === void 0;
|
|
17888
|
-
/** Whether a step does anything. */
|
|
17889
|
-
const stepActs = (step) => step.removeWorktree || step.deleteBranch || stepPrunes(step);
|
|
17890
|
-
/**
|
|
17891
|
-
* What `ork worktrees clean` would do. A worktree with uncommitted changes, an
|
|
17892
|
-
* unmerged branch, or a detached worktree holding commits no branch has, is
|
|
17893
|
-
* kept unless `force`; one checked out in the main checkout or in a worktree
|
|
17894
|
-
* Ork didn't create, whose run is still going, or an `ork/` branch no run
|
|
17895
|
-
* recorded creating, is kept even then.
|
|
17896
|
-
*/
|
|
17897
|
-
const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => filter.key === void 0 || keyOf(item) === filter.key).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
|
|
17898
|
-
/**
|
|
17899
|
-
* Carries out one step; what it did goes into `done` as it goes. It touches
|
|
17900
|
-
* only its own item: a missing worktree is dropped by its own path (`git
|
|
17901
|
-
* worktree remove`), never by `git worktree prune`, which would drop every
|
|
17902
|
-
* missing worktree in the repository, the user's own included.
|
|
17903
|
-
*/
|
|
17904
|
-
const applyStep = async (root, step, force, done) => {
|
|
17905
|
-
const { item } = step;
|
|
17906
|
-
if (stepPrunes(step) && !step.removeWorktree && item.path !== null) {
|
|
17907
|
-
await gitOk(root, [
|
|
17908
|
-
"worktree",
|
|
17909
|
-
"remove",
|
|
17910
|
-
item.path
|
|
17911
|
-
]);
|
|
17912
|
-
done.push(`pruned ${item.path}`);
|
|
17913
|
-
}
|
|
17914
|
-
if (step.removeWorktree && item.path !== null) {
|
|
17915
|
-
await removeWorktree(root, item.path, force);
|
|
17916
|
-
done.push(`removed ${item.path}`);
|
|
17917
|
-
}
|
|
17918
|
-
if (step.deleteBranch && item.branch !== void 0) {
|
|
17919
|
-
await gitOk(root, [
|
|
17920
|
-
"branch",
|
|
17921
|
-
force ? "-D" : "-d",
|
|
17922
|
-
item.branch
|
|
17923
|
-
]);
|
|
17924
|
-
done.push(`deleted ${item.branch}`);
|
|
17925
|
-
}
|
|
17926
|
-
};
|
|
17927
|
-
/**
|
|
17928
|
-
* Carries out a clean plan, step by step. What it did goes into `done` as it
|
|
17929
|
-
* goes, so a caller still has it when git fails halfway.
|
|
17930
|
-
*/
|
|
17931
|
-
const applyClean = async (root, steps, force, done = []) => {
|
|
17932
|
-
for (const step of steps) await applyStep(root, step, force, done);
|
|
17933
|
-
return done;
|
|
17934
|
-
};
|
|
17935
18214
|
// ../core/src/worktree/names.ts
|
|
17936
18215
|
/**
|
|
17937
18216
|
* @file The names Ork gives a node's worktree and branch, from the project's
|
|
@@ -18731,4 +19010,4 @@ const createWorkspace = (options) => {
|
|
|
18731
19010
|
close
|
|
18732
19011
|
};
|
|
18733
19012
|
};
|
|
18734
|
-
export {
|
|
19013
|
+
export { sendFromScript as $, DISCUSSIONS as $n, programsRunBy as $r, gitOk as $t, declaredTriggers as A, lockWaitsOf as An, hasInRepoFiles as Ar, endedWith as At, planPrune as B, PROJECT_FILE as Bn, projectRootOf as Br, feedbackListTable as Bt, compareVersions as C, ORK_LAYOUT as Ci, linkNodes as Cn, toolLinks as Cr, startRun as Ct, triggersReport as D, EXPERIMENTS as Di, requestStop as Dn, AGENT_SOURCES as Dr, runScope as Dt, lateTriggers as E, firstLine as Ei, requestPause as En, deleteAgent as Er, heldTimeout as Et, activeTree as F, processAlive as Fn, projectsHome as Fr, slotWaitLine as Ft, parseInputPairs as G, endOf as Gn, writePlainFile as Gr, applyClean as Gt, listOrkWorktrees as H, loadProject as Hn, estimateTokens as Hr, runListTable as Ht, isNodeEvent as I, stillTheSameProcess as In, runDirOf as Ir, lowerChild as It, driverRefusals as J, END_KINDS as Jn, applyEdits as Jr, planClean as Jt, resolveInputs as K, outcomeEnd as Kn, skillsIn as Kr, cleanStep as Kt, openStore as L, isItem as Ln, shownPath as Lr, programFound as Lt, workflowTextHash as M, runStatus as Mn, newProjectId as Mr, stoppedEnd as Mt, takeWatchLock as N, slotWaitsOf as Nn, placeFiles as Nr, declaredProblem as Nt, createWatcher as O, experimentsOn as Oi, tellLiveRun as On, catalogAgent as Or, calledWorkflows as Ot, watchingPid as P, templateOf$1 as Pn, placeFor as Pr, slotClock as Pt, scriptRefusal as Q, FEEDBACK_TARGETS as Qn, beforeOf as Qr, git as Qt, storePath as R, nestItems as Rn, workflowDirOf as Rr, startAt as Rt, detectInstaller as S, AGENT_TOOL_LAYOUTS as Si, endLinksOf as Sn, linkTools as Sr, walkSummary as St, parseVersion as T, DRIVER_IDS as Ti, requestContinue as Tn, createAgent as Tr, userMessageText as Tt, createReplay as U, machineRulesOf as Un, guardedBase as Ur, usageListTable as Ut, createdWorktrees as V, gitRulesOf as Vn, LESSON_CAP_TOKENS as Vr, followUpListTable as Vt, replayLoopAnswers as W, createSignals as Wn, plainPathInside as Wr, worktreeListTable as Wt, driverQueues as X, FEEDBACK_KINDS as Xn, readDocument as Xr, stepPrunes as Xt, sessionRefusals as Y, endFor as Yn, newWorkflowText as Yr, stepActs as Yt, TOO_LONG as Z, FEEDBACK_SEVERITIES as Zn, renameNodeTypes as Zr, inspectProject as Zt, capsHere as _, TOOL_PREFIX as _i, deliverFollowUp as _n, maskReplacer as _r, planNodeSession as _t, planRemove as a, WorkflowSchema as ai, nodeSays as an, createFeedback as ar, adoptedCalls as at, runningRunIds as b, driverChecks as bi, DEFAULT_FOLLOW_UPS_RULE as bn, applySteps as br, checkWorkflowText as bt, repoRoot as c, settingsOf as ci, mcpNotText as cn, targetOf as cr, DETACHED_ENV as ct, planLine as d, isToolNode as di, createFollowUps as dn, renderTemplate as dr, NOT_STARTED as dt, renderJsonSchema as ei, clearGitLocalEnv as en, planPost as er, createRelay as et, removeRefusal as f, DriverSchema as fi, checkFollowUpsDue as fn, readCondition as fr, limitsNow as ft, usageReport as g, INTEGRATIONS as gi, takeFollowUps as gn, MASK as gr, explainSession as gt, USAGE_DIMENSIONS as h, timeoutMs as hi, startFollowUpChecks as hn, socketRoom as hr, explainWorkflowText as ht, lostWork as i, EdgeInputSchema as ii, sessionDriver as in, redact as ir, createCalls as it, readProjectTriggers as j, pausedOf as jn, inRepoPlace as jr, failedWith as jt, triggerStanding as k, knownExperiments as ki, liveStatus as kn, loadAgents as kr, lockWaitLine as kt, labelOf as l, NodeSchema as li, serversIn as ln, evaluate as lr, detachRun as lt, resolveBase as m, retiredDriverHint as mi, recordPublished as mn, socketPath as mr, planReason as mt, resumedOptions as n, serializeWorkflow as ni, modelDriver as nn, sendPost as nr, answerDecision as nt, projectHead as o, CheckReportSchema as oi, declaredOutputOf as on, fingerprint as or, detachResume as ot, stateParts as p, NodeIdSchema as pi, markDue as pn, serveRun as pr, limitsReport as pt, budgetWarnings as q, END_DETAIL_LIMIT as qn, WorkflowEditSchema as qr, keyOf as qt, applyRemove as r, markValue as ri, namingNoDriver as rn, publishPost as rr, pendingDecisions as rt, readWorktrees as s, expandEdge as si, MCP_FILE as sn, normalizeTitle as sr, resumable as st, createWorkspace as t, parseWorkflow as ti, envReadsOf as tn, runGh as tr, followRunTree as tt, mergeStateOf as u, isClaudeNode as ui, FOLLOW_UP_KINDS as un, parseTemplate as ur, followEvents as ut, planUpgrade as v, integrationFor as vi, followUpProblem as vn, maskSecrets as vr, parseReport as vt, isNewer as w, AGENT_DRIVERS as wi, unlinkNodes as wn, toolsInUse as wr, MESSAGE_LIMIT as wt, runningScript as x, readyDriver as xi, addressOf as xn, planMoveInto as xr, walkChecks as xt, releaseNotes as y, PREFERRED_DRIVER as yi, liveSessionOf as yn, planAgentMigration as yr, reportSchemaFor as yt, applyPrune as z, countedTokens as zn, inside as zr, decisionListTable as zt };
|