@opencode-cockpit/subagents 0.7.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/agent/plugin.js +134 -0
- package/dist/cli/preview.js +99 -0
- package/dist/core/adapt/v1.js +341 -0
- package/dist/core/adapt/v2.js +379 -0
- package/dist/core/model/changes.js +17 -0
- package/dist/core/model/model.js +301 -0
- package/dist/core/sample.js +261 -0
- package/dist/core/view/markdown.js +211 -0
- package/dist/core/view/report.js +58 -0
- package/dist/core/view/rows.js +146 -0
- package/dist/core/view/screen.js +767 -0
- package/dist/core/view/sidebar.js +151 -0
- package/dist/server.js +2 -0
- package/dist/tui/index.js +991 -0
- package/dist/tui/render.js +103 -0
- package/dist/tui/source.js +228 -0
- package/dist/tui/view/overlay.js +87 -0
- package/dist/tui/view/sidebar.js +78 -0
- package/package.json +64 -0
- package/server.js +6 -0
- package/tui.js +6 -0
- package/types/agent/plugin.d.ts +32 -0
- package/types/cli/preview.d.ts +10 -0
- package/types/core/adapt/v1.d.ts +33 -0
- package/types/core/adapt/v2.d.ts +22 -0
- package/types/core/model/changes.d.ts +98 -0
- package/types/core/model/model.d.ts +99 -0
- package/types/core/sample.d.ts +8 -0
- package/types/core/view/markdown.d.ts +22 -0
- package/types/core/view/report.d.ts +15 -0
- package/types/core/view/rows.d.ts +43 -0
- package/types/core/view/screen.d.ts +101 -0
- package/types/core/view/sidebar.d.ts +30 -0
- package/types/server.d.ts +2 -0
- package/types/tui/index.d.ts +26 -0
- package/types/tui/render.d.ts +27 -0
- package/types/tui/source.d.ts +45 -0
- package/types/tui/view/overlay.d.ts +30 -0
- package/types/tui/view/sidebar.d.ts +22 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a subagent did, in one vocabulary for both OpenCodes.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode 1 says it as `message.part.updated` / `message.part.delta` / `session.status`; OpenCode 2
|
|
5
|
+
* as `session.tool.called` / `session.reasoning.delta` / `session.execution.started`. The two
|
|
6
|
+
* adapters in `tui/data/` translate each into these changes, and everything after them — the model,
|
|
7
|
+
* the rows — is written once. Shapes measured from real runs (test/fixtures, docs/opencode/agents.md).
|
|
8
|
+
*
|
|
9
|
+
* `id` is always the session the change is about; a subagent *is* a session with a parent.
|
|
10
|
+
*/
|
|
11
|
+
export type ToolState = "pending" | "running" | "completed" | "failed";
|
|
12
|
+
export type Change =
|
|
13
|
+
/** A session exists, or learned something about itself. Only fields present are applied. */
|
|
14
|
+
{
|
|
15
|
+
type: "session";
|
|
16
|
+
id: string;
|
|
17
|
+
parentID?: string;
|
|
18
|
+
agent?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
/** The model it runs on, as the host names it: `space-bunny-free`. */
|
|
21
|
+
model?: string;
|
|
22
|
+
/** Launched in the background: the main agent carried on without waiting for it. */
|
|
23
|
+
background?: boolean;
|
|
24
|
+
/** Permissions its agent's rules deny it outright (OpenCode 1 says; OpenCode 2 does not). */
|
|
25
|
+
denied?: string[];
|
|
26
|
+
at: number;
|
|
27
|
+
}
|
|
28
|
+
/** It finished one step — one model turn, with the calls it made. */
|
|
29
|
+
| {
|
|
30
|
+
type: "step";
|
|
31
|
+
id: string;
|
|
32
|
+
at: number;
|
|
33
|
+
}
|
|
34
|
+
/** Whether it is working. `waiting` is a permission or a question it is held on. */
|
|
35
|
+
| {
|
|
36
|
+
type: "status";
|
|
37
|
+
id: string;
|
|
38
|
+
status: "busy" | "idle" | "failed" | "waiting";
|
|
39
|
+
error?: string;
|
|
40
|
+
at: number;
|
|
41
|
+
}
|
|
42
|
+
/** Something said *to* it: the first is the task it was given, the rest are messages. */
|
|
43
|
+
| {
|
|
44
|
+
type: "prompt";
|
|
45
|
+
id: string;
|
|
46
|
+
key: string;
|
|
47
|
+
text: string;
|
|
48
|
+
at: number;
|
|
49
|
+
}
|
|
50
|
+
/** Its thinking, whole (`text`) or as it streams (`delta`). `key` names one block of it. */
|
|
51
|
+
| {
|
|
52
|
+
type: "thinking";
|
|
53
|
+
id: string;
|
|
54
|
+
key: string;
|
|
55
|
+
text?: string;
|
|
56
|
+
delta?: string;
|
|
57
|
+
done?: boolean;
|
|
58
|
+
at: number;
|
|
59
|
+
}
|
|
60
|
+
/** What it is writing back, the same way. */
|
|
61
|
+
| {
|
|
62
|
+
type: "reply";
|
|
63
|
+
id: string;
|
|
64
|
+
key: string;
|
|
65
|
+
text?: string;
|
|
66
|
+
delta?: string;
|
|
67
|
+
done?: boolean;
|
|
68
|
+
at: number;
|
|
69
|
+
}
|
|
70
|
+
/** A tool call, as it moves from pending to done. `call` names it; later changes fill it in. */
|
|
71
|
+
| {
|
|
72
|
+
type: "tool";
|
|
73
|
+
id: string;
|
|
74
|
+
call: string;
|
|
75
|
+
name?: string;
|
|
76
|
+
state?: ToolState;
|
|
77
|
+
input?: Record<string, unknown>;
|
|
78
|
+
/** What it returned — or, while running, what it has printed so far. */
|
|
79
|
+
output?: string;
|
|
80
|
+
error?: string;
|
|
81
|
+
/** When the call itself started and ended, when the host says; otherwise when we heard. */
|
|
82
|
+
started?: number;
|
|
83
|
+
ended?: number;
|
|
84
|
+
/** Its result in a few words, when the host gives the number: `9 matches`, `exit 1`. */
|
|
85
|
+
summary?: string;
|
|
86
|
+
at: number;
|
|
87
|
+
}
|
|
88
|
+
/** Running totals for the session, as the host keeps them. */
|
|
89
|
+
| {
|
|
90
|
+
type: "usage";
|
|
91
|
+
id: string;
|
|
92
|
+
tokens?: number;
|
|
93
|
+
cost?: number;
|
|
94
|
+
at: number;
|
|
95
|
+
};
|
|
96
|
+
/** OpenCode 2 starts every subagent's task with this line; the task is what follows it. */
|
|
97
|
+
export declare const SUBAGENT_PREAMBLE = "You are a subagent spawned by another session.";
|
|
98
|
+
export declare function taskText(text: string): string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagents, built from changes. Pure: no OpenCode, no terminal — a test feeds it the changes a real
|
|
3
|
+
* run produced and checks what came out.
|
|
4
|
+
*
|
|
5
|
+
* Every session is kept, the conversation you are in included: a subagent is only "a subagent" in
|
|
6
|
+
* relation to the session it hangs under, and `subagentsOf` walks that from whichever conversation is
|
|
7
|
+
* on screen.
|
|
8
|
+
*/
|
|
9
|
+
import type { Change, ToolState } from "./changes.ts";
|
|
10
|
+
export type Status = "starting" | "running" | "waiting" | "done" | "failed";
|
|
11
|
+
export type Entry = {
|
|
12
|
+
kind: "prompt";
|
|
13
|
+
key: string;
|
|
14
|
+
text: string;
|
|
15
|
+
at: number;
|
|
16
|
+
first: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "thinking";
|
|
19
|
+
key: string;
|
|
20
|
+
text: string;
|
|
21
|
+
done: boolean;
|
|
22
|
+
at: number;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "reply";
|
|
25
|
+
key: string;
|
|
26
|
+
text: string;
|
|
27
|
+
done: boolean;
|
|
28
|
+
at: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "tool";
|
|
31
|
+
call: string;
|
|
32
|
+
name: string;
|
|
33
|
+
state: ToolState;
|
|
34
|
+
input: Record<string, unknown>;
|
|
35
|
+
output: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
summary?: string;
|
|
38
|
+
at: number;
|
|
39
|
+
ended?: number;
|
|
40
|
+
};
|
|
41
|
+
export interface Session {
|
|
42
|
+
id: string;
|
|
43
|
+
parentID?: string;
|
|
44
|
+
agent: string;
|
|
45
|
+
title: string;
|
|
46
|
+
/** The task it was given — its first prompt. */
|
|
47
|
+
task?: string;
|
|
48
|
+
status: Status;
|
|
49
|
+
/** When the current status began: how long it has been waiting, say. */
|
|
50
|
+
since: number;
|
|
51
|
+
error?: string;
|
|
52
|
+
started: number;
|
|
53
|
+
/** When it last went idle or failed; cleared when it works again. */
|
|
54
|
+
ended?: number;
|
|
55
|
+
entries: Entry[];
|
|
56
|
+
tokens: number;
|
|
57
|
+
cost: number;
|
|
58
|
+
model?: string;
|
|
59
|
+
background?: boolean;
|
|
60
|
+
denied: string[];
|
|
61
|
+
steps: number;
|
|
62
|
+
/** When anything was last heard about it: a run gone quiet this long is asked about. */
|
|
63
|
+
seen: number;
|
|
64
|
+
}
|
|
65
|
+
export interface Model {
|
|
66
|
+
sessions: Map<string, Session>;
|
|
67
|
+
}
|
|
68
|
+
export declare const emptyModel: () => Model;
|
|
69
|
+
/** Applies one change in place. Unknown sessions are created, so order never loses anything. */
|
|
70
|
+
export declare function apply(model: Model, change: Change): void;
|
|
71
|
+
export declare function applyAll(model: Model, changes: Iterable<Change>): Model;
|
|
72
|
+
export interface Node {
|
|
73
|
+
session: Session;
|
|
74
|
+
/** 0 for a subagent of the conversation on screen, 1 for one it launched, … */
|
|
75
|
+
depth: number;
|
|
76
|
+
}
|
|
77
|
+
/** The subagents under `root`, depth first, oldest first at each level. */
|
|
78
|
+
export declare function subagentsOf(model: Model, root: string): Node[];
|
|
79
|
+
/** The conversation a session belongs to: up its parents to the one with none. */
|
|
80
|
+
export declare function rootOf(model: Model, id: string): string;
|
|
81
|
+
/** What a tool call is *about*, in a few words: the file, the pattern, the command. */
|
|
82
|
+
export declare function toolTarget(name: string, input: Record<string, unknown>): string;
|
|
83
|
+
export interface Activity {
|
|
84
|
+
kind: "starting" | "tool" | "thinking" | "writing" | "waiting" | "done" | "failed";
|
|
85
|
+
/** The tool's name, for `tool`. */
|
|
86
|
+
tool?: string;
|
|
87
|
+
text: string;
|
|
88
|
+
/** When the current thing started, for its elapsed time. */
|
|
89
|
+
since: number;
|
|
90
|
+
}
|
|
91
|
+
/** What a subagent is doing right now — the line under its name in the sidebar. */
|
|
92
|
+
export declare function activityOf(s: Session): Activity;
|
|
93
|
+
/** Counts for the block's heading. */
|
|
94
|
+
export declare function countsOf(nodes: readonly Node[]): {
|
|
95
|
+
total: number;
|
|
96
|
+
running: number;
|
|
97
|
+
done: number;
|
|
98
|
+
failed: number;
|
|
99
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A run caught mid-flight, for the preview: build has launched three subagents — one searching, one
|
|
3
|
+
* held on a permission, one finished. Fixed times, so the preview draws the same thing every time.
|
|
4
|
+
*/
|
|
5
|
+
import type { Change } from "./model/changes.ts";
|
|
6
|
+
export declare const SAMPLE_ROOT = "ses_build";
|
|
7
|
+
export declare const SAMPLE_NOW = 1790000060000;
|
|
8
|
+
export declare function sample(): Change[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A subagent's answer as rows: the markdown it writes, drawn rather than shown as source.
|
|
3
|
+
*
|
|
4
|
+
* Only what agents actually write: headings, **bold**, `code`, lists, quotes and fenced code. Inline
|
|
5
|
+
* styles survive wrapping — a line is cut into styled words first, then the words are laid out —
|
|
6
|
+
* so a path in `code` that crosses the edge keeps its colour on both lines.
|
|
7
|
+
*/
|
|
8
|
+
import { type Row, type Run } from "./rows.ts";
|
|
9
|
+
type Style = Omit<Run, "text">;
|
|
10
|
+
/** `**bold**` and `` `code` `` in one line, as styled pieces. */
|
|
11
|
+
export declare function inline(text: string, base: Style): Run[];
|
|
12
|
+
/**
|
|
13
|
+
* Styled pieces onto lines of `width`, word by word. `first` leads the first line and `rest` every
|
|
14
|
+
* line after it — a bullet, then the indent that lines up under the words.
|
|
15
|
+
*/
|
|
16
|
+
export declare function flow(pieces: readonly Run[], width: number, first: Run[], rest: Run[]): Row[];
|
|
17
|
+
export interface MarkdownOptions {
|
|
18
|
+
/** Columns of margin on the left of every row. */
|
|
19
|
+
indent: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function markdownRows(text: string, width: number, { indent }: MarkdownOptions): Row[];
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The main agent's view of its subagents: what `subagents_list` answers.
|
|
3
|
+
*
|
|
4
|
+
* Written for a model, not a person — plain lines, every id in full, and the one thing it needs to do
|
|
5
|
+
* with them said once: continue a subagent by its id rather than start a new one, so the follow-up
|
|
6
|
+
* keeps everything that subagent already read and tried. Pure, like everything in `core/`.
|
|
7
|
+
*/
|
|
8
|
+
import type { Node } from "../model/model.ts";
|
|
9
|
+
export interface ReportInput {
|
|
10
|
+
nodes: readonly Node[];
|
|
11
|
+
now: number;
|
|
12
|
+
/** Which OpenCode: the id goes in `task_id` on 1 and `sessionID` on 2. */
|
|
13
|
+
version: 1 | 2;
|
|
14
|
+
}
|
|
15
|
+
export declare function subagentReport({ nodes, now, version }: ReportInput): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rows of styled runs — the one drawing of every Subagents surface, pure so a test can measure it.
|
|
3
|
+
*
|
|
4
|
+
* Tones name a meaning, never a colour; `tui/render.ts` is the only place that knows what a tone
|
|
5
|
+
* looks like, so the preview CLI and OpenCode draw the same rows (docs/building/terminal-ui.md).
|
|
6
|
+
*/
|
|
7
|
+
export type Tone = "text" | "muted" | "accent" | "info" | "tool" | "success" | "error" | "warning" | "border";
|
|
8
|
+
/**
|
|
9
|
+
* What sits behind a run: the header band, a block (code, the input), a card (the task, your
|
|
10
|
+
* messages), or the item under the cursor.
|
|
11
|
+
*/
|
|
12
|
+
export type Fill = "none" | "band" | "block" | "card" | "selected";
|
|
13
|
+
export interface Run {
|
|
14
|
+
text: string;
|
|
15
|
+
tone?: Tone;
|
|
16
|
+
fill?: Fill;
|
|
17
|
+
bold?: boolean;
|
|
18
|
+
/** The terminal's DIM: thinking, and what is not the point. */
|
|
19
|
+
faint?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type Row = Run[];
|
|
22
|
+
export declare const rowText: (row: Row) => string;
|
|
23
|
+
/** Columns a string takes. Wide characters (CJK, most emoji) take two. */
|
|
24
|
+
export declare function widthOf(text: string, limit?: number): number;
|
|
25
|
+
/** `text` in at most `width` columns, ending in `…` when cut. */
|
|
26
|
+
export declare function cut(text: string, width: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* A row exactly `width` columns wide: runs cut where they overflow, and padded — in the last run's
|
|
29
|
+
* fill — where they fall short, so a band reaches both edges. Every row every surface draws goes
|
|
30
|
+
* through here; the grid test holds it to it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function fit(row: Row, width: number): Row;
|
|
33
|
+
/** Left and right parts on one row, the right part flush against the edge; the left gives way. */
|
|
34
|
+
export declare function spread(left: Row, right: Row, width: number): Row;
|
|
35
|
+
/** Words onto lines of at most `width` columns; newlines in the text are kept; a long word breaks. */
|
|
36
|
+
export declare function wrap(text: string, width: number): string[];
|
|
37
|
+
/** A duration in the fewest characters that still reads: `4s`, `51s`, `2m04s`, `1h12m`. */
|
|
38
|
+
export declare function elapsed(ms: number): string;
|
|
39
|
+
/** `1.2k`, `24k`, `1.1M` — tokens in a narrow column. */
|
|
40
|
+
export declare function compact(n: number): string;
|
|
41
|
+
/** A spinner frame, from a clock that ticks on every paint. */
|
|
42
|
+
export declare const SPINNER: string[];
|
|
43
|
+
export declare const spin: (frame: number) => string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One subagent, in the pane: its run as a timeline you can move through and open.
|
|
3
|
+
*
|
|
4
|
+
* ▌⠙ EXPLORE Scan architecture opportunities running 3m32s
|
|
5
|
+
* space-bunny-free · background · launched by build · 108 calls · 14 steps · 2.2M tok
|
|
6
|
+
* ‹ 2/3 › general Review diff explore Scan arch… general Verify
|
|
7
|
+
*
|
|
8
|
+
* ▎ Task from build
|
|
9
|
+
* ▎ Inspect the codebase architecture and docs; identify one or two…
|
|
10
|
+
*
|
|
11
|
+
* ◇ Thinking Need inspect sizes to identify. Glob source…
|
|
12
|
+
* › read docs/building/a-new-bay.md
|
|
13
|
+
* ⌄ grep "loadConfig" *.ts 9 matches · 1.4s
|
|
14
|
+
* │ pattern loadConfig
|
|
15
|
+
* │ include *.ts
|
|
16
|
+
* │ ─
|
|
17
|
+
* │ packages/shell/src/core/config.ts:80: export function loadConfig(
|
|
18
|
+
*
|
|
19
|
+
* ## Findings … (the answer, as markdown)
|
|
20
|
+
*
|
|
21
|
+
* Every item — a call, a block of thinking, a message you sent — is selectable (`j`/`k`) and opens
|
|
22
|
+
* or folds (`enter`, a click). Every row is exactly `width`; there are exactly `height` rows; only
|
|
23
|
+
* the body scrolls. Pure, like everything in `core/`.
|
|
24
|
+
*/
|
|
25
|
+
import { type Entry, type Node, type Session } from "../model/model.ts";
|
|
26
|
+
import { type Row } from "./rows.ts";
|
|
27
|
+
export interface ScreenInput {
|
|
28
|
+
session: Session;
|
|
29
|
+
/** Every subagent of the conversation, for "2/3" and the switcher. */
|
|
30
|
+
nodes: readonly Node[];
|
|
31
|
+
/** The agent that launched it: "Task from build". */
|
|
32
|
+
launcher?: string;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
now: number;
|
|
36
|
+
frame: number;
|
|
37
|
+
/** First body row shown; undefined follows the run as it grows. */
|
|
38
|
+
top?: number;
|
|
39
|
+
/** The item under the cursor. */
|
|
40
|
+
selected?: string;
|
|
41
|
+
/** Whether a message to the subagent was yours — the rest came from the main agent continuing it. */
|
|
42
|
+
yours?: (entry: Extract<Entry, {
|
|
43
|
+
kind: "prompt";
|
|
44
|
+
}>) => boolean;
|
|
45
|
+
/** Calls whose output is shown whole rather than its first lines (`a`). */
|
|
46
|
+
whole?: ReadonlySet<string>;
|
|
47
|
+
/** The cursor just moved: bring the selected item into view. Scrolling does not. */
|
|
48
|
+
reveal?: boolean;
|
|
49
|
+
/** Items opened by hand; running calls are open unless folded by hand (`closed`). */
|
|
50
|
+
open: ReadonlySet<string>;
|
|
51
|
+
closed: ReadonlySet<string>;
|
|
52
|
+
/** Every thinking block open, not just the ones opened by hand. */
|
|
53
|
+
thinking: boolean;
|
|
54
|
+
/** The details view in place of the timeline. */
|
|
55
|
+
details: boolean;
|
|
56
|
+
/** A message being typed at the bottom of the pane. */
|
|
57
|
+
input?: {
|
|
58
|
+
draft: string;
|
|
59
|
+
busy: boolean;
|
|
60
|
+
};
|
|
61
|
+
/** A line under the keys: what just happened. */
|
|
62
|
+
notice?: string;
|
|
63
|
+
/** What each item drew last time, kept by the caller between paints (`createScreenCache`). */
|
|
64
|
+
cache?: ScreenCache;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Each item's rows from the paint before, and what they were drawn from. A paint redraws only the
|
|
68
|
+
* items that changed — a scroll, a spinner tick or a new call no longer lays out every call, every
|
|
69
|
+
* block of thinking and the whole answer again (32 ms a paint on a 200-call run, measured).
|
|
70
|
+
*/
|
|
71
|
+
export interface ScreenCache {
|
|
72
|
+
items: Map<string, {
|
|
73
|
+
sig: string;
|
|
74
|
+
lines: Line[];
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
export declare const createScreenCache: () => ScreenCache;
|
|
78
|
+
export interface Screen {
|
|
79
|
+
rows: Row[];
|
|
80
|
+
/** The item each row belongs to, for a click. */
|
|
81
|
+
items: (string | undefined)[];
|
|
82
|
+
/** Selectable items, in order, for `j`/`k`. */
|
|
83
|
+
keys: string[];
|
|
84
|
+
/** Items drawn open, so a toggle knows which way to go. */
|
|
85
|
+
opened: string[];
|
|
86
|
+
/** The first body row shown, resolved. */
|
|
87
|
+
top: number;
|
|
88
|
+
/** The furthest the body can scroll. */
|
|
89
|
+
most: number;
|
|
90
|
+
/** Where the body starts on screen, for mapping a click. */
|
|
91
|
+
bodyAt: number;
|
|
92
|
+
}
|
|
93
|
+
/** A body row, and the item it belongs to. */
|
|
94
|
+
export interface Line {
|
|
95
|
+
row: Row;
|
|
96
|
+
item?: string;
|
|
97
|
+
}
|
|
98
|
+
export declare const itemKey: (entry: Entry) => string;
|
|
99
|
+
export declare function screenRows(input: ScreenInput): Screen;
|
|
100
|
+
/** For tests and the preview: the columns a row takes. */
|
|
101
|
+
export declare const rowWidth: (row: Row) => number;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sidebar block: a heading with counts, then two lines per subagent — who it is, and what it is
|
|
3
|
+
* doing now. Every line of a subagent carries its id, so a click on either opens it.
|
|
4
|
+
*
|
|
5
|
+
* The sidebar is a narrow column shared with Context, Shells and the statusline; rows are exactly
|
|
6
|
+
* its width, and a subagent's lines stay two however long its task or target is.
|
|
7
|
+
*
|
|
8
|
+
* Subagents 2 running
|
|
9
|
+
* ⠙ explore Map the auth flow
|
|
10
|
+
* └ grep "session" 9 calls · 51s
|
|
11
|
+
* ● general Update README
|
|
12
|
+
* └ done 3 calls · 28s
|
|
13
|
+
*/
|
|
14
|
+
import { type Node } from "../model/model.ts";
|
|
15
|
+
import { type Row } from "./rows.ts";
|
|
16
|
+
export interface SidebarLine {
|
|
17
|
+
row: Row;
|
|
18
|
+
/** The subagent a click on this line opens. */
|
|
19
|
+
id?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SidebarInput {
|
|
22
|
+
nodes: readonly Node[];
|
|
23
|
+
width: number;
|
|
24
|
+
now: number;
|
|
25
|
+
/** The spinner's clock. */
|
|
26
|
+
frame: number;
|
|
27
|
+
/** Subagents shown before the rest fold into a count; the sidebar is shared. */
|
|
28
|
+
limit?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function sidebarLines(input: SidebarInput): SidebarLine[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import { type Host } from "@opencode-cockpit/client/host";
|
|
3
|
+
export interface SubagentsTuiOptions {
|
|
4
|
+
/** Subagents shown in the sidebar before the rest fold into a count. */
|
|
5
|
+
sidebarRows?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Minutes a finished subagent stays in the sidebar; unset keeps it for the conversation. It is only
|
|
8
|
+
* out of the sidebar — `/subagents` and the pane's `[` `]` still reach it, and it comes back if it
|
|
9
|
+
* works again.
|
|
10
|
+
*/
|
|
11
|
+
hideFinishedAfter?: number;
|
|
12
|
+
/** Where the block sits among sidebar blocks; lower draws first (Shell 150, statusline 200). */
|
|
13
|
+
sidebarOrder?: number;
|
|
14
|
+
keybinds?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/** Subagents' TUI half as a factory, so the `opencode-cockpit` bundle can include it. */
|
|
17
|
+
export declare function createSubagentsTui({ source }?: {
|
|
18
|
+
source?: string;
|
|
19
|
+
}): (api: Host, rawOptions?: unknown) => Promise<void>;
|
|
20
|
+
/** One entry for both OpenCodes: v1 calls `tui`, v2 calls `setup` (docs/opencode/v2.md). */
|
|
21
|
+
declare const _default: {
|
|
22
|
+
id: string;
|
|
23
|
+
tui: (api: import("@opencode-ai/plugin/tui").TuiPluginApi, options?: unknown) => Promise<void>;
|
|
24
|
+
setup: (ctx: import("@opencode-cockpit/client/host").V2Context) => Promise<() => void>;
|
|
25
|
+
};
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only place that knows what a tone looks like: every colour from the user's OpenCode theme,
|
|
3
|
+
* through the host (so OpenCode 2's tokens arrive under the same names — client/host `themeFromV2`).
|
|
4
|
+
*
|
|
5
|
+
* Nothing of OpenTUI is imported at runtime: it is the host's, not installed beside a published
|
|
6
|
+
* plugin (docs/opencode/v2.md). `StyledText` and the colour class are borrowed from objects the host
|
|
7
|
+
* made, as Shell's and Review's pools do.
|
|
8
|
+
*/
|
|
9
|
+
import type { Theme } from "@opencode-cockpit/client/host";
|
|
10
|
+
import type { RGBA, TextRenderable } from "@opentui/core";
|
|
11
|
+
import type { Fill, Row, Run, Tone } from "../core/view/rows.ts";
|
|
12
|
+
export declare function toneColour(theme: Theme, tone: Tone | undefined): RGBA;
|
|
13
|
+
export declare function fillColour(theme: Theme, fill: Fill | undefined): RGBA | undefined;
|
|
14
|
+
/** Bold is bold; faint is the muted colour in italics — the terminal's DIM draws too dark to read. */
|
|
15
|
+
export declare const attributesOf: (run: Run) => number;
|
|
16
|
+
/**
|
|
17
|
+
* A surface colour certain to paint. A theme may leave its backgrounds transparent (OpenCode's
|
|
18
|
+
* "system" theme), and a full-window surface painted with one lets the conversation show through —
|
|
19
|
+
* the 0.6 fix, carried over from Shell and Review.
|
|
20
|
+
*/
|
|
21
|
+
export declare function solidSurface(theme: Theme): RGBA;
|
|
22
|
+
export interface RowPool {
|
|
23
|
+
draw: (rows: readonly Row[], theme: Theme) => void;
|
|
24
|
+
clear: () => void;
|
|
25
|
+
}
|
|
26
|
+
/** Rows onto a pool of lines — Shell's pool: only lines that changed are touched. */
|
|
27
|
+
export declare function createRowPool(lines: readonly TextRenderable[]): RowPool;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where subagents come from: the host's live events, and the stored runs of subagents that existed
|
|
3
|
+
* before this plugin did. The only file that reaches past `Host` into a specific OpenCode — through
|
|
4
|
+
* `api.v1` / `api.v2`, as Status's snapshot does — and every shape it reads was measured on 1.18.32
|
|
5
|
+
* and 2.0.15 (docs/opencode/agents.md). Everything it learns becomes `Change`s for the model.
|
|
6
|
+
*/
|
|
7
|
+
import type { Host } from "@opencode-cockpit/client/host";
|
|
8
|
+
import type { Log } from "@opencode-cockpit/client/log";
|
|
9
|
+
import type { Change } from "../core/model/changes.ts";
|
|
10
|
+
export interface Source {
|
|
11
|
+
/** Loads what exists under a conversation: its subagents, theirs, and their runs so far. */
|
|
12
|
+
load: (root: string) => Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Says something to a subagent; a busy one picks it up mid-run. `agent` is the subagent's own: sent
|
|
15
|
+
* without one, OpenCode 1 answered as its default `build` agent — other tools, other permissions.
|
|
16
|
+
*/
|
|
17
|
+
send: (id: string, text: string, busy: boolean, agent: string) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Stops a subagent's run. Measured: on OpenCode 1 its task fails as "aborted" and the main agent
|
|
20
|
+
* carries on; on OpenCode 2 it is interrupted, and the main agent may start it again.
|
|
21
|
+
*/
|
|
22
|
+
stop: (id: string) => Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Moves the subagents a conversation is blocked on into the background, so it carries on — what
|
|
25
|
+
* OpenCode's own `ctrl+b` does. Measured: OpenCode 2 always; OpenCode 1 only when started with
|
|
26
|
+
* OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true, and it says `false` otherwise. Resolves to
|
|
27
|
+
* whether it happened.
|
|
28
|
+
*/
|
|
29
|
+
background: (parentID: string) => Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Tells a conversation something, as its user. Used before a stop: told nothing, the main agent
|
|
32
|
+
* read the stopped subagent as failed and launched it again.
|
|
33
|
+
*/
|
|
34
|
+
note: (sessionID: string, text: string, busy: boolean, agent?: string) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Adds something to a conversation without starting a turn: the main agent reads it next time it
|
|
37
|
+
* is asked. Measured: OpenCode 1 `noReply` (drawn as your message), OpenCode 2 a synthetic message
|
|
38
|
+
* with `resume: false` (drawn as one line) — neither answered, both recalled it on the next turn.
|
|
39
|
+
*/
|
|
40
|
+
quiet: (sessionID: string, text: string, agent?: string) => Promise<void>;
|
|
41
|
+
/** What the host says about a session now, for a run that went quiet; nothing when it does not know. */
|
|
42
|
+
check: (id: string) => Change[];
|
|
43
|
+
dispose: () => void;
|
|
44
|
+
}
|
|
45
|
+
export declare function createSource(api: Host, log: Log, emit: (changes: Change[]) => void): Source;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { Host } from "@opencode-cockpit/client/host";
|
|
3
|
+
import type { BoxRenderable, TextRenderable } from "@opentui/core";
|
|
4
|
+
import type { JSX } from "solid-js";
|
|
5
|
+
/** Enough lines for any terminal: a slot's tree is read once, so the pool cannot grow later. */
|
|
6
|
+
export declare const MAX_LINES = 300;
|
|
7
|
+
export interface OverlayProps {
|
|
8
|
+
api: Host;
|
|
9
|
+
onReady: (parts: {
|
|
10
|
+
backdrop: BoxRenderable;
|
|
11
|
+
panel: BoxRenderable;
|
|
12
|
+
lines: TextRenderable[];
|
|
13
|
+
}) => void;
|
|
14
|
+
/** A click landed outside the pane. */
|
|
15
|
+
onDismiss: () => void;
|
|
16
|
+
/** A click landed on the pane, at this row of it. */
|
|
17
|
+
onClick: (y: number) => void;
|
|
18
|
+
/** The wheel turned over the pane: negative is up. */
|
|
19
|
+
onScroll: (delta: number) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The pane's surface — Review's shape: a full-window, transparent backdrop that catches clicks off
|
|
23
|
+
* the pane, and the pane inside it, right-aligned, half the window or all of it.
|
|
24
|
+
*
|
|
25
|
+
* **Nothing here is reactive.** The host reads a slot's children once (docs/opencode/gotchas.md), so
|
|
26
|
+
* the component hands its boxes up and the plugin assigns to them. Anchored bottom-right, because an
|
|
27
|
+
* absolute box is placed against the `app_bottom` container at the foot of the window. Only props a
|
|
28
|
+
* box actually uses: an unused one (`titleColor`) blanked Review's pane on OpenCode 2.
|
|
29
|
+
*/
|
|
30
|
+
export declare function Overlay(props: OverlayProps): JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import type { Host } from "@opencode-cockpit/client/host";
|
|
3
|
+
import type { BoxRenderable } from "@opentui/core";
|
|
4
|
+
import { type JSX } from "solid-js";
|
|
5
|
+
import type { SidebarLine } from "../../core/view/sidebar.ts";
|
|
6
|
+
export interface SidebarProps {
|
|
7
|
+
api: Host;
|
|
8
|
+
/** The block's lines, from `core/view/sidebar.ts`; a signal, so `<For>` redraws them. */
|
|
9
|
+
lines: () => readonly SidebarLine[];
|
|
10
|
+
onOpen: (id: string) => void;
|
|
11
|
+
/** The block's own box, so its rows can be drawn at the width the sidebar really gives it. */
|
|
12
|
+
onReady?: (box: BoxRenderable) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The Subagents block: the rows `core/view/sidebar.ts` produced, one `<text>` per row.
|
|
16
|
+
*
|
|
17
|
+
* Shell's and Status's sidebar pattern, proven live on both OpenCodes: a signal read by `<For>`
|
|
18
|
+
* redraws, where anything decided once in a slot's tree never would (docs/opencode/gotchas.md).
|
|
19
|
+
* Every line of a subagent opens it; mouse-up, not mouse-down, because the host acts on the release
|
|
20
|
+
* that follows (as Shell's sidebar found).
|
|
21
|
+
*/
|
|
22
|
+
export declare function SidebarBlock(props: SidebarProps): JSX.Element;
|