sisyphi 1.2.19 → 1.2.20
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/cli.js +508 -281
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +193 -287
- package/dist/daemon.js.map +1 -1
- package/dist/templates/agent-plugin/agents/spec.md +18 -7
- package/dist/tui.js +98 -79
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/agent-plugin/agents/spec.md +18 -7
|
@@ -77,7 +77,7 @@ Run this pass on **every startup**, **before** entering resume logic.
|
|
|
77
77
|
|
|
78
78
|
### 1. Explore
|
|
79
79
|
|
|
80
|
-
Use Bash + Glob + Grep + Read to explore the codebase relevant to the user's stated topic. Identify files, modules, and existing patterns that will be affected.
|
|
80
|
+
Use Bash + Glob + Grep + Read + the Agent tool to explore the codebase relevant to the user's stated topic. Identify files, modules, and existing patterns that will be affected.
|
|
81
81
|
|
|
82
82
|
### 2. Gauge Clarity
|
|
83
83
|
|
|
@@ -227,7 +227,7 @@ Dispatch a single `requirements-writer` subagent for the entire design (see "Sub
|
|
|
227
227
|
|
|
228
228
|
Validate the chunk (parseable JSON + `groups` array where each group has `id` and `requirements`). If invalid, increment N and re-dispatch; if it fails twice, bail.
|
|
229
229
|
|
|
230
|
-
**Snapshot prior approvals before merging.** If `context/requirements.json` already exists (this is a re-dispatch — §5.1 bounce-return or §5.2 writer-
|
|
230
|
+
**Snapshot prior approvals before merging.** If `context/requirements.json` already exists (this is a re-dispatch — §5.1 bounce-return or §5.2 fresh-writer escalation; the §5.2 lead-patch path never reaches here), read its current `groups[].requirements[]` and `groups[].safeAssumptions[]` and build an in-memory **approval map**: for every item where `status === 'approved'` AND `userNotes` is empty/absent, record `contentKey(item) → true`. The content key is the SHA-256 of the canonical-JSON `{title, ears}` after normalizing every string with `trim()` + collapse runs of whitespace to a single space. Items with `status: 'draft'`/`'rejected'` or non-empty `userNotes` are NOT added — the re-dispatch was triggered by their unresolved state, and the writer is meant to revisit them.
|
|
231
231
|
|
|
232
232
|
**Merge.** Replace `groups` entirely with the new chunk; preserve `meta`. Set `meta.stage = 'stage-2-in-progress'`. Then walk the new `groups[].requirements[]` and `groups[].safeAssumptions[]`: for each item, compute its content key; if the approval map contains it, set `status = 'approved'` and clear `userNotes`. Otherwise leave whatever the writer emitted untouched. Delete the chunk file.
|
|
233
233
|
|
|
@@ -283,22 +283,33 @@ With `meta.stage === 'stage-2-verdict-pending'`:
|
|
|
283
283
|
|
|
284
284
|
1. All requirements (incl. safeAssumptions) `status === 'approved'` → atomic-write `meta.stage = 'stage-2-done'`; proceed to Stage 3.
|
|
285
285
|
2. Any `status === 'rejected'` → §5.1 bounce-to-design.
|
|
286
|
-
3. Else (some `draft` with non-empty `userNotes`, no `rejected`) → §5.2
|
|
286
|
+
3. Else (some `draft` with non-empty `userNotes`, no `rejected`) → §5.2 comment resolution.
|
|
287
287
|
|
|
288
288
|
### §5.1 Bounce-to-design
|
|
289
289
|
|
|
290
290
|
Increment `meta.bounceIterations` (init 0 if absent; **never decrements**). If new value > 3, bail. Quote rejected items + `userNotes` to user. Dispatch engineer in revision mode (Stage 1 revision contract) with rejected items as feedback. After engineer returns, run `crtr human show` to display the revised design; re-sign-off. Re-render to text via `hl doc render`. Atomic-write `meta.stage = 'stage-2-in-progress'`. Return to Step 2. REQ ids may shift — each pass is independent. User comments flow to the engineer; the writer re-extracts from the revised design.
|
|
291
291
|
|
|
292
|
-
### §5.2
|
|
292
|
+
### §5.2 Comment resolution
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
The design is unchanged here — design-level objections route through bounce-to-design (§5.1, where the user picked "Bounce"). What remains is `draft` items carrying `userNotes`. **Resolve them in place by default; escalate to a fresh writer only when the comments demand broad re-extraction.** Patching in place is what keeps already-approved items from being reworded and re-asked: the writer regenerates the whole document and its wording drifts, so re-dispatching it for a localized tweak forces the human to re-approve everything.
|
|
295
|
+
|
|
296
|
+
**Triage the commented items.** Classify every `draft`-with-`userNotes` item:
|
|
297
|
+
|
|
298
|
+
- **Localized** — satisfiable by editing that requirement's own fields: reword its EARS clause, fix/add/remove a criterion, tighten or split its scope, correct a detail, clarify wording.
|
|
299
|
+
- **Broad** — implies behavior not captured anywhere (a coverage gap / a missing requirement), a regrouping or restructure across the document, or otherwise can't be met by editing the named items alone. (If a comment reveals the *design* is wrong rather than the requirement text, that is a bounce — tell the user and route to §5.1.)
|
|
300
|
+
|
|
301
|
+
**All comments localized → lead patch (no writer).** Edit each commented requirement in place to satisfy its `userNotes`. Hold the same bar as the writer (`agents/spec/requirements-writer.md`): keep the EARS shape valid (exactly one of `when`/`while`/`if`/`where` plus `shall`) and behavioral, not technical — verbs an external observer sees; no function names, file paths, or algorithms. Stay within the approved design; do not invent behavior it doesn't support. For each patched item: apply the edit, record a one-line rationale in `agentNotes` (e.g. `Revised per review: <what changed>`), clear `userNotes`, and set `status = 'draft'`. **Leave every `approved` item byte-for-byte untouched** — do not run the Step 2 snapshot/merge; that path is for the writer only. Atomic-write `requirements.json` with `meta.stage = 'stage-2-in-progress'` and return to Step 3. Only the patched items reappear as fresh decisions; approved items keep their `status` and surface with the `preAnswered` ◆ marker. Tell the user: `"Revised the N commented requirement(s) in place — only those are back for review; previously-approved items carry forward (◆)."`
|
|
302
|
+
|
|
303
|
+
**Any comment broad → fresh-writer escalation.** Increment `meta.writerRedispatchIterations` (init 0 if absent; **never decrements**). If new value > 3, bail: `"Stage 2 writer-redispatch cap reached after 3 passes. Latest comments preserved in requirements.json. Re-spawn spec fresh or escalate."` Atomic-write `meta.stage = 'writer-redispatch-pending'` BEFORE re-dispatching. Tell user: `"Comments span more than the named items — re-running the writer over the design; re-flag anything it misses on the next pass."` After the writer chunk merge (Step 2, which resets `meta.stage = 'stage-2-in-progress'`), return to Step 3.
|
|
304
|
+
|
|
305
|
+
**Patch not converging.** If an item you already patched comes back with another comment, the localized edit isn't landing — escalate that round via fresh-writer (or bounce via §5.1 if the new comment points at the design) rather than patching the same item a third time.
|
|
295
306
|
|
|
296
307
|
### Step 6 — Stage-2 state-machine table
|
|
297
308
|
|
|
298
309
|
| `meta.stage` | Set at | Resume action on lead respawn |
|
|
299
310
|
|---|---|---|
|
|
300
|
-
| `stage-2-in-progress` | Step 2 merge / §5.1 bounce-returns / §5.2 writer-merge | If `meta.openAskId` set → Resume Logic re-attach; else → Step 3 (issue review deck) |
|
|
301
|
-
| `writer-redispatch-pending` | §5.2 entry | §5.2 writer re-dispatch |
|
|
311
|
+
| `stage-2-in-progress` | Step 2 merge / §5.1 bounce-returns / §5.2 writer-merge / §5.2 lead patch | If `meta.openAskId` set → Resume Logic re-attach; else → Step 3 (issue review deck) |
|
|
312
|
+
| `writer-redispatch-pending` | §5.2 fresh-writer escalation entry | §5.2 fresh-writer re-dispatch |
|
|
302
313
|
| `stage-2-verdict-pending` | Step 4 atomic writeback | Step 5 verdict |
|
|
303
314
|
| `stage-2-done` | Step 5 case 1 | Stage 3 entry |
|
|
304
315
|
|
package/dist/tui.js
CHANGED
|
@@ -55,6 +55,11 @@ function setupTerminal() {
|
|
|
55
55
|
console.error(err);
|
|
56
56
|
process.exit(1);
|
|
57
57
|
});
|
|
58
|
+
process.on("unhandledRejection", (reason) => {
|
|
59
|
+
cleanup2();
|
|
60
|
+
console.error(reason);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
58
63
|
return cleanup2;
|
|
59
64
|
}
|
|
60
65
|
function writeToStdout(data) {
|
|
@@ -309,7 +314,7 @@ var init_paths = __esm({
|
|
|
309
314
|
});
|
|
310
315
|
|
|
311
316
|
// src/shared/platform.ts
|
|
312
|
-
import { execSync } from "child_process";
|
|
317
|
+
import { execFileSync, execSync } from "child_process";
|
|
313
318
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
314
319
|
function detectPlatform() {
|
|
315
320
|
if (cachedPlatform) return cachedPlatform;
|
|
@@ -892,7 +897,7 @@ var init_notify = __esm({
|
|
|
892
897
|
});
|
|
893
898
|
|
|
894
899
|
// src/daemon/ask-store.ts
|
|
895
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
|
|
900
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync13, readdirSync as readdirSync6, unlinkSync } from "fs";
|
|
896
901
|
import { basename as basename3 } from "path";
|
|
897
902
|
function readDecisions(cwd2, sessionId2, askId2) {
|
|
898
903
|
const p = askDecisionsPath(cwd2, sessionId2, askId2);
|
|
@@ -1200,7 +1205,6 @@ function createAppState(cwd2) {
|
|
|
1200
1205
|
const strategyScroll = new ThrottledScroll(requestRender);
|
|
1201
1206
|
const roadmapScroll = new ThrottledScroll(requestRender);
|
|
1202
1207
|
const expanded = /* @__PURE__ */ new Set();
|
|
1203
|
-
expanded.add("section:needs-you");
|
|
1204
1208
|
expanded.add("section:running");
|
|
1205
1209
|
return {
|
|
1206
1210
|
rows,
|
|
@@ -1261,6 +1265,8 @@ function createAppState(cwd2) {
|
|
|
1261
1265
|
inlineDeck: null,
|
|
1262
1266
|
visuals: /* @__PURE__ */ new Map(),
|
|
1263
1267
|
reviewPanel: null,
|
|
1268
|
+
pendingFocus: null,
|
|
1269
|
+
inlineDeckStartAskId: null,
|
|
1264
1270
|
cwd: cwd2
|
|
1265
1271
|
};
|
|
1266
1272
|
}
|
|
@@ -1471,7 +1477,7 @@ async function exportSessionToZip(sessionId2, cwd2, options) {
|
|
|
1471
1477
|
|
|
1472
1478
|
// src/shared/clipboard.ts
|
|
1473
1479
|
init_platform();
|
|
1474
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
1480
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
1475
1481
|
function detectClipboard() {
|
|
1476
1482
|
const platform = detectPlatform();
|
|
1477
1483
|
if (platform === "darwin") {
|
|
@@ -1535,7 +1541,7 @@ function copyToClipboard(text) {
|
|
|
1535
1541
|
return { reason: c.hint === null ? "No clipboard backend available" : c.hint };
|
|
1536
1542
|
}
|
|
1537
1543
|
try {
|
|
1538
|
-
|
|
1544
|
+
execFileSync2(c.copy.cmd, c.copy.args, { input: text, stdio: ["pipe", "ignore", "pipe"] });
|
|
1539
1545
|
return null;
|
|
1540
1546
|
} catch (err) {
|
|
1541
1547
|
const msg = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
@@ -1865,28 +1871,12 @@ function sessionSortKey(s) {
|
|
|
1865
1871
|
}
|
|
1866
1872
|
function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles = [], aggregateInbox = []) {
|
|
1867
1873
|
const nodes = [];
|
|
1868
|
-
const inboxBySession = /* @__PURE__ */ new Map();
|
|
1869
|
-
for (const item of aggregateInbox) {
|
|
1870
|
-
const sessionId2 = sessionIdFromDir(item.dir);
|
|
1871
|
-
const arr = inboxBySession.get(sessionId2) ?? [];
|
|
1872
|
-
arr.push(item);
|
|
1873
|
-
inboxBySession.set(sessionId2, arr);
|
|
1874
|
-
}
|
|
1875
|
-
const needsYou = [];
|
|
1876
1874
|
const running = [];
|
|
1877
1875
|
const done = [];
|
|
1878
1876
|
for (const s of sessions) {
|
|
1879
|
-
if (
|
|
1880
|
-
else if (s.status === "completed") done.push(s);
|
|
1877
|
+
if (s.status === "completed") done.push(s);
|
|
1881
1878
|
else running.push(s);
|
|
1882
1879
|
}
|
|
1883
|
-
needsYou.sort((a, b) => {
|
|
1884
|
-
const aItems = inboxBySession.get(a.id);
|
|
1885
|
-
const bItems = inboxBySession.get(b.id);
|
|
1886
|
-
const aOldest = Math.min(...aItems.map((i) => Date.parse(i.blockedSince)));
|
|
1887
|
-
const bOldest = Math.min(...bItems.map((i) => Date.parse(i.blockedSince)));
|
|
1888
|
-
return aOldest - bOldest;
|
|
1889
|
-
});
|
|
1890
1880
|
running.sort((a, b) => {
|
|
1891
1881
|
const k = sessionSortKey(a) - sessionSortKey(b);
|
|
1892
1882
|
if (k !== 0) return k;
|
|
@@ -2039,7 +2029,7 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
|
|
|
2039
2029
|
}
|
|
2040
2030
|
}
|
|
2041
2031
|
}
|
|
2042
|
-
function emitSessionRow(s
|
|
2032
|
+
function emitSessionRow(s) {
|
|
2043
2033
|
const sessionNodeId = `session:${s.id}`;
|
|
2044
2034
|
const isSelected = selectedSession?.id === s.id;
|
|
2045
2035
|
const isExpanded = expanded.has(sessionNodeId);
|
|
@@ -2059,38 +2049,31 @@ function buildTree(sessions, selectedSession, expanded, cwd2, polledContextFiles
|
|
|
2059
2049
|
createdAt: s.createdAt,
|
|
2060
2050
|
completedAt: isSelected ? selectedSession?.completedAt : void 0,
|
|
2061
2051
|
activeMs: isSelected ? selectedSession?.activeMs ?? s.activeMs : s.activeMs,
|
|
2062
|
-
askCount: askCount > 0 ? askCount : void 0,
|
|
2063
2052
|
orphaned: s.orphaned ?? false
|
|
2064
2053
|
});
|
|
2065
2054
|
if (isExpanded && isSelected) {
|
|
2066
2055
|
emitSessionChildren(s);
|
|
2067
2056
|
}
|
|
2068
2057
|
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
pendingCount: aggregateInbox.length
|
|
2079
|
-
});
|
|
2080
|
-
for (const s of needsYou) {
|
|
2081
|
-
emitSessionRow(s, inboxBySession.get(s.id)?.length ?? 0);
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2058
|
+
nodes.push({
|
|
2059
|
+
id: "needs-you-virtual",
|
|
2060
|
+
type: "needs-you-virtual",
|
|
2061
|
+
depth: 0,
|
|
2062
|
+
expandable: false,
|
|
2063
|
+
expanded: false,
|
|
2064
|
+
sessionId: "",
|
|
2065
|
+
pendingCount: aggregateInbox.length
|
|
2066
|
+
});
|
|
2084
2067
|
emitSection("running", running.length);
|
|
2085
2068
|
if (sectionExpanded("running")) {
|
|
2086
2069
|
for (const s of running) {
|
|
2087
|
-
emitSessionRow(s
|
|
2070
|
+
emitSessionRow(s);
|
|
2088
2071
|
}
|
|
2089
2072
|
}
|
|
2090
2073
|
emitSection("done", done.length);
|
|
2091
2074
|
if (sectionExpanded("done")) {
|
|
2092
2075
|
for (const s of done) {
|
|
2093
|
-
emitSessionRow(s
|
|
2076
|
+
emitSessionRow(s);
|
|
2094
2077
|
}
|
|
2095
2078
|
}
|
|
2096
2079
|
return nodes;
|
|
@@ -4909,8 +4892,8 @@ function handleInlineDeckKey(input, key, state2, actions) {
|
|
|
4909
4892
|
handle.unmount();
|
|
4910
4893
|
const nodes = actions.getNodes();
|
|
4911
4894
|
const i = nodes.findIndex((n) => n.id === "needs-you-virtual");
|
|
4912
|
-
const
|
|
4913
|
-
state2.cursorNodeId =
|
|
4895
|
+
const next = nodes.find((n) => n.type === "session") ?? nodes[i + 1] ?? nodes[0];
|
|
4896
|
+
state2.cursorNodeId = next?.id ?? null;
|
|
4914
4897
|
state2.focusPane = "tree";
|
|
4915
4898
|
requestRender();
|
|
4916
4899
|
return;
|
|
@@ -5530,11 +5513,16 @@ function rawSend(request, timeoutMs = 1e4) {
|
|
|
5530
5513
|
function send(request) {
|
|
5531
5514
|
return rawSend(request, 8e3);
|
|
5532
5515
|
}
|
|
5533
|
-
async function inboxList() {
|
|
5534
|
-
const res = await send({ type: "inbox-list" });
|
|
5516
|
+
async function inboxList(cwd2) {
|
|
5517
|
+
const res = await send({ type: "inbox-list", cwd: cwd2 });
|
|
5535
5518
|
if (!res.ok) return [];
|
|
5536
5519
|
return res.data?.items ?? [];
|
|
5537
5520
|
}
|
|
5521
|
+
async function focusGet(cwd2) {
|
|
5522
|
+
const res = await send({ type: "focus-get", cwd: cwd2 });
|
|
5523
|
+
if (!res.ok) return null;
|
|
5524
|
+
return res.data?.focus ?? null;
|
|
5525
|
+
}
|
|
5538
5526
|
|
|
5539
5527
|
// src/tui/lib/tmux.ts
|
|
5540
5528
|
init_paths();
|
|
@@ -5845,15 +5833,6 @@ function renderNodeContent(node, maxWidth) {
|
|
|
5845
5833
|
switch (node.type) {
|
|
5846
5834
|
case "section": {
|
|
5847
5835
|
switch (node.section) {
|
|
5848
|
-
case "needs-you":
|
|
5849
|
-
return {
|
|
5850
|
-
icon: "",
|
|
5851
|
-
label: "Needs You",
|
|
5852
|
-
meta: node.count > 0 ? `${node.count}` : "",
|
|
5853
|
-
color: node.count > 0 ? "red" : "gray",
|
|
5854
|
-
dim: false,
|
|
5855
|
-
metaColor: "red"
|
|
5856
|
-
};
|
|
5857
5836
|
case "running":
|
|
5858
5837
|
return {
|
|
5859
5838
|
icon: "",
|
|
@@ -5891,15 +5870,13 @@ function renderNodeContent(node, maxWidth) {
|
|
|
5891
5870
|
const cyclePart = node.cycleCount > 0 ? `C${node.cycleCount}` : "";
|
|
5892
5871
|
const dur = formatDuration(node.activeMs);
|
|
5893
5872
|
const agopart = node.status === "completed" && node.completedAt ? formatTimeAgo(node.completedAt) : "";
|
|
5894
|
-
const
|
|
5895
|
-
const meta = [askBadge, cyclePart, dur, agopart].filter(Boolean).join(" ");
|
|
5896
|
-
const metaColor = node.askCount ? "red" : void 0;
|
|
5873
|
+
const meta = [cyclePart, dur, agopart].filter(Boolean).join(" ");
|
|
5897
5874
|
const suffix = node.orphaned ? "\u26A0 orphan" : void 0;
|
|
5898
5875
|
const suffixColor = node.orphaned ? "red" : void 0;
|
|
5899
5876
|
const displayText = node.name ?? node.task;
|
|
5900
5877
|
const suffixWidth = suffix ? suffix.length + 1 : 0;
|
|
5901
5878
|
const maxLabel = Math.max(8, maxWidth - meta.length - 4 - suffixWidth);
|
|
5902
|
-
return { icon, label: truncate(displayText, maxLabel), meta, color, dim: dim2,
|
|
5879
|
+
return { icon, label: truncate(displayText, maxLabel), meta, color, dim: dim2, suffix, suffixColor };
|
|
5903
5880
|
}
|
|
5904
5881
|
case "cycle": {
|
|
5905
5882
|
const isRunning = !node.completedAt;
|
|
@@ -7341,7 +7318,7 @@ function renderFleetRollup(rect, state2, focused) {
|
|
|
7341
7318
|
}
|
|
7342
7319
|
const uniqueSessions = new Set(items.map((i) => sessionIdFromDir(i.dir))).size;
|
|
7343
7320
|
lines = [];
|
|
7344
|
-
lines.push([seg("
|
|
7321
|
+
lines.push([seg(" Inbox", { color: "red", bold: true })]);
|
|
7345
7322
|
lines.push(singleLine(` ${items.length} pending across ${uniqueSessions} sessions`, { dim: true }));
|
|
7346
7323
|
lines.push(singleLine(" "));
|
|
7347
7324
|
lines.push([seg(" By Type", { color: "cyan", bold: true })]);
|
|
@@ -8312,9 +8289,20 @@ function mountResolutionPanel(opts, state2) {
|
|
|
8312
8289
|
};
|
|
8313
8290
|
return deck;
|
|
8314
8291
|
}
|
|
8292
|
+
function firstBuildableFrom(start) {
|
|
8293
|
+
if (queue.length === 0) return null;
|
|
8294
|
+
for (let off = 0; off < queue.length; off++) {
|
|
8295
|
+
const idx = (start + off) % queue.length;
|
|
8296
|
+
const deck = buildDeck(idx);
|
|
8297
|
+
if (deck) return { idx, deck };
|
|
8298
|
+
}
|
|
8299
|
+
return null;
|
|
8300
|
+
}
|
|
8301
|
+
const initial = firstBuildableFrom(currentIndex);
|
|
8302
|
+
if (!initial) return null;
|
|
8303
|
+
currentIndex = initial.idx;
|
|
8304
|
+
const initialDeck = initial.deck;
|
|
8315
8305
|
const { cwd: initCwd, sessionId: initSessionId, askId: initAskId } = itemCoords(item());
|
|
8316
|
-
const initialDeck = buildDeck(currentIndex);
|
|
8317
|
-
if (!initialDeck) return null;
|
|
8318
8306
|
let currentDeck = initialDeck;
|
|
8319
8307
|
const initialProgress = readProgress(initCwd, initSessionId, initAskId);
|
|
8320
8308
|
let answeredCount = initialProgress?.responses.length ?? 0;
|
|
@@ -8375,14 +8363,14 @@ function mountResolutionPanel(opts, state2) {
|
|
|
8375
8363
|
teardown();
|
|
8376
8364
|
return;
|
|
8377
8365
|
}
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
const nextCoords = itemCoords(nextItem);
|
|
8381
|
-
const nextDeck = buildDeck(currentIndex);
|
|
8382
|
-
if (!nextDeck) {
|
|
8366
|
+
const next = firstBuildableFrom(Math.min(currentIndex, queue.length - 1));
|
|
8367
|
+
if (!next) {
|
|
8383
8368
|
teardown();
|
|
8384
8369
|
return;
|
|
8385
8370
|
}
|
|
8371
|
+
currentIndex = next.idx;
|
|
8372
|
+
const nextDeck = next.deck;
|
|
8373
|
+
const nextCoords = itemCoords(queue[currentIndex]);
|
|
8386
8374
|
currentDeck = nextDeck;
|
|
8387
8375
|
const nextProgress = readProgress(nextCoords.cwd, nextCoords.sessionId, nextCoords.askId);
|
|
8388
8376
|
answeredCount = nextProgress?.responses.length ?? 0;
|
|
@@ -8393,7 +8381,10 @@ function mountResolutionPanel(opts, state2) {
|
|
|
8393
8381
|
});
|
|
8394
8382
|
setDeckWatch(nextCoords, nextDeck);
|
|
8395
8383
|
requestRender();
|
|
8396
|
-
})()
|
|
8384
|
+
})().catch((err) => {
|
|
8385
|
+
notify(state2, `Failed to submit answer: ${err instanceof Error ? err.message : String(err)}`);
|
|
8386
|
+
requestRender();
|
|
8387
|
+
});
|
|
8397
8388
|
};
|
|
8398
8389
|
let watchedDeckPath = null;
|
|
8399
8390
|
let lastWatchedDeckJson = "";
|
|
@@ -8437,7 +8428,8 @@ function mountResolutionPanel(opts, state2) {
|
|
|
8437
8428
|
const { cwd: cwd2, sessionId: sessionId2, askId: askId2 } = itemCoords(it);
|
|
8438
8429
|
const cur = readMeta(cwd2, sessionId2, askId2);
|
|
8439
8430
|
if (cur?.status === "pending") {
|
|
8440
|
-
void updateMeta(cwd2, sessionId2, askId2, { status: "in-progress", startedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
8431
|
+
void updateMeta(cwd2, sessionId2, askId2, { status: "in-progress", startedAt: (/* @__PURE__ */ new Date()).toISOString() }).catch(() => {
|
|
8432
|
+
});
|
|
8441
8433
|
}
|
|
8442
8434
|
},
|
|
8443
8435
|
onComplete: (responses) => {
|
|
@@ -8617,22 +8609,23 @@ function mountReviewActionPanel(opts) {
|
|
|
8617
8609
|
// src/tui/panels/inbox-deck.ts
|
|
8618
8610
|
var lastMountDims = null;
|
|
8619
8611
|
function mountInlineDeck(state2, cols, rows) {
|
|
8612
|
+
const deckQueue = state2.aggregateInbox.filter((i) => i.kind !== "review");
|
|
8613
|
+
let startIndex = 0;
|
|
8614
|
+
if (state2.inlineDeckStartAskId) {
|
|
8615
|
+
const idx = deckQueue.findIndex((i) => askIdFromDir(i.dir) === state2.inlineDeckStartAskId);
|
|
8616
|
+
if (idx >= 0) startIndex = idx;
|
|
8617
|
+
state2.inlineDeckStartAskId = null;
|
|
8618
|
+
}
|
|
8620
8619
|
return mountResolutionPanel(
|
|
8621
8620
|
{
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
// would tear the whole inbox surface down the moment it advanced onto one.
|
|
8625
|
-
// Excluding them keeps the resolver to deck-backed asks; once those drain
|
|
8626
|
-
// it tears down and renderInboxDeckRows re-dispatches the now-front review.
|
|
8627
|
-
aggregateInbox: state2.aggregateInbox.filter((i) => i.kind !== "review"),
|
|
8628
|
-
startIndex: 0,
|
|
8621
|
+
aggregateInbox: deckQueue,
|
|
8622
|
+
startIndex,
|
|
8629
8623
|
cols,
|
|
8630
8624
|
rows,
|
|
8631
8625
|
daemonSend: send,
|
|
8632
8626
|
onUnmount: () => {
|
|
8633
8627
|
state2.inlineDeck = null;
|
|
8634
8628
|
state2.visuals.clear();
|
|
8635
|
-
state2.focusPane = "tree";
|
|
8636
8629
|
requestRender();
|
|
8637
8630
|
},
|
|
8638
8631
|
onOrphanTakeover: makeOrphanTakeover(state2, {
|
|
@@ -8962,13 +8955,19 @@ function startApp(state2, cleanup2) {
|
|
|
8962
8955
|
let contextFiles = [];
|
|
8963
8956
|
const listPromise = send({ type: "list", cwd: state2.cwd });
|
|
8964
8957
|
const statusPromise = state2.selectedSessionId ? send({ type: "status", sessionId: state2.selectedSessionId, cwd: state2.cwd }) : null;
|
|
8965
|
-
const inboxPromise = inboxList();
|
|
8966
|
-
const
|
|
8958
|
+
const inboxPromise = inboxList(state2.cwd);
|
|
8959
|
+
const focusPromise = focusGet(state2.cwd);
|
|
8960
|
+
const [listRes, statusRes, aggregateInbox, focusReq] = await Promise.all([
|
|
8967
8961
|
listPromise,
|
|
8968
8962
|
statusPromise ?? Promise.resolve(null),
|
|
8969
|
-
inboxPromise
|
|
8963
|
+
inboxPromise,
|
|
8964
|
+
focusPromise
|
|
8970
8965
|
]);
|
|
8971
8966
|
state2.aggregateInbox = aggregateInbox;
|
|
8967
|
+
if (focusReq !== null && !state2.resolutionActive) {
|
|
8968
|
+
state2.pendingFocus = { sessionId: focusReq.sessionId, askId: focusReq.askId, attempts: 0 };
|
|
8969
|
+
state2.searchFilter = null;
|
|
8970
|
+
}
|
|
8972
8971
|
const sessions = listRes.ok ? listRes.data?.sessions ?? [] : [];
|
|
8973
8972
|
if (!state2.resolutionActive) {
|
|
8974
8973
|
const aliveWindows = listAllWindowIds();
|
|
@@ -9169,6 +9168,10 @@ function startApp(state2, cleanup2) {
|
|
|
9169
9168
|
const q = state2.searchFilter.toLowerCase();
|
|
9170
9169
|
return s.task.toLowerCase().includes(q) || s.id.toLowerCase().includes(q);
|
|
9171
9170
|
}) : state2.sessions;
|
|
9171
|
+
if (state2.pendingFocus) {
|
|
9172
|
+
state2.expanded.add("section:running");
|
|
9173
|
+
state2.expanded.add("section:done");
|
|
9174
|
+
}
|
|
9172
9175
|
const statusFP = filteredSessions.map((s) => `${s.status}:${s.windowAlive}:${s.runningAgentCount}:${s.orphaned ?? false}`).join(",");
|
|
9173
9176
|
const inboxFP = `${state2.aggregateInbox.length}:${state2.aggregateInbox.map((i) => askIdFromDir(i.dir)).join(",")}`;
|
|
9174
9177
|
const cacheKey = `${state2.expanded.size}:${filteredSessions.length}:${state2.selectedSession?.id}:${state2.contextFiles.length}:${state2.searchFilter}:${statusFP}:${inboxFP}`;
|
|
@@ -9195,6 +9198,22 @@ function startApp(state2, cleanup2) {
|
|
|
9195
9198
|
const onNeedsYou = cursorNode?.type === "needs-you-virtual";
|
|
9196
9199
|
if (prevCursorOnNeedsYou && !onNeedsYou && state2.inlineDeck) state2.inlineDeck.unmount();
|
|
9197
9200
|
prevCursorOnNeedsYou = onNeedsYou;
|
|
9201
|
+
if (state2.pendingFocus) {
|
|
9202
|
+
const { askId: askId2 } = state2.pendingFocus;
|
|
9203
|
+
const askPresent = state2.aggregateInbox.some((i) => askIdFromDir(i.dir) === askId2);
|
|
9204
|
+
const nyIdx = nodes.findIndex((n) => n.type === "needs-you-virtual");
|
|
9205
|
+
if (askPresent && nyIdx >= 0) {
|
|
9206
|
+
state2.cursorIndex = nyIdx;
|
|
9207
|
+
state2.cursorNodeId = nodes[nyIdx].id;
|
|
9208
|
+
state2.inlineDeckStartAskId = askId2;
|
|
9209
|
+
state2.focusPane = "detail";
|
|
9210
|
+
state2.pendingFocus = null;
|
|
9211
|
+
requestRender();
|
|
9212
|
+
} else {
|
|
9213
|
+
state2.pendingFocus.attempts++;
|
|
9214
|
+
if (state2.pendingFocus.attempts > 25) state2.pendingFocus = null;
|
|
9215
|
+
}
|
|
9216
|
+
}
|
|
9198
9217
|
const rawSessionId = cursorNode?.sessionId;
|
|
9199
9218
|
const newSessionId = rawSessionId ? rawSessionId : null;
|
|
9200
9219
|
if (newSessionId !== state2.selectedSessionId) {
|