opencode-courier 0.4.0 → 0.4.1
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 +8 -3
- package/dist/tree/tui.d.ts +2 -2
- package/dist/tui.js +135 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ line to the server log naming both ([more](docs/reference.md#the-opencode-versio
|
|
|
116
116
|
|
|
117
117
|
| opencode-courier | OpenCode V2 (`opencode` and `@opencode/plugin`) |
|
|
118
118
|
|---|---|
|
|
119
|
-
| 0.4.0 | 2.0.28 |
|
|
119
|
+
| 0.4.0, 0.4.1 | 2.0.28 |
|
|
120
120
|
| 0.2.3, 0.3.0 | 2.0.26 |
|
|
121
121
|
| 0.2.2 | 2.0.24 |
|
|
122
122
|
| 0.2.1 | 2.0.23 |
|
|
@@ -264,14 +264,19 @@ courier 2 running · 1 waiting on you · group pair 1/2
|
|
|
264
264
|
by depth, with what it does, its report and its group's standing; a session that started others gets
|
|
265
265
|
a bar of the parts reported, a leaf how long it ran, its last tool call and its cost. `↑`/`↓` select,
|
|
266
266
|
`enter` opens the session, `s` stops it and everything under it after you confirm, as `courier_stop`
|
|
267
|
-
would, and `q` closes the panel; `/courier stop <session>` stops from the prompt.
|
|
267
|
+
would, and `q` closes the panel; `/courier stop <session>` stops from the prompt. While open, the panel
|
|
268
|
+
follows you between sessions: into one of its tree it keeps that tree, the session on screen marked
|
|
269
|
+
`(on screen)`; elsewhere it shows that session's tree, or closes if it started none.
|
|
268
270
|
|
|
269
|
-
`ctrl+x k` (the leader key, then `k`), `/asks
|
|
271
|
+
`ctrl+x k` (the leader key, then `k`), `/asks` (or `/ask`), the palette's "Courier: what waits on you" or a click
|
|
270
272
|
on "waiting on you" opens the asks inbox: every permission request, question and form waiting
|
|
271
273
|
anywhere in the tree, oldest first, with where it comes from, answered in a dialog with no model in
|
|
272
274
|
between; the session that started the tree hears once that you settled it. The TUI plays OpenCode's
|
|
273
275
|
sounds for a child's report, a group's release and a new ask ([more](docs/reference.md#in-the-terminal)).
|
|
274
276
|
|
|
277
|
+
While a permission prompt or form is shown in the session, OpenCode hides the footer, so open the
|
|
278
|
+
inbox with `ctrl+x k` (or `/asks` once the prompt is back).
|
|
279
|
+
|
|
275
280
|
## Native child sessions
|
|
276
281
|
|
|
277
282
|
Off by default. With `"nativeChildren": true` in the plugin's `options`, a child started without
|
package/dist/tree/tui.d.ts
CHANGED
|
@@ -41,8 +41,8 @@ export declare function stoppedLine({ stopped, cancelled }: Stopped): string;
|
|
|
41
41
|
/** The usage the `/courier` command shows for an input it does not know. */
|
|
42
42
|
export declare const USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
|
|
43
43
|
/**
|
|
44
|
-
* Adds the tree panel to the `session.panel` slot, opened by `/courier tree` or the
|
|
45
|
-
* stop <session>`, which stops a session under the open one
|
|
44
|
+
* Adds the tree panel to the `session.panel` slot, opened by `/courier tree` or the palette, which follows the person
|
|
45
|
+
* between sessions while open, and `/courier stop <session>`, which stops a session under the open one once confirmed.
|
|
46
46
|
*/
|
|
47
47
|
export declare function addTreePanel(ctx: Context): () => void;
|
|
48
48
|
/**
|
package/dist/tui.js
CHANGED
|
@@ -518,6 +518,7 @@ function addAsksInbox(ctx) {
|
|
|
518
518
|
}, SETTLE_MS);
|
|
519
519
|
});
|
|
520
520
|
ctx.keymap.layer(() => ({
|
|
521
|
+
mode: "global",
|
|
521
522
|
commands: [{
|
|
522
523
|
id: INBOX_COMMAND,
|
|
523
524
|
title: "Courier: what waits on you",
|
|
@@ -526,7 +527,8 @@ function addAsksInbox(ctx) {
|
|
|
526
527
|
bind: "<leader>k",
|
|
527
528
|
palette: true,
|
|
528
529
|
slash: {
|
|
529
|
-
name: "asks"
|
|
530
|
+
name: "asks",
|
|
531
|
+
aliases: ["ask"]
|
|
530
532
|
},
|
|
531
533
|
run: () => void open()
|
|
532
534
|
}]
|
|
@@ -550,7 +552,7 @@ import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
|
550
552
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
551
553
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
552
554
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
553
|
-
import { createEffect, createResource, createSignal, For, onCleanup, Show } from "solid-js";
|
|
555
|
+
import { createEffect, createResource, createRoot, createSignal, For, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
554
556
|
|
|
555
557
|
// src/tree/rpc.ts
|
|
556
558
|
import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
|
|
@@ -813,10 +815,12 @@ async function confirmStop(ctx, courier, location, by, target) {
|
|
|
813
815
|
}
|
|
814
816
|
return true;
|
|
815
817
|
}
|
|
818
|
+
var FOCUS_PROMPT = "pane.focus.left";
|
|
816
819
|
function TreePanel(props) {
|
|
817
820
|
const {
|
|
818
821
|
ctx,
|
|
819
822
|
input,
|
|
823
|
+
view,
|
|
820
824
|
courier,
|
|
821
825
|
location
|
|
822
826
|
} = props;
|
|
@@ -824,7 +828,7 @@ function TreePanel(props) {
|
|
|
824
828
|
const [read, {
|
|
825
829
|
refetch
|
|
826
830
|
}] = createResource(() => ({
|
|
827
|
-
sessionID:
|
|
831
|
+
sessionID: view.root,
|
|
828
832
|
changed: props.changed()
|
|
829
833
|
}), ({
|
|
830
834
|
sessionID
|
|
@@ -839,12 +843,40 @@ function TreePanel(props) {
|
|
|
839
843
|
sessionID,
|
|
840
844
|
error: reason(error)
|
|
841
845
|
})));
|
|
842
|
-
const latest = () => read.state === "ready" || read.state === "refreshing" ? read.latest :
|
|
846
|
+
const latest = () => read.state === "ready" || read.state === "refreshing" ? read.latest : view.tree && {
|
|
847
|
+
sessionID: view.root,
|
|
848
|
+
tree: view.tree
|
|
849
|
+
};
|
|
843
850
|
const rows = () => latest()?.tree?.rows ?? [];
|
|
851
|
+
createEffect(() => {
|
|
852
|
+
const tree = latest()?.tree;
|
|
853
|
+
if (tree)
|
|
854
|
+
view.note({
|
|
855
|
+
tree
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
createEffect(() => view.note({
|
|
859
|
+
presentation: input.presentation,
|
|
860
|
+
focused: input.focused
|
|
861
|
+
}));
|
|
862
|
+
onMount(() => {
|
|
863
|
+
if (!view.refocus)
|
|
864
|
+
return;
|
|
865
|
+
const giveBack = setTimeout(() => ctx.keymap.dispatch(FOCUS_PROMPT));
|
|
866
|
+
onCleanup(() => clearTimeout(giveBack));
|
|
867
|
+
});
|
|
844
868
|
const [now, setNow] = createSignal(Date.now());
|
|
845
869
|
const tick = setInterval(() => setNow(Date.now()), TICK_MS);
|
|
846
870
|
onCleanup(() => clearInterval(tick));
|
|
847
871
|
const [selected, setSelected] = createSignal(0);
|
|
872
|
+
let placed = false;
|
|
873
|
+
createEffect(() => {
|
|
874
|
+
const index = placed ? -1 : rows().findIndex((row) => row.sessionID === input.sessionID);
|
|
875
|
+
if (index < 0)
|
|
876
|
+
return;
|
|
877
|
+
placed = true;
|
|
878
|
+
setSelected(index);
|
|
879
|
+
});
|
|
848
880
|
const current = () => rows()[Math.min(selected(), rows().length - 1)];
|
|
849
881
|
let scroll;
|
|
850
882
|
createEffect(() => {
|
|
@@ -864,7 +896,7 @@ function TreePanel(props) {
|
|
|
864
896
|
const stopSelected = () => {
|
|
865
897
|
const row = current();
|
|
866
898
|
if (row)
|
|
867
|
-
confirmStop(ctx, courier, location,
|
|
899
|
+
confirmStop(ctx, courier, location, view.root, {
|
|
868
900
|
sessionID: row.sessionID,
|
|
869
901
|
title: row.title,
|
|
870
902
|
under: reach(rows(), row.sessionID) - 1
|
|
@@ -920,7 +952,7 @@ function TreePanel(props) {
|
|
|
920
952
|
run: () => input.close()
|
|
921
953
|
}]
|
|
922
954
|
}));
|
|
923
|
-
const root = () => parts(rows(),
|
|
955
|
+
const root = () => parts(rows(), view.root);
|
|
924
956
|
const rowColor = (row) => {
|
|
925
957
|
if (row.state === "waiting")
|
|
926
958
|
return theme.text.feedback.warning.base;
|
|
@@ -1010,6 +1042,7 @@ function TreePanel(props) {
|
|
|
1010
1042
|
_$insert(_el$17, indent, _el$18);
|
|
1011
1043
|
_$insert(_el$17, () => chosen() ? "\u25B8 " : " ", _el$18);
|
|
1012
1044
|
_$insert(_el$17, () => row.title, _el$18);
|
|
1045
|
+
_$insert(_el$17, () => row.sessionID === input.sessionID ? " (on screen)" : "", _el$18);
|
|
1013
1046
|
_$insert(_el$19, () => statusLine(row));
|
|
1014
1047
|
_$insert(_el$16, _$createComponent(For, {
|
|
1015
1048
|
get each() {
|
|
@@ -1066,6 +1099,23 @@ function addTreePanel(ctx) {
|
|
|
1066
1099
|
location
|
|
1067
1100
|
} = courierOf(ctx);
|
|
1068
1101
|
const [changed, stopListening] = changes(ctx, PANEL_CHANGES, PANEL_SETTLE_MS);
|
|
1102
|
+
let following;
|
|
1103
|
+
let attempt = 0;
|
|
1104
|
+
const viewOf = (sessionID) => {
|
|
1105
|
+
const here = following?.at === sessionID ? following : undefined;
|
|
1106
|
+
const refocus = here?.refocus === true;
|
|
1107
|
+
if (here)
|
|
1108
|
+
here.refocus = false;
|
|
1109
|
+
return {
|
|
1110
|
+
root: here?.root ?? sessionID,
|
|
1111
|
+
tree: here?.tree,
|
|
1112
|
+
refocus,
|
|
1113
|
+
note: (fields) => {
|
|
1114
|
+
if (here && following === here)
|
|
1115
|
+
Object.assign(here, fields);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1069
1119
|
const release = ctx.ui.slot({
|
|
1070
1120
|
append: "session.panel",
|
|
1071
1121
|
render: (input) => _$createComponent(Show, {
|
|
@@ -1076,6 +1126,9 @@ function addTreePanel(ctx) {
|
|
|
1076
1126
|
return _$createComponent(TreePanel, {
|
|
1077
1127
|
ctx,
|
|
1078
1128
|
input,
|
|
1129
|
+
get view() {
|
|
1130
|
+
return viewOf(input.sessionID);
|
|
1131
|
+
},
|
|
1079
1132
|
courier,
|
|
1080
1133
|
location,
|
|
1081
1134
|
changed
|
|
@@ -1083,13 +1136,82 @@ function addTreePanel(ctx) {
|
|
|
1083
1136
|
}
|
|
1084
1137
|
})
|
|
1085
1138
|
});
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1139
|
+
const show = (from, at, root, tree) => {
|
|
1140
|
+
following = {
|
|
1141
|
+
...from,
|
|
1142
|
+
root,
|
|
1143
|
+
at,
|
|
1144
|
+
to: undefined,
|
|
1145
|
+
tree,
|
|
1146
|
+
refocus: !from.focused
|
|
1147
|
+
};
|
|
1148
|
+
ctx.ui.panel.open(PANEL, {
|
|
1149
|
+
presentation: from.presentation
|
|
1150
|
+
});
|
|
1151
|
+
};
|
|
1152
|
+
const treeOf = (sessionID) => courier.tree({
|
|
1153
|
+
sessionID
|
|
1154
|
+
}, {
|
|
1155
|
+
location: location(sessionID)
|
|
1156
|
+
}).catch(() => {
|
|
1157
|
+
return;
|
|
1158
|
+
});
|
|
1159
|
+
const follow = (from, sessionID) => {
|
|
1160
|
+
const token = ++attempt;
|
|
1161
|
+
const under = (tree) => tree?.rows.some((row) => row.sessionID === sessionID) === true;
|
|
1162
|
+
Object.assign(from, {
|
|
1163
|
+
at: undefined,
|
|
1164
|
+
to: sessionID
|
|
1165
|
+
});
|
|
1166
|
+
if (sessionID === from.root || under(from.tree))
|
|
1167
|
+
return show(from, sessionID, from.root, from.tree);
|
|
1168
|
+
(async () => {
|
|
1169
|
+
const inside = await treeOf(from.root);
|
|
1170
|
+
const own = under(inside) ? undefined : await treeOf(sessionID);
|
|
1171
|
+
if (token !== attempt || following !== from)
|
|
1172
|
+
return;
|
|
1173
|
+
if (under(inside))
|
|
1174
|
+
show(from, sessionID, from.root, inside);
|
|
1175
|
+
else if (own?.rows.length)
|
|
1176
|
+
show(from, sessionID, sessionID, own);
|
|
1177
|
+
else
|
|
1178
|
+
following = undefined;
|
|
1179
|
+
})();
|
|
1180
|
+
};
|
|
1181
|
+
const stopFollowing = createRoot((dispose) => {
|
|
1182
|
+
createEffect(() => {
|
|
1183
|
+
const route = ctx.ui.router.current();
|
|
1184
|
+
const sessionID = route.type === "session" ? route.sessionID : undefined;
|
|
1185
|
+
const shown2 = ctx.ui.panel.current();
|
|
1186
|
+
untrack(() => {
|
|
1187
|
+
if (!following || shown2 && shown2.sessionID === sessionID || !following.at && following.to === sessionID)
|
|
1188
|
+
return;
|
|
1189
|
+
if (!shown2 && following.at === sessionID || !sessionID)
|
|
1190
|
+
following = undefined;
|
|
1191
|
+
else
|
|
1192
|
+
follow(following, sessionID);
|
|
1092
1193
|
});
|
|
1194
|
+
});
|
|
1195
|
+
return dispose;
|
|
1196
|
+
});
|
|
1197
|
+
const openPanel = () => {
|
|
1198
|
+
const route = ctx.ui.router.current();
|
|
1199
|
+
const before = following;
|
|
1200
|
+
if (route.type === "session" && following?.at !== route.sessionID)
|
|
1201
|
+
following = {
|
|
1202
|
+
root: route.sessionID,
|
|
1203
|
+
at: route.sessionID,
|
|
1204
|
+
presentation: "panel",
|
|
1205
|
+
focused: true
|
|
1206
|
+
};
|
|
1207
|
+
if (ctx.ui.panel.open(PANEL))
|
|
1208
|
+
return;
|
|
1209
|
+
following = before;
|
|
1210
|
+
ctx.ui.toast.show({
|
|
1211
|
+
title: "courier",
|
|
1212
|
+
message: "Open a session first",
|
|
1213
|
+
variant: "warning"
|
|
1214
|
+
});
|
|
1093
1215
|
};
|
|
1094
1216
|
const stopUnder = async (target) => {
|
|
1095
1217
|
const route = ctx.ui.router.current();
|
|
@@ -1153,6 +1275,7 @@ function addTreePanel(ctx) {
|
|
|
1153
1275
|
}]
|
|
1154
1276
|
}));
|
|
1155
1277
|
return () => {
|
|
1278
|
+
stopFollowing();
|
|
1156
1279
|
stopListening();
|
|
1157
1280
|
release();
|
|
1158
1281
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-courier",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Multi-session orchestration for OpenCode V2: spawn child sessions, be woken by them. No polling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivo Pogace <71701666+ivopogace@users.noreply.github.com>",
|