jeo-code 0.8.42 → 0.8.43
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 +23 -1
- package/README.ja.md +2 -1
- package/README.ko.md +2 -1
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/package.json +1 -1
- package/src/agent/ast-edit-tool.ts +2 -2
- package/src/agent/engine.ts +44 -7
- package/src/agent/job-registry.ts +186 -28
- package/src/agent/job-tool.ts +1 -1
- package/src/agent/lsp-rename-tool.ts +2 -2
- package/src/agent/monitor-tool.ts +51 -0
- package/src/agent/subagent-registry.ts +2 -2
- package/src/agent/subagent-tool.ts +1 -1
- package/src/agent/subagents.ts +2 -2
- package/src/agent/task-tool.ts +47 -7
- package/src/agent/tool-schemas.ts +16 -0
- package/src/agent/tools.ts +36 -4
- package/src/commands/launch/slash-handlers.ts +2 -0
- package/src/commands/launch/stream.ts +16 -1
- package/src/commands/launch.ts +38 -8
- package/src/tui/app.ts +57 -41
- package/src/types/playwriter.d.ts +15 -0
package/src/tui/app.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { renderFooter, type FooterData } from "./components/footer";
|
|
|
20
20
|
import { renderForgeMark, forgeMarkHeight, forgeMarkFrameCount, forgeBeat } from "./components/ascii-art";
|
|
21
21
|
import { evolutionTrack, createStageProgress, type StageProgress, transitionMessage } from "./components/evolution";
|
|
22
22
|
import type { TaskSubEvent } from "../agent/task-tool";
|
|
23
|
+
import type { MonitorJobEvent } from "../agent/job-registry";
|
|
23
24
|
import { supportsUnicode } from "./components/capability";
|
|
24
25
|
import { centerBlock, padLineTo, boxBlock, BOX_ASCII, BOX_UNICODE } from "./components/layout";
|
|
25
26
|
import { SECTION_GAP, sectionLabel, stackSections } from "./components/section";
|
|
@@ -274,19 +275,15 @@ export class LaunchTui {
|
|
|
274
275
|
// Cumulative token usage for the live turn (engine onUsage event).
|
|
275
276
|
private turnUsage: { inputTokens: number; outputTokens: number } | null = null;
|
|
276
277
|
// True while at least one delegated subagent SLOT is in flight — drives the `(sub)`
|
|
277
|
-
// status marker. Derived from `subagentLiveSlots.size`
|
|
278
|
-
//
|
|
279
|
-
// clears the marker while its siblings are still visibly running.
|
|
278
|
+
// status marker. Derived from `subagentLiveSlots.size` rather than a plain boolean
|
|
279
|
+
// so one completed concurrent run cannot clear still-running siblings.
|
|
280
280
|
private subagentActive = false;
|
|
281
|
-
// Latest nested subagent activity, PER CONCURRENT SLOT
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// and, worse, cleared the whole `(sub)` marker the instant ANY one worker finished).
|
|
285
|
-
// Keyed by the event's fan-out `index` (1-based); a single/detached run with no
|
|
286
|
-
// index shares the fixed key 0. `subagentLiveOrder` tracks touch order so the
|
|
281
|
+
// Latest nested subagent activity, PER CONCURRENT SLOT. Detached runs use their
|
|
282
|
+
// stable registry id, fan-out workers use their 1-based index, and legacy
|
|
283
|
+
// synchronous runs share one slot. `subagentLiveOrder` tracks touch order so the
|
|
287
284
|
// status row can show the MOST RECENTLY active slot plus a "+N more" count.
|
|
288
|
-
private readonly subagentLiveSlots = new Map<
|
|
289
|
-
private readonly subagentLiveOrder:
|
|
285
|
+
private readonly subagentLiveSlots = new Map<string, string>();
|
|
286
|
+
private readonly subagentLiveOrder: string[] = [];
|
|
290
287
|
// Bounded activity-history ring: one plain-text entry per ledger append, with a
|
|
291
288
|
// turn-relative timestamp. Powers Ctrl+O's "recent activity" tail so the detail
|
|
292
289
|
// view ALWAYS answers "what has been happening", even before the first reply.
|
|
@@ -1004,10 +1001,9 @@ export class LaunchTui {
|
|
|
1004
1001
|
}
|
|
1005
1002
|
// A delegated subagent's LATEST nested event beats the parent's static
|
|
1006
1003
|
// "Task: <role> …" card title — a long task otherwise reads as a stall even
|
|
1007
|
-
// though the subagent is actively reading/editing/running underneath. When
|
|
1008
|
-
//
|
|
1009
|
-
//
|
|
1010
|
-
// one subagent whose label keeps randomly changing.
|
|
1004
|
+
// though the subagent is actively reading/editing/running underneath. When
|
|
1005
|
+
// multiple concurrent slots live, append a "+N more running" count so parallel
|
|
1006
|
+
// work does not look like one subagent whose label keeps randomly changing.
|
|
1011
1007
|
if (this.subagentActive && this.subagentLiveSlots.size > 0) {
|
|
1012
1008
|
const mostRecent = this.subagentLiveOrder[this.subagentLiveOrder.length - 1];
|
|
1013
1009
|
const line = (mostRecent !== undefined ? this.subagentLiveSlots.get(mostRecent) : undefined)
|
|
@@ -1063,6 +1059,7 @@ export class LaunchTui {
|
|
|
1063
1059
|
const color = this.theme.color;
|
|
1064
1060
|
const role = e.role || "subagent";
|
|
1065
1061
|
const roleLabel = e.index && e.total ? `${role.toUpperCase()}[${e.index}/${e.total}]` : role.toUpperCase();
|
|
1062
|
+
const detachedLabel = e.detached && e.id ? ` [${e.id}]` : "";
|
|
1066
1063
|
const badge = categoryBadge("subagent", { color });
|
|
1067
1064
|
const ok = this.unicode ? "✓" : "v";
|
|
1068
1065
|
const bad = this.unicode ? "✗" : "x";
|
|
@@ -1070,10 +1067,14 @@ export class LaunchTui {
|
|
|
1070
1067
|
const last = this.unicode ? "└─" : "`-";
|
|
1071
1068
|
const detail = (e.detail ?? "").split("\n").find(l => l.trim().length > 0)?.trim().slice(0, 140) ?? "";
|
|
1072
1069
|
const summary = e.summary ? ` — ${e.summary}` : "";
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
const slot = e.
|
|
1070
|
+
// Detached runs must retain their registry identity so concurrent detached events
|
|
1071
|
+
// cannot collide. Fan-out indices remain separate, while legacy synchronous/eval
|
|
1072
|
+
// events preserve their single shared slot.
|
|
1073
|
+
const slot = e.detached && e.id
|
|
1074
|
+
? `detached:${e.id}`
|
|
1075
|
+
: e.index !== undefined
|
|
1076
|
+
? `fanout:${e.index}`
|
|
1077
|
+
: "single";
|
|
1077
1078
|
const touchSlot = (line: string) => {
|
|
1078
1079
|
this.subagentLiveSlots.set(slot, line);
|
|
1079
1080
|
const at = this.subagentLiveOrder.indexOf(slot);
|
|
@@ -1086,41 +1087,58 @@ export class LaunchTui {
|
|
|
1086
1087
|
// activity readable in scrollback and visually separate from parent tools.
|
|
1087
1088
|
switch (e.kind) {
|
|
1088
1089
|
case "start":
|
|
1089
|
-
touchSlot(`${roleLabel} ${this.unicode ? "▸" : ">"} ${detail || "starting"}`);
|
|
1090
|
-
this.appendLedger(`${badge} ${this.unicode ? "▸" : ">"} ${roleLabel} · ${detail}\n`, "subagent");
|
|
1090
|
+
touchSlot(`${roleLabel}${detachedLabel} ${this.unicode ? "▸" : ">"} ${detail || "starting"}`);
|
|
1091
|
+
this.appendLedger(`${badge} ${this.unicode ? "▸" : ">"} ${roleLabel}${detachedLabel} · ${detail}\n`, "subagent");
|
|
1091
1092
|
break;
|
|
1092
1093
|
case "step":
|
|
1093
|
-
touchSlot(`${roleLabel} ${this.unicode ? "·" : "-"} ${detail || "working"}`);
|
|
1094
|
-
this.appendLedger(` ${badge} ${branch} ${roleLabel} · ${detail || "working"}\n`, "subagent");
|
|
1094
|
+
touchSlot(`${roleLabel}${detachedLabel} ${this.unicode ? "·" : "-"} ${detail || "working"}`);
|
|
1095
|
+
this.appendLedger(` ${badge} ${branch} ${roleLabel}${detachedLabel} · ${detail || "working"}\n`, "subagent");
|
|
1095
1096
|
break;
|
|
1096
1097
|
case "tool":
|
|
1097
|
-
touchSlot(`${roleLabel} ${e.success === false ? bad : ok} ${detail || "tool"}`);
|
|
1098
|
-
this.appendLedger(` ${badge} ${branch} ${roleLabel} ${e.success === false ? bad : ok} ${detail || "tool"}${summary}\n`, "subagent");
|
|
1098
|
+
touchSlot(`${roleLabel}${detachedLabel} ${e.success === false ? bad : ok} ${detail || "tool"}`);
|
|
1099
|
+
this.appendLedger(` ${badge} ${branch} ${roleLabel}${detachedLabel} ${e.success === false ? bad : ok} ${detail || "tool"}${summary}\n`, "subagent");
|
|
1099
1100
|
break;
|
|
1100
1101
|
case "error":
|
|
1101
|
-
touchSlot(`${roleLabel} ${bad} ${detail || "error"}`);
|
|
1102
|
-
this.appendLedger(` ${badge} ${branch} ${roleLabel} ${bad} ${detail || "error"}\n`, "subagent");
|
|
1102
|
+
touchSlot(`${roleLabel}${detachedLabel} ${bad} ${detail || "error"}`);
|
|
1103
|
+
this.appendLedger(` ${badge} ${branch} ${roleLabel}${detachedLabel} ${bad} ${detail || "error"}\n`, "subagent");
|
|
1103
1104
|
break;
|
|
1104
1105
|
case "thinking":
|
|
1105
1106
|
// Live-only preview (mirrors the main turn's dimmed "Thinking" block) — never
|
|
1106
1107
|
// persisted to the ledger; see TaskSubEvent.kind's doc comment for why.
|
|
1107
|
-
touchSlot(`${roleLabel} ${this.unicode ? "…" : "..."} ${detail || "thinking"}`);
|
|
1108
|
+
touchSlot(`${roleLabel}${detachedLabel} ${this.unicode ? "…" : "..."} ${detail || "thinking"}`);
|
|
1108
1109
|
break;
|
|
1109
1110
|
case "done":
|
|
1110
|
-
// Clear ONLY this slot —
|
|
1111
|
-
//
|
|
1112
|
-
// marker for the whole batch (the bug this per-slot map fixes).
|
|
1111
|
+
// Clear ONLY this slot — completing one concurrent run must leave its
|
|
1112
|
+
// still-running siblings visible and keep the `(sub)` marker active.
|
|
1113
1113
|
this.subagentLiveSlots.delete(slot);
|
|
1114
1114
|
{
|
|
1115
1115
|
const at = this.subagentLiveOrder.indexOf(slot);
|
|
1116
1116
|
if (at !== -1) this.subagentLiveOrder.splice(at, 1);
|
|
1117
1117
|
}
|
|
1118
1118
|
this.subagentActive = this.subagentLiveSlots.size > 0;
|
|
1119
|
-
this.appendLedger(`${badge} ${last} ${roleLabel} done${e.tokens ? ` (${e.tokens.input + e.tokens.output} tok)` : ""}${e.success === false ? " (incomplete)" : ""}: ${detail}\n`, "subagent");
|
|
1119
|
+
this.appendLedger(`${badge} ${last} ${roleLabel}${detachedLabel} done${e.tokens ? ` (${e.tokens.input + e.tokens.output} tok)` : ""}${e.success === false ? " (incomplete)" : ""}: ${detail}\n`, "subagent");
|
|
1120
1120
|
break;
|
|
1121
1121
|
}
|
|
1122
1122
|
this.draw();
|
|
1123
1123
|
}
|
|
1124
|
+
/** Render turn-scoped monitor lifecycle/output separately from subagent activity. */
|
|
1125
|
+
onMonitorEvent(e: MonitorJobEvent): void {
|
|
1126
|
+
if (this.finished) return;
|
|
1127
|
+
const badge = categoryBadge("progress", { color: this.theme.color });
|
|
1128
|
+
const label = sanitizeForFrame(
|
|
1129
|
+
`${e.record.id} · ${e.record.category} · ${e.record.description}`,
|
|
1130
|
+
).replace(/\s+/g, " ").trim().slice(0, 180);
|
|
1131
|
+
const branch = this.unicode ? "│" : "|";
|
|
1132
|
+
if (e.type === "start") {
|
|
1133
|
+
this.appendLedger(`${badge} ${this.unicode ? "◉" : "o"} Monitor ${label} started\n`, "monitor");
|
|
1134
|
+
} else if (e.type === "line") {
|
|
1135
|
+
const line = sanitizeForFrame(e.line).split("\n").find(row => row.trim())?.trim().slice(0, 180) ?? "";
|
|
1136
|
+
this.appendLedger(`${badge} ${branch} Monitor ${label}${line ? ` — ${line}` : ""}\n`, "monitor");
|
|
1137
|
+
} else {
|
|
1138
|
+
this.appendLedger(`${badge} ${this.unicode ? "└" : "`"} Monitor ${label} done\n`, "monitor");
|
|
1139
|
+
}
|
|
1140
|
+
this.draw();
|
|
1141
|
+
}
|
|
1124
1142
|
|
|
1125
1143
|
start(): void {
|
|
1126
1144
|
this.startedAt = Date.now();
|
|
@@ -1484,13 +1502,12 @@ export class LaunchTui {
|
|
|
1484
1502
|
* block shows only the most-recent lines, capped at ~30% of the screen height (a
|
|
1485
1503
|
* ceiling guards a tall terminal), so it grows with the stream and shrinks with the
|
|
1486
1504
|
* viewport. Returns [] when there is nothing to show. */
|
|
1487
|
-
/** gjc-parity multi-line parallel-subagent panel: when
|
|
1488
|
-
*
|
|
1505
|
+
/** gjc-parity multi-line parallel-subagent panel: when MORE THAN ONE concurrent
|
|
1506
|
+
* fan-out or detached slot is live, render one status line PER active slot
|
|
1489
1507
|
* (most-recently-touched first) instead of collapsing everything into a single
|
|
1490
|
-
* "+N more running" summary line (see `currentActivity()`). A single
|
|
1491
|
-
*
|
|
1492
|
-
*
|
|
1493
|
-
* activity is on screen at once, capped so a large fan-out can't blow the frame. */
|
|
1508
|
+
* "+N more running" summary line (see `currentActivity()`). A single subagent
|
|
1509
|
+
* still uses the plain status-line path; this panel makes concurrent work visibly
|
|
1510
|
+
* parallel while capping rows so a large batch cannot blow the frame. */
|
|
1494
1511
|
private static readonly SUBAGENT_PANEL_MAX_ROWS = 6;
|
|
1495
1512
|
private renderSubagentPanel(cols: number): string[] {
|
|
1496
1513
|
if (this.subagentLiveSlots.size <= 1) return [];
|
|
@@ -1689,9 +1706,8 @@ export class LaunchTui {
|
|
|
1689
1706
|
if (this.runningTool && this.liveToolOutput.trim()) {
|
|
1690
1707
|
tail.push(...this.renderLiveBlock("Output", this.liveToolOutput, cols, rows, 8));
|
|
1691
1708
|
}
|
|
1692
|
-
// Parallel subagent panel
|
|
1693
|
-
//
|
|
1694
|
-
// so a genuinely parallel batch reads as parallel instead of a single rotating line.
|
|
1709
|
+
// Parallel subagent panel: more than one fan-out or detached slot gets its own
|
|
1710
|
+
// multi-line block so genuinely parallel work does not read as one rotating line.
|
|
1695
1711
|
if (this.subagentActive) {
|
|
1696
1712
|
tail.push(...this.renderSubagentPanel(cols));
|
|
1697
1713
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/types/playwriter.d.ts
|
|
2
|
+
// Declaration file to allow TypeScript to recognize the optional Playwriter CLI module.
|
|
3
|
+
// This is only needed for the example automation script in scripts/playwriter-apply.ts.
|
|
4
|
+
// The actual Playwriter package is expected to be installed globally or run via npx.
|
|
5
|
+
|
|
6
|
+
declare module "playwriter" {
|
|
7
|
+
export class Playwriter {
|
|
8
|
+
/** Connect to the running Chrome extension bridge */
|
|
9
|
+
connect(): Promise<void>;
|
|
10
|
+
/** Open a new page (tab) in the connected browser */
|
|
11
|
+
newPage(): Promise<any>;
|
|
12
|
+
/** Disconnect from the bridge */
|
|
13
|
+
disconnect(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
}
|