opencode-courier 0.4.1 → 0.6.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/README.md +38 -25
- package/dist/index.js +7 -4
- package/dist/person/form.d.ts +4 -1
- package/dist/person/form.js +5 -0
- package/dist/person/index.d.ts +2 -1
- package/dist/person/index.js +1 -0
- package/dist/person/permission.d.ts +37 -2
- package/dist/person/permission.js +91 -17
- package/dist/person/ports.d.ts +8 -2
- package/dist/person/rpc-handlers.d.ts +37 -30
- package/dist/person/rpc-handlers.js +26 -16
- package/dist/person/rpc.d.ts +43 -24
- package/dist/person/rpc.js +4 -3
- package/dist/person/text.d.ts +5 -0
- package/dist/person/text.js +21 -19
- package/dist/person/tools.d.ts +1 -1
- package/dist/person/tools.js +23 -12
- package/dist/person/tui.d.ts +23 -12
- package/dist/tree/domain.d.ts +23 -0
- package/dist/tree/domain.js +26 -0
- package/dist/tree/events.d.ts +2 -2
- package/dist/tree/events.js +5 -3
- package/dist/tree/index.d.ts +1 -1
- package/dist/tree/panel.d.ts +7 -2
- package/dist/tree/panel.js +19 -16
- package/dist/tree/ports.d.ts +7 -0
- package/dist/tree/roster.d.ts +5 -0
- package/dist/tree/roster.js +5 -3
- package/dist/tree/rpc-handlers.d.ts +45 -5
- package/dist/tree/rpc-handlers.js +30 -1
- package/dist/tree/rpc.d.ts +79 -19
- package/dist/tree/rpc.js +20 -8
- package/dist/tree/subtree.d.ts +40 -9
- package/dist/tree/subtree.js +40 -19
- package/dist/tree/summary.d.ts +2 -2
- package/dist/tree/summary.js +8 -16
- package/dist/tree/text.js +4 -1
- package/dist/tree/tools.js +2 -1
- package/dist/tree/tui.d.ts +12 -5
- package/dist/tui.js +585 -176
- package/package.json +4 -4
package/dist/tree/subtree.d.ts
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Plugin } from "@opencode/plugin";
|
|
2
|
+
import type { CourierPorts, Pending, RosterEntry, RosterStorage, ShownForm } from "./ports.js";
|
|
3
|
+
import { reportOf } from "./report.js";
|
|
4
|
+
import { type Exists } from "./roster.js";
|
|
2
5
|
/** A session below the one a subtree is read from, with how many levels below it is: a child is at depth 1. */
|
|
3
6
|
export interface Below {
|
|
4
7
|
readonly entry: RosterEntry;
|
|
5
8
|
readonly depth: number;
|
|
6
9
|
}
|
|
10
|
+
/** What a read drops: the entries past retention at `now`, as `courier_children` drops them; `exists` tells a worktree still there. */
|
|
11
|
+
export interface Retention {
|
|
12
|
+
readonly now: number;
|
|
13
|
+
readonly exists?: Exists;
|
|
14
|
+
}
|
|
7
15
|
/**
|
|
8
|
-
* The sessions started from a session, directly or through others, read one prefix scan per
|
|
9
|
-
*
|
|
16
|
+
* The sessions started from a session, directly or through others, every one of them, read one prefix scan per
|
|
17
|
+
* parent, in the order a tree is drawn; with `retention`, without the expired entries and what is under them.
|
|
10
18
|
*/
|
|
11
|
-
export declare function subtree(storage: RosterStorage, rootID: string,
|
|
19
|
+
export declare function subtree(storage: RosterStorage, rootID: string, retention?: Retention): Promise<{
|
|
12
20
|
nodes: Below[];
|
|
13
|
-
|
|
21
|
+
}>;
|
|
22
|
+
type Info = Awaited<ReturnType<Plugin.Context["session"]["get"]>>;
|
|
23
|
+
/** A session of a tree as a read looks it up: OpenCode's info of it, if it still knows it, and what it waits on. */
|
|
24
|
+
export interface Looked extends Below {
|
|
25
|
+
readonly info: Info | undefined;
|
|
26
|
+
readonly pending: ReadonlyArray<Pending>;
|
|
27
|
+
readonly forms: ReadonlyArray<ShownForm>;
|
|
28
|
+
}
|
|
29
|
+
/** A session as a listing looks it up: `Looked` plus its report state, and whether it is live (`isLive`). */
|
|
30
|
+
export interface Listed extends Looked {
|
|
31
|
+
readonly report: Awaited<ReturnType<typeof reportOf>>;
|
|
32
|
+
readonly live: boolean;
|
|
33
|
+
}
|
|
34
|
+
export type ReadPorts = Pick<CourierPorts, "storage" | "session" | "pending" | "forms" | "now" | "limits">;
|
|
35
|
+
/** Every session under a session, without those past retention, each looked up, in the order a tree is drawn. */
|
|
36
|
+
export declare function lookUp(ports: ReadPorts, rootID: string): Promise<Looked[]>;
|
|
37
|
+
/**
|
|
38
|
+
* The listing of at most `max` of the sessions under a session: the live ones first, then the newest, with what is
|
|
39
|
+
* above them, in the order a tree is drawn (`cut`); `leftOut` counts the rest.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readTree(ports: ReadPorts, rootID: string, max?: number): Promise<{
|
|
42
|
+
kept: Listed[];
|
|
43
|
+
leftOut: number;
|
|
14
44
|
}>;
|
|
15
45
|
/**
|
|
16
|
-
* The subtree under a session
|
|
17
|
-
* its parent and its report state.
|
|
46
|
+
* The subtree under a session as courier_tree lists it (`readTree`): each session as courier_children shows it, plus
|
|
47
|
+
* its depth, its parent and its report state; `truncated` counts the older sessions left out.
|
|
18
48
|
*/
|
|
19
49
|
export declare function tree(ports: CourierPorts, rootID: string): Promise<{
|
|
20
50
|
truncated?: number | undefined;
|
|
@@ -41,7 +71,7 @@ export declare function tree(ports: CourierPorts, rootID: string): Promise<{
|
|
|
41
71
|
updated?: number | undefined;
|
|
42
72
|
idle?: number | undefined;
|
|
43
73
|
lastText?: string | undefined;
|
|
44
|
-
pending?: readonly
|
|
74
|
+
pending?: readonly Pending[] | undefined;
|
|
45
75
|
} | {
|
|
46
76
|
report?: {
|
|
47
77
|
status?: "done" | "partial" | "blocked" | "failed" | undefined;
|
|
@@ -61,7 +91,7 @@ export declare function tree(ports: CourierPorts, rootID: string): Promise<{
|
|
|
61
91
|
updated?: number | undefined;
|
|
62
92
|
idle?: number | undefined;
|
|
63
93
|
lastText?: string | undefined;
|
|
64
|
-
pending?: readonly
|
|
94
|
+
pending?: readonly Pending[] | undefined;
|
|
65
95
|
} | {
|
|
66
96
|
report?: {
|
|
67
97
|
status?: "done" | "partial" | "blocked" | "failed" | undefined;
|
|
@@ -99,3 +129,4 @@ export declare function tree(ports: CourierPorts, rootID: string): Promise<{
|
|
|
99
129
|
title: string;
|
|
100
130
|
})[];
|
|
101
131
|
}>;
|
|
132
|
+
export {};
|
package/dist/tree/subtree.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
+
import { busy, cut, isLive } from "./domain.js";
|
|
1
2
|
import { lastStatus, reportOf } from "./report.js";
|
|
3
|
+
import { isExpired } from "./roster.js";
|
|
2
4
|
import { described } from "./status.js";
|
|
3
5
|
import { childStore } from "./store.js";
|
|
4
6
|
/**
|
|
5
|
-
* The sessions started from a session, directly or through others, read one prefix scan per
|
|
6
|
-
*
|
|
7
|
+
* The sessions started from a session, directly or through others, every one of them, read one prefix scan per
|
|
8
|
+
* parent, in the order a tree is drawn; with `retention`, without the expired entries and what is under them.
|
|
7
9
|
*/
|
|
8
|
-
export async function subtree(storage, rootID,
|
|
10
|
+
export async function subtree(storage, rootID, retention) {
|
|
9
11
|
const children = childStore(storage);
|
|
10
12
|
const byParent = new Map();
|
|
11
13
|
const seen = new Set([rootID]);
|
|
12
|
-
let truncated = false;
|
|
13
14
|
const expand = async (level, depth) => {
|
|
14
|
-
if (!level.length
|
|
15
|
+
if (!level.length)
|
|
15
16
|
return;
|
|
16
17
|
const found = await Promise.all(level.map((parentID) => children.children(parentID)));
|
|
17
18
|
const next = [];
|
|
18
19
|
for (const [index, entries] of found.entries()) {
|
|
19
|
-
const
|
|
20
|
-
const kept = fresh.slice(0, Math.max(0, max - (seen.size - 1)));
|
|
21
|
-
truncated ||= kept.length < fresh.length;
|
|
20
|
+
const kept = entries.filter((entry) => !seen.has(entry.sessionID) && !(retention && isExpired(entry, retention.now, retention.exists)));
|
|
22
21
|
for (const entry of kept)
|
|
23
22
|
seen.add(entry.sessionID);
|
|
24
23
|
if (kept.length)
|
|
@@ -28,25 +27,47 @@ export async function subtree(storage, rootID, max = Number.POSITIVE_INFINITY) {
|
|
|
28
27
|
return expand(next, depth + 1);
|
|
29
28
|
};
|
|
30
29
|
await expand([rootID], 1);
|
|
31
|
-
return { nodes: drawn(byParent, rootID)
|
|
30
|
+
return { nodes: drawn(byParent, rootID) };
|
|
32
31
|
}
|
|
33
32
|
/** The nodes in the order a tree is drawn: each followed by what it started. */
|
|
34
33
|
function drawn(byParent, parentID) {
|
|
35
34
|
return (byParent.get(parentID) ?? []).flatMap((node) => [node, ...drawn(byParent, node.entry.sessionID)]);
|
|
36
35
|
}
|
|
36
|
+
/** Every session under a session, without those past retention, each looked up, in the order a tree is drawn. */
|
|
37
|
+
export async function lookUp(ports, rootID) {
|
|
38
|
+
const { nodes } = await subtree(ports.storage, rootID, { now: ports.now() });
|
|
39
|
+
return Promise.all(nodes.map(async (node) => {
|
|
40
|
+
const [info, pending, forms] = await Promise.all([
|
|
41
|
+
ports.session.get({ sessionID: node.entry.sessionID }).catch(() => undefined),
|
|
42
|
+
ports.pending(node.entry.sessionID).catch(() => []),
|
|
43
|
+
ports.forms(node.entry.sessionID).catch(() => []),
|
|
44
|
+
]);
|
|
45
|
+
// A form outlives its session's turn only when its withdrawal was missed, as while no instance followed the events.
|
|
46
|
+
return { ...node, info, pending, forms: info && busy(info.time) ? forms : [] };
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
37
49
|
/**
|
|
38
|
-
* The
|
|
39
|
-
*
|
|
50
|
+
* The listing of at most `max` of the sessions under a session: the live ones first, then the newest, with what is
|
|
51
|
+
* above them, in the order a tree is drawn (`cut`); `leftOut` counts the rest.
|
|
52
|
+
*/
|
|
53
|
+
export async function readTree(ports, rootID, max = ports.limits.maxTotal) {
|
|
54
|
+
const children = childStore(ports.storage);
|
|
55
|
+
const all = await Promise.all((await lookUp(ports, rootID)).map(async (node) => {
|
|
56
|
+
const report = await reportOf(children, node.entry.sessionID).catch(() => undefined);
|
|
57
|
+
return { ...node, report, live: isLive(node.info?.time, node.pending.length + node.forms.length, report) };
|
|
58
|
+
}));
|
|
59
|
+
const listing = cut(all.map((node) => ({ sessionID: node.entry.sessionID, parentID: node.entry.parentID, createdAt: node.entry.createdAt, live: node.live, node })), max);
|
|
60
|
+
return { kept: listing.kept.map(({ node }) => node), leftOut: listing.leftOut };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The subtree under a session as courier_tree lists it (`readTree`): each session as courier_children shows it, plus
|
|
64
|
+
* its depth, its parent and its report state; `truncated` counts the older sessions left out.
|
|
40
65
|
*/
|
|
41
66
|
export async function tree(ports, rootID) {
|
|
42
|
-
const {
|
|
67
|
+
const { kept, leftOut } = await readTree(ports, rootID);
|
|
43
68
|
const children = childStore(ports.storage);
|
|
44
|
-
const listed = await Promise.all(
|
|
45
|
-
const [info,
|
|
46
|
-
described(ports, entry),
|
|
47
|
-
reportOf(children, entry.sessionID).catch(() => undefined),
|
|
48
|
-
lastStatus(children, entry.sessionID).catch(() => undefined),
|
|
49
|
-
]);
|
|
69
|
+
const listed = await Promise.all(kept.map(async ({ entry, depth, report }) => {
|
|
70
|
+
const [info, last] = await Promise.all([described(ports, entry), lastStatus(children, entry.sessionID).catch(() => undefined)]);
|
|
50
71
|
return {
|
|
51
72
|
...info,
|
|
52
73
|
parentID: entry.parentID,
|
|
@@ -56,5 +77,5 @@ export async function tree(ports, rootID) {
|
|
|
56
77
|
: {}),
|
|
57
78
|
};
|
|
58
79
|
}));
|
|
59
|
-
return { sessionID: rootID, sessions: listed, ...(
|
|
80
|
+
return { sessionID: rootID, sessions: listed, ...(leftOut ? { truncated: leftOut } : {}) };
|
|
60
81
|
}
|
package/dist/tree/summary.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CourierPorts } from "./ports.js";
|
|
2
2
|
import type { Summary } from "./rpc.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Every session started under a session, counted by what it is doing, and its open join groups, those of its own
|
|
5
|
+
* children, with how far each has reported.
|
|
6
6
|
*/
|
|
7
7
|
export declare function summary(ports: CourierPorts, sessionID: string): Promise<Summary>;
|
package/dist/tree/summary.js
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
import { busy } from "./domain.js";
|
|
2
2
|
import { groupStandings } from "./group.js";
|
|
3
3
|
import { groupStore } from "./store.js";
|
|
4
|
-
import {
|
|
5
|
-
/** What a session is doing: waiting on a request it made, running, or idle; undefined when OpenCode no longer knows it. */
|
|
6
|
-
|
|
7
|
-
const [info, pending] = await Promise.all([
|
|
8
|
-
ports.session.get({ sessionID }).catch(() => undefined),
|
|
9
|
-
ports.pending(sessionID).catch(() => []),
|
|
10
|
-
]);
|
|
4
|
+
import { lookUp } from "./subtree.js";
|
|
5
|
+
/** What a session is doing: waiting on a request it made or a form it shows, running, or idle; undefined when OpenCode no longer knows it. */
|
|
6
|
+
function activity({ info, pending, forms }) {
|
|
11
7
|
if (!info)
|
|
12
8
|
return undefined;
|
|
13
|
-
if (pending.length)
|
|
9
|
+
if (pending.length || forms.length)
|
|
14
10
|
return "waiting";
|
|
15
11
|
return busy(info.time) ? "running" : "idle";
|
|
16
12
|
}
|
|
17
13
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* Every session started under a session, counted by what it is doing, and its open join groups, those of its own
|
|
15
|
+
* children, with how far each has reported.
|
|
20
16
|
*/
|
|
21
17
|
export async function summary(ports, sessionID) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
return { activities: await Promise.all(nodes.map(({ entry }) => activity(ports, entry.sessionID))), truncated };
|
|
25
|
-
};
|
|
26
|
-
const [{ activities, truncated }, standings] = await Promise.all([counted(), groupStandings(groupStore(ports.storage), sessionID)]);
|
|
18
|
+
const [looked, standings] = await Promise.all([lookUp(ports, sessionID), groupStandings(groupStore(ports.storage), sessionID)]);
|
|
19
|
+
const activities = looked.map(activity);
|
|
27
20
|
const count = (kind) => activities.filter((item) => item === kind).length;
|
|
28
21
|
return {
|
|
29
22
|
running: count("running"),
|
|
30
23
|
waiting: count("waiting"),
|
|
31
24
|
idle: count("idle"),
|
|
32
25
|
groups: standings.map(({ name, reported, members }) => ({ name, reported, members })),
|
|
33
|
-
...(truncated ? { truncated: ports.limits.maxTotal } : {}),
|
|
34
26
|
};
|
|
35
27
|
}
|
package/dist/tree/text.js
CHANGED
|
@@ -160,7 +160,10 @@ export function cleanupText(result) {
|
|
|
160
160
|
export function treeText(tree) {
|
|
161
161
|
if (!tree.sessions.length)
|
|
162
162
|
return "No sessions started with courier_spawn.";
|
|
163
|
-
|
|
163
|
+
if (tree.truncated === undefined)
|
|
164
|
+
return JSON.stringify(tree, null, 2);
|
|
165
|
+
const older = tree.truncated === 1 ? "1 older session is" : `${tree.truncated} older sessions are`;
|
|
166
|
+
const note = `\n${older} not listed: at most maxTotal are, the live ones first, then the newest; courier_tree on a listed session lists what is under it.`;
|
|
164
167
|
return JSON.stringify(tree, null, 2) + note;
|
|
165
168
|
}
|
|
166
169
|
/** What courier_stop did to each session, and to the messages and worktrees. */
|
package/dist/tree/tools.js
CHANGED
|
@@ -128,7 +128,8 @@ export function addTreeTools(tools, ports) {
|
|
|
128
128
|
options: { codemode: false },
|
|
129
129
|
description: "List the whole tree of sessions under this one (or under sessionID): every session started with courier_spawn " +
|
|
130
130
|
"from it, directly or through others, each with its depth, what courier_children shows, and whether it still " +
|
|
131
|
-
"owes a report, sent progress, or ended without one. For a one-off look, not for waiting
|
|
131
|
+
"owes a report, sent progress, or ended without one. For a one-off look, not for waiting. A long tree lists at most " +
|
|
132
|
+
"maxTotal sessions, the live ones first, then the newest, and says how many older ones are left out.",
|
|
132
133
|
input: TreeInput,
|
|
133
134
|
execute: async (input, context) => {
|
|
134
135
|
const result = await tree(ports.courier, input.sessionID || context.sessionID).catch(rethrow("courier_tree"));
|
package/dist/tree/tui.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin/tui";
|
|
2
|
-
import { type Row, type Stopped, type Summary, type Wait } from "./rpc.js";
|
|
2
|
+
import { type Forgotten, type Row, type Stopped, type Summary, type Wait } from "./rpc.js";
|
|
3
3
|
type Context = Plugin.Context;
|
|
4
|
+
/** The events after which a summary may read differently: a turn started or ended, a request or form, a session made or removed. */
|
|
5
|
+
export declare const CHANGES: Set<string>;
|
|
4
6
|
/** How long after the first of a burst of changes the entry asks the server again. */
|
|
5
7
|
export declare const SETTLE_MS = 250;
|
|
6
8
|
/** The panel's settle: each of its asks reads every row's messages, so a busy tree is asked at most once a second. */
|
|
@@ -9,8 +11,11 @@ export declare const PANEL_SETTLE_MS = 1000;
|
|
|
9
11
|
export declare const PANEL = "courier-tree";
|
|
10
12
|
/** How often the panel redraws its running times and how long ago each last tool call was. */
|
|
11
13
|
export declare const TICK_MS = 1000;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The status entry's text, such as "courier 1 waiting on you · 2 running · group pair 1/2"; empty when there is nothing to show.
|
|
16
|
+
* Within `maxWidth` it keeps whole parts, most important first, and counts the groups it drops: "· +2 groups".
|
|
17
|
+
*/
|
|
18
|
+
export declare function summaryLine(summary: Summary, maxWidth?: number): string;
|
|
14
19
|
/** A span of time, coarsest first: "45s", "3m 05s", "2h 07m". */
|
|
15
20
|
export declare function duration(ms: number): string;
|
|
16
21
|
/** How many of a session's children have reported, out of those it started, among the rows read. */
|
|
@@ -23,7 +28,7 @@ export declare function bar({ reported, started }: {
|
|
|
23
28
|
reported: number;
|
|
24
29
|
started: number;
|
|
25
30
|
}, width?: number): string;
|
|
26
|
-
/** What a paused session waits on, such as "asks permission: shell echo hi". */
|
|
31
|
+
/** What a paused session waits on, such as "asks permission: shell echo hi" or "shows a form: Web Search". */
|
|
27
32
|
export declare function waitLine(wait: Wait): string;
|
|
28
33
|
/** What a row's session is doing, its report and its group standing: "idle · reported done · group pair held". */
|
|
29
34
|
export declare function statusLine(row: Row): string;
|
|
@@ -38,11 +43,13 @@ export declare function detailLines(row: Row, rows: ReadonlyArray<Row>, now: num
|
|
|
38
43
|
export declare function addTreeStatus(ctx: Context, onWaiting?: (sessionID: string) => void): () => void;
|
|
39
44
|
/** The toast after a stop: how many sessions were interrupted, and how many scheduled messages were cancelled. */
|
|
40
45
|
export declare function stoppedLine({ stopped, cancelled }: Stopped): string;
|
|
46
|
+
/** The toast after a forget, as courier_cleanup reports it: forgotten, its worktree removed or already gone, or kept and why. */
|
|
47
|
+
export declare function forgottenLine(title: string, result: Forgotten): string;
|
|
41
48
|
/** The usage the `/courier` command shows for an input it does not know. */
|
|
42
49
|
export declare const USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
|
|
43
50
|
/**
|
|
44
51
|
* Adds the tree panel to the `session.panel` slot, opened by `/courier tree` or the palette, which follows the person
|
|
45
|
-
* between sessions
|
|
52
|
+
* between sessions until closed, and `/courier stop <session>`, which stops a session under the open one once confirmed.
|
|
46
53
|
*/
|
|
47
54
|
export declare function addTreePanel(ctx: Context): () => void;
|
|
48
55
|
/**
|