@krapht/ork 1.0.0-alpha.14 → 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__-DcRdK0Du.mjs +359 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
- package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
- package/dist/bin.mjs +328 -1734
- package/dist/brief-BvMtIFio.mjs +58 -0
- package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
- package/dist/context-BN9A2Nnu.mjs +106 -0
- package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
- package/dist/flags-CdgIH1L6.mjs +230 -0
- package/dist/follow-ups-DxCsOby1.mjs +496 -0
- package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
- package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
- package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
- package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
- package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
- package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
- package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
- package/dist/post-C9soCx7i.mjs +46 -0
- package/dist/print-BaWiWf4v.mjs +19 -0
- package/dist/project-records-BRiyQzUo.mjs +230 -0
- package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
- package/dist/run-B-j3Qxqc.mjs +1464 -0
- package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
- package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
- package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
- package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
- package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
- package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
- package/dist/store-Dyr1mP8K.mjs +16 -0
- package/dist/ui/assets/app-BPSCxb_l.css +1 -0
- package/dist/ui/assets/app-CnNRowFn.js +111 -0
- package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
- package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
- package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
- package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
- package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
- package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
- package/package.json +8 -4
- package/dist/ui/assets/app-D_nAPnHM.css +0 -1
- package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $t as gitOk, Qt as git } from "./src-DN-RPsil.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ as sendFromScript, L as openStore, Q as scriptRefusal, Z as TOO_LONG } from "./src-DN-RPsil.mjs";
|
|
2
2
|
import { l as writeErr } from "./output-B1kwo0YU.mjs";
|
|
3
|
-
import { u as inheritedScrubber } from "./src-
|
|
3
|
+
import { u as inheritedScrubber } from "./src-DHDQ0itC.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
// src/commands/send.ts
|
|
6
6
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { It as lowerChild, Jn as END_KINDS, Kn as outcomeEnd, Lt as programFound, Qn as FEEDBACK_TARGETS, Rt as startAt, Xn as FEEDBACK_KINDS, Yn as endFor, Zn as FEEDBACK_SEVERITIES, gr as MASK, un as FOLLOW_UP_KINDS, vr as maskSecrets, vt as parseReport, wi as AGENT_DRIVERS, zn as countedTokens } from "./src-DN-RPsil.mjs";
|
|
2
|
+
import { n as holdGuard, r as orphanGuard } from "./orphan-guard-BFKg8VSd.mjs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { existsSync, linkSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -448,8 +449,10 @@ const classifyProviderError = (text, status) => {
|
|
|
448
449
|
* it can see (a turn cap, an exit code, a provider's error), and the report
|
|
449
450
|
* the session handed in decides the rest. The runner then writes the end in
|
|
450
451
|
* the node's numbers, its detail scrubbed with the feedback redactor. A
|
|
451
|
-
* session the driver could not run
|
|
452
|
-
*
|
|
452
|
+
* session the driver could not run (or that was stopped, with what it had
|
|
453
|
+
* spent) ends as `driverFailure` says, one whose process died as
|
|
454
|
+
* `failureReason` explains, and one that reached its token cap as
|
|
455
|
+
* `tokenCapOutcome` says.
|
|
453
456
|
*/
|
|
454
457
|
/** The end a report gives: done, a reported failure, or none handed in. */
|
|
455
458
|
const reportEnd = (report) => {
|
|
@@ -474,9 +477,15 @@ const errorEnd = (text, fallback = "unknown", status) => endFor(classifyProvider
|
|
|
474
477
|
*/
|
|
475
478
|
const settleEnd = (outcome, facts, scrub) => {
|
|
476
479
|
const end = outcome.report === null ? outcome.end ?? outcomeEnd(outcome) : reportEnd(outcome.report);
|
|
480
|
+
const { usage } = outcome;
|
|
481
|
+
const used = {
|
|
482
|
+
...facts,
|
|
483
|
+
usedTokens: countedTokens(usage),
|
|
484
|
+
...usage.costUsd > 0 ? { usedUsd: usage.costUsd } : {}
|
|
485
|
+
};
|
|
477
486
|
return {
|
|
478
487
|
...outcome,
|
|
479
|
-
end: endFor(end.kind,
|
|
488
|
+
end: endFor(end.kind, used, end.detail, scrub)
|
|
480
489
|
};
|
|
481
490
|
};
|
|
482
491
|
/**
|
|
@@ -509,6 +518,17 @@ const tokenCapOutcome = ({ budgetTokens }, usage, sessionId) => budgetTokens ===
|
|
|
509
518
|
usage,
|
|
510
519
|
errors: [`Reached the token budget (${String(budgetTokens)})`]
|
|
511
520
|
};
|
|
521
|
+
const REASON_LIMIT = 500;
|
|
522
|
+
/** Noise a Node crash ends stderr with, or a stack frame: never the reason. */
|
|
523
|
+
const isNoise = (line) => /^Node\.js v\d/u.test(line) || /^\s+at /u.test(line);
|
|
524
|
+
/**
|
|
525
|
+
* Why a process ended without a result: the last block of its stderr as one
|
|
526
|
+
* masked, capped line (the reason reaches the terminal, which is not masked).
|
|
527
|
+
*/
|
|
528
|
+
const failureReason = (name, stderr, code) => {
|
|
529
|
+
const block = stderr.split("\n").filter((line) => !isNoise(line)).join("\n").split(/\n\s*\n/u).map((text) => text.trim().replaceAll(/\s*\n\s*/gu, " ")).findLast((text) => text !== "");
|
|
530
|
+
return block === void 0 ? `${name} exited with code ${String(code)}` : maskSecrets(block).slice(0, REASON_LIMIT);
|
|
531
|
+
};
|
|
512
532
|
/**
|
|
513
533
|
* The kind of a failure to run the driver: its program missing, else what a
|
|
514
534
|
* provider's words in it say, else a crash.
|
|
@@ -519,39 +539,92 @@ const failureKind = (error) => {
|
|
|
519
539
|
if (/\bENOENT\b/u.test(text)) return "tool_missing";
|
|
520
540
|
return classifyProviderError(text)?.kind ?? "driver_crashed";
|
|
521
541
|
};
|
|
542
|
+
const NOTHING_SPENT = {
|
|
543
|
+
usage: {
|
|
544
|
+
costUsd: 0,
|
|
545
|
+
inputTokens: 0,
|
|
546
|
+
outputTokens: 0,
|
|
547
|
+
cacheReadTokens: 0,
|
|
548
|
+
cacheWriteTokens: 0,
|
|
549
|
+
turns: 0,
|
|
550
|
+
durationMs: 0
|
|
551
|
+
},
|
|
552
|
+
sessionId: ""
|
|
553
|
+
};
|
|
522
554
|
/**
|
|
523
555
|
* A finished event for a session the driver could not run, ended as `kind` with
|
|
524
|
-
* `message` (Ork's words or the tool's, masked) as its detail
|
|
556
|
+
* `message` (Ork's words or the tool's, masked) as its detail; a stopped one
|
|
557
|
+
* keeps what it had `spent` (its tokens decoded so far, dollars $0).
|
|
525
558
|
*/
|
|
526
|
-
const driverFailure = (message, kind, scope) => ({
|
|
559
|
+
const driverFailure = (message, kind, scope, spent = NOTHING_SPENT) => ({
|
|
527
560
|
type: "session.finished",
|
|
528
561
|
outcome: {
|
|
529
562
|
subtype: "error_driver",
|
|
530
563
|
end: endFor(kind, scope.facts, kind === "interrupted" ? void 0 : message, scope.scrub),
|
|
531
|
-
sessionId:
|
|
564
|
+
sessionId: spent.sessionId,
|
|
532
565
|
report: null,
|
|
533
566
|
text: "",
|
|
534
|
-
usage:
|
|
535
|
-
costUsd: 0,
|
|
536
|
-
inputTokens: 0,
|
|
537
|
-
outputTokens: 0,
|
|
538
|
-
cacheReadTokens: 0,
|
|
539
|
-
cacheWriteTokens: 0,
|
|
540
|
-
turns: 0,
|
|
541
|
-
durationMs: 0
|
|
542
|
-
},
|
|
567
|
+
usage: spent.usage,
|
|
543
568
|
errors: [message]
|
|
544
569
|
}
|
|
545
570
|
});
|
|
571
|
+
// ../runtime/src/leg-spawn.ts
|
|
572
|
+
/**
|
|
573
|
+
* @file Starting one process (leg) of a session: at the project's priority, and
|
|
574
|
+
* off Windows in its own process group beside the orphan guard, so it and
|
|
575
|
+
* what it started end with `ork`, whatever ends `ork`. The process's pid, and
|
|
576
|
+
* the SIGINT that stops it, stay the driver's own.
|
|
577
|
+
*/
|
|
578
|
+
/** The fd of the orphan guard's pipe from `ork`. */
|
|
579
|
+
const WATCH_FD = 3;
|
|
580
|
+
/**
|
|
581
|
+
* Starts the orphan guard, then becomes the driver. The pipe is closed by a
|
|
582
|
+
* bare `exec`: macOS's `sh` keeps a copy of a descriptor an `exec` command
|
|
583
|
+
* closes, which the driver and its leftovers would then inherit.
|
|
584
|
+
*/
|
|
585
|
+
const GUARDED = `${orphanGuard(WATCH_FD)}exec ${String(WATCH_FD)}<&-; exec "$0" "$@"`;
|
|
586
|
+
const piped = (child) => child.stdin !== null && child.stdout !== null && child.stderr !== null;
|
|
587
|
+
/**
|
|
588
|
+
* Spawns the leg's process. A program that isn't there is spawned bare, so it
|
|
589
|
+
* fails as spawn's ENOENT; the guard is released once the driver exits, so a
|
|
590
|
+
* session that ends normally leaves its leftovers alone.
|
|
591
|
+
*/
|
|
592
|
+
const spawnLeg = (start) => {
|
|
593
|
+
const [bin, args] = startAt(start.priority, start.bin, start.args, start.env);
|
|
594
|
+
const guarded = process.platform !== "win32" && programFound(start.bin, start.env);
|
|
595
|
+
const child = spawn(guarded ? "/bin/sh" : bin, guarded ? [
|
|
596
|
+
"-c",
|
|
597
|
+
GUARDED,
|
|
598
|
+
bin,
|
|
599
|
+
...args
|
|
600
|
+
] : args, {
|
|
601
|
+
cwd: start.cwd,
|
|
602
|
+
detached: guarded,
|
|
603
|
+
env: { ...start.env },
|
|
604
|
+
stdio: [
|
|
605
|
+
"pipe",
|
|
606
|
+
"pipe",
|
|
607
|
+
"pipe",
|
|
608
|
+
guarded ? "pipe" : "ignore"
|
|
609
|
+
]
|
|
610
|
+
});
|
|
611
|
+
if (!piped(child)) throw new Error(`${start.bin} was started without pipes`);
|
|
612
|
+
if (guarded) {
|
|
613
|
+
const release = holdGuard(child.stdio[WATCH_FD]);
|
|
614
|
+
child.once("exit", release);
|
|
615
|
+
}
|
|
616
|
+
return child;
|
|
617
|
+
};
|
|
546
618
|
// ../runtime/src/session-leg.ts
|
|
547
619
|
/**
|
|
548
620
|
* @file One process (leg) of a session the runner drives: it writes the queued
|
|
549
621
|
* stdin lines, yields the adapter's decoded events, and says how it ended. A
|
|
550
622
|
* session is one leg, or several when a paused node's tool call was deferred
|
|
551
623
|
* or a message waits for a tool that takes none mid-session; a resumed leg
|
|
552
|
-
* carries what the legs before it spent, and its start is not news.
|
|
624
|
+
* carries what the legs before it spent, and its start is not news. Off
|
|
625
|
+
* Windows the process ends with `ork`, whatever ends `ork` (the orphan
|
|
626
|
+
* guard).
|
|
553
627
|
*/
|
|
554
|
-
const REASON_LIMIT = 500;
|
|
555
628
|
/** Marks a session's start with the driver that runs it. */
|
|
556
629
|
const withDriver = (event, driver) => event.type === "session.started" ? {
|
|
557
630
|
...event,
|
|
@@ -565,10 +638,11 @@ const settled = (outcome, input) => settleEnd(input.reported === void 0 ? outcom
|
|
|
565
638
|
...outcome,
|
|
566
639
|
report: input.reported()
|
|
567
640
|
}, input.scope.facts, input.scope.scrub);
|
|
641
|
+
/** What this leg `used`, with what the legs before it spent. */
|
|
642
|
+
const withCarried = (input, used) => used === void 0 || input.carried === void 0 ? used ?? input.carried : input.adapter.usage.combine(input.carried, used);
|
|
568
643
|
/** The session's end at its token cap, its earlier legs' usage included. */
|
|
569
644
|
const tokenCapEnd = (input, used, sessionId) => {
|
|
570
|
-
const
|
|
571
|
-
const capped = tokenCapOutcome(input.scope.facts, usage, sessionId);
|
|
645
|
+
const capped = tokenCapOutcome(input.scope.facts, used === void 0 ? void 0 : withCarried(input, used), sessionId);
|
|
572
646
|
return capped === void 0 ? void 0 : {
|
|
573
647
|
type: "session.finished",
|
|
574
648
|
outcome: settled(capped, input)
|
|
@@ -588,37 +662,36 @@ const legStep = (decoded, input) => {
|
|
|
588
662
|
const events = [];
|
|
589
663
|
let ended;
|
|
590
664
|
for (const said of decoded.events) {
|
|
591
|
-
|
|
665
|
+
if (said.type === "session.started" && carried !== void 0) continue;
|
|
666
|
+
if (said.type !== "session.finished") {
|
|
667
|
+
events.push(withDriver(said, adapter.driver));
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const own = said.outcome.usage;
|
|
671
|
+
const usage = carried === void 0 ? own : adapter.usage.combine(carried, own);
|
|
672
|
+
const last = {
|
|
592
673
|
...said,
|
|
593
|
-
outcome: settled(
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
674
|
+
outcome: settled({
|
|
675
|
+
...said.outcome,
|
|
676
|
+
usage
|
|
677
|
+
}, input)
|
|
678
|
+
};
|
|
679
|
+
const { sessionId, end } = last.outcome;
|
|
680
|
+
if (deferred) ended = {
|
|
598
681
|
kind: "deferred",
|
|
599
|
-
sessionId
|
|
600
|
-
usage:
|
|
601
|
-
last
|
|
682
|
+
sessionId,
|
|
683
|
+
usage: own,
|
|
684
|
+
last
|
|
685
|
+
};
|
|
686
|
+
else if (input.withhold() && wentWell(end?.kind)) ended = {
|
|
687
|
+
kind: "continued",
|
|
688
|
+
sessionId,
|
|
689
|
+
usage: own,
|
|
690
|
+
last
|
|
602
691
|
};
|
|
603
692
|
else {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
...event,
|
|
607
|
-
outcome: {
|
|
608
|
-
...event.outcome,
|
|
609
|
-
usage
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
if (input.withhold() && wentWell(event.outcome.end?.kind)) ended = {
|
|
613
|
-
kind: "continued",
|
|
614
|
-
sessionId: event.outcome.sessionId,
|
|
615
|
-
usage: event.outcome.usage,
|
|
616
|
-
last
|
|
617
|
-
};
|
|
618
|
-
else {
|
|
619
|
-
events.push(last);
|
|
620
|
-
ended = { kind: "finished" };
|
|
621
|
-
}
|
|
693
|
+
events.push(last);
|
|
694
|
+
ended = { kind: "finished" };
|
|
622
695
|
}
|
|
623
696
|
}
|
|
624
697
|
return ended === void 0 ? { events } : {
|
|
@@ -626,28 +699,9 @@ const legStep = (decoded, input) => {
|
|
|
626
699
|
ended
|
|
627
700
|
};
|
|
628
701
|
};
|
|
629
|
-
/** Noise a Node crash ends stderr with, or a stack frame: never the reason. */
|
|
630
|
-
const isNoise = (line) => /^Node\.js v\d/u.test(line) || /^\s+at /u.test(line);
|
|
631
|
-
/**
|
|
632
|
-
* Why a process ended without a result: the last block of its stderr as one
|
|
633
|
-
* masked, capped line (the reason reaches the terminal, which is not masked).
|
|
634
|
-
*/
|
|
635
|
-
const failureReason = (name, stderr, code) => {
|
|
636
|
-
const block = stderr.split("\n").filter((line) => !isNoise(line)).join("\n").split(/\n\s*\n/u).map((text) => text.trim().replaceAll(/\s*\n\s*/gu, " ")).findLast((text) => text !== "");
|
|
637
|
-
return block === void 0 ? `${name} exited with code ${String(code)}` : maskSecrets(block).slice(0, REASON_LIMIT);
|
|
638
|
-
};
|
|
639
702
|
/** Runs one process of a session, yielding its events; returns how it ended. */
|
|
640
703
|
const runLeg = async function* (input) {
|
|
641
|
-
const
|
|
642
|
-
const child = spawn(bin, args, {
|
|
643
|
-
cwd: input.cwd,
|
|
644
|
-
env: { ...input.env },
|
|
645
|
-
stdio: [
|
|
646
|
-
"pipe",
|
|
647
|
-
"pipe",
|
|
648
|
-
"pipe"
|
|
649
|
-
]
|
|
650
|
-
});
|
|
704
|
+
const child = spawnLeg(input);
|
|
651
705
|
lowerChild(input.priority, child.pid);
|
|
652
706
|
const exited = new Promise((resolve) => {
|
|
653
707
|
child.on("close", (code, signal) => {
|
|
@@ -680,11 +734,14 @@ const runLeg = async function* (input) {
|
|
|
680
734
|
if (input.adapter.command.input === void 0) child.stdin.end();
|
|
681
735
|
let outcome;
|
|
682
736
|
let sessionId = "";
|
|
737
|
+
/** What this leg has used so far, as the last usage step said. */
|
|
738
|
+
let used;
|
|
683
739
|
const { decoder } = input;
|
|
684
740
|
const decode = decoder?.decode ?? input.adapter.decode;
|
|
685
741
|
try {
|
|
686
742
|
for await (const line of createInterface({ input: child.stdout })) {
|
|
687
743
|
const decoded = decode(line);
|
|
744
|
+
used = decoded.usage ?? used;
|
|
688
745
|
const { events, ended } = legStep(decoded, input);
|
|
689
746
|
for (const event of decoded.events) if (event.type === "session.started") sessionId = event.sessionId;
|
|
690
747
|
yield* events;
|
|
@@ -712,11 +769,18 @@ const runLeg = async function* (input) {
|
|
|
712
769
|
reason: `could not start ${input.bin}: ${end.message}`,
|
|
713
770
|
end: failureKind(end)
|
|
714
771
|
};
|
|
715
|
-
if (stopped || end.signal !== null)
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
772
|
+
if (stopped || end.signal !== null) {
|
|
773
|
+
const usage = withCarried(input, used);
|
|
774
|
+
return {
|
|
775
|
+
kind: "failed",
|
|
776
|
+
reason: "interrupted",
|
|
777
|
+
end: "interrupted",
|
|
778
|
+
...usage === void 0 ? {} : { spent: {
|
|
779
|
+
usage,
|
|
780
|
+
sessionId
|
|
781
|
+
} }
|
|
782
|
+
};
|
|
783
|
+
}
|
|
720
784
|
const reason = failureReason(input.adapter.command.bin, stderr, end.code);
|
|
721
785
|
return {
|
|
722
786
|
kind: "failed",
|
|
@@ -802,6 +866,7 @@ const releaseTool = defineTool({
|
|
|
802
866
|
});
|
|
803
867
|
// ../runtime/src/tools/delegate.ts
|
|
804
868
|
const BRIEF = z.string().min(1).describe("Self-contained: the goal, the files that matter, and what done looks like. The child does not see your conversation.");
|
|
869
|
+
const DESCRIPTION = z.string().exactOptional().describe("A few words naming this task; shown as the child's label.");
|
|
805
870
|
/** What the leader reads back: each report without the usage details. */
|
|
806
871
|
const reportLine = (report) => {
|
|
807
872
|
const { usage, ...output } = report.output;
|
|
@@ -827,7 +892,8 @@ const delegateTool = defineTool({
|
|
|
827
892
|
const ids = ports.ownership?.children.map((child) => child.id) ?? [];
|
|
828
893
|
return { tasks: z.array(z.strictObject({
|
|
829
894
|
child: z.enum(ids).describe(`The id of a node you own: ${ids.join(", ")}.`),
|
|
830
|
-
brief: BRIEF
|
|
895
|
+
brief: BRIEF,
|
|
896
|
+
description: DESCRIPTION
|
|
831
897
|
})).min(1) };
|
|
832
898
|
},
|
|
833
899
|
available: ({ ports }) => (ports.ownership?.children.length ?? 0) > 0,
|
|
@@ -843,7 +909,8 @@ const spawnWorkerTool = defineTool({
|
|
|
843
909
|
const ids = ports.ownership?.templates ?? [];
|
|
844
910
|
return { tasks: z.array(z.strictObject({
|
|
845
911
|
template: z.enum(ids).describe(`The id of a child worker you own: ${ids.join(", ")}.`),
|
|
846
|
-
brief: BRIEF
|
|
912
|
+
brief: BRIEF,
|
|
913
|
+
description: DESCRIPTION
|
|
847
914
|
})).min(1) };
|
|
848
915
|
},
|
|
849
916
|
available: ({ ports }) => ports.ownership !== void 0 && ports.ownership.templates.length > 0 && ports.ownership.workersLeft() > 0,
|
|
@@ -884,6 +951,74 @@ const feedbackTool = defineTool({
|
|
|
884
951
|
return Promise.resolve(text(sent.ok ? "Recorded. Thank you; carry on with your task." : `Not recorded: ${sent.error}.`));
|
|
885
952
|
}
|
|
886
953
|
});
|
|
954
|
+
// ../runtime/src/tools/follow-up.ts
|
|
955
|
+
/**
|
|
956
|
+
* @file `follow_up`: a session's way to leave work the change leaves for later
|
|
957
|
+
* (a change for a later run, a note for another project, a message a person
|
|
958
|
+
* must send, a task or a check for the maintainer). Ork keeps it where the
|
|
959
|
+
* project's `followUps` rule says, merges duplicates, caps it per session,
|
|
960
|
+
* and never sends or runs anything on its own.
|
|
961
|
+
*/
|
|
962
|
+
const isFor = (value) => /^(?:agent|me|project:\S.*|person:\S.*)$/u.test(value);
|
|
963
|
+
const isDue = (value) => /^(?:now|after-merge|after-deploy|\d{4}-\d{2}-\d{2})$/u.test(value);
|
|
964
|
+
const followUpTool = defineTool({
|
|
965
|
+
name: "follow_up",
|
|
966
|
+
description: "Leave work this change leaves for later and that is not yours to do now: a change for a later run (for: agent), something for another registered project (for: project:<name>), a message a person must get (for: person:<name>, kind: message, with a draft), or a task or check for the maintainer (for: me, with steps). Ork keeps it, merges duplicates, and sends or runs nothing itself. Not for problems with Ork or the workflow (use feedback), nor for what you can finish now.",
|
|
967
|
+
shape: {
|
|
968
|
+
title: z.string().min(1).max(200).describe("One line."),
|
|
969
|
+
body: z.string().max(4e3).optional().describe("What and why, briefly."),
|
|
970
|
+
for: z.string().refine(isFor, "agent, me, project:<name> or person:<name>").describe("Who acts: agent, me, project:<name> or person:<name>."),
|
|
971
|
+
kind: z.enum(FOLLOW_UP_KINDS),
|
|
972
|
+
due: z.string().refine(isDue, "now, after-merge, after-deploy or YYYY-MM-DD").describe("now, after-merge, after-deploy, or a YYYY-MM-DD date."),
|
|
973
|
+
where: z.array(z.string()).max(20).optional().describe("Files it is about, relative to the project root."),
|
|
974
|
+
ref: z.string().min(1).optional().describe("The pull request or branch an after-merge follow-up waits on, when you know it."),
|
|
975
|
+
draft: z.strictObject({
|
|
976
|
+
to: z.string().min(1),
|
|
977
|
+
channel: z.string().optional().describe("github (to: an issue or PR URL), email, slack… A hint for the maintainer."),
|
|
978
|
+
text: z.string().min(1).max(4e3)
|
|
979
|
+
}).optional().describe("For a message: what the maintainer will send."),
|
|
980
|
+
steps: z.array(z.strictObject({
|
|
981
|
+
text: z.string().trim().min(1),
|
|
982
|
+
command: z.string().optional().describe("Shown to the maintainer to copy; Ork never runs it.")
|
|
983
|
+
})).max(20).optional().describe("For a task or check: what to do, in order.")
|
|
984
|
+
},
|
|
985
|
+
available: ({ ports }) => ports.followUps !== void 0,
|
|
986
|
+
run: async ({ ports }, args) => {
|
|
987
|
+
const port = ports.followUps;
|
|
988
|
+
if (port === void 0) return text("No follow-ups here.");
|
|
989
|
+
if (!isFor(args.for) || !isDue(args.due)) return text("Not kept.");
|
|
990
|
+
const input = {
|
|
991
|
+
title: args.title,
|
|
992
|
+
for: args.for,
|
|
993
|
+
kind: args.kind,
|
|
994
|
+
due: args.due,
|
|
995
|
+
...args.body === void 0 ? {} : { body: args.body },
|
|
996
|
+
...args.where === void 0 ? {} : { where: args.where },
|
|
997
|
+
...args.ref === void 0 ? {} : { ref: args.ref },
|
|
998
|
+
...args.draft === void 0 ? {} : { draft: {
|
|
999
|
+
to: args.draft.to,
|
|
1000
|
+
text: args.draft.text,
|
|
1001
|
+
...args.draft.channel === void 0 ? {} : { channel: args.draft.channel }
|
|
1002
|
+
} },
|
|
1003
|
+
...args.steps === void 0 ? {} : { steps: args.steps.map((step) => ({
|
|
1004
|
+
text: step.text,
|
|
1005
|
+
...step.command === void 0 ? {} : { command: step.command }
|
|
1006
|
+
})) }
|
|
1007
|
+
};
|
|
1008
|
+
const result = await port.add(input);
|
|
1009
|
+
if (!result.ok) return {
|
|
1010
|
+
...text(`Not kept: ${result.error}.`),
|
|
1011
|
+
isError: true
|
|
1012
|
+
};
|
|
1013
|
+
const { followUp, merged, note } = result.added;
|
|
1014
|
+
return text([
|
|
1015
|
+
merged ? `Merged into follow-up ${followUp.id}, which was already kept.` : `Kept as follow-up ${followUp.id}.`,
|
|
1016
|
+
...note === void 0 ? [] : [note],
|
|
1017
|
+
...result.relayed === void 0 ? [] : [`Also sent to ${result.relayed.run}/${result.relayed.node}, a live run of that project.`],
|
|
1018
|
+
"Carry on with your task."
|
|
1019
|
+
].join(" "));
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
887
1022
|
// ../runtime/src/tools/input.ts
|
|
888
1023
|
/**
|
|
889
1024
|
* @file `read_input`: what the workflow sent a node back with, whole. A loop's
|
|
@@ -1074,6 +1209,7 @@ const ORK_TOOLS = [
|
|
|
1074
1209
|
claimTool,
|
|
1075
1210
|
releaseTool,
|
|
1076
1211
|
feedbackTool,
|
|
1212
|
+
followUpTool,
|
|
1077
1213
|
{
|
|
1078
1214
|
name: REPORT_TOOL$1,
|
|
1079
1215
|
bind: (context) => context.report === void 0 || checksItsReport(context.driver) ? null : bound(context.report)
|
|
@@ -1218,7 +1354,9 @@ const createSessionRuntime = (adapter, options = {}) => ({
|
|
|
1218
1354
|
}
|
|
1219
1355
|
const leg = yield* runLeg(legOf(legSpec, spent));
|
|
1220
1356
|
if (leg.kind === "failed") {
|
|
1221
|
-
|
|
1357
|
+
const { spent: stopped } = leg;
|
|
1358
|
+
if (stopped?.sessionId === "") stopped.sessionId = legSpec.resume ?? "";
|
|
1359
|
+
yield driverFailure(leg.reason, leg.end, scope, stopped);
|
|
1222
1360
|
return;
|
|
1223
1361
|
}
|
|
1224
1362
|
if (leg.kind === "finished") return;
|