@krapht/ork 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__build_info__-CtSVVuZE.mjs +691 -0
- package/dist/bin.mjs +826 -526
- package/dist/clean-Dcr807be.mjs +88 -0
- package/dist/{context-BFGC_C02.mjs → context-C_bksb0C.mjs} +45 -129
- package/dist/{find-BcvhT6UN.mjs → find-DptmbhGd.mjs} +1 -1
- package/dist/{format-DqpObyMl.mjs → format-J-a21Yvn.mjs} +3 -163
- package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-D1Qc5Mit.mjs} +1 -1
- package/dist/{limits-BKYAmqCR.mjs → limits-BDZCmGEu.mjs} +1 -1
- package/dist/list-ZvY2OcLq.mjs +110 -0
- package/dist/{mcp-BWsdQyri.mjs → mcp-CHVKKg3B.mjs} +6 -6
- package/dist/{output-Q2EdeVUt.mjs → output-CXSJO00X.mjs} +68 -24
- package/dist/{pause-DKACDfV1.mjs → pause-BPSmg4yz.mjs} +4 -4
- package/dist/{relay-CbK-Wme9.mjs → relay-Do-aNbSD.mjs} +2 -2
- package/dist/remove-AwaMMaHf.mjs +194 -0
- package/dist/{send-C6yr04Fe.mjs → send-DhGBkowy.mjs} +3 -3
- package/dist/{src-CZOVF2yU.mjs → src-BHnxpJyH.mjs} +1 -1
- package/dist/{src-Cz1yOUTg.mjs → src-CGqMati3.mjs} +1411 -747
- package/dist/{src-D5ruZFgD.mjs → src-CpDKIulI.mjs} +1200 -55
- package/dist/{src-DtN1k-B3.mjs → src-UWkk1sl9.mjs} +1 -1
- package/dist/ui/assets/app-BgVDC_5l.js +67 -0
- package/dist/ui/assets/{app-C722xNZ3.css → app-DVf68fj5.css} +1 -1
- package/dist/ui/assets/{index-BoG6O-Lp.js → index-MQNNgsyT.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/ui-DhutUMgQ.mjs +472 -0
- package/dist/{usage-BIjf9Xdt.mjs → usage-CT7Z-yjr.mjs} +5 -4
- package/dist/{watch-B8o4Szd_.mjs → watch-0Qr2UfCQ.mjs} +6 -6
- package/dist/worktrees-DHl80k96.mjs +212 -0
- package/package.json +4 -4
- package/dist/__build_info__-CXXMLvjt.mjs +0 -104
- package/dist/ui/assets/app-BlDdP396.js +0 -61
- package/dist/ui-CZS1XKla.mjs +0 -160
package/dist/bin.mjs
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
3
|
-
import {
|
|
4
|
-
import { n as newRunId, t as findRun } from "./find-
|
|
5
|
-
import { a as write, c as openStore, i as style, l as storePath, n as cost, o as writeErr, s as nodeOf, t as confirm } from "./output-
|
|
6
|
-
import {
|
|
7
|
-
import { a as
|
|
8
|
-
import { i as
|
|
9
|
-
import {
|
|
10
|
-
import { i as
|
|
2
|
+
import { $ as resumable, $t as planMoveInto, A as parseInputPairs, At as FEEDBACK_KINDS, Bn as isClaudeNode, Bt as normalizeTitle, F as endLink, Fn as WorkflowSchema, G as tellLiveRun, Ht as maskReplacer, I as endLinksOf, It as sendPost, Jn as ORK_LAYOUT, Jt as serveRun, Ln as settingsOf, Mt as FEEDBACK_TARGETS, N as createCalls, P as addressOf, Pt as planPost, Q as detachResume, Qt as applySteps, R as peerOf, Rt as createFeedback, T as inspectProject, V as flattenTree, W as requestStop, Wn as INTEGRATIONS, Xn as firstLine, Xt as wakeRun, Yn as CLAUDE_DRIVERS, Yt as socketPath, Z as adoptedCalls, Zt as planAgentMigration, _n as inside, an as catalogAgent, cn as hasInRepoFiles, d as runningRunIds, dn as placeFiles, dt as runStatus, en as linkTools, et as createReplay, f as runningScript, fn as placeFor, ft as runTree, g as triggersReport, h as lateTriggers, hn as shownPath, i as NOT_STARTED, in as deleteAgent, it as followEvents, j as resolveInputs, jn as serializeWorkflow, jt as FEEDBACK_SEVERITIES, kt as gitRulesOf, l as planUpgrade, ln as inRepoPlace, lt as liveStatus, m as isNewer, mn as runDirOf, n as resolveBase, nn as toolsInUse, on as loadAgents, pn as projectsHome, qn as AGENT_TOOL_LAYOUTS, rn as createAgent, rt as detachRun, st as planNodeSession, t as createWorkspace, tn as toolLinks, tt as replayLoopAnswers, u as releaseNotes, un as newProjectId, vn as projectRootOf, vt as walkSummary, xt as userMessageText, yt as startRun, z as tellNode, zn as beforeOf, zt as fingerprint } from "./src-CGqMati3.mjs";
|
|
3
|
+
import { C as checkNote, D as placeOf, O as feedbackActions, S as previewPost, T as acceptItem, _ as answerDecision, b as builtinWorkflowFile, c as checkDue, d as pendingNotice, f as readUpdateCache, g as nodeSession, h as writeUpdateCache, i as registryOf, k as openFixRun, l as fetchLatest, m as updateCachePath, n as ORK_VERSION, o as updateSwitchOff, p as refreshUpdateCache, r as isSwitchedOn, s as runInstall, t as ORK_NAME, u as newerVersion, v as pendingDecisions, w as recordNote, y as builtinProblem } from "./__build_info__-CtSVVuZE.mjs";
|
|
4
|
+
import { n as newRunId, t as findRun } from "./find-DptmbhGd.mjs";
|
|
5
|
+
import { a as write, c as openStore, i as style, l as storePath, n as cost, o as writeErr, s as nodeOf, t as confirm } from "./output-CXSJO00X.mjs";
|
|
6
|
+
import { n as listOrkWorktrees, t as createdWorktrees } from "./list-ZvY2OcLq.mjs";
|
|
7
|
+
import { a as planClean, t as applyClean } from "./clean-Dcr807be.mjs";
|
|
8
|
+
import { a as formatRunEvent, i as formatEvent, n as formatRuns, o as createTerminalHuman, r as formatTree, t as ago } from "./format-J-a21Yvn.mjs";
|
|
9
|
+
import { a as validate, c as warningLines, d as refusedBuiltin, f as workflowArg, i as loadWorkflow, l as WORKFLOW_DIR, m as registeredProjects, o as validateFile, p as projectAt, r as formatReport, s as walkLoaded, t as context, u as WORKFLOW_SUFFIX } from "./context-C_bksb0C.mjs";
|
|
10
|
+
import { i as runLimits, r as limitsRefusal, t as chatNotice } from "./limits-BDZCmGEu.mjs";
|
|
11
|
+
import { _ as resultOf, a as builtinExecutors, d as maskedEnv, f as maskedNamesOfRun, g as clearOutputFiles, h as sessionEnv, i as onPath, l as clearStateFiles, m as runScrubber, n as missingProgramMessage, r as missingPrograms, t as envProblems } from "./src-UWkk1sl9.mjs";
|
|
12
|
+
import { i as decideRefusal, n as chatRefusal } from "./in-a-run-D1Qc5Mit.mjs";
|
|
11
13
|
import { defineCommand, runMain } from "citty";
|
|
12
14
|
import { createInterface } from "node:readline/promises";
|
|
13
15
|
import path from "node:path";
|
|
14
16
|
import { stringify } from "yaml";
|
|
15
|
-
import {
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
16
19
|
import { createHash } from "node:crypto";
|
|
17
20
|
import { homedir } from "node:os";
|
|
18
|
-
import { setTimeout } from "node:timers/promises";
|
|
19
21
|
import { execFile, spawn } from "node:child_process";
|
|
22
|
+
import { setTimeout } from "node:timers/promises";
|
|
20
23
|
import { styleText } from "node:util";
|
|
21
24
|
// ../core/src/control/queue.ts
|
|
22
25
|
const PERMISSION_OPTIONS = [{
|
|
@@ -208,41 +211,6 @@ const startControl = async (options) => {
|
|
|
208
211
|
}
|
|
209
212
|
};
|
|
210
213
|
};
|
|
211
|
-
// ../core/src/feedback/human.ts
|
|
212
|
-
const RATINGS = ["good", "bad"];
|
|
213
|
-
const MAX_TITLE = 120;
|
|
214
|
-
/** The run event a note becomes; its title is the note's first line. */
|
|
215
|
-
const humanNoteEvent = (note) => {
|
|
216
|
-
const text = maskSecrets(note.text.trim());
|
|
217
|
-
const [first = ""] = text.split("\n");
|
|
218
|
-
const title = first.slice(0, MAX_TITLE);
|
|
219
|
-
const kind = note.kind ?? "friction";
|
|
220
|
-
const target = targetOf({
|
|
221
|
-
kind,
|
|
222
|
-
title,
|
|
223
|
-
...note.target === void 0 ? {} : { target: note.target }
|
|
224
|
-
});
|
|
225
|
-
const feedback = {
|
|
226
|
-
source: "human",
|
|
227
|
-
kind,
|
|
228
|
-
target,
|
|
229
|
-
severity: note.severity ?? (note.rating === "bad" ? "high" : "medium"),
|
|
230
|
-
title,
|
|
231
|
-
body: text,
|
|
232
|
-
evidence: note.rating === void 0 ? [] : [`rating: ${note.rating}`],
|
|
233
|
-
fingerprint: fingerprint({
|
|
234
|
-
target,
|
|
235
|
-
kind,
|
|
236
|
-
title
|
|
237
|
-
}),
|
|
238
|
-
origin: { workflow: note.workflow }
|
|
239
|
-
};
|
|
240
|
-
return {
|
|
241
|
-
type: "feedback.recorded",
|
|
242
|
-
...note.node === void 0 ? {} : { node: note.node },
|
|
243
|
-
feedback
|
|
244
|
-
};
|
|
245
|
-
};
|
|
246
214
|
// ../core/src/feedback/signals.ts
|
|
247
215
|
const ORK_TOOL = "mcp__ork__";
|
|
248
216
|
/**
|
|
@@ -402,6 +370,47 @@ const createSignals = (options = {}) => {
|
|
|
402
370
|
return signal === null ? [event] : [event, ...record(...signal)];
|
|
403
371
|
};
|
|
404
372
|
};
|
|
373
|
+
/** A title's words, as fingerprints compare them. */
|
|
374
|
+
const titleWords = (title) => new Set(normalizeTitle(title).split(" ").filter((word) => word !== ""));
|
|
375
|
+
/** The share of two titles' words they have in common (Jaccard). */
|
|
376
|
+
const titleOverlap = (a, b) => {
|
|
377
|
+
const left = titleWords(a);
|
|
378
|
+
const right = titleWords(b);
|
|
379
|
+
const union = /* @__PURE__ */ new Set([...left, ...right]);
|
|
380
|
+
if (union.size === 0) return 0;
|
|
381
|
+
return [...left].filter((word) => right.has(word)).length / union.size;
|
|
382
|
+
};
|
|
383
|
+
const unsettled = (item) => item.status === "new" || item.status === "triaged";
|
|
384
|
+
const firstSeen = (a, b) => a.firstSeen.localeCompare(b.firstSeen) || a.id.localeCompare(b.id);
|
|
385
|
+
/**
|
|
386
|
+
* One kind of item's groups, oldest first: an item joins the first group whose
|
|
387
|
+
* first-seen item it is near enough, so every member is within the rule of the
|
|
388
|
+
* item the others go under, never only of one another.
|
|
389
|
+
*/
|
|
390
|
+
const groupsOf = (items) => {
|
|
391
|
+
const groups = [];
|
|
392
|
+
for (const item of items.toSorted(firstSeen)) {
|
|
393
|
+
const group = groups.find(([root]) => root !== void 0 && titleOverlap(root.title, item.title) >= .8);
|
|
394
|
+
if (group === void 0) groups.push([item]);
|
|
395
|
+
else group.push(item);
|
|
396
|
+
}
|
|
397
|
+
return groups;
|
|
398
|
+
};
|
|
399
|
+
/** The merges triage would make among `items`, oldest group first. */
|
|
400
|
+
const proposeMerges = (items) => {
|
|
401
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
402
|
+
for (const item of items.filter((i) => unsettled(i))) {
|
|
403
|
+
const key = `${item.target}/${item.kind}`;
|
|
404
|
+
byKind.set(key, [...byKind.get(key) ?? [], item]);
|
|
405
|
+
}
|
|
406
|
+
return [...byKind.values()].flatMap((sameKind) => groupsOf(sameKind)).filter((group) => group.length > 1 && group.some((item) => item.status === "new")).map((group) => {
|
|
407
|
+
const [into, ...from] = group;
|
|
408
|
+
return into === void 0 ? void 0 : {
|
|
409
|
+
into,
|
|
410
|
+
from
|
|
411
|
+
};
|
|
412
|
+
}).filter((merge) => merge !== void 0).toSorted((a, b) => firstSeen(a.into, b.into));
|
|
413
|
+
};
|
|
405
414
|
// ../core/src/remote/relay.ts
|
|
406
415
|
/**
|
|
407
416
|
* @file A run's side of the relay (RFC-003): its nodes' links, read from the
|
|
@@ -526,97 +535,6 @@ const createRecorder = (store, run, runDir) => {
|
|
|
526
535
|
store.begin(run);
|
|
527
536
|
return appendRecorder(store, run.id, runDir);
|
|
528
537
|
};
|
|
529
|
-
const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
|
|
530
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
531
|
-
const text = (record, key) => {
|
|
532
|
-
const value = record[key];
|
|
533
|
-
return typeof value === "string" ? value : void 0;
|
|
534
|
-
};
|
|
535
|
-
const notifiedOf = (value) => {
|
|
536
|
-
if (!isRecord(value)) return void 0;
|
|
537
|
-
const version = text(value, "version");
|
|
538
|
-
const at = text(value, "at");
|
|
539
|
-
return version === void 0 || at === void 0 ? void 0 : {
|
|
540
|
-
version,
|
|
541
|
-
at
|
|
542
|
-
};
|
|
543
|
-
};
|
|
544
|
-
/** The cache, or `null` when there is none or it cannot be read. */
|
|
545
|
-
const readUpdateCache = (file) => {
|
|
546
|
-
let parsed;
|
|
547
|
-
try {
|
|
548
|
-
parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
549
|
-
} catch {
|
|
550
|
-
return null;
|
|
551
|
-
}
|
|
552
|
-
if (!isRecord(parsed)) return null;
|
|
553
|
-
const checkedAt = text(parsed, "checkedAt");
|
|
554
|
-
if (checkedAt === void 0) return null;
|
|
555
|
-
const latest = text(parsed, "latest");
|
|
556
|
-
const notified = notifiedOf(parsed["notified"]);
|
|
557
|
-
return {
|
|
558
|
-
checkedAt,
|
|
559
|
-
...latest === void 0 ? {} : { latest },
|
|
560
|
-
...notified === void 0 ? {} : { notified }
|
|
561
|
-
};
|
|
562
|
-
};
|
|
563
|
-
/** Writes the cache whole (a temporary file, then a rename). */
|
|
564
|
-
const writeUpdateCache = (file, cache) => {
|
|
565
|
-
mkdirSync(path.dirname(file), { recursive: true });
|
|
566
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
567
|
-
writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
|
|
568
|
-
renameSync(temporary, file);
|
|
569
|
-
};
|
|
570
|
-
const age = (iso, now) => {
|
|
571
|
-
const time = Date.parse(iso);
|
|
572
|
-
return Number.isNaN(time) ? Infinity : now.getTime() - time;
|
|
573
|
-
};
|
|
574
|
-
/** Whether it is time to ask the registry again. */
|
|
575
|
-
const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
|
|
576
|
-
/** The package's `latest` version on the registry, or `null` on any failure. */
|
|
577
|
-
const fetchLatest = async (name, options = {}) => {
|
|
578
|
-
const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
|
|
579
|
-
const get = options.fetch ?? globalThis.fetch;
|
|
580
|
-
try {
|
|
581
|
-
const response = await get(`${registry}/-/package/${name}/dist-tags`, {
|
|
582
|
-
headers: { accept: "application/json" },
|
|
583
|
-
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
584
|
-
});
|
|
585
|
-
if (!response.ok) return null;
|
|
586
|
-
const tags = await response.json();
|
|
587
|
-
return (isRecord(tags) ? text(tags, "latest") : void 0) ?? null;
|
|
588
|
-
} catch {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
/**
|
|
593
|
-
* Asks the registry and records the answer. A failed check is recorded too, so
|
|
594
|
-
* an offline machine is not asked again until the interval passes; the last
|
|
595
|
-
* known `latest` is kept.
|
|
596
|
-
*/
|
|
597
|
-
const refreshUpdateCache = async (file, name, now, options = {}) => {
|
|
598
|
-
const before = readUpdateCache(file);
|
|
599
|
-
const latest = await fetchLatest(name, options) ?? before?.latest;
|
|
600
|
-
const cache = {
|
|
601
|
-
...before,
|
|
602
|
-
checkedAt: now.toISOString(),
|
|
603
|
-
...latest === void 0 ? {} : { latest }
|
|
604
|
-
};
|
|
605
|
-
writeUpdateCache(file, cache);
|
|
606
|
-
return cache;
|
|
607
|
-
};
|
|
608
|
-
/** The newer version the cache knows of, if any. */
|
|
609
|
-
const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
|
|
610
|
-
/**
|
|
611
|
-
* The version to tell the user about now: newer than theirs, and not already
|
|
612
|
-
* told today. `null` when there is nothing to say.
|
|
613
|
-
*/
|
|
614
|
-
const pendingNotice = (cache, current, now) => {
|
|
615
|
-
const newer = newerVersion(cache, current);
|
|
616
|
-
if (newer === null) return null;
|
|
617
|
-
const told = cache?.notified;
|
|
618
|
-
return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
|
|
619
|
-
};
|
|
620
538
|
// src/runs/chat.ts
|
|
621
539
|
/**
|
|
622
540
|
* @file `ork chat`'s two paths. A node of a live run gets the message through
|
|
@@ -1273,7 +1191,7 @@ const flagValues = (rawArgs, flag) => {
|
|
|
1273
1191
|
};
|
|
1274
1192
|
const isDriver = (value) => CLAUDE_DRIVERS.some((d) => d.id === value);
|
|
1275
1193
|
/** Loads the drivers only when a run starts, so other commands stay fast. */
|
|
1276
|
-
const loadRuntime = async (driver) => (await import("./src-
|
|
1194
|
+
const loadRuntime = async (driver) => (await import("./src-BHnxpJyH.mjs")).createRuntime(driver, sessionEnv(process.env));
|
|
1277
1195
|
/**
|
|
1278
1196
|
* Ctrl-C asks the run to stop, and so does a terminal closing or a polite kill,
|
|
1279
1197
|
* so that the run ends tidily and leaves no result unscrubbed.
|
|
@@ -1304,7 +1222,7 @@ const run = defineCommand({
|
|
|
1304
1222
|
args: {
|
|
1305
1223
|
workflow: {
|
|
1306
1224
|
type: "positional",
|
|
1307
|
-
description: "A workflow file,
|
|
1225
|
+
description: "A workflow file, the name of one in the project's workflows (.ork/workflows/), or one of Ork's own (ork:feedback-apply)",
|
|
1308
1226
|
required: true
|
|
1309
1227
|
},
|
|
1310
1228
|
node: {
|
|
@@ -1375,6 +1293,7 @@ const run = defineCommand({
|
|
|
1375
1293
|
return;
|
|
1376
1294
|
}
|
|
1377
1295
|
const cwd = process.cwd();
|
|
1296
|
+
if (refusedBuiltin([args.workflow], cwd)) return;
|
|
1378
1297
|
const target = workflowArg(args.workflow, cwd);
|
|
1379
1298
|
const store = openStore();
|
|
1380
1299
|
if (args.detach) {
|
|
@@ -1591,18 +1510,44 @@ const chat = defineCommand({
|
|
|
1591
1510
|
}
|
|
1592
1511
|
}
|
|
1593
1512
|
});
|
|
1594
|
-
// src/commands/
|
|
1513
|
+
// src/commands/decide.ts
|
|
1595
1514
|
/**
|
|
1596
|
-
* @file `ork decide
|
|
1597
|
-
*
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
|
-
* recorded state (ADR-026).
|
|
1515
|
+
* @file `ork decide`: answer a live run's open decisions from any terminal (it
|
|
1516
|
+
* writes to the store, then wakes the run on its socket; specs/messaging.md).
|
|
1517
|
+
* With `--id` and `--choose`, `--note` and `--rating` also note how the
|
|
1518
|
+
* decision's node went, as the run's feedback (specs/feedback.md).
|
|
1601
1519
|
*/
|
|
1602
|
-
const failed = (message) => {
|
|
1520
|
+
const failed$2 = (message) => {
|
|
1603
1521
|
writeErr(message);
|
|
1604
1522
|
process.exitCode = 1;
|
|
1605
1523
|
};
|
|
1524
|
+
/**
|
|
1525
|
+
* Notes the answered decision's node, when there's a note or a rating; a rating
|
|
1526
|
+
* alone is its note's words.
|
|
1527
|
+
*/
|
|
1528
|
+
const noteDecision = (store, id, note, rating) => {
|
|
1529
|
+
if (note === void 0 && rating === void 0) return void 0;
|
|
1530
|
+
const decision = store.decision(id);
|
|
1531
|
+
const [run] = decision === void 0 ? [] : store.lookup(decision.runId);
|
|
1532
|
+
if (decision === void 0 || run === void 0) return {
|
|
1533
|
+
ok: false,
|
|
1534
|
+
error: `No decision ${id} to note.`
|
|
1535
|
+
};
|
|
1536
|
+
return recordNote(store, run, {
|
|
1537
|
+
text: note ?? `Rated ${rating ?? ""} at ${decision.question}`,
|
|
1538
|
+
node: decision.node,
|
|
1539
|
+
rating
|
|
1540
|
+
}, "--");
|
|
1541
|
+
};
|
|
1542
|
+
/** Why a note given with an answer can't be recorded, before answering. */
|
|
1543
|
+
const noteProblem = (note, rating) => {
|
|
1544
|
+
if (note === void 0 && rating === void 0) return void 0;
|
|
1545
|
+
const checked = checkNote({
|
|
1546
|
+
text: note ?? "rated",
|
|
1547
|
+
rating
|
|
1548
|
+
}, "--");
|
|
1549
|
+
return checked.ok ? void 0 : checked.error;
|
|
1550
|
+
};
|
|
1606
1551
|
/** One line per open decision, for `--list`. */
|
|
1607
1552
|
const formatDecisions = (decisions) => decisions.length === 0 ? ["No open decisions."] : decisions.map((d) => `${style("bold", d.id)} ${style("cyan", d.node)} ${d.question} ${style("dim", `[${d.options.map((o) => o.id === d.recommended ? `${o.id}*` : o.id).join(", ")}]`)}`);
|
|
1608
1553
|
const decide = defineCommand({
|
|
@@ -1638,12 +1583,20 @@ const decide = defineCommand({
|
|
|
1638
1583
|
choose: {
|
|
1639
1584
|
type: "string",
|
|
1640
1585
|
description: "…with this option"
|
|
1586
|
+
},
|
|
1587
|
+
note: {
|
|
1588
|
+
type: "string",
|
|
1589
|
+
description: "…and note how its node went; the note joins the run's feedback"
|
|
1590
|
+
},
|
|
1591
|
+
rating: {
|
|
1592
|
+
type: "string",
|
|
1593
|
+
description: "…rating it good or bad"
|
|
1641
1594
|
}
|
|
1642
1595
|
},
|
|
1643
1596
|
run: async ({ args }) => {
|
|
1644
1597
|
const refusal = args.id !== void 0 || args.choose !== void 0 || process.stdin.isTTY && !args.list && !args.json ? decideRefusal() : null;
|
|
1645
1598
|
if (refusal !== null) {
|
|
1646
|
-
failed(refusal);
|
|
1599
|
+
failed$2(refusal);
|
|
1647
1600
|
return;
|
|
1648
1601
|
}
|
|
1649
1602
|
const store = openStore();
|
|
@@ -1655,19 +1608,35 @@ const decide = defineCommand({
|
|
|
1655
1608
|
try {
|
|
1656
1609
|
if (args.id !== void 0 || args.choose !== void 0) {
|
|
1657
1610
|
if (args.id === void 0 || args.choose === void 0) {
|
|
1658
|
-
failed("--id and --choose go together.");
|
|
1611
|
+
failed$2("--id and --choose go together.");
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const problem = noteProblem(args.note, args.rating);
|
|
1615
|
+
if (problem !== void 0) {
|
|
1616
|
+
failed$2(problem);
|
|
1659
1617
|
return;
|
|
1660
1618
|
}
|
|
1661
1619
|
const answered = await answerDecision(store, args.id, args.choose);
|
|
1662
|
-
if (answered.ok)
|
|
1663
|
-
|
|
1620
|
+
if (!answered.ok) {
|
|
1621
|
+
failed$2(answered.error);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
write(`${style("green", "✓")} ${args.id}: ${args.choose}`);
|
|
1625
|
+
const noted = noteDecision(store, args.id, args.note, args.rating);
|
|
1626
|
+
if (noted === void 0) return;
|
|
1627
|
+
if (noted.ok) write(`${style("green", "✓")} noted in item ${noted.item}`);
|
|
1628
|
+
else failed$2(noted.error);
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
if (args.note !== void 0 || args.rating !== void 0) {
|
|
1632
|
+
failed$2("--note and --rating go with --id and --choose.");
|
|
1664
1633
|
return;
|
|
1665
1634
|
}
|
|
1666
1635
|
const filter = {};
|
|
1667
1636
|
if (args.run !== void 0 && args.run !== "") {
|
|
1668
1637
|
const found = findRun(store, args.run, process.cwd());
|
|
1669
1638
|
if (!found.ok) {
|
|
1670
|
-
failed(found.error);
|
|
1639
|
+
failed$2(found.error);
|
|
1671
1640
|
return;
|
|
1672
1641
|
}
|
|
1673
1642
|
filter.runId = found.run.id;
|
|
@@ -1700,6 +1669,17 @@ const decide = defineCommand({
|
|
|
1700
1669
|
}
|
|
1701
1670
|
}
|
|
1702
1671
|
});
|
|
1672
|
+
// src/commands/control.ts
|
|
1673
|
+
/**
|
|
1674
|
+
* @file `ork stop` and `ork resume`: stop a live run, or one of its nodes
|
|
1675
|
+
* (ADR-041), from any terminal (it writes to the store, then wakes the run on
|
|
1676
|
+
* its socket; specs/messaging.md), and continue a stopped or crashed run with
|
|
1677
|
+
* its recorded state (ADR-026). `ork decide` is in `decide.ts`.
|
|
1678
|
+
*/
|
|
1679
|
+
const failed$1 = (message) => {
|
|
1680
|
+
writeErr(message);
|
|
1681
|
+
process.exitCode = 1;
|
|
1682
|
+
};
|
|
1703
1683
|
const stop = defineCommand({
|
|
1704
1684
|
meta: {
|
|
1705
1685
|
name: "stop",
|
|
@@ -1721,13 +1701,13 @@ const stop = defineCommand({
|
|
|
1721
1701
|
try {
|
|
1722
1702
|
const found = findRun(store, args.run, process.cwd());
|
|
1723
1703
|
if (!found.ok) {
|
|
1724
|
-
failed(found.error);
|
|
1704
|
+
failed$1(found.error);
|
|
1725
1705
|
return;
|
|
1726
1706
|
}
|
|
1727
1707
|
const { run } = found;
|
|
1728
1708
|
const asked = await requestStop(store, run, void 0, args.node);
|
|
1729
1709
|
if (!asked.ok) {
|
|
1730
|
-
failed(asked.error);
|
|
1710
|
+
failed$1(asked.error);
|
|
1731
1711
|
return;
|
|
1732
1712
|
}
|
|
1733
1713
|
if (args.node !== void 0) {
|
|
@@ -1790,7 +1770,7 @@ const resume = defineCommand({
|
|
|
1790
1770
|
},
|
|
1791
1771
|
run: async ({ args, rawArgs }) => {
|
|
1792
1772
|
if (args.driver !== void 0 && !isDriver(args.driver)) {
|
|
1793
|
-
failed(`--driver must be sdk or cli, got \`${args.driver}\``);
|
|
1773
|
+
failed$1(`--driver must be sdk or cli, got \`${args.driver}\``);
|
|
1794
1774
|
return;
|
|
1795
1775
|
}
|
|
1796
1776
|
const store = openStore();
|
|
@@ -1802,14 +1782,14 @@ const resume = defineCommand({
|
|
|
1802
1782
|
...args.driver === void 0 ? {} : { driver: args.driver }
|
|
1803
1783
|
}) : found;
|
|
1804
1784
|
if (!found.ok || !ready.ok) {
|
|
1805
|
-
failed(ready.ok ? "" : ready.error);
|
|
1785
|
+
failed$1(ready.ok ? "" : ready.error);
|
|
1806
1786
|
return;
|
|
1807
1787
|
}
|
|
1808
1788
|
if (args.detach) {
|
|
1809
1789
|
const flags = rawArgs.filter((a) => a !== "--detach" && a !== args.run);
|
|
1810
1790
|
const detached = await detachResume(store, found.run, flags);
|
|
1811
1791
|
if (detached.ok) write(`▸ run ${found.run.id} resumed in the background`);
|
|
1812
|
-
else failed(`${detached.error}\n(output: ${detached.log})`);
|
|
1792
|
+
else failed$1(`${detached.error}\n(output: ${detached.log})`);
|
|
1813
1793
|
return;
|
|
1814
1794
|
}
|
|
1815
1795
|
const { exitCode } = await runWorkflowFile({
|
|
@@ -1852,22 +1832,20 @@ const resume = defineCommand({
|
|
|
1852
1832
|
*/
|
|
1853
1833
|
/** Set in the background copy of `ork` that only runs the check. */
|
|
1854
1834
|
const UPDATE_CHILD_ENV = "ORK_UPDATE_CHECK_CHILD";
|
|
1855
|
-
/**
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1835
|
+
/**
|
|
1836
|
+
* Why the automatic check is off for this command, or `null` when it is on: the
|
|
1837
|
+
* environment's switches (core's, which `ork ui` reads too), then where a
|
|
1838
|
+
* notice on stderr would go.
|
|
1839
|
+
*/
|
|
1859
1840
|
const updateCheckOff = (env, argv, terminal) => {
|
|
1860
|
-
|
|
1861
|
-
if (
|
|
1862
|
-
if (
|
|
1841
|
+
const switched = updateSwitchOff(env);
|
|
1842
|
+
if (switched !== null) return switched === "CI" ? "in CI" : "turned off";
|
|
1843
|
+
if (isSwitchedOn(env["ORK_DETACHED"]) || !terminal) return "no terminal";
|
|
1863
1844
|
if (argv.includes("--json")) return "JSON output";
|
|
1864
1845
|
if (argv[0] === "upgrade") return "ork upgrade checks for itself";
|
|
1846
|
+
if (argv[0] === "ui" && argv.includes("--serve")) return "ork ui's server checks for itself";
|
|
1865
1847
|
return null;
|
|
1866
1848
|
};
|
|
1867
|
-
const registryOf = (env) => {
|
|
1868
|
-
const registry = env[UPDATE_REGISTRY_ENV];
|
|
1869
|
-
return registry === void 0 || registry === "" ? void 0 : registry;
|
|
1870
|
-
};
|
|
1871
1849
|
/** The notice's text. */
|
|
1872
1850
|
const updateNotice = (latest, current) => `Ork ${latest} is out (you have ${current}). Upgrade with: ork upgrade`;
|
|
1873
1851
|
/**
|
|
@@ -1918,54 +1896,11 @@ const showUpdateNotice = (file = updateCachePath(), now = /* @__PURE__ */ new Da
|
|
|
1918
1896
|
};
|
|
1919
1897
|
// src/update/upgrade.ts
|
|
1920
1898
|
/**
|
|
1921
|
-
* @file
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
|
|
1926
|
-
*/
|
|
1927
|
-
const REPOSITORY = "https://github.com/mimshins/ork";
|
|
1928
|
-
/** The GitHub release page of a version, with its changes. */
|
|
1929
|
-
const releaseNotes = (name, version) => `${REPOSITORY}/releases/tag/${encodeURIComponent(`${name}@${version}`)}`;
|
|
1930
|
-
/** The running script's real path, which tells how Ork was installed. */
|
|
1931
|
-
const runningScript = () => {
|
|
1932
|
-
const script = process.argv[1] ?? "";
|
|
1933
|
-
try {
|
|
1934
|
-
return realpathSync(script);
|
|
1935
|
-
} catch {
|
|
1936
|
-
return script;
|
|
1937
|
-
}
|
|
1938
|
-
};
|
|
1939
|
-
const planUpgrade = (options) => {
|
|
1940
|
-
const { latest, name } = options;
|
|
1941
|
-
if (latest === null) return { kind: "unreachable" };
|
|
1942
|
-
if (!isNewer(latest, options.current)) return { kind: "latest" };
|
|
1943
|
-
const spec = `${name}@${latest}`;
|
|
1944
|
-
const installer = detectInstaller(options.script, options.installers);
|
|
1945
|
-
if (installer?.command !== void 0) return {
|
|
1946
|
-
kind: "command",
|
|
1947
|
-
latest,
|
|
1948
|
-
installer,
|
|
1949
|
-
argv: installer.command(spec)
|
|
1950
|
-
};
|
|
1951
|
-
return {
|
|
1952
|
-
kind: "advice",
|
|
1953
|
-
latest,
|
|
1954
|
-
text: installer?.advice?.(spec, options.script.replaceAll("\\", "/")) ?? `Ork could not tell how it was installed. With npm: npm install --global ${spec}`
|
|
1955
|
-
};
|
|
1956
|
-
};
|
|
1957
|
-
/** Runs the installer's command on this terminal; resolves its exit code. */
|
|
1958
|
-
const runInstall = (argv) => new Promise((resolve) => {
|
|
1959
|
-
const [program = "", ...args] = argv;
|
|
1960
|
-
const child = spawn(program, args, { stdio: "inherit" });
|
|
1961
|
-
child.on("error", (error) => {
|
|
1962
|
-
process.stderr.write(`Could not run ${program}: ${error.message}\n`);
|
|
1963
|
-
resolve(127);
|
|
1964
|
-
});
|
|
1965
|
-
child.on("close", (code) => {
|
|
1966
|
-
resolve(code ?? 1);
|
|
1967
|
-
});
|
|
1968
|
-
});
|
|
1899
|
+
* @file `ork doctor`'s line for Ork itself (ADR-038): the running version
|
|
1900
|
+
* against the last update check, read from the record without asking the
|
|
1901
|
+
* network. What `ork upgrade` does is core's (`planUpgrade`, `runInstall`),
|
|
1902
|
+
* shared with the server behind `ork ui`.
|
|
1903
|
+
*/
|
|
1969
1904
|
/** `ork doctor`'s line for Ork itself, from the last check. */
|
|
1970
1905
|
const versionCheck = (current, cache, checksOff) => {
|
|
1971
1906
|
const newer = newerVersion(cache, current);
|
|
@@ -2301,7 +2236,7 @@ const doctor = defineCommand({
|
|
|
2301
2236
|
const runningRuns = () => {
|
|
2302
2237
|
const store = openStore();
|
|
2303
2238
|
try {
|
|
2304
|
-
return store
|
|
2239
|
+
return runningRunIds(store).map((id) => id.slice(0, 8));
|
|
2305
2240
|
} finally {
|
|
2306
2241
|
store.close();
|
|
2307
2242
|
}
|
|
@@ -2914,33 +2849,6 @@ const migrateHere = async (cwd, options) => {
|
|
|
2914
2849
|
store.close();
|
|
2915
2850
|
}
|
|
2916
2851
|
};
|
|
2917
|
-
// src/project/worktrees.ts
|
|
2918
|
-
/** One line of state: where it is and what clean would care about. */
|
|
2919
|
-
const worktreeState = (item) => {
|
|
2920
|
-
const parts = [];
|
|
2921
|
-
if (item.path === null) parts.push("no worktree");
|
|
2922
|
-
else if (item.missing) parts.push("missing (prunable)");
|
|
2923
|
-
if (item.dirty) parts.push(style("yellow", "uncommitted changes"));
|
|
2924
|
-
const commits = item.merged ? style("green", "merged") : `${String(item.ahead)} commit(s) not merged`;
|
|
2925
|
-
if (item.checkedOut !== void 0) parts.push(`on the workflow's branch ${item.checkedOut}`, commits);
|
|
2926
|
-
else if (item.branch === void 0) parts.push(item.ahead > 0 ? style("yellow", `detached, ${String(item.ahead)} commit(s) on no branch`) : "detached, nothing committed");
|
|
2927
|
-
else parts.push(commits);
|
|
2928
|
-
if (item.inMainCheckout) parts.push("checked out here");
|
|
2929
|
-
return parts.join(" · ");
|
|
2930
|
-
};
|
|
2931
|
-
/** A worktree's name: its branch, or its run and node when created detached. */
|
|
2932
|
-
const labelOf = (item) => item.branch ?? `${item.runId}/${item.nodeId} (detached)`;
|
|
2933
|
-
const formatWorktrees = (items) => {
|
|
2934
|
-
if (items.length === 0) return ["No Ork worktrees or branches."];
|
|
2935
|
-
const width = Math.max(...items.map((i) => labelOf(i).length));
|
|
2936
|
-
return items.flatMap((item) => [`${style("bold", labelOf(item).padEnd(width))} ${worktreeState(item)}`, ...item.path === null ? [] : [` ${style("dim", item.path)}`]]);
|
|
2937
|
-
};
|
|
2938
|
-
const formatPlan = (steps) => steps.map((step) => {
|
|
2939
|
-
const actions = [...step.removeWorktree ? ["remove worktree"] : [], ...step.deleteBranch ? ["delete branch"] : []];
|
|
2940
|
-
const what = actions.length > 0 ? actions.join(" + ") : "keep";
|
|
2941
|
-
const why = step.skip === void 0 ? "" : style("dim", ` (${step.skip})`);
|
|
2942
|
-
return ` ${labelOf(step.item)}: ${what}${why}`;
|
|
2943
|
-
});
|
|
2944
2852
|
// src/commands/project-records.ts
|
|
2945
2853
|
/**
|
|
2946
2854
|
* @file `ork projects`' subcommands on one project's record (ADR-035): `forget`
|
|
@@ -3006,124 +2914,8 @@ const move = defineCommand({
|
|
|
3006
2914
|
* follows its moved clone (ADR-035); none writes into a repository. `ork
|
|
3007
2915
|
* migrate` moves an attached project into its repository, brings its agents
|
|
3008
2916
|
* and skills under `.ork/`, or drafts a workflow from its agents (ADR-036),
|
|
3009
|
-
* and commits nothing. `ork worktrees`
|
|
3010
|
-
|
|
3011
|
-
* rules named them, and any `ork/` branch), with their state read from git,
|
|
3012
|
-
* and cleans them up safely after showing the plan and asking.
|
|
3013
|
-
*/
|
|
3014
|
-
const projectRoot = async () => (await inspectProject(process.cwd()))?.root ?? null;
|
|
3015
|
-
/** What Ork created in the repo at `root`, from its runs' records and git. */
|
|
3016
|
-
const itemsIn = async (root) => {
|
|
3017
|
-
const store = openStore();
|
|
3018
|
-
try {
|
|
3019
|
-
return await listOrkWorktrees(root, createdWorktrees(store, root));
|
|
3020
|
-
} finally {
|
|
3021
|
-
store.close();
|
|
3022
|
-
}
|
|
3023
|
-
};
|
|
3024
|
-
const notGit = () => {
|
|
3025
|
-
process.stderr.write("Not in a git repository with a commit.\n");
|
|
3026
|
-
process.exitCode = 1;
|
|
3027
|
-
};
|
|
3028
|
-
const show = async (json) => {
|
|
3029
|
-
const root = await projectRoot();
|
|
3030
|
-
if (root === null) {
|
|
3031
|
-
notGit();
|
|
3032
|
-
return;
|
|
3033
|
-
}
|
|
3034
|
-
const items = await itemsIn(root);
|
|
3035
|
-
if (json) write(JSON.stringify(items, void 0, 2));
|
|
3036
|
-
else for (const line of formatWorktrees(items)) write(line);
|
|
3037
|
-
};
|
|
3038
|
-
const list = defineCommand({
|
|
3039
|
-
meta: {
|
|
3040
|
-
name: "list",
|
|
3041
|
-
description: "List Ork's worktrees and branches (the default)"
|
|
3042
|
-
},
|
|
3043
|
-
args: { json: {
|
|
3044
|
-
type: "boolean",
|
|
3045
|
-
description: "Print JSON",
|
|
3046
|
-
default: false
|
|
3047
|
-
} },
|
|
3048
|
-
run: ({ args }) => show(args.json)
|
|
3049
|
-
});
|
|
3050
|
-
const clean = defineCommand({
|
|
3051
|
-
meta: {
|
|
3052
|
-
name: "clean",
|
|
3053
|
-
description: "Remove Ork's worktrees (merged ones by default); never one with uncommitted changes unless --force"
|
|
3054
|
-
},
|
|
3055
|
-
args: {
|
|
3056
|
-
run: {
|
|
3057
|
-
type: "string",
|
|
3058
|
-
description: "Only this run's worktrees"
|
|
3059
|
-
},
|
|
3060
|
-
all: {
|
|
3061
|
-
type: "boolean",
|
|
3062
|
-
description: "Include worktrees whose branch is not merged",
|
|
3063
|
-
default: false
|
|
3064
|
-
},
|
|
3065
|
-
branches: {
|
|
3066
|
-
type: "boolean",
|
|
3067
|
-
description: "Also delete the branches",
|
|
3068
|
-
default: false
|
|
3069
|
-
},
|
|
3070
|
-
force: {
|
|
3071
|
-
type: "boolean",
|
|
3072
|
-
description: "Remove uncommitted changes and unmerged branches too",
|
|
3073
|
-
default: false
|
|
3074
|
-
},
|
|
3075
|
-
yes: {
|
|
3076
|
-
type: "boolean",
|
|
3077
|
-
description: "Do not ask",
|
|
3078
|
-
default: false
|
|
3079
|
-
}
|
|
3080
|
-
},
|
|
3081
|
-
run: async ({ args }) => {
|
|
3082
|
-
const root = await projectRoot();
|
|
3083
|
-
if (root === null) {
|
|
3084
|
-
notGit();
|
|
3085
|
-
return;
|
|
3086
|
-
}
|
|
3087
|
-
const filter = { merged: !args.all };
|
|
3088
|
-
if (args.run !== void 0) filter.runId = args.run;
|
|
3089
|
-
const steps = planClean(await itemsIn(root), filter, {
|
|
3090
|
-
branches: args.branches,
|
|
3091
|
-
force: args.force
|
|
3092
|
-
});
|
|
3093
|
-
const acting = steps.filter((s) => s.removeWorktree || s.deleteBranch);
|
|
3094
|
-
if (steps.length === 0 || acting.length === 0) {
|
|
3095
|
-
for (const line of formatPlan(steps)) write(line);
|
|
3096
|
-
write("Nothing to clean.");
|
|
3097
|
-
return;
|
|
3098
|
-
}
|
|
3099
|
-
write("Plan:");
|
|
3100
|
-
for (const line of formatPlan(steps)) write(line);
|
|
3101
|
-
if (!args.yes) {
|
|
3102
|
-
if (!process.stdin.isTTY) {
|
|
3103
|
-
process.stderr.write("Pass --yes to clean without a terminal.\n");
|
|
3104
|
-
process.exitCode = 1;
|
|
3105
|
-
return;
|
|
3106
|
-
}
|
|
3107
|
-
if (!await confirm("Go ahead? [y/N] ")) return;
|
|
3108
|
-
}
|
|
3109
|
-
for (const line of await applyClean(root, acting, args.force)) write(`${style("green", "✓")} ${line}`);
|
|
3110
|
-
}
|
|
3111
|
-
});
|
|
3112
|
-
const worktrees = defineCommand({
|
|
3113
|
-
meta: {
|
|
3114
|
-
name: "worktrees",
|
|
3115
|
-
description: "Track and clean up the git worktrees Ork creates for runs"
|
|
3116
|
-
},
|
|
3117
|
-
subCommands: {
|
|
3118
|
-
list,
|
|
3119
|
-
clean
|
|
3120
|
-
},
|
|
3121
|
-
run: async ({ rawArgs }) => {
|
|
3122
|
-
const [first] = rawArgs;
|
|
3123
|
-
if (first === "list" || first === "clean") return;
|
|
3124
|
-
await show(rawArgs.includes("--json"));
|
|
3125
|
-
}
|
|
3126
|
-
});
|
|
2917
|
+
* and commits nothing. `ork worktrees` is worktrees.ts.
|
|
2918
|
+
*/
|
|
3127
2919
|
const json$2 = {
|
|
3128
2920
|
type: "boolean",
|
|
3129
2921
|
description: "Print JSON",
|
|
@@ -3220,6 +3012,20 @@ const migrate = defineCommand({
|
|
|
3220
3012
|
}, (file) => formatReport(validateFile(file, process.cwd())));
|
|
3221
3013
|
}
|
|
3222
3014
|
});
|
|
3015
|
+
// src/store.ts
|
|
3016
|
+
/**
|
|
3017
|
+
* @file Opening the user's store (`~/.ork/state.db`) for the length of one
|
|
3018
|
+
* command, and closing it after, whatever happens.
|
|
3019
|
+
*/
|
|
3020
|
+
/** Runs `body` with the user's store open, and closes it after. */
|
|
3021
|
+
const withStore = async (body) => {
|
|
3022
|
+
const store = openStore();
|
|
3023
|
+
try {
|
|
3024
|
+
await body(store);
|
|
3025
|
+
} finally {
|
|
3026
|
+
store.close();
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3223
3029
|
// src/runs/feedback.ts
|
|
3224
3030
|
/**
|
|
3225
3031
|
* @file `ork feedback`'s helpers (specs/feedback.md): finding an item by a
|
|
@@ -3269,6 +3075,7 @@ const formatFeedbackItem = (item, records, now = Date.now()) => {
|
|
|
3269
3075
|
];
|
|
3270
3076
|
if (item.workflows.length > 0) lines.push(`Workflows: ${item.workflows.join(", ")}`);
|
|
3271
3077
|
if (item.models.length > 0) lines.push(`Models: ${item.models.join(", ")}`);
|
|
3078
|
+
if (item.merged.length > 0) lines.push(`Merged into it: ${item.merged.map((id) => id.slice(0, 12)).join(", ")} (ork feedback split ${item.id.slice(0, 12)} takes them apart)`);
|
|
3272
3079
|
for (const record of records) {
|
|
3273
3080
|
const { feedback } = record;
|
|
3274
3081
|
const where = record.node === void 0 ? "" : ` · ${record.node}`;
|
|
@@ -3279,181 +3086,533 @@ const formatFeedbackItem = (item, records, now = Date.now()) => {
|
|
|
3279
3086
|
}
|
|
3280
3087
|
return lines;
|
|
3281
3088
|
};
|
|
3282
|
-
const oneOf = (name, value, allowed) => {
|
|
3283
|
-
if (value === void 0) return { ok: true };
|
|
3284
|
-
const found = allowed.find((a) => a === value);
|
|
3285
|
-
return found === void 0 ? {
|
|
3286
|
-
ok: false,
|
|
3287
|
-
error: `--${name} is one of ${allowed.join(", ")}`
|
|
3288
|
-
} : {
|
|
3289
|
-
ok: true,
|
|
3290
|
-
value: found
|
|
3291
|
-
};
|
|
3292
|
-
};
|
|
3293
3089
|
/**
|
|
3294
3090
|
* `ork feedback note`: records a person's note on a run, or one of its nodes,
|
|
3295
3091
|
* as that run's feedback; returns the item it joins, or why it could not.
|
|
3296
3092
|
*/
|
|
3297
3093
|
const noteRun = (store, cwd, args) => {
|
|
3298
|
-
|
|
3094
|
+
const found = findRun(store, args.run, cwd);
|
|
3095
|
+
if (!found.ok) return found;
|
|
3096
|
+
return recordNote(store, found.run, args, "--");
|
|
3097
|
+
};
|
|
3098
|
+
// src/runs/apply.ts
|
|
3099
|
+
/**
|
|
3100
|
+
* @file `ork feedback apply` (ADR-059): runs Ork's own `ork:feedback-apply` in
|
|
3101
|
+
* the project an item came from, with the item as its untrusted input. Its
|
|
3102
|
+
* worker proposes a fix on a branch and its gate asks to keep or discard it;
|
|
3103
|
+
* Ork never merges. Kept, the item is `resolved`; discarded, the run's
|
|
3104
|
+
* worktree and branch go and the item stays `accepted`.
|
|
3105
|
+
*/
|
|
3106
|
+
/** The most of an item's records the worker reads, newest first. */
|
|
3107
|
+
const RECORDS = 10;
|
|
3108
|
+
/** The most of a record's body the worker reads. */
|
|
3109
|
+
const BODY_CHARS = 1500;
|
|
3110
|
+
const BUILTIN = "ork:feedback-apply";
|
|
3111
|
+
/** An item and its newest records, as the worker reads them. */
|
|
3112
|
+
const aboutItem = (item, records) => {
|
|
3113
|
+
const newest = records.toReversed().slice(0, RECORDS);
|
|
3114
|
+
const lines = [`Item ${item.id.slice(0, 12)}: ${item.title}`, `About: ${item.target} (${item.kind}), severity ${item.severity}; seen ${String(item.count)} time(s) in ${String(item.runs.length)} run(s).`];
|
|
3115
|
+
if (item.workflows.length > 0) lines.push(`Workflows: ${item.workflows.join(", ")}`);
|
|
3116
|
+
for (const record of newest) {
|
|
3117
|
+
const { feedback } = record;
|
|
3118
|
+
const where = record.node === void 0 ? "" : `, node ${record.node}`;
|
|
3119
|
+
lines.push("", `Record from run ${record.runId}${where} (${feedback.source}):`);
|
|
3120
|
+
if (feedback.title !== item.title) lines.push(feedback.title);
|
|
3121
|
+
if (feedback.body !== "") lines.push(feedback.body.slice(0, BODY_CHARS));
|
|
3122
|
+
if (feedback.evidence.length > 0) lines.push(`Evidence: ${feedback.evidence.join(", ")}`);
|
|
3123
|
+
}
|
|
3124
|
+
if (records.length > newest.length) lines.push("", `(${String(records.length - newest.length)} older record(s) left out.)`);
|
|
3125
|
+
return lines.join("\n");
|
|
3126
|
+
};
|
|
3127
|
+
const text = (value) => typeof value === "string" ? value : "";
|
|
3128
|
+
/** What a run of `ork:feedback-apply` ended with. */
|
|
3129
|
+
const outcomeOf = (result) => {
|
|
3130
|
+
const node = (id) => result?.nodes.find((n) => n.id === id);
|
|
3131
|
+
const fix = node("fix");
|
|
3132
|
+
const choice = text(node("keep")?.output?.["choice"]);
|
|
3133
|
+
const branch = text(fix?.output?.["branch"]);
|
|
3134
|
+
const summary = text(fix?.output?.["summary"]);
|
|
3135
|
+
if (choice === "keep") return {
|
|
3136
|
+
kind: "kept",
|
|
3137
|
+
branch,
|
|
3138
|
+
summary
|
|
3139
|
+
};
|
|
3140
|
+
if (choice === "discard") return {
|
|
3141
|
+
kind: "discarded",
|
|
3142
|
+
branch
|
|
3143
|
+
};
|
|
3144
|
+
if (fix?.ok === false && result?.status === "failed") return {
|
|
3145
|
+
kind: "nothing",
|
|
3146
|
+
summary: summary || (fix.error ?? "")
|
|
3147
|
+
};
|
|
3148
|
+
return {
|
|
3149
|
+
kind: "unfinished",
|
|
3150
|
+
reasons: result?.reasons ?? []
|
|
3151
|
+
};
|
|
3152
|
+
};
|
|
3153
|
+
/**
|
|
3154
|
+
* Removes what a run left in the repository at `root`: its worktrees and
|
|
3155
|
+
* branches.
|
|
3156
|
+
*/
|
|
3157
|
+
const discardRun = async (store, root, runId) => {
|
|
3158
|
+
const items = await listOrkWorktrees(root, createdWorktrees(store, root));
|
|
3159
|
+
const steps = planClean(items, {
|
|
3160
|
+
merged: false,
|
|
3161
|
+
runId
|
|
3162
|
+
}, {
|
|
3163
|
+
branches: true,
|
|
3164
|
+
force: true
|
|
3165
|
+
});
|
|
3166
|
+
return applyClean(root, steps.filter((s) => s.removeWorktree || s.deleteBranch), true);
|
|
3167
|
+
};
|
|
3168
|
+
/** How many commits the run's worktrees hold that the current branch hasn't. */
|
|
3169
|
+
const commitsAhead = async (store, root, runId) => (await listOrkWorktrees(root, createdWorktrees(store, root))).filter((item) => item.runId === runId).reduce((sum, item) => sum + item.ahead, 0);
|
|
3170
|
+
/** What to say, and the item's status, after the run ends as `outcome`. */
|
|
3171
|
+
const settle = async (options, root, runId, outcome) => {
|
|
3172
|
+
const { store, item } = options;
|
|
3173
|
+
const short = item.id.slice(0, 12);
|
|
3174
|
+
switch (outcome.kind) {
|
|
3175
|
+
case "kept":
|
|
3176
|
+
if (await (options.ahead ?? commitsAhead)(store, root, runId) === 0) {
|
|
3177
|
+
const done = await discardRun(store, root, runId);
|
|
3178
|
+
return [`the fix's branch holds no commits, so there's nothing to keep; ${short} stays accepted`, ...done];
|
|
3179
|
+
}
|
|
3180
|
+
store.setFeedbackStatus(item.id, "resolved");
|
|
3181
|
+
return [`kept the fix on ${outcome.branch}; ${short} is resolved`, "Review and merge it like any other change; Ork never merges it."];
|
|
3182
|
+
case "discarded": {
|
|
3183
|
+
const done = await discardRun(store, root, runId);
|
|
3184
|
+
return [`discarded the fix; ${short} stays accepted`, ...done];
|
|
3185
|
+
}
|
|
3186
|
+
case "nothing": return [`no fix proposed; ${short} stays accepted`, ...outcome.summary === "" ? [] : [outcome.summary]];
|
|
3187
|
+
case "unfinished": return [`run ${runId} didn't finish, so ${short} stays as it was`, ...outcome.reasons];
|
|
3188
|
+
default: return outcome;
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3191
|
+
/** Proposes a fix for an item, in the project it came from. */
|
|
3192
|
+
const applyItem = async (options) => {
|
|
3193
|
+
const { store, item } = options;
|
|
3194
|
+
const short = item.id.slice(0, 12);
|
|
3195
|
+
const allowed = feedbackActions(item, openFixRun(store, item)).apply;
|
|
3196
|
+
if (!allowed.ok) return {
|
|
3299
3197
|
ok: false,
|
|
3300
|
-
error:
|
|
3198
|
+
error: allowed.error
|
|
3301
3199
|
};
|
|
3302
|
-
const found =
|
|
3200
|
+
const found = placeOf(store, item, void 0, options.registered);
|
|
3303
3201
|
if (!found.ok) return found;
|
|
3304
|
-
const {
|
|
3305
|
-
const
|
|
3306
|
-
|
|
3202
|
+
const { place, project } = found;
|
|
3203
|
+
const problem = builtinProblem(place, BUILTIN);
|
|
3204
|
+
const file = builtinWorkflowFile(place, BUILTIN);
|
|
3205
|
+
if (problem !== void 0 || file === void 0) return {
|
|
3307
3206
|
ok: false,
|
|
3308
|
-
error:
|
|
3309
|
-
};
|
|
3310
|
-
const
|
|
3311
|
-
|
|
3312
|
-
const
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
...rating.ok && rating.value !== void 0 ? { rating: rating.value } : {}
|
|
3207
|
+
error: problem ?? `${BUILTIN} couldn't be written.`
|
|
3208
|
+
};
|
|
3209
|
+
const records = store.feedbackRecords(item.id).filter((r) => store.lookup(r.runId)[0]?.project === project);
|
|
3210
|
+
options.out(`▸ proposing a fix for ${short} in ${place.root}`);
|
|
3211
|
+
const { result } = await runWorkflowFile({
|
|
3212
|
+
...options,
|
|
3213
|
+
file,
|
|
3214
|
+
cwd: place.root,
|
|
3215
|
+
inputs: [`item=${short}`, `about=${aboutItem(item, records)}`],
|
|
3216
|
+
untrusted: ["about"],
|
|
3217
|
+
inPlace: false,
|
|
3218
|
+
json: false,
|
|
3219
|
+
onRun: (handle) => {
|
|
3220
|
+
store.setFeedbackStatus(item.id, "accepted");
|
|
3221
|
+
options.onRun?.(handle);
|
|
3222
|
+
}
|
|
3325
3223
|
});
|
|
3326
|
-
|
|
3224
|
+
if (result === null) return {
|
|
3225
|
+
ok: false,
|
|
3226
|
+
error: `${BUILTIN} didn't start.`
|
|
3227
|
+
};
|
|
3228
|
+
const outcome = outcomeOf(result);
|
|
3327
3229
|
return {
|
|
3328
3230
|
ok: true,
|
|
3329
|
-
|
|
3231
|
+
outcome,
|
|
3232
|
+
lines: await settle(options, place.root, result.runId, outcome)
|
|
3330
3233
|
};
|
|
3331
3234
|
};
|
|
3332
|
-
// src/
|
|
3235
|
+
// src/runs/triage.ts
|
|
3333
3236
|
/**
|
|
3334
|
-
* @file `ork
|
|
3335
|
-
*
|
|
3336
|
-
*
|
|
3237
|
+
* @file `ork feedback triage` (ADR-059): the merges Ork's rules propose among
|
|
3238
|
+
* unsettled items, each made only when the person says yes, then every new
|
|
3239
|
+
* item marked `triaged`, so a declined merge isn't asked again until a new
|
|
3240
|
+
* item joins it. `ork feedback split` takes a merge apart.
|
|
3337
3241
|
*/
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3242
|
+
const lineOf = (item) => `${item.id.slice(0, 12)} ${`${String(item.count)}×`.padStart(4)} ${item.title}`;
|
|
3243
|
+
/** A proposed merge as the person reads it before saying yes. */
|
|
3244
|
+
const formatMerge = (merge) => [`Merge into ${lineOf(merge.into)}`, ...merge.from.map((item) => ` ${lineOf(item)}`)];
|
|
3245
|
+
/**
|
|
3246
|
+
* Proposes each merge, makes those `ask` says yes to, and marks every item that
|
|
3247
|
+
* was new `triaged`.
|
|
3248
|
+
*/
|
|
3249
|
+
const triage$1 = async (store, ask) => {
|
|
3250
|
+
const items = store.feedbackItems();
|
|
3251
|
+
const fresh = items.filter((item) => item.status === "new");
|
|
3252
|
+
let merged = 0;
|
|
3253
|
+
let declined = 0;
|
|
3254
|
+
for (const merge of proposeMerges(items)) if (await ask(merge)) merged += store.mergeFeedback(merge.into.id, merge.from.map((item) => item.id));
|
|
3255
|
+
else declined += 1;
|
|
3256
|
+
for (const item of fresh) store.setFeedbackStatus(item.id, "triaged");
|
|
3257
|
+
return {
|
|
3258
|
+
merged,
|
|
3259
|
+
declined,
|
|
3260
|
+
triaged: fresh.length
|
|
3261
|
+
};
|
|
3346
3262
|
};
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
type: "boolean",
|
|
3364
|
-
description: "Print JSON",
|
|
3365
|
-
default: false
|
|
3366
|
-
}
|
|
3367
|
-
},
|
|
3368
|
-
run: ({ args }) => withStore((store) => {
|
|
3369
|
-
const limit = Number(args.limit ?? 20);
|
|
3370
|
-
const runs = store.runs({
|
|
3371
|
-
limit: Number.isInteger(limit) && limit > 0 ? limit : 20,
|
|
3372
|
-
...args.all ? {} : { project: placeFor(process.cwd(), store.projects()).root }
|
|
3373
|
-
});
|
|
3374
|
-
const statusOf = (run) => runStatus(run, run.status === "running" ? store.events(run.id).map((s) => s.event) : []);
|
|
3375
|
-
if (args.json) write(JSON.stringify(runs, void 0, 2));
|
|
3376
|
-
else for (const line of formatRuns(runs, args.all, Date.now(), statusOf)) write(line);
|
|
3377
|
-
})
|
|
3378
|
-
});
|
|
3379
|
-
const runArg = {
|
|
3263
|
+
/** What triage did, in a line. */
|
|
3264
|
+
const triageSummary = ({ merged, declined, triaged }) => {
|
|
3265
|
+
return [
|
|
3266
|
+
merged === 0 ? "nothing merged" : `merged ${String(merged)} item(s)`,
|
|
3267
|
+
...declined === 0 ? [] : [`${String(declined)} merge(s) declined`],
|
|
3268
|
+
`${String(triaged)} new item(s) triaged`
|
|
3269
|
+
].join("; ");
|
|
3270
|
+
};
|
|
3271
|
+
// src/commands/feedback-loop.ts
|
|
3272
|
+
/**
|
|
3273
|
+
* @file The `ork feedback` subcommands that close the loop (ADR-059): `apply`
|
|
3274
|
+
* runs Ork's own workflow to propose a fix for an item, `triage` merges items
|
|
3275
|
+
* about the same problem by rules after asking, and `split` takes a merge
|
|
3276
|
+
* apart.
|
|
3277
|
+
*/
|
|
3278
|
+
const itemArg$1 = {
|
|
3380
3279
|
type: "positional",
|
|
3381
|
-
description: "A
|
|
3382
|
-
required:
|
|
3280
|
+
description: "A feedback item's id, or a prefix of one",
|
|
3281
|
+
required: true
|
|
3383
3282
|
};
|
|
3384
|
-
const
|
|
3283
|
+
const apply = defineCommand({
|
|
3385
3284
|
meta: {
|
|
3386
|
-
name: "
|
|
3387
|
-
description: "
|
|
3285
|
+
name: "apply",
|
|
3286
|
+
description: "Have Ork propose a fix for an item on a branch, then keep or discard it"
|
|
3388
3287
|
},
|
|
3389
3288
|
args: {
|
|
3390
|
-
|
|
3391
|
-
|
|
3289
|
+
item: itemArg$1,
|
|
3290
|
+
driver: {
|
|
3392
3291
|
type: "string",
|
|
3393
|
-
description: "
|
|
3394
|
-
},
|
|
3395
|
-
follow: {
|
|
3396
|
-
type: "boolean",
|
|
3397
|
-
alias: "f",
|
|
3398
|
-
description: "Keep printing a live run's events until it ends",
|
|
3399
|
-
default: false
|
|
3292
|
+
description: "sdk (API key or cloud provider) or cli (your claude login)"
|
|
3400
3293
|
},
|
|
3401
|
-
|
|
3402
|
-
type: "
|
|
3403
|
-
description: "
|
|
3404
|
-
|
|
3294
|
+
"run-id": {
|
|
3295
|
+
type: "string",
|
|
3296
|
+
description: "Internal: the id `ork ui` picked for the run"
|
|
3297
|
+
}
|
|
3298
|
+
},
|
|
3299
|
+
run: async ({ args }) => {
|
|
3300
|
+
if (args.driver !== void 0 && !isDriver(args.driver)) {
|
|
3301
|
+
writeErr(`--driver must be sdk or cli, got \`${args.driver}\``);
|
|
3302
|
+
process.exitCode = 1;
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
const driver = args.driver;
|
|
3306
|
+
const store = openStore();
|
|
3307
|
+
const human = terminalHuman();
|
|
3308
|
+
try {
|
|
3309
|
+
const found = findFeedbackItem(store, args.item);
|
|
3310
|
+
if (!found.ok) {
|
|
3311
|
+
writeErr(found.error);
|
|
3312
|
+
process.exitCode = 1;
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
const applied = await applyItem({
|
|
3316
|
+
store,
|
|
3317
|
+
item: found.item,
|
|
3318
|
+
registered: registeredProjects(),
|
|
3319
|
+
runtime: loadRuntime,
|
|
3320
|
+
...human === void 0 ? {} : { human },
|
|
3321
|
+
...driver === void 0 ? {} : { driver },
|
|
3322
|
+
...args["run-id"] === void 0 ? {} : { runId: args["run-id"] },
|
|
3323
|
+
out: write,
|
|
3324
|
+
err: writeErr,
|
|
3325
|
+
onRun: (handle) => {
|
|
3326
|
+
onInterrupt(handle.interrupt);
|
|
3327
|
+
}
|
|
3328
|
+
});
|
|
3329
|
+
if (!applied.ok) {
|
|
3330
|
+
writeErr(applied.error);
|
|
3331
|
+
process.exitCode = 1;
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
const [first, ...rest] = applied.lines;
|
|
3335
|
+
const mark = applied.outcome.kind === "kept" ? style("green", "✓") : style("yellow", "·");
|
|
3336
|
+
write(`${mark} ${first ?? ""}`);
|
|
3337
|
+
for (const line of rest) write(` ${line}`);
|
|
3338
|
+
if (applied.outcome.kind === "unfinished") process.exitCode = 1;
|
|
3339
|
+
} finally {
|
|
3340
|
+
human?.close();
|
|
3341
|
+
store.close();
|
|
3405
3342
|
}
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
const triage = defineCommand({
|
|
3346
|
+
meta: {
|
|
3347
|
+
name: "triage",
|
|
3348
|
+
description: "Merge new items about the same problem as others, asking about each"
|
|
3406
3349
|
},
|
|
3350
|
+
args: { yes: {
|
|
3351
|
+
type: "boolean",
|
|
3352
|
+
description: "Make every merge without asking",
|
|
3353
|
+
default: false
|
|
3354
|
+
} },
|
|
3407
3355
|
run: ({ args }) => withStore(async (store) => {
|
|
3408
|
-
|
|
3356
|
+
if (!args.yes && !process.stdin.isTTY) {
|
|
3357
|
+
writeErr("Pass --yes to triage without a terminal.");
|
|
3358
|
+
process.exitCode = 1;
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3361
|
+
const done = await triage$1(store, async (merge) => {
|
|
3362
|
+
for (const line of formatMerge(merge)) write(line);
|
|
3363
|
+
return args.yes || await confirm("Merge them? [y/N] ");
|
|
3364
|
+
});
|
|
3365
|
+
write(`${style("green", "✓")} ${triageSummary(done)}`);
|
|
3366
|
+
})
|
|
3367
|
+
});
|
|
3368
|
+
const split = defineCommand({
|
|
3369
|
+
meta: {
|
|
3370
|
+
name: "split",
|
|
3371
|
+
description: "Take apart the items triage merged into one"
|
|
3372
|
+
},
|
|
3373
|
+
args: { item: itemArg$1 },
|
|
3374
|
+
run: ({ args }) => withStore((store) => {
|
|
3375
|
+
const found = findFeedbackItem(store, args.item);
|
|
3409
3376
|
if (!found.ok) {
|
|
3410
3377
|
writeErr(found.error);
|
|
3411
3378
|
process.exitCode = 1;
|
|
3412
3379
|
return;
|
|
3413
3380
|
}
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
|
|
3381
|
+
const moved = store.splitFeedback(found.item.id);
|
|
3382
|
+
write(moved === 0 ? `Nothing was merged into ${found.item.id.slice(0, 12)}.` : `${style("green", "✓")} split ${String(moved)} item(s) back out of ${found.item.id.slice(0, 12)}`);
|
|
3383
|
+
})
|
|
3384
|
+
});
|
|
3385
|
+
// src/runs/jsonl.ts
|
|
3386
|
+
/**
|
|
3387
|
+
* @file `ork feedback export` and `import` (specs/feedback.md): feedback
|
|
3388
|
+
* records as JSON lines, one record a line, so a team can move feedback
|
|
3389
|
+
* between machines without GitHub. A line is checked before it's added, and a
|
|
3390
|
+
* record already here (its run and event) is skipped.
|
|
3391
|
+
*/
|
|
3392
|
+
const Text = z.string();
|
|
3393
|
+
const LineSchema = z.strictObject({
|
|
3394
|
+
feedbackVersion: z.literal(1),
|
|
3395
|
+
runId: z.string().min(1),
|
|
3396
|
+
seq: z.number().int().nonnegative(),
|
|
3397
|
+
at: z.string().min(1),
|
|
3398
|
+
node: z.string().min(1).optional(),
|
|
3399
|
+
feedback: z.strictObject({
|
|
3400
|
+
source: z.enum([
|
|
3401
|
+
"system",
|
|
3402
|
+
"agent",
|
|
3403
|
+
"human"
|
|
3404
|
+
]),
|
|
3405
|
+
kind: z.enum(FEEDBACK_KINDS),
|
|
3406
|
+
target: z.enum(FEEDBACK_TARGETS),
|
|
3407
|
+
severity: z.enum(FEEDBACK_SEVERITIES),
|
|
3408
|
+
title: Text.min(1),
|
|
3409
|
+
body: Text,
|
|
3410
|
+
evidence: z.array(Text),
|
|
3411
|
+
code: Text.optional(),
|
|
3412
|
+
fingerprint: z.string().regex(/^[0-9a-f]{8,64}$/u),
|
|
3413
|
+
origin: z.strictObject({
|
|
3414
|
+
workflow: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/u).optional(),
|
|
3415
|
+
workflowHash: Text.optional(),
|
|
3416
|
+
nodeType: Text.optional(),
|
|
3417
|
+
model: Text.optional(),
|
|
3418
|
+
driver: Text.optional(),
|
|
3419
|
+
version: Text.optional()
|
|
3420
|
+
})
|
|
3421
|
+
})
|
|
3422
|
+
});
|
|
3423
|
+
/** Records as JSON lines, with the format's version on each. */
|
|
3424
|
+
const exportLines = (records) => records.map(({ id: _id, ...record }) => JSON.stringify({
|
|
3425
|
+
feedbackVersion: 1,
|
|
3426
|
+
...record
|
|
3427
|
+
})).map((line) => `${line}\n`).join("");
|
|
3428
|
+
/** The records in a JSONL file's text, and what was wrong with the rest. */
|
|
3429
|
+
const parseLines = (text) => {
|
|
3430
|
+
const records = [];
|
|
3431
|
+
const problems = [];
|
|
3432
|
+
for (const [index, line] of text.split("\n").entries()) {
|
|
3433
|
+
if (line.trim() === "") continue;
|
|
3434
|
+
let value;
|
|
3435
|
+
try {
|
|
3436
|
+
value = JSON.parse(line);
|
|
3437
|
+
} catch {
|
|
3438
|
+
problems.push(`line ${String(index + 1)}: not JSON`);
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
const parsed = LineSchema.safeParse(value);
|
|
3442
|
+
if (!parsed.success) {
|
|
3443
|
+
const [issue] = parsed.error.issues;
|
|
3444
|
+
problems.push(`line ${String(index + 1)}: ${issue?.path.join(".") ?? ""} ${issue?.message ?? "isn't a feedback record"}`.trim());
|
|
3445
|
+
continue;
|
|
3446
|
+
}
|
|
3447
|
+
const { feedbackVersion: _version, node, feedback, ...rest } = parsed.data;
|
|
3448
|
+
const { code, origin, ...report } = feedback;
|
|
3449
|
+
records.push({
|
|
3450
|
+
...rest,
|
|
3451
|
+
...node === void 0 ? {} : { node },
|
|
3452
|
+
feedback: {
|
|
3453
|
+
...report,
|
|
3454
|
+
...code === void 0 ? {} : { code },
|
|
3455
|
+
origin: Object.fromEntries(Object.entries(origin).filter(([, v]) => v !== void 0))
|
|
3456
|
+
}
|
|
3417
3457
|
});
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3458
|
+
}
|
|
3459
|
+
return {
|
|
3460
|
+
records,
|
|
3461
|
+
problems
|
|
3462
|
+
};
|
|
3463
|
+
};
|
|
3464
|
+
// src/runs/publish.ts
|
|
3465
|
+
/** What will be sent, as the person reads it before saying yes. */
|
|
3466
|
+
const formatPreview = (post, plan) => plan.kind === "comment" ? [
|
|
3467
|
+
`A thread for this exists (${plan.url}), so this comment goes on it:`,
|
|
3468
|
+
"",
|
|
3469
|
+
post.comment
|
|
3470
|
+
] : [
|
|
3471
|
+
"A new thread in the Agent feedback category of mimshins/ork:",
|
|
3472
|
+
"",
|
|
3473
|
+
`Title: ${post.title}`,
|
|
3474
|
+
"",
|
|
3475
|
+
post.body
|
|
3476
|
+
];
|
|
3477
|
+
// src/commands/feedback-share.ts
|
|
3478
|
+
/**
|
|
3479
|
+
* @file The `ork feedback` subcommands that move feedback off this machine,
|
|
3480
|
+
* only when the person says so (ADR-059): `publish` shares an item about Ork
|
|
3481
|
+
* with its maintainers, through the person's own `gh`, after showing exactly
|
|
3482
|
+
* what will be posted; `export` and `import` move records as JSON lines.
|
|
3483
|
+
*/
|
|
3484
|
+
const failed = (message) => {
|
|
3485
|
+
writeErr(message);
|
|
3486
|
+
process.exitCode = 1;
|
|
3487
|
+
};
|
|
3488
|
+
const publish = defineCommand({
|
|
3489
|
+
meta: {
|
|
3490
|
+
name: "publish",
|
|
3491
|
+
description: "Share an item about Ork with its maintainers, after showing exactly what goes"
|
|
3492
|
+
},
|
|
3493
|
+
args: {
|
|
3494
|
+
item: {
|
|
3495
|
+
type: "positional",
|
|
3496
|
+
description: "A feedback item's id, or a prefix of one",
|
|
3497
|
+
required: true
|
|
3498
|
+
},
|
|
3499
|
+
"hide-names": {
|
|
3500
|
+
type: "boolean",
|
|
3501
|
+
description: "Also hide the repositories' and workflows' names",
|
|
3502
|
+
default: false
|
|
3503
|
+
}
|
|
3504
|
+
},
|
|
3505
|
+
run: ({ args }) => withStore(async (store) => {
|
|
3506
|
+
if (!process.stdin.isTTY) {
|
|
3507
|
+
failed("publish shows what it will post and asks you first: run it in a terminal.");
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
const found = findFeedbackItem(store, args.item);
|
|
3511
|
+
if (!found.ok) {
|
|
3512
|
+
failed(found.error);
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const preview = previewPost(store, found.item, {
|
|
3516
|
+
home: homedir(),
|
|
3517
|
+
registered: registeredProjects(),
|
|
3518
|
+
hideNames: args["hide-names"]
|
|
3423
3519
|
});
|
|
3520
|
+
if (!preview.ok) {
|
|
3521
|
+
failed(preview.error);
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
const planned = await planPost(preview.post);
|
|
3525
|
+
if (!planned.ok) {
|
|
3526
|
+
failed(planned.error);
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
for (const line of formatPreview(preview.post, planned.plan)) write(line);
|
|
3530
|
+
write("");
|
|
3531
|
+
if (!await confirm("Post it with your own gh login? [y/N] ")) {
|
|
3532
|
+
write("Nothing was sent.");
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const sent = await sendPost(preview.post, planned.plan);
|
|
3536
|
+
if (!sent.ok) {
|
|
3537
|
+
failed(sent.error);
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
store.setFeedbackStatus(found.item.id, "published");
|
|
3541
|
+
write(`${style("green", "✓")} ${sent.kind === "created" ? "posted" : "added a +1 to"} ${sent.url}`);
|
|
3424
3542
|
})
|
|
3425
3543
|
});
|
|
3426
|
-
const
|
|
3544
|
+
const exportFeedback = defineCommand({
|
|
3427
3545
|
meta: {
|
|
3428
|
-
name: "
|
|
3429
|
-
description: "
|
|
3546
|
+
name: "export",
|
|
3547
|
+
description: "Write feedback records as JSON lines, to share them"
|
|
3430
3548
|
},
|
|
3431
3549
|
args: {
|
|
3432
|
-
|
|
3433
|
-
json: {
|
|
3550
|
+
all: {
|
|
3434
3551
|
type: "boolean",
|
|
3435
|
-
description: "
|
|
3552
|
+
description: "Dismissed items' records too",
|
|
3436
3553
|
default: false
|
|
3554
|
+
},
|
|
3555
|
+
output: {
|
|
3556
|
+
type: "string",
|
|
3557
|
+
alias: "o",
|
|
3558
|
+
description: "A file to write (default: the terminal)"
|
|
3437
3559
|
}
|
|
3438
3560
|
},
|
|
3439
3561
|
run: ({ args }) => withStore((store) => {
|
|
3440
|
-
const
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
process.
|
|
3562
|
+
const records = store.exportFeedback({ all: args.all });
|
|
3563
|
+
const text = exportLines(records);
|
|
3564
|
+
if (args.output === void 0) {
|
|
3565
|
+
process.stdout.write(text);
|
|
3444
3566
|
return;
|
|
3445
3567
|
}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3568
|
+
writeFileSync(args.output, text);
|
|
3569
|
+
write(`${style("green", "✓")} wrote ${String(records.length)} record(s) to ${args.output}`);
|
|
3570
|
+
})
|
|
3571
|
+
});
|
|
3572
|
+
const importFeedback = defineCommand({
|
|
3573
|
+
meta: {
|
|
3574
|
+
name: "import",
|
|
3575
|
+
description: "Add feedback records from a JSON-lines file"
|
|
3576
|
+
},
|
|
3577
|
+
args: { file: {
|
|
3578
|
+
type: "positional",
|
|
3579
|
+
description: "What `ork feedback export` wrote",
|
|
3580
|
+
required: true
|
|
3581
|
+
} },
|
|
3582
|
+
run: ({ args }) => withStore((store) => {
|
|
3583
|
+
let text;
|
|
3584
|
+
try {
|
|
3585
|
+
text = readFileSync(args.file, "utf8");
|
|
3586
|
+
} catch {
|
|
3587
|
+
failed(`Can't read ${args.file}.`);
|
|
3451
3588
|
return;
|
|
3452
3589
|
}
|
|
3453
|
-
|
|
3454
|
-
|
|
3590
|
+
const { records, problems } = parseLines(text);
|
|
3591
|
+
const added = records.filter((r) => store.importFeedback(r)).length;
|
|
3592
|
+
for (const problem of problems) writeErr(problem);
|
|
3593
|
+
write(`${style("green", "✓")} added ${String(added)} record(s); ${String(records.length - added)} were here already${problems.length === 0 ? "" : `; ${String(problems.length)} line(s) left out`}`);
|
|
3594
|
+
if (problems.length > 0) process.exitCode = 1;
|
|
3455
3595
|
})
|
|
3456
3596
|
});
|
|
3597
|
+
// src/commands/feedback.ts
|
|
3598
|
+
/**
|
|
3599
|
+
* @file `ork feedback`: the feedback every run recorded, grouped into items
|
|
3600
|
+
* (specs/feedback.md): list and show them, note a run, dismiss and restore an
|
|
3601
|
+
* item, and accept one, as a lesson its workflow reads from then on. What
|
|
3602
|
+
* closes the loop further (apply, triage, split) is in `feedback-loop.ts`,
|
|
3603
|
+
* and what moves feedback off the machine (publish, export, import) in
|
|
3604
|
+
* `feedback-share.ts`.
|
|
3605
|
+
*/
|
|
3606
|
+
/** Runs `body` on the item `prefix` names, or says why there's none. */
|
|
3607
|
+
const withItem = (prefix, body) => withStore((store) => {
|
|
3608
|
+
const found = findFeedbackItem(store, prefix);
|
|
3609
|
+
if (!found.ok) {
|
|
3610
|
+
writeErr(found.error);
|
|
3611
|
+
process.exitCode = 1;
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
body(store, found.item);
|
|
3615
|
+
});
|
|
3457
3616
|
const itemArg = {
|
|
3458
3617
|
type: "positional",
|
|
3459
3618
|
description: "A feedback item's id, or a prefix of one",
|
|
@@ -3467,7 +3626,7 @@ const json$1 = {
|
|
|
3467
3626
|
const feedback = defineCommand({
|
|
3468
3627
|
meta: {
|
|
3469
3628
|
name: "feedback",
|
|
3470
|
-
description: "What got in the way in your runs, grouped: list, show, note,
|
|
3629
|
+
description: "What got in the way in your runs, grouped: list, show, note, triage, accept, apply, publish, export, import"
|
|
3471
3630
|
},
|
|
3472
3631
|
subCommands: {
|
|
3473
3632
|
list: defineCommand({
|
|
@@ -3498,22 +3657,16 @@ const feedback = defineCommand({
|
|
|
3498
3657
|
item: itemArg,
|
|
3499
3658
|
json: json$1
|
|
3500
3659
|
},
|
|
3501
|
-
run: ({ args }) =>
|
|
3502
|
-
const
|
|
3503
|
-
if (!found.ok) {
|
|
3504
|
-
writeErr(found.error);
|
|
3505
|
-
process.exitCode = 1;
|
|
3506
|
-
return;
|
|
3507
|
-
}
|
|
3508
|
-
const records = store.feedbackRecords(found.item.id);
|
|
3660
|
+
run: ({ args }) => withItem(args.item, (store, item) => {
|
|
3661
|
+
const records = store.feedbackRecords(item.id);
|
|
3509
3662
|
if (args.json) {
|
|
3510
3663
|
write(JSON.stringify({
|
|
3511
|
-
...
|
|
3664
|
+
...item,
|
|
3512
3665
|
records
|
|
3513
3666
|
}, void 0, 2));
|
|
3514
3667
|
return;
|
|
3515
3668
|
}
|
|
3516
|
-
for (const line of formatFeedbackItem(
|
|
3669
|
+
for (const line of formatFeedbackItem(item, records)) write(line);
|
|
3517
3670
|
})
|
|
3518
3671
|
}),
|
|
3519
3672
|
note: defineCommand({
|
|
@@ -3564,21 +3717,59 @@ const feedback = defineCommand({
|
|
|
3564
3717
|
process.exitCode = 1;
|
|
3565
3718
|
})
|
|
3566
3719
|
}),
|
|
3720
|
+
accept: defineCommand({
|
|
3721
|
+
meta: {
|
|
3722
|
+
name: "accept",
|
|
3723
|
+
description: "Accept an item; with --lesson, every later session of its workflow reads it"
|
|
3724
|
+
},
|
|
3725
|
+
args: {
|
|
3726
|
+
item: itemArg,
|
|
3727
|
+
lesson: {
|
|
3728
|
+
type: "boolean",
|
|
3729
|
+
description: "Add it to the workflow's lessons (.ork/lessons/<workflow>.md)",
|
|
3730
|
+
default: false
|
|
3731
|
+
},
|
|
3732
|
+
message: {
|
|
3733
|
+
type: "string",
|
|
3734
|
+
alias: "m",
|
|
3735
|
+
description: "The lesson's words (default: the item's title)"
|
|
3736
|
+
},
|
|
3737
|
+
workflow: {
|
|
3738
|
+
type: "string",
|
|
3739
|
+
description: "Whose lessons, when it was seen in more than one"
|
|
3740
|
+
}
|
|
3741
|
+
},
|
|
3742
|
+
run: ({ args }) => withItem(args.item, (store, item) => {
|
|
3743
|
+
const accepted = acceptItem(store, item, {
|
|
3744
|
+
lesson: args.lesson,
|
|
3745
|
+
text: args.message,
|
|
3746
|
+
workflow: args.workflow
|
|
3747
|
+
}, registeredProjects());
|
|
3748
|
+
if (!accepted.ok) {
|
|
3749
|
+
writeErr(accepted.error);
|
|
3750
|
+
process.exitCode = 1;
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
const [first, ...rest] = accepted.lines;
|
|
3754
|
+
write(`${style("green", "✓")} ${first ?? ""}`);
|
|
3755
|
+
for (const line of rest) write(` ${line}`);
|
|
3756
|
+
})
|
|
3757
|
+
}),
|
|
3758
|
+
apply,
|
|
3759
|
+
triage,
|
|
3760
|
+
split,
|
|
3761
|
+
publish,
|
|
3762
|
+
export: exportFeedback,
|
|
3763
|
+
import: importFeedback,
|
|
3567
3764
|
dismiss: defineCommand({
|
|
3568
3765
|
meta: {
|
|
3569
3766
|
name: "dismiss",
|
|
3570
3767
|
description: "Dismiss an item: it leaves the list, and stays dismissed"
|
|
3571
3768
|
},
|
|
3572
3769
|
args: { item: itemArg },
|
|
3573
|
-
run: ({ args }) =>
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
writeErr(found.error);
|
|
3577
|
-
process.exitCode = 1;
|
|
3578
|
-
return;
|
|
3579
|
-
}
|
|
3580
|
-
store.setFeedbackStatus(found.item.id, "dismissed");
|
|
3581
|
-
write(`${style("green", "✓")} dismissed ${found.item.id}: ${found.item.title}`);
|
|
3770
|
+
run: ({ args }) => withItem(args.item, (store, item) => {
|
|
3771
|
+
store.setFeedbackStatus(item.id, "dismissed");
|
|
3772
|
+
write(`${style("green", "✓")} dismissed ${item.id}: ${item.title}`);
|
|
3582
3773
|
})
|
|
3583
3774
|
}),
|
|
3584
3775
|
restore: defineCommand({
|
|
@@ -3587,19 +3778,128 @@ const feedback = defineCommand({
|
|
|
3587
3778
|
description: "Bring a dismissed item back to the list, as new"
|
|
3588
3779
|
},
|
|
3589
3780
|
args: { item: itemArg },
|
|
3590
|
-
run: ({ args }) =>
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
writeErr(found.error);
|
|
3594
|
-
process.exitCode = 1;
|
|
3595
|
-
return;
|
|
3596
|
-
}
|
|
3597
|
-
store.setFeedbackStatus(found.item.id, "new");
|
|
3598
|
-
write(`${style("green", "✓")} restored ${found.item.id}: ${found.item.title}`);
|
|
3781
|
+
run: ({ args }) => withItem(args.item, (store, item) => {
|
|
3782
|
+
store.setFeedbackStatus(item.id, "new");
|
|
3783
|
+
write(`${style("green", "✓")} restored ${item.id}: ${item.title}`);
|
|
3599
3784
|
})
|
|
3600
3785
|
})
|
|
3601
3786
|
}
|
|
3602
3787
|
});
|
|
3788
|
+
// src/commands/runs.ts
|
|
3789
|
+
/**
|
|
3790
|
+
* @file `ork ps`, `ork logs` and `ork tree`: read what every project's runs
|
|
3791
|
+
* recorded in `~/.ork/state.db` (specs/event-store.md).
|
|
3792
|
+
*/
|
|
3793
|
+
const ps = defineCommand({
|
|
3794
|
+
meta: {
|
|
3795
|
+
name: "ps",
|
|
3796
|
+
description: "List this project's runs, newest first"
|
|
3797
|
+
},
|
|
3798
|
+
args: {
|
|
3799
|
+
all: {
|
|
3800
|
+
type: "boolean",
|
|
3801
|
+
description: "Every project's runs",
|
|
3802
|
+
default: false
|
|
3803
|
+
},
|
|
3804
|
+
limit: {
|
|
3805
|
+
type: "string",
|
|
3806
|
+
description: "How many (default 20)"
|
|
3807
|
+
},
|
|
3808
|
+
json: {
|
|
3809
|
+
type: "boolean",
|
|
3810
|
+
description: "Print JSON",
|
|
3811
|
+
default: false
|
|
3812
|
+
}
|
|
3813
|
+
},
|
|
3814
|
+
run: ({ args }) => withStore((store) => {
|
|
3815
|
+
const limit = Number(args.limit ?? 20);
|
|
3816
|
+
const runs = store.runs({
|
|
3817
|
+
limit: Number.isInteger(limit) && limit > 0 ? limit : 20,
|
|
3818
|
+
...args.all ? {} : { project: placeFor(process.cwd(), store.projects()).root }
|
|
3819
|
+
});
|
|
3820
|
+
const statusOf = (run) => runStatus(run, run.status === "running" ? store.events(run.id).map((s) => s.event) : []);
|
|
3821
|
+
if (args.json) write(JSON.stringify(runs, void 0, 2));
|
|
3822
|
+
else for (const line of formatRuns(runs, args.all, Date.now(), statusOf)) write(line);
|
|
3823
|
+
})
|
|
3824
|
+
});
|
|
3825
|
+
const runArg = {
|
|
3826
|
+
type: "positional",
|
|
3827
|
+
description: "A run id or a prefix of one (default: this project's newest)",
|
|
3828
|
+
required: false
|
|
3829
|
+
};
|
|
3830
|
+
const logs = defineCommand({
|
|
3831
|
+
meta: {
|
|
3832
|
+
name: "logs",
|
|
3833
|
+
description: "Print a run's transcript"
|
|
3834
|
+
},
|
|
3835
|
+
args: {
|
|
3836
|
+
run: runArg,
|
|
3837
|
+
node: {
|
|
3838
|
+
type: "string",
|
|
3839
|
+
description: "Only this node's events"
|
|
3840
|
+
},
|
|
3841
|
+
follow: {
|
|
3842
|
+
type: "boolean",
|
|
3843
|
+
alias: "f",
|
|
3844
|
+
description: "Keep printing a live run's events until it ends",
|
|
3845
|
+
default: false
|
|
3846
|
+
},
|
|
3847
|
+
json: {
|
|
3848
|
+
type: "boolean",
|
|
3849
|
+
description: "Print the stored events as JSON lines",
|
|
3850
|
+
default: false
|
|
3851
|
+
}
|
|
3852
|
+
},
|
|
3853
|
+
run: ({ args }) => withStore(async (store) => {
|
|
3854
|
+
const found = findRun(store, args.run, process.cwd());
|
|
3855
|
+
if (!found.ok) {
|
|
3856
|
+
writeErr(found.error);
|
|
3857
|
+
process.exitCode = 1;
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
const abort = new AbortController();
|
|
3861
|
+
process.once("SIGINT", () => {
|
|
3862
|
+
abort.abort();
|
|
3863
|
+
});
|
|
3864
|
+
await followRun(store, found.run, write, {
|
|
3865
|
+
follow: args.follow,
|
|
3866
|
+
json: args.json,
|
|
3867
|
+
signal: abort.signal,
|
|
3868
|
+
...args.node === void 0 ? {} : { node: args.node }
|
|
3869
|
+
});
|
|
3870
|
+
})
|
|
3871
|
+
});
|
|
3872
|
+
const tree = defineCommand({
|
|
3873
|
+
meta: {
|
|
3874
|
+
name: "tree",
|
|
3875
|
+
description: "Show a run's ownership tree with status, tokens and cost"
|
|
3876
|
+
},
|
|
3877
|
+
args: {
|
|
3878
|
+
run: runArg,
|
|
3879
|
+
json: {
|
|
3880
|
+
type: "boolean",
|
|
3881
|
+
description: "Print JSON",
|
|
3882
|
+
default: false
|
|
3883
|
+
}
|
|
3884
|
+
},
|
|
3885
|
+
run: ({ args }) => withStore((store) => {
|
|
3886
|
+
const found = findRun(store, args.run, process.cwd());
|
|
3887
|
+
if (!found.ok) {
|
|
3888
|
+
writeErr(found.error);
|
|
3889
|
+
process.exitCode = 1;
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
const { run } = found;
|
|
3893
|
+
const events = store.events(run.id).map((s) => s.event);
|
|
3894
|
+
const roots = runTree(events);
|
|
3895
|
+
if (args.json) {
|
|
3896
|
+
write(JSON.stringify(roots, void 0, 2));
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
write(`${style("bold", `run ${run.id}`)} ${style("dim", `of ${run.workflow} · ${runStatus(run, events)} · ${cost(run.costUsd)}`)}`);
|
|
3900
|
+
for (const line of formatTree(roots)) write(line);
|
|
3901
|
+
})
|
|
3902
|
+
});
|
|
3603
3903
|
// src/agents/manage.ts
|
|
3604
3904
|
/**
|
|
3605
3905
|
* @file What `ork agents` prints and does: the catalog as a table, one agent in
|
|
@@ -3904,23 +4204,23 @@ const main = defineCommand({
|
|
|
3904
4204
|
ps,
|
|
3905
4205
|
logs,
|
|
3906
4206
|
tree,
|
|
3907
|
-
usage: () => import("./usage-
|
|
3908
|
-
prune: () => import("./usage-
|
|
4207
|
+
usage: () => import("./usage-CT7Z-yjr.mjs").then((module) => module.usage),
|
|
4208
|
+
prune: () => import("./usage-CT7Z-yjr.mjs").then((module) => module.prune),
|
|
3909
4209
|
decide,
|
|
3910
|
-
relay: () => import("./relay-
|
|
3911
|
-
pause: () => import("./pause-
|
|
3912
|
-
continue: () => import("./pause-
|
|
4210
|
+
relay: () => import("./relay-Do-aNbSD.mjs").then((module) => module.relay),
|
|
4211
|
+
pause: () => import("./pause-BPSmg4yz.mjs").then((module) => module.pause),
|
|
4212
|
+
continue: () => import("./pause-BPSmg4yz.mjs").then((module) => module.continueRun),
|
|
3913
4213
|
stop,
|
|
3914
4214
|
chat,
|
|
3915
|
-
send: () => import("./send-
|
|
4215
|
+
send: () => import("./send-DhGBkowy.mjs").then((module) => module.send),
|
|
3916
4216
|
resume,
|
|
3917
4217
|
feedback,
|
|
3918
4218
|
context,
|
|
3919
|
-
worktrees,
|
|
3920
|
-
wt: worktrees,
|
|
3921
|
-
watch: () => import("./watch-
|
|
3922
|
-
mcp: () => import("./mcp-
|
|
3923
|
-
ui: () => import("./ui-
|
|
4219
|
+
worktrees: () => import("./worktrees-DHl80k96.mjs").then((module) => module.worktrees),
|
|
4220
|
+
wt: () => import("./worktrees-DHl80k96.mjs").then((module) => module.worktrees),
|
|
4221
|
+
watch: () => import("./watch-0Qr2UfCQ.mjs").then((module) => module.watch),
|
|
4222
|
+
mcp: () => import("./mcp-CHVKKg3B.mjs").then((module) => module.mcp),
|
|
4223
|
+
ui: () => import("./ui-DhutUMgQ.mjs").then((module) => module.ui),
|
|
3924
4224
|
doctor,
|
|
3925
4225
|
upgrade
|
|
3926
4226
|
}
|