tldr-experts 0.31.0 → 0.32.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/CHANGELOG.md +188 -0
- package/README.md +2 -0
- package/dist/hooks/budget-gate.js +1 -1
- package/dist/hooks/{chunk-me0kzc6k.js → chunk-5at77jx8.js} +11 -1
- package/dist/hooks/{chunk-3r0hhgcf.js → chunk-whvae1vw.js} +474 -14
- package/dist/hooks/session-start.js +2 -2
- package/dist/hooks/statusline.js +2 -2
- package/dist/tldrx.js +523 -100
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
DEFAULT_ON_HOST_TOKENS_EXCEED,
|
|
6
6
|
WAVES_FILE,
|
|
7
7
|
asRunBudget,
|
|
8
|
+
buildProgress,
|
|
8
9
|
nowRfc3339,
|
|
9
10
|
spentBasis,
|
|
11
|
+
storyDependsOn,
|
|
10
12
|
tallyOf,
|
|
11
13
|
validateRunBudget
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-5at77jx8.js";
|
|
13
15
|
import {
|
|
14
16
|
EventLog,
|
|
15
17
|
OUTCOME_NOT_RECORDED,
|
|
@@ -47,13 +49,16 @@ import {
|
|
|
47
49
|
FRAMEWORK_ROOT,
|
|
48
50
|
MAX_ITEM_CHARS,
|
|
49
51
|
PLAN_STATUSES,
|
|
52
|
+
PROJECT_FRAMEWORK_DIR,
|
|
50
53
|
listRunDirs,
|
|
54
|
+
parseHandoff,
|
|
51
55
|
parseYaml,
|
|
52
|
-
parseYamlRepairing
|
|
56
|
+
parseYamlRepairing,
|
|
57
|
+
withoutSrcToken
|
|
53
58
|
} from "./chunk-9bd6b3xf.js";
|
|
54
59
|
|
|
55
60
|
// src/core/statusline/runSnapshot.ts
|
|
56
|
-
import { existsSync as
|
|
61
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
57
62
|
import { join as join4 } from "node:path";
|
|
58
63
|
|
|
59
64
|
// src/core/run/RunStore.ts
|
|
@@ -653,23 +658,123 @@ var PLAN_SHAPE_RULES = [
|
|
|
653
658
|
{
|
|
654
659
|
issue: "#319",
|
|
655
660
|
text: "**End-to-end coverage is not the last story.** Either a test-harness story runs in W1 and later stories " + "depend on it and run against it, or each UI or feature story carries the e2e command in its own dod. A " + "final e2e story that depends on everything is the story never reached. The `plan` check names a command " + "some stories of one epic and repo carry and a sibling does not — an advisory: say in that story why not."
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
issue: "#365",
|
|
664
|
+
text: "**A story that adds an invariant over EXISTING data lands in the same story as, or in a wave after, the " + "story that makes existing rows and fixtures satisfy it.** A check constraint, a `NOT NULL`, a required " + "field or a validation rule written ahead of the story that populates what it enforces makes that earlier " + "story's own dod structurally red — every fixture the enforcing story's own tests run against still lacks " + "the value, and two stories in the SAME wave run in separate worktrees that never see each other's writes, " + "so 'later wave' is the only way one story's data can satisfy another's rule. If the invariant must land " + "first, write it NON-ENFORCING — nullable, consistency-only, no constraint — and let the later story " + "tighten it once the data exists. The `plan` check refuses a story whose acceptance or test plan names an " + "enforcement over a field a later story's acceptance or test plan names populating."
|
|
656
665
|
}
|
|
657
666
|
];
|
|
658
667
|
|
|
659
668
|
// src/core/run/runOutcome.ts
|
|
669
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
660
670
|
import { join as join3 } from "node:path";
|
|
661
671
|
|
|
662
672
|
// src/core/build/handoff.ts
|
|
663
673
|
var LINE_BREAK_MARK = "⏎";
|
|
664
674
|
var LINE_BREAK_NOTE = `\`${LINE_BREAK_MARK}\` marks a line break inside a quoted command; the review log beside this file ` + "keeps the command verbatim.";
|
|
665
675
|
var FINDING_STATUS_RE = new RegExp(`—\\s+(${PLAN_STATUSES.join("|")})\\s+—`);
|
|
676
|
+
function findingStatus(bullet) {
|
|
677
|
+
const found = FINDING_STATUS_RE.exec(bullet)?.[1];
|
|
678
|
+
return found === undefined ? null : found;
|
|
679
|
+
}
|
|
680
|
+
var FINDING_ID_RE = /^\s*(?:[-*]\s+)?(\S+)\s+·\s/;
|
|
681
|
+
function findingId(bullet) {
|
|
682
|
+
return FINDING_ID_RE.exec(bullet)?.[1] ?? null;
|
|
683
|
+
}
|
|
666
684
|
var FINDING_REASON_RE = new RegExp(`—\\s+(?:${PLAN_STATUSES.join("|")})\\s+—\\s+[^:]*:\\s*(\\S.*)$`);
|
|
685
|
+
function findingReason(bullet) {
|
|
686
|
+
const found = FINDING_REASON_RE.exec(withoutSrcToken(bullet).trimEnd())?.[1];
|
|
687
|
+
return found === undefined ? null : found.trim();
|
|
688
|
+
}
|
|
667
689
|
|
|
668
690
|
// src/core/build/dependencyHold.ts
|
|
691
|
+
function dependencyIsPending(status) {
|
|
692
|
+
return status === "review" || status === "in_progress";
|
|
693
|
+
}
|
|
694
|
+
function decidingHold(holds) {
|
|
695
|
+
return holds.find((hold) => !dependencyIsPending(hold.status)) ?? holds[0] ?? null;
|
|
696
|
+
}
|
|
697
|
+
function dependencyWaitReason(held) {
|
|
698
|
+
return `it waits on dependency ${held.id}, which is \`${held.status}\` and is offered again by the next ` + `invocation; ${held.id} \`done\` is what releases it, and no reopen is owed`;
|
|
699
|
+
}
|
|
669
700
|
var NOT_A_HOLD_REASON = "its block is not a dependency hold — a reviewer, a DoD or a developer recorded it, " + "and reopening a dependency does not answer that; `tldrx story reopen <id>` does";
|
|
670
701
|
|
|
671
702
|
// src/core/run/runOutcome.ts
|
|
672
703
|
var BUILD_HANDOFF = join3(BUILD_PHASE, "handoff.md");
|
|
704
|
+
var FINDINGS = "Findings";
|
|
705
|
+
var REASON_NOT_RECORDED = "no reason recorded — `04-build/handoff.md` carries no `## Findings` bullet for it";
|
|
706
|
+
function storiesView(runDir) {
|
|
707
|
+
const progress = buildProgress(runDir);
|
|
708
|
+
if (progress === null)
|
|
709
|
+
return null;
|
|
710
|
+
const stories = progress.waves.flatMap((wave) => wave.stories);
|
|
711
|
+
const counts = {
|
|
712
|
+
total: stories.length,
|
|
713
|
+
done: stories.filter((s) => s.status === "done").length,
|
|
714
|
+
in_progress: stories.filter((s) => s.status === "in_progress").length,
|
|
715
|
+
review: stories.filter((s) => s.status === "review").length,
|
|
716
|
+
blocked: stories.filter((s) => s.status === "blocked").length,
|
|
717
|
+
todo: stories.filter((s) => s.status === "todo").length
|
|
718
|
+
};
|
|
719
|
+
const reasons = blockedReasons(runDir);
|
|
720
|
+
const statusById = new Map(stories.map((story) => [story.id, story.status]));
|
|
721
|
+
const unfinished = stories.filter((story) => story.status !== "done").map((story) => {
|
|
722
|
+
const waitingOn = story.status === "todo" && !progress.implicit ? pendingHold(storyDependsOn(runDir, story.id), statusById) : null;
|
|
723
|
+
return {
|
|
724
|
+
id: story.id,
|
|
725
|
+
status: story.status,
|
|
726
|
+
reason: reasons.get(story.id) ?? REASON_NOT_RECORDED,
|
|
727
|
+
...waitingOn === null ? {} : { waitingOn }
|
|
728
|
+
};
|
|
729
|
+
});
|
|
730
|
+
const waiting = unfinished.find((story) => story.waitingOn !== undefined);
|
|
731
|
+
return {
|
|
732
|
+
counts,
|
|
733
|
+
unfinished,
|
|
734
|
+
firstBlocked: unfinished.find((story) => story.status === "blocked") ?? null,
|
|
735
|
+
firstWaiting: waiting?.waitingOn === undefined ? null : { id: waiting.id, waitingOn: waiting.waitingOn, reason: dependencyWaitReason(waiting.waitingOn) }
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function pendingHold(dependsOn, statusById) {
|
|
739
|
+
const holds = [];
|
|
740
|
+
for (const id of dependsOn) {
|
|
741
|
+
const status = statusById.get(id);
|
|
742
|
+
if (status === undefined || status === "done")
|
|
743
|
+
continue;
|
|
744
|
+
holds.push({ id, status });
|
|
745
|
+
}
|
|
746
|
+
const deciding = decidingHold(holds);
|
|
747
|
+
return deciding !== null && dependencyIsPending(deciding.status) ? deciding : null;
|
|
748
|
+
}
|
|
749
|
+
function blockedReasons(runDir) {
|
|
750
|
+
const out = new Map;
|
|
751
|
+
const path = join3(runDir, BUILD_HANDOFF);
|
|
752
|
+
if (!existsSync2(path))
|
|
753
|
+
return out;
|
|
754
|
+
let text;
|
|
755
|
+
try {
|
|
756
|
+
text = readFileSync3(path, "utf8");
|
|
757
|
+
} catch {
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
760
|
+
let bullets;
|
|
761
|
+
try {
|
|
762
|
+
bullets = parseHandoff(text).sections.find((section) => section.name === FINDINGS)?.bullets.map((b) => b.text) ?? [];
|
|
763
|
+
} catch {
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
766
|
+
for (const bullet of bullets) {
|
|
767
|
+
if (findingStatus(bullet) === "done")
|
|
768
|
+
continue;
|
|
769
|
+
const id = findingId(bullet);
|
|
770
|
+
const reason = findingReason(bullet);
|
|
771
|
+
if (id === null || reason === null)
|
|
772
|
+
continue;
|
|
773
|
+
if (!out.has(id))
|
|
774
|
+
out.set(id, reason);
|
|
775
|
+
}
|
|
776
|
+
return out;
|
|
777
|
+
}
|
|
673
778
|
var NO_BUILD_PHASE = "this run's workflow has no Build phase, so no story could be delivered";
|
|
674
779
|
function describeRunOutcome(outcome) {
|
|
675
780
|
if (outcome === undefined)
|
|
@@ -689,6 +794,7 @@ function statusWithOutcome(status, outcome) {
|
|
|
689
794
|
|
|
690
795
|
// src/core/run/autoGate.ts
|
|
691
796
|
var AUTO_GATE_ACTOR = "auto";
|
|
797
|
+
var RATE_LIMIT_RESUME_NOTE_PREFIX = "run auto resumed this stage: its auto gate was held by an unfinished " + "story a rate-limit warning parked, and the provider's own clock has since cleared it.";
|
|
692
798
|
var RETRYABLE_CONDITIONS = new Set(["checks", "claim-sources"]);
|
|
693
799
|
var AUTO_GATE_REFUSED_PREFIX = "auto-gate refused — held by: ";
|
|
694
800
|
var HELD_SEPARATOR = " · ";
|
|
@@ -725,7 +831,15 @@ function describeGateSignature(sig) {
|
|
|
725
831
|
}
|
|
726
832
|
|
|
727
833
|
// src/core/facilitator/skipIf.ts
|
|
728
|
-
import { existsSync as
|
|
834
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
835
|
+
|
|
836
|
+
// src/core/facilitator/seedSolution.ts
|
|
837
|
+
var WHAT_STAGE_OWN_INPUT_PREFIXES = [
|
|
838
|
+
`${PROJECT_FRAMEWORK_DIR}/memory/`,
|
|
839
|
+
`${PROJECT_FRAMEWORK_DIR}/map/`
|
|
840
|
+
];
|
|
841
|
+
|
|
842
|
+
// src/core/facilitator/skipIf.ts
|
|
729
843
|
function phaseDirs(runDir) {
|
|
730
844
|
try {
|
|
731
845
|
return readdirSync(runDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^0[1-9]-/.test(entry.name)).map((entry) => entry.name).sort();
|
|
@@ -734,19 +848,19 @@ function phaseDirs(runDir) {
|
|
|
734
848
|
}
|
|
735
849
|
}
|
|
736
850
|
function openQuestionIds(path) {
|
|
737
|
-
if (!
|
|
851
|
+
if (!existsSync3(path))
|
|
738
852
|
return [];
|
|
739
853
|
try {
|
|
740
|
-
return openBlocks(parseQuestions(
|
|
854
|
+
return openBlocks(parseQuestions(readFileSync4(path, "utf8")).blocks).map((block) => block.id);
|
|
741
855
|
} catch {
|
|
742
856
|
return [];
|
|
743
857
|
}
|
|
744
858
|
}
|
|
745
859
|
function blockingQuestionIds(path) {
|
|
746
|
-
if (!
|
|
860
|
+
if (!existsSync3(path))
|
|
747
861
|
return [];
|
|
748
862
|
try {
|
|
749
|
-
return openBlocks(parseQuestions(
|
|
863
|
+
return openBlocks(parseQuestions(readFileSync4(path, "utf8")).blocks).filter((block) => !isAdvisory(block)).map((block) => block.id);
|
|
750
864
|
} catch {
|
|
751
865
|
return [];
|
|
752
866
|
}
|
|
@@ -828,10 +942,10 @@ function fromTolerantRead(root) {
|
|
|
828
942
|
}
|
|
829
943
|
function readBudgetMirror(runDir) {
|
|
830
944
|
const path = join4(runDir, "run.yml");
|
|
831
|
-
if (!
|
|
945
|
+
if (!existsSync4(path))
|
|
832
946
|
return { ceiling_usd: 0, spent_usd: 0 };
|
|
833
947
|
try {
|
|
834
|
-
const text =
|
|
948
|
+
const text = readFileSync5(path, "utf8");
|
|
835
949
|
return {
|
|
836
950
|
ceiling_usd: numberAfter(text, "ceiling_usd"),
|
|
837
951
|
spent_usd: numberAfter(text, "spent_usd")
|
|
@@ -848,16 +962,306 @@ function numberAfter(text, key) {
|
|
|
848
962
|
// src/core/run/waiting.ts
|
|
849
963
|
import { basename as basename2, join as join6 } from "node:path";
|
|
850
964
|
|
|
965
|
+
// src/core/facilitator/agentEvents.ts
|
|
966
|
+
function rateLimitLine(frame) {
|
|
967
|
+
const parts = [frame.status];
|
|
968
|
+
if (frame.utilization !== null)
|
|
969
|
+
parts.push(`${String(Math.round(frame.utilization * 100))}% of`);
|
|
970
|
+
parts.push(frame.window === null ? "the provider's window" : `the ${frame.window} window`);
|
|
971
|
+
if (frame.resetsAt !== null)
|
|
972
|
+
parts.push(`· resets ${new Date(frame.resetsAt * 1000).toISOString()}`);
|
|
973
|
+
return parts.join(" ");
|
|
974
|
+
}
|
|
975
|
+
var STRUCTURED_OUTPUT_TOOL = "StructuredOutput";
|
|
976
|
+
class AgentStream {
|
|
977
|
+
openTools = new Map;
|
|
978
|
+
seenQuestions = new Set;
|
|
979
|
+
provider;
|
|
980
|
+
codexStructured = null;
|
|
981
|
+
questionCount = 0;
|
|
982
|
+
constructor(provider = "claude") {
|
|
983
|
+
this.provider = provider;
|
|
984
|
+
}
|
|
985
|
+
push(line) {
|
|
986
|
+
const doc = parseLine(line);
|
|
987
|
+
if (doc === null)
|
|
988
|
+
return [];
|
|
989
|
+
if (this.provider === "codex")
|
|
990
|
+
return this.codex(doc);
|
|
991
|
+
switch (doc.type) {
|
|
992
|
+
case "system":
|
|
993
|
+
return this.system(doc);
|
|
994
|
+
case "assistant":
|
|
995
|
+
return this.assistant(doc);
|
|
996
|
+
case "user":
|
|
997
|
+
return this.user(doc);
|
|
998
|
+
case "result":
|
|
999
|
+
return this.result(doc);
|
|
1000
|
+
case "rate_limit_event":
|
|
1001
|
+
return this.rateLimit(doc);
|
|
1002
|
+
default:
|
|
1003
|
+
return [];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
codex(doc) {
|
|
1007
|
+
if (doc.type === "thread.started") {
|
|
1008
|
+
return [{ kind: "start", model: null, sessionId: str(doc.thread_id) }];
|
|
1009
|
+
}
|
|
1010
|
+
if (doc.type === "turn.failed") {
|
|
1011
|
+
return [{ kind: "error", message: str(obj(doc.error)?.message) ?? "the Codex turn failed" }];
|
|
1012
|
+
}
|
|
1013
|
+
if (doc.type === "turn.completed") {
|
|
1014
|
+
const usage = obj(doc.usage);
|
|
1015
|
+
const structured = this.codexStructured;
|
|
1016
|
+
return [
|
|
1017
|
+
{
|
|
1018
|
+
kind: "cost",
|
|
1019
|
+
usd: null,
|
|
1020
|
+
inputTokens: num(usage?.input_tokens),
|
|
1021
|
+
outputTokens: num(usage?.output_tokens),
|
|
1022
|
+
cacheCreationTokens: num(usage?.cache_write_input_tokens),
|
|
1023
|
+
cacheReadTokens: num(usage?.cached_input_tokens)
|
|
1024
|
+
},
|
|
1025
|
+
...this.questions(obj(structured)?.questions_asked),
|
|
1026
|
+
{ kind: "done", ok: true, structured, costUsd: 0 }
|
|
1027
|
+
];
|
|
1028
|
+
}
|
|
1029
|
+
if (doc.type !== "item.started" && doc.type !== "item.completed")
|
|
1030
|
+
return [];
|
|
1031
|
+
const item = obj(doc.item);
|
|
1032
|
+
if (item === null)
|
|
1033
|
+
return [];
|
|
1034
|
+
const id = str(item.id);
|
|
1035
|
+
if (item.type === "agent_message") {
|
|
1036
|
+
const message = str(item.text);
|
|
1037
|
+
if (message === null)
|
|
1038
|
+
return [];
|
|
1039
|
+
const structured = parseLine(message);
|
|
1040
|
+
if (structured !== null)
|
|
1041
|
+
this.codexStructured = structured;
|
|
1042
|
+
return [{ kind: "text", text: message }];
|
|
1043
|
+
}
|
|
1044
|
+
if (item.type !== "command_execution")
|
|
1045
|
+
return [];
|
|
1046
|
+
const command = str(item.command);
|
|
1047
|
+
if (doc.type === "item.started") {
|
|
1048
|
+
return [{ kind: "tool", id, name: "Bash", target: command }];
|
|
1049
|
+
}
|
|
1050
|
+
return [{
|
|
1051
|
+
kind: "tool-done",
|
|
1052
|
+
id,
|
|
1053
|
+
name: "Bash",
|
|
1054
|
+
ok: item.status === "completed" && (item.exit_code === 0 || item.exit_code === null),
|
|
1055
|
+
ms: null,
|
|
1056
|
+
countsAsRead: true
|
|
1057
|
+
}];
|
|
1058
|
+
}
|
|
1059
|
+
system(doc) {
|
|
1060
|
+
if (doc.subtype !== "init")
|
|
1061
|
+
return [];
|
|
1062
|
+
return [{ kind: "start", model: str(doc.model), sessionId: str(doc.session_id) }];
|
|
1063
|
+
}
|
|
1064
|
+
rateLimit(doc) {
|
|
1065
|
+
const info = obj(doc.rate_limit_info);
|
|
1066
|
+
const status = str(info?.status);
|
|
1067
|
+
if (info === null || status === null)
|
|
1068
|
+
return [];
|
|
1069
|
+
const window = str(info.rateLimitType);
|
|
1070
|
+
const named = window === null ? null : obj(obj(info.unifiedWindows)?.[window]);
|
|
1071
|
+
return [{
|
|
1072
|
+
kind: "rate-limit",
|
|
1073
|
+
status,
|
|
1074
|
+
window,
|
|
1075
|
+
utilization: maybeNum(info.utilization) ?? maybeNum(named?.utilization),
|
|
1076
|
+
resetsAt: maybeNum(info.resetsAt) ?? maybeNum(named?.resetsAt)
|
|
1077
|
+
}];
|
|
1078
|
+
}
|
|
1079
|
+
assistant(doc) {
|
|
1080
|
+
const message = obj(doc.message);
|
|
1081
|
+
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
1082
|
+
const at = millis(doc.timestamp);
|
|
1083
|
+
const events = [];
|
|
1084
|
+
for (const raw of blocks) {
|
|
1085
|
+
const block = obj(raw);
|
|
1086
|
+
if (block === null)
|
|
1087
|
+
continue;
|
|
1088
|
+
if (block.type === "text") {
|
|
1089
|
+
const text = str(block.text);
|
|
1090
|
+
if (text !== null && text.trim() !== "")
|
|
1091
|
+
events.push({ kind: "text", text: text.trim() });
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (block.type !== "tool_use")
|
|
1095
|
+
continue;
|
|
1096
|
+
const name = str(block.name) ?? "tool";
|
|
1097
|
+
const id = str(block.id);
|
|
1098
|
+
if (name === STRUCTURED_OUTPUT_TOOL) {
|
|
1099
|
+
events.push(...this.questions(obj(block.input)?.questions_asked));
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (id !== null)
|
|
1103
|
+
this.openTools.set(id, { name, at });
|
|
1104
|
+
events.push({ kind: "tool", id, name, target: toolTarget(name, obj(block.input)) });
|
|
1105
|
+
}
|
|
1106
|
+
const usage = obj(message?.usage);
|
|
1107
|
+
if (usage !== null) {
|
|
1108
|
+
events.push({
|
|
1109
|
+
kind: "cost",
|
|
1110
|
+
usd: null,
|
|
1111
|
+
inputTokens: num(usage.input_tokens),
|
|
1112
|
+
outputTokens: num(usage.output_tokens),
|
|
1113
|
+
cacheCreationTokens: num(usage.cache_creation_input_tokens),
|
|
1114
|
+
cacheReadTokens: num(usage.cache_read_input_tokens)
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
return events;
|
|
1118
|
+
}
|
|
1119
|
+
user(doc) {
|
|
1120
|
+
const message = obj(doc.message);
|
|
1121
|
+
const blocks = Array.isArray(message?.content) ? message.content : [];
|
|
1122
|
+
const at = millis(doc.timestamp);
|
|
1123
|
+
const events = [];
|
|
1124
|
+
for (const raw of blocks) {
|
|
1125
|
+
const block = obj(raw);
|
|
1126
|
+
if (block === null || block.type !== "tool_result")
|
|
1127
|
+
continue;
|
|
1128
|
+
const id = str(block.tool_use_id);
|
|
1129
|
+
const open = id === null ? undefined : this.openTools.get(id);
|
|
1130
|
+
if (id !== null)
|
|
1131
|
+
this.openTools.delete(id);
|
|
1132
|
+
if (open === undefined)
|
|
1133
|
+
continue;
|
|
1134
|
+
events.push({
|
|
1135
|
+
kind: "tool-done",
|
|
1136
|
+
id,
|
|
1137
|
+
name: open.name,
|
|
1138
|
+
ok: block.is_error !== true,
|
|
1139
|
+
ms: at === null || open.at === null ? null : Math.max(0, at - open.at)
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
return events;
|
|
1143
|
+
}
|
|
1144
|
+
result(doc) {
|
|
1145
|
+
const events = [];
|
|
1146
|
+
const structured = doc.structured_output ?? null;
|
|
1147
|
+
events.push(...this.questions(obj(structured)?.questions_asked));
|
|
1148
|
+
const cost = typeof doc.total_cost_usd === "number" ? doc.total_cost_usd : 0;
|
|
1149
|
+
const usage = obj(doc.usage);
|
|
1150
|
+
events.push({
|
|
1151
|
+
kind: "cost",
|
|
1152
|
+
usd: cost,
|
|
1153
|
+
inputTokens: num(usage?.input_tokens),
|
|
1154
|
+
outputTokens: num(usage?.output_tokens),
|
|
1155
|
+
cacheCreationTokens: num(usage?.cache_creation_input_tokens),
|
|
1156
|
+
cacheReadTokens: num(usage?.cache_read_input_tokens)
|
|
1157
|
+
});
|
|
1158
|
+
const ok = doc.is_error !== true;
|
|
1159
|
+
if (!ok) {
|
|
1160
|
+
const errors = Array.isArray(doc.errors) ? doc.errors : [];
|
|
1161
|
+
const first = errors.find((entry) => typeof entry === "string");
|
|
1162
|
+
events.push({ kind: "error", message: first ?? str(doc.subtype) ?? "the sub-agent reported an error" });
|
|
1163
|
+
}
|
|
1164
|
+
events.push({ kind: "done", ok, structured, costUsd: cost });
|
|
1165
|
+
return events;
|
|
1166
|
+
}
|
|
1167
|
+
questions(value) {
|
|
1168
|
+
if (!Array.isArray(value))
|
|
1169
|
+
return [];
|
|
1170
|
+
const events = [];
|
|
1171
|
+
for (const entry of value) {
|
|
1172
|
+
if (typeof entry !== "string" || entry.trim() === "")
|
|
1173
|
+
continue;
|
|
1174
|
+
const text = entry.trim();
|
|
1175
|
+
if (this.seenQuestions.has(text))
|
|
1176
|
+
continue;
|
|
1177
|
+
this.seenQuestions.add(text);
|
|
1178
|
+
this.questionCount += 1;
|
|
1179
|
+
events.push({ kind: "question", index: this.questionCount, text });
|
|
1180
|
+
}
|
|
1181
|
+
return events;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function toolTarget(name, input) {
|
|
1185
|
+
if (input === null)
|
|
1186
|
+
return null;
|
|
1187
|
+
const named = (() => {
|
|
1188
|
+
switch (name) {
|
|
1189
|
+
case "Read":
|
|
1190
|
+
case "Write":
|
|
1191
|
+
case "NotebookEdit":
|
|
1192
|
+
return input.file_path ?? input.notebook_path;
|
|
1193
|
+
case "Edit":
|
|
1194
|
+
case "MultiEdit":
|
|
1195
|
+
return input.file_path;
|
|
1196
|
+
case "Bash":
|
|
1197
|
+
case "BashOutput":
|
|
1198
|
+
return input.command;
|
|
1199
|
+
case "Grep":
|
|
1200
|
+
case "Glob":
|
|
1201
|
+
return input.pattern;
|
|
1202
|
+
case "WebFetch":
|
|
1203
|
+
return input.url;
|
|
1204
|
+
case "WebSearch":
|
|
1205
|
+
return input.query;
|
|
1206
|
+
case "Task":
|
|
1207
|
+
case "Agent":
|
|
1208
|
+
return input.description;
|
|
1209
|
+
case "TodoWrite":
|
|
1210
|
+
return null;
|
|
1211
|
+
default:
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
})();
|
|
1215
|
+
if (typeof named === "string" && named !== "")
|
|
1216
|
+
return named;
|
|
1217
|
+
if (named !== null && named !== undefined)
|
|
1218
|
+
return null;
|
|
1219
|
+
for (const value of Object.values(input)) {
|
|
1220
|
+
if (typeof value === "string" && value !== "")
|
|
1221
|
+
return value;
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
function parseLine(line) {
|
|
1226
|
+
const trimmed = line.trim();
|
|
1227
|
+
if (trimmed === "" || !trimmed.startsWith("{"))
|
|
1228
|
+
return null;
|
|
1229
|
+
try {
|
|
1230
|
+
const doc = JSON.parse(trimmed);
|
|
1231
|
+
return typeof doc === "object" && doc !== null && !Array.isArray(doc) ? doc : null;
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function obj(value) {
|
|
1237
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1238
|
+
}
|
|
1239
|
+
function str(value) {
|
|
1240
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
1241
|
+
}
|
|
1242
|
+
function num(value) {
|
|
1243
|
+
return typeof value === "number" ? value : 0;
|
|
1244
|
+
}
|
|
1245
|
+
function maybeNum(value) {
|
|
1246
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1247
|
+
}
|
|
1248
|
+
function millis(value) {
|
|
1249
|
+
if (typeof value !== "string")
|
|
1250
|
+
return null;
|
|
1251
|
+
const at = Date.parse(value);
|
|
1252
|
+
return Number.isFinite(at) ? at : null;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
851
1255
|
// src/core/run/prepared.ts
|
|
852
|
-
import { existsSync as
|
|
1256
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, statSync } from "node:fs";
|
|
853
1257
|
import { join as join5 } from "node:path";
|
|
854
1258
|
var PENDING_JSON = "pending.json";
|
|
855
1259
|
function preparedBundles(runDir, stageId) {
|
|
856
1260
|
const dir = join5(runDir, ".agent", stageId);
|
|
857
|
-
if (!
|
|
1261
|
+
if (!existsSync5(dir))
|
|
858
1262
|
return [];
|
|
859
1263
|
const found = [];
|
|
860
|
-
if (
|
|
1264
|
+
if (existsSync5(join5(dir, PENDING_JSON)))
|
|
861
1265
|
found.push(dir);
|
|
862
1266
|
let entries;
|
|
863
1267
|
try {
|
|
@@ -873,7 +1277,7 @@ function preparedBundles(runDir, stageId) {
|
|
|
873
1277
|
} catch {
|
|
874
1278
|
continue;
|
|
875
1279
|
}
|
|
876
|
-
if (
|
|
1280
|
+
if (existsSync5(join5(child, PENDING_JSON)))
|
|
877
1281
|
found.push(child);
|
|
878
1282
|
}
|
|
879
1283
|
return found;
|
|
@@ -900,6 +1304,52 @@ function startedHeadless(runDir, stageId) {
|
|
|
900
1304
|
return lastStageStartMode(runDir, stageId) === "headless";
|
|
901
1305
|
}
|
|
902
1306
|
|
|
1307
|
+
// src/core/run/rateLimitPark.ts
|
|
1308
|
+
function currentRateLimitPark(runDir, stageId) {
|
|
1309
|
+
let events;
|
|
1310
|
+
try {
|
|
1311
|
+
events = EventLog.forRun(runDir).readAll().events;
|
|
1312
|
+
} catch {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
let stageStartIndex = -1;
|
|
1316
|
+
for (let index = events.length - 1;index >= 0; index -= 1) {
|
|
1317
|
+
const event = events[index];
|
|
1318
|
+
if (event !== undefined && event.stage === stageId && event.type === "stage.started") {
|
|
1319
|
+
stageStartIndex = index;
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (stageStartIndex === -1)
|
|
1324
|
+
return null;
|
|
1325
|
+
let park = null;
|
|
1326
|
+
for (let index = events.length - 1;index >= stageStartIndex; index -= 1) {
|
|
1327
|
+
const event = events[index];
|
|
1328
|
+
if (event === undefined || event.type !== "agent.rate_limited" || event.stage !== stageId)
|
|
1329
|
+
continue;
|
|
1330
|
+
const payload = event.payload;
|
|
1331
|
+
const parked = payload.parked;
|
|
1332
|
+
if (typeof parked !== "string" || parked === "")
|
|
1333
|
+
continue;
|
|
1334
|
+
park = {
|
|
1335
|
+
status: typeof payload.status === "string" ? payload.status : "",
|
|
1336
|
+
window: typeof payload.window === "string" ? payload.window : null,
|
|
1337
|
+
utilization: typeof payload.utilization === "number" ? payload.utilization : null,
|
|
1338
|
+
resetsAt: typeof payload.resets_at === "number" ? payload.resets_at : null,
|
|
1339
|
+
parked
|
|
1340
|
+
};
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
if (park === null)
|
|
1344
|
+
return null;
|
|
1345
|
+
const view = storiesView(runDir);
|
|
1346
|
+
if (view === null || view.unfinished.length === 0)
|
|
1347
|
+
return null;
|
|
1348
|
+
if (view.unfinished.some((story) => story.status !== "todo"))
|
|
1349
|
+
return null;
|
|
1350
|
+
return park;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
903
1353
|
// src/core/run/waiting.ts
|
|
904
1354
|
var MOVABLE_KINDS = [
|
|
905
1355
|
"gate",
|
|
@@ -949,6 +1399,16 @@ function waitingFor(run, runDir) {
|
|
|
949
1399
|
switch (entry.stage.status) {
|
|
950
1400
|
case "awaiting_gate": {
|
|
951
1401
|
const held = heldByNote(entry.stage.gate?.note ?? "");
|
|
1402
|
+
if (held.length === 1 && held[0] === "stories") {
|
|
1403
|
+
const park = currentRateLimitPark(runDir, entry.stage.id);
|
|
1404
|
+
if (park !== null) {
|
|
1405
|
+
return {
|
|
1406
|
+
kind: "gate",
|
|
1407
|
+
message: `gate on ${entry.phase.id}/${entry.stage.id} — parked by a rate-limit warning ` + `(${rateLimitLine(park)})` + (park.resetsAt === null ? " — it stated no reset instant, so nothing here knows when it resumes" : `, resumes automatically at ${new Date(park.resetsAt * 1000).toISOString()}`),
|
|
1408
|
+
questions: open
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
952
1412
|
return {
|
|
953
1413
|
kind: "gate",
|
|
954
1414
|
message: `gate on ${entry.phase.id}/${entry.stage.id}` + (held.length === 0 ? "" : ` — held by ${held.join(", ")}`) + ` — \`tldrx approve\` or \`tldrx reject --note "…"\``,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
runSnapshot,
|
|
21
21
|
statusWithOutcome,
|
|
22
22
|
whatIsWaiting
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-whvae1vw.js";
|
|
24
24
|
import"./chunk-ae6bkfs5.js";
|
|
25
25
|
import {
|
|
26
26
|
expertsDir,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
pathsIntersect,
|
|
29
29
|
readExpertDomain,
|
|
30
30
|
stackExpertNames
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-5at77jx8.js";
|
|
32
32
|
import {
|
|
33
33
|
isFinished
|
|
34
34
|
} from "./chunk-akemhezh.js";
|
package/dist/hooks/statusline.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bar,
|
|
4
4
|
runSnapshot
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-whvae1vw.js";
|
|
6
6
|
import"./chunk-ae6bkfs5.js";
|
|
7
|
-
import"./chunk-
|
|
7
|
+
import"./chunk-5at77jx8.js";
|
|
8
8
|
import"./chunk-akemhezh.js";
|
|
9
9
|
import"./chunk-8ds2ret8.js";
|
|
10
10
|
import"./chunk-yyhwf3bp.js";
|