opencode-courier 0.5.0 → 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/README.md +39 -30
- package/dist/index.js +9 -5
- 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 +14 -0
- package/dist/person/permission.js +15 -0
- 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/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 +31 -4
- package/dist/tree/index.d.ts +2 -1
- package/dist/tree/index.js +2 -1
- package/dist/tree/panel.d.ts +7 -2
- package/dist/tree/panel.js +29 -17
- package/dist/tree/ports.d.ts +36 -0
- package/dist/tree/roster.d.ts +5 -0
- package/dist/tree/roster.js +5 -3
- package/dist/tree/rpc-handlers.d.ts +55 -6
- package/dist/tree/rpc-handlers.js +30 -1
- package/dist/tree/rpc.d.ts +111 -22
- package/dist/tree/rpc.js +30 -9
- package/dist/tree/stop.js +2 -0
- package/dist/tree/store.d.ts +4 -1
- package/dist/tree/store.js +51 -0
- 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 +17 -16
- package/dist/tree/text.js +4 -1
- package/dist/tree/tools.js +2 -1
- package/dist/tree/tui.d.ts +17 -6
- package/dist/tree/wake.d.ts +68 -0
- package/dist/tree/wake.js +119 -0
- package/dist/tui.js +617 -197
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -18,7 +18,8 @@ var Permission = Schema.Struct({
|
|
|
18
18
|
requestID: Schema.String,
|
|
19
19
|
action: Schema.String,
|
|
20
20
|
resources: Schema.Array(Schema.String),
|
|
21
|
-
save: Schema.optionalKey(Schema.Array(Schema.String))
|
|
21
|
+
save: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
22
|
+
passesOn: Schema.optionalKey(Schema.Struct({ sessionID: Schema.String, requestID: Schema.String }))
|
|
22
23
|
});
|
|
23
24
|
var Prompt = Schema.Struct({
|
|
24
25
|
question: Schema.String,
|
|
@@ -36,8 +37,7 @@ var Asks = Schema.Struct({
|
|
|
36
37
|
top: Schema.String,
|
|
37
38
|
sessions: Schema.Array(Session),
|
|
38
39
|
permissions: Schema.Array(Permission),
|
|
39
|
-
questions: Schema.Array(Question)
|
|
40
|
-
truncated: Schema.optionalKey(Count)
|
|
40
|
+
questions: Schema.Array(Question)
|
|
41
41
|
});
|
|
42
42
|
var Answered = Schema.Struct({
|
|
43
43
|
answered: Schema.Boolean,
|
|
@@ -66,12 +66,22 @@ var REJECTED = "Refused by the session that started you. Do without it, or repor
|
|
|
66
66
|
var QUESTION_FORM = "question";
|
|
67
67
|
var INBOX_COMMAND = "courier.asks";
|
|
68
68
|
var SETTLE_MS = 250;
|
|
69
|
+
var topSession = (asks) => ({
|
|
70
|
+
sessionID: asks.top,
|
|
71
|
+
title: "",
|
|
72
|
+
parentID: "",
|
|
73
|
+
depth: 0
|
|
74
|
+
});
|
|
69
75
|
function asksOf(asks, forms) {
|
|
70
|
-
|
|
76
|
+
const prompting = new Set(asks.permissions.flatMap((request) => request.sessionID === asks.top && request.passesOn ? [request.passesOn.requestID] : []));
|
|
77
|
+
return [topSession(asks), ...asks.sessions].flatMap((session) => [...asks.permissions.filter((request) => request.sessionID === session.sessionID).map((request) => ({
|
|
71
78
|
kind: "permission",
|
|
72
79
|
key: `permission ${request.requestID}`,
|
|
73
80
|
session,
|
|
74
|
-
request
|
|
81
|
+
request,
|
|
82
|
+
...session.depth > 0 && prompting.has(request.requestID) ? {
|
|
83
|
+
prompted: true
|
|
84
|
+
} : {}
|
|
75
85
|
})), ...asks.questions.filter((question) => question.sessionID === session.sessionID).map((question) => ({
|
|
76
86
|
kind: "question",
|
|
77
87
|
key: `question ${question.requestID}`,
|
|
@@ -84,11 +94,13 @@ function asksOf(asks, forms) {
|
|
|
84
94
|
form
|
|
85
95
|
}))]);
|
|
86
96
|
}
|
|
87
|
-
function pathOf(asks, sessionID, topTitle) {
|
|
97
|
+
function pathOf(asks, sessionID, topTitle, currentTitle = () => {
|
|
98
|
+
return;
|
|
99
|
+
}) {
|
|
88
100
|
const byID = new Map(asks.sessions.map((session) => [session.sessionID, session]));
|
|
89
101
|
const titles = [];
|
|
90
102
|
for (let at = byID.get(sessionID);at && titles.length <= byID.size; at = byID.get(at.parentID))
|
|
91
|
-
titles.unshift(at.title);
|
|
103
|
+
titles.unshift(currentTitle(at.sessionID) || at.title);
|
|
92
104
|
return [topTitle, ...titles].join(" \u203A ");
|
|
93
105
|
}
|
|
94
106
|
var clip = (text, max = 80) => text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
@@ -97,9 +109,13 @@ function askTitle(ask) {
|
|
|
97
109
|
case "permission": {
|
|
98
110
|
const {
|
|
99
111
|
action,
|
|
100
|
-
resources
|
|
112
|
+
resources,
|
|
113
|
+
passesOn
|
|
101
114
|
} = ask.request;
|
|
102
|
-
|
|
115
|
+
const what = resources.length ? `${action} ${resources.join(", ")}` : action;
|
|
116
|
+
if (ask.session.depth > 0)
|
|
117
|
+
return clip(`Permission: ${what}`);
|
|
118
|
+
return clip(passesOn ? `Permission (top session): courier_answer once for "${what}"` : `Permission (top session): ${what}`);
|
|
103
119
|
}
|
|
104
120
|
case "question": {
|
|
105
121
|
const [first, ...rest] = ask.question.questions;
|
|
@@ -112,14 +128,89 @@ function askTitle(ask) {
|
|
|
112
128
|
}
|
|
113
129
|
}
|
|
114
130
|
function askFooter(ask) {
|
|
115
|
-
|
|
131
|
+
const top = ask.session.depth === 0;
|
|
132
|
+
return [top ? "top session" : `depth ${ask.session.depth}`, ask.session.group ? `group ${ask.session.group}` : "", ask.kind === "permission" && ask.prompted ? "the top's prompt asks" : "", ask.kind === "question" && ask.question.stopped ? "cut off, answered by message" : ""].filter(Boolean).join(" \xB7 ");
|
|
116
133
|
}
|
|
117
|
-
|
|
118
|
-
function shown(field, answers) {
|
|
119
|
-
if ("hidden" in field && field.hidden)
|
|
120
|
-
return false;
|
|
134
|
+
function active(field, answers) {
|
|
121
135
|
const when = "when" in field ? field.when ?? [] : [];
|
|
122
|
-
return when.every((
|
|
136
|
+
return when.every(({
|
|
137
|
+
key,
|
|
138
|
+
op,
|
|
139
|
+
value
|
|
140
|
+
}) => {
|
|
141
|
+
const answer = answers[key];
|
|
142
|
+
if (answer === undefined)
|
|
143
|
+
return false;
|
|
144
|
+
const hit = Array.isArray(answer) ? answer.some((item) => item === value) : answer === value;
|
|
145
|
+
return op === "eq" ? hit : !hit;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
149
|
+
var isDate = (text) => /^\d{4}-\d{2}-\d{2}$/.test(text) && new Date(`${text}T00:00:00.000Z`).toISOString().slice(0, 10) === text;
|
|
150
|
+
function misfit(field, value) {
|
|
151
|
+
if (field.type === "string") {
|
|
152
|
+
if (typeof value !== "string")
|
|
153
|
+
return "Expected text";
|
|
154
|
+
if (field.required && !value)
|
|
155
|
+
return "Answer required";
|
|
156
|
+
if (field.minLength !== undefined && value.length < field.minLength)
|
|
157
|
+
return `Must be at least ${field.minLength} characters`;
|
|
158
|
+
if (field.maxLength !== undefined && value.length > field.maxLength)
|
|
159
|
+
return `Must be at most ${field.maxLength} characters`;
|
|
160
|
+
if (field.pattern !== undefined) {
|
|
161
|
+
try {
|
|
162
|
+
if (!new RegExp(field.pattern).test(value))
|
|
163
|
+
return `Must match pattern: ${field.pattern}`;
|
|
164
|
+
} catch {
|
|
165
|
+
return `Invalid pattern: ${field.pattern}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (field.format === "email" && !EMAIL.test(value))
|
|
169
|
+
return "Expected an email address";
|
|
170
|
+
if (field.format === "uri" && !URL.canParse(value))
|
|
171
|
+
return "Expected a URL";
|
|
172
|
+
if (field.format === "date" && !isDate(value))
|
|
173
|
+
return "Expected a date (YYYY-MM-DD)";
|
|
174
|
+
if (field.format === "date-time" && Number.isNaN(new Date(value).getTime()))
|
|
175
|
+
return "Expected a date and time";
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (field.type === "number" || field.type === "integer") {
|
|
179
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
180
|
+
return "Expected a number";
|
|
181
|
+
if (field.type === "integer" && !Number.isInteger(value))
|
|
182
|
+
return "Expected an integer";
|
|
183
|
+
if (typeof field.minimum === "number" && value < field.minimum)
|
|
184
|
+
return `Must be at least ${field.minimum}`;
|
|
185
|
+
if (typeof field.maximum === "number" && value > field.maximum)
|
|
186
|
+
return `Must be at most ${field.maximum}`;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (field.type === "multiselect") {
|
|
190
|
+
if (!Array.isArray(value))
|
|
191
|
+
return "Expected selections";
|
|
192
|
+
if (field.required && !value.length)
|
|
193
|
+
return "Select at least one option";
|
|
194
|
+
if (field.minItems !== undefined && value.length < field.minItems)
|
|
195
|
+
return `Select at least ${field.minItems}`;
|
|
196
|
+
if (field.maxItems !== undefined && value.length > field.maxItems)
|
|
197
|
+
return `Select at most ${field.maxItems}`;
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
var numberOf = (limit) => typeof limit === "number" ? limit : undefined;
|
|
202
|
+
function limitsOf(field) {
|
|
203
|
+
const range = (low, high, unit = "") => {
|
|
204
|
+
if (low !== undefined && high !== undefined)
|
|
205
|
+
return `${low}-${high}${unit}`;
|
|
206
|
+
if (low !== undefined)
|
|
207
|
+
return `at least ${low}${unit}`;
|
|
208
|
+
if (high !== undefined)
|
|
209
|
+
return `at most ${high}${unit}`;
|
|
210
|
+
return;
|
|
211
|
+
};
|
|
212
|
+
const parts = field.type === "number" || field.type === "integer" ? [range(numberOf(field.minimum), numberOf(field.maximum))] : field.type === "string" ? [range(field.minLength, field.maxLength, " characters"), field.format, field.pattern ? `/${field.pattern}/` : undefined] : [];
|
|
213
|
+
return parts.filter(Boolean).join(", ") || undefined;
|
|
123
214
|
}
|
|
124
215
|
function chooseOptions(choices, picked, {
|
|
125
216
|
multiple = false,
|
|
@@ -163,8 +254,10 @@ function toggle(picked, value) {
|
|
|
163
254
|
var OWN = Symbol("own");
|
|
164
255
|
var DONE = Symbol("done");
|
|
165
256
|
var SKIP = Symbol("skip");
|
|
166
|
-
|
|
257
|
+
var NOTE = Symbol("note");
|
|
258
|
+
function addAsksInbox(ctx, follows) {
|
|
167
259
|
const person = ctx.client.rpc(personRpc);
|
|
260
|
+
const currentTitle = (sessionID) => ctx.data.session.get(sessionID)?.title;
|
|
168
261
|
const location = (sessionID) => ctx.data.session.get(sessionID)?.location ?? ctx.location ?? ctx.data.location.default();
|
|
169
262
|
const openSession = () => {
|
|
170
263
|
const route = ctx.ui.router.current();
|
|
@@ -186,31 +279,46 @@ function addAsksInbox(ctx) {
|
|
|
186
279
|
for (const ask of listed)
|
|
187
280
|
if (!firstSeen.has(ask.key))
|
|
188
281
|
firstSeen.set(ask.key, seen++);
|
|
189
|
-
|
|
282
|
+
const rank = (ask) => ask.session.depth === 0 ? 0 : 1;
|
|
283
|
+
listed.sort((a, b) => rank(a) - rank(b) || firstSeen.get(a.key) - firstSeen.get(b.key));
|
|
190
284
|
return {
|
|
191
285
|
asks,
|
|
192
286
|
listed
|
|
193
287
|
};
|
|
194
288
|
}
|
|
289
|
+
const reason = (error) => {
|
|
290
|
+
if (error instanceof Error)
|
|
291
|
+
return error.message;
|
|
292
|
+
return typeof error === "object" && error && "message" in error ? String(error.message) : String(error);
|
|
293
|
+
};
|
|
195
294
|
const fail = (what) => (error) => ctx.ui.toast.show({
|
|
196
295
|
title: "courier",
|
|
197
|
-
message: `${what}: ${
|
|
296
|
+
message: `${what}: ${reason(error)}`,
|
|
198
297
|
variant: "error"
|
|
199
298
|
});
|
|
200
299
|
async function choose(title, choices, {
|
|
201
300
|
multiple = false,
|
|
202
301
|
own = false,
|
|
203
|
-
optional = false
|
|
302
|
+
optional = false,
|
|
303
|
+
asks = "",
|
|
304
|
+
start = []
|
|
204
305
|
} = {}) {
|
|
205
|
-
const picked = [];
|
|
306
|
+
const picked = [...start];
|
|
206
307
|
for (;; ) {
|
|
207
308
|
const value = await ctx.ui.dialog.select({
|
|
208
309
|
title,
|
|
209
|
-
|
|
310
|
+
...!multiple && start[0] !== undefined ? {
|
|
311
|
+
current: start[0]
|
|
312
|
+
} : {},
|
|
313
|
+
options: [...asks ? [{
|
|
314
|
+
title: asks,
|
|
315
|
+
value: NOTE,
|
|
316
|
+
disabled: true
|
|
317
|
+
}] : [], ...chooseOptions(choices, picked, {
|
|
210
318
|
multiple,
|
|
211
319
|
own,
|
|
212
320
|
optional
|
|
213
|
-
})
|
|
321
|
+
})]
|
|
214
322
|
});
|
|
215
323
|
if (value === undefined)
|
|
216
324
|
return;
|
|
@@ -313,6 +421,9 @@ function addAsksInbox(ctx) {
|
|
|
313
421
|
...field.description ? {
|
|
314
422
|
description: field.description
|
|
315
423
|
} : {},
|
|
424
|
+
...field.placeholder ? {
|
|
425
|
+
placeholder: field.placeholder
|
|
426
|
+
} : {},
|
|
316
427
|
...field.default ? {
|
|
317
428
|
value: field.default
|
|
318
429
|
} : {}
|
|
@@ -324,11 +435,16 @@ function addAsksInbox(ctx) {
|
|
|
324
435
|
}
|
|
325
436
|
}
|
|
326
437
|
async function numeric(title, field, optional) {
|
|
438
|
+
const label = field.title ?? field.key;
|
|
327
439
|
const whole = field.type === "integer";
|
|
328
440
|
for (;; ) {
|
|
329
441
|
const text = await typed(title, {
|
|
330
442
|
...field.description ? {
|
|
331
443
|
description: field.description
|
|
444
|
+
} : {},
|
|
445
|
+
...placeholder(field),
|
|
446
|
+
...field.default !== undefined ? {
|
|
447
|
+
default: String(field.default)
|
|
332
448
|
} : {}
|
|
333
449
|
}, optional);
|
|
334
450
|
if (text === undefined || text === SKIP)
|
|
@@ -338,13 +454,36 @@ function addAsksInbox(ctx) {
|
|
|
338
454
|
return value;
|
|
339
455
|
ctx.ui.toast.show({
|
|
340
456
|
title: "courier",
|
|
341
|
-
message: `${
|
|
457
|
+
message: `${label} takes ${whole ? "a whole number" : "a number"}`,
|
|
458
|
+
variant: "warning"
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const placeholder = (field) => {
|
|
463
|
+
const limits = limitsOf(field);
|
|
464
|
+
return limits ? {
|
|
465
|
+
placeholder: limits
|
|
466
|
+
} : {};
|
|
467
|
+
};
|
|
468
|
+
async function answerField(form, field) {
|
|
469
|
+
for (;; ) {
|
|
470
|
+
const value = await askField(form, field);
|
|
471
|
+
if (value === undefined || value === SKIP)
|
|
472
|
+
return value;
|
|
473
|
+
const problem = misfit(field, value);
|
|
474
|
+
if (!problem)
|
|
475
|
+
return value;
|
|
476
|
+
ctx.ui.toast.show({
|
|
477
|
+
title: "courier",
|
|
478
|
+
message: `${field.title ?? field.key}: ${problem}`,
|
|
342
479
|
variant: "warning"
|
|
343
480
|
});
|
|
344
481
|
}
|
|
345
482
|
}
|
|
346
|
-
async function
|
|
347
|
-
const
|
|
483
|
+
async function askField(form, field) {
|
|
484
|
+
const label = field.title ?? field.key;
|
|
485
|
+
const title = form.title ? `${form.title} \xB7 ${label}` : label;
|
|
486
|
+
const asks = field.description ?? "";
|
|
348
487
|
const optional = !(("required" in field) && field.required);
|
|
349
488
|
switch (field.type) {
|
|
350
489
|
case "external":
|
|
@@ -356,7 +495,14 @@ function addAsksInbox(ctx) {
|
|
|
356
495
|
case "boolean":
|
|
357
496
|
return ctx.ui.dialog.select({
|
|
358
497
|
title,
|
|
359
|
-
|
|
498
|
+
...field.default !== undefined ? {
|
|
499
|
+
current: field.default
|
|
500
|
+
} : {},
|
|
501
|
+
options: [...asks ? [{
|
|
502
|
+
title: asks,
|
|
503
|
+
value: NOTE,
|
|
504
|
+
disabled: true
|
|
505
|
+
}] : [], {
|
|
360
506
|
title: "Yes",
|
|
361
507
|
value: true
|
|
362
508
|
}, {
|
|
@@ -367,7 +513,9 @@ function addAsksInbox(ctx) {
|
|
|
367
513
|
case "multiselect": {
|
|
368
514
|
const picked = await choose(title, field.options, {
|
|
369
515
|
multiple: true,
|
|
370
|
-
own: !!field.custom
|
|
516
|
+
own: !!field.custom,
|
|
517
|
+
asks,
|
|
518
|
+
start: field.default ?? []
|
|
371
519
|
});
|
|
372
520
|
if (picked?.length === 0 && optional)
|
|
373
521
|
return SKIP;
|
|
@@ -375,10 +523,15 @@ function addAsksInbox(ctx) {
|
|
|
375
523
|
}
|
|
376
524
|
case "string": {
|
|
377
525
|
if (!field.options?.length)
|
|
378
|
-
return typed(title,
|
|
526
|
+
return typed(title, {
|
|
527
|
+
...field,
|
|
528
|
+
...placeholder(field)
|
|
529
|
+
}, optional);
|
|
379
530
|
const picked = await choose(title, field.options, {
|
|
380
531
|
own: !!field.custom,
|
|
381
|
-
optional
|
|
532
|
+
optional,
|
|
533
|
+
asks,
|
|
534
|
+
start: field.default === undefined ? [] : [field.default]
|
|
382
535
|
});
|
|
383
536
|
return picked && (picked[0] ?? SKIP);
|
|
384
537
|
}
|
|
@@ -393,19 +546,36 @@ function addAsksInbox(ctx) {
|
|
|
393
546
|
} = ask;
|
|
394
547
|
const answer2 = {};
|
|
395
548
|
for (const field of form.fields) {
|
|
396
|
-
if (!
|
|
549
|
+
if (!active(field, answer2))
|
|
550
|
+
continue;
|
|
551
|
+
if ("hidden" in field && field.hidden) {
|
|
552
|
+
if ("default" in field && field.default !== undefined)
|
|
553
|
+
answer2[field.key] = field.default;
|
|
397
554
|
continue;
|
|
398
|
-
|
|
555
|
+
}
|
|
556
|
+
const value = await answerField(form, field);
|
|
399
557
|
if (value === undefined)
|
|
400
558
|
return false;
|
|
401
559
|
if (value !== SKIP)
|
|
402
560
|
answer2[field.key] = value;
|
|
403
561
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
562
|
+
try {
|
|
563
|
+
await ctx.client.session.form.reply({
|
|
564
|
+
sessionID: form.sessionID,
|
|
565
|
+
formID: form.id,
|
|
566
|
+
answer: answer2
|
|
567
|
+
});
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const tag = error?._tag;
|
|
570
|
+
if (tag !== "FormNotFoundError" && tag !== "FormAlreadySettledError")
|
|
571
|
+
throw error;
|
|
572
|
+
ctx.ui.toast.show({
|
|
573
|
+
title: "courier",
|
|
574
|
+
message: "It no longer waits: answered or settled some other way.",
|
|
575
|
+
variant: "warning"
|
|
576
|
+
});
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
409
579
|
return true;
|
|
410
580
|
}
|
|
411
581
|
const answer = (ask) => {
|
|
@@ -418,6 +588,68 @@ function addAsksInbox(ctx) {
|
|
|
418
588
|
return answerForm(ask);
|
|
419
589
|
}
|
|
420
590
|
};
|
|
591
|
+
let listing;
|
|
592
|
+
let following;
|
|
593
|
+
const keysOf = (listed) => listed.map((ask) => ask.key).join(`
|
|
594
|
+
`);
|
|
595
|
+
let reads = 0;
|
|
596
|
+
const follow = async () => {
|
|
597
|
+
const sessionID = listing?.sessionID;
|
|
598
|
+
if (!sessionID)
|
|
599
|
+
return;
|
|
600
|
+
const ticket = ++reads;
|
|
601
|
+
const found = await read(sessionID);
|
|
602
|
+
const shown = listing;
|
|
603
|
+
if (ticket === reads && shown?.sessionID === sessionID && keysOf(found.listed) !== shown.keys)
|
|
604
|
+
shown.replace(found);
|
|
605
|
+
};
|
|
606
|
+
async function showList(sessionID, {
|
|
607
|
+
asks,
|
|
608
|
+
listed
|
|
609
|
+
}) {
|
|
610
|
+
let replace = () => {
|
|
611
|
+
return;
|
|
612
|
+
};
|
|
613
|
+
const newer = new Promise((resolve) => {
|
|
614
|
+
replace = resolve;
|
|
615
|
+
});
|
|
616
|
+
listing = {
|
|
617
|
+
sessionID,
|
|
618
|
+
keys: keysOf(listed),
|
|
619
|
+
replace
|
|
620
|
+
};
|
|
621
|
+
const top = ctx.data.session.get(asks.top)?.title ?? asks.top;
|
|
622
|
+
const choosing = ctx.ui.dialog.select({
|
|
623
|
+
title: `Waiting on you (${listed.length})`,
|
|
624
|
+
placeholder: "Search",
|
|
625
|
+
options: listed.map((ask) => ({
|
|
626
|
+
title: askTitle(ask),
|
|
627
|
+
value: ask,
|
|
628
|
+
description: pathOf(asks, ask.session.sessionID, top, currentTitle),
|
|
629
|
+
footer: askFooter(ask)
|
|
630
|
+
}))
|
|
631
|
+
});
|
|
632
|
+
ctx.ui.dialog.set({
|
|
633
|
+
size: "large"
|
|
634
|
+
});
|
|
635
|
+
try {
|
|
636
|
+
return await Promise.race([choosing.then((chosen) => ({
|
|
637
|
+
chosen
|
|
638
|
+
})), newer.then((read2) => ({
|
|
639
|
+
read: read2
|
|
640
|
+
}))]);
|
|
641
|
+
} finally {
|
|
642
|
+
listing = undefined;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function answerChosen(chosen) {
|
|
646
|
+
const done = await answer(chosen).catch((error) => {
|
|
647
|
+
fail("The answer was not passed on")(error);
|
|
648
|
+
return false;
|
|
649
|
+
});
|
|
650
|
+
if (done)
|
|
651
|
+
answered.add(chosen.key);
|
|
652
|
+
}
|
|
421
653
|
let opened = false;
|
|
422
654
|
async function open(sessionID = openSession()) {
|
|
423
655
|
if (!sessionID) {
|
|
@@ -432,83 +664,95 @@ function addAsksInbox(ctx) {
|
|
|
432
664
|
return;
|
|
433
665
|
opened = true;
|
|
434
666
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
ctx.ui.toast.show({
|
|
446
|
-
title: "courier",
|
|
447
|
-
message: "Nothing in this courier tree waits on you.",
|
|
448
|
-
variant: "info"
|
|
449
|
-
});
|
|
450
|
-
return;
|
|
667
|
+
let found = await read(sessionID).catch(fail("The asks could not be read"));
|
|
668
|
+
let first = true;
|
|
669
|
+
let shown = false;
|
|
670
|
+
while (found?.listed.length) {
|
|
671
|
+
first = false;
|
|
672
|
+
const next = await showList(sessionID, found);
|
|
673
|
+
shown = "read" in next;
|
|
674
|
+
if ("read" in next) {
|
|
675
|
+
found = next.read;
|
|
676
|
+
continue;
|
|
451
677
|
}
|
|
452
|
-
|
|
453
|
-
const choosing = ctx.ui.dialog.select({
|
|
454
|
-
title: `Waiting on you (${listed.length})`,
|
|
455
|
-
placeholder: "Search",
|
|
456
|
-
options: listed.map((ask) => ({
|
|
457
|
-
title: askTitle(ask),
|
|
458
|
-
value: ask,
|
|
459
|
-
description: pathOf(asks, ask.session.sessionID, top),
|
|
460
|
-
footer: askFooter(ask)
|
|
461
|
-
}))
|
|
462
|
-
});
|
|
463
|
-
ctx.ui.dialog.set({
|
|
464
|
-
size: "large"
|
|
465
|
-
});
|
|
466
|
-
const chosen = await choosing;
|
|
467
|
-
if (!chosen)
|
|
678
|
+
if (!next.chosen)
|
|
468
679
|
return;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return false;
|
|
472
|
-
});
|
|
473
|
-
if (done)
|
|
474
|
-
answered.add(chosen.key);
|
|
680
|
+
await answerChosen(next.chosen);
|
|
681
|
+
found = await read(sessionID).catch(fail("The asks could not be read"));
|
|
475
682
|
}
|
|
683
|
+
if (!found)
|
|
684
|
+
return;
|
|
685
|
+
if (first)
|
|
686
|
+
ctx.ui.toast.show({
|
|
687
|
+
title: "courier",
|
|
688
|
+
message: "Nothing in this courier tree waits on you.",
|
|
689
|
+
variant: "info"
|
|
690
|
+
});
|
|
691
|
+
if (shown)
|
|
692
|
+
ctx.ui.dialog.clear();
|
|
476
693
|
} finally {
|
|
477
694
|
opened = false;
|
|
695
|
+
clearTimeout(following);
|
|
696
|
+
following = undefined;
|
|
478
697
|
}
|
|
479
698
|
}
|
|
480
699
|
const sounded = new Set;
|
|
700
|
+
const readTops = new Set;
|
|
701
|
+
let named = new Set;
|
|
481
702
|
let settling;
|
|
482
703
|
const sound = async () => {
|
|
483
704
|
const sessionID = openSession();
|
|
484
|
-
if (!sessionID)
|
|
705
|
+
if (!sessionID) {
|
|
706
|
+
named.clear();
|
|
485
707
|
return;
|
|
708
|
+
}
|
|
486
709
|
const {
|
|
487
710
|
asks,
|
|
488
711
|
listed
|
|
489
712
|
} = await read(sessionID);
|
|
713
|
+
const eventSessions = named;
|
|
714
|
+
named = new Set;
|
|
490
715
|
const top = ctx.data.session.get(asks.top)?.title ?? asks.top;
|
|
716
|
+
const first = !readTops.has(asks.top);
|
|
717
|
+
readTops.add(asks.top);
|
|
491
718
|
for (const ask of listed) {
|
|
492
719
|
if (sounded.has(ask.key))
|
|
493
720
|
continue;
|
|
494
721
|
sounded.add(ask.key);
|
|
722
|
+
if (first && !eventSessions.has(ask.session.sessionID))
|
|
723
|
+
continue;
|
|
724
|
+
if (!ctx.data.session.get(ask.session.sessionID)?.parentID)
|
|
725
|
+
continue;
|
|
495
726
|
ctx.attention.notify({
|
|
496
|
-
title: pathOf(asks, ask.session.sessionID, top),
|
|
727
|
+
title: pathOf(asks, ask.session.sessionID, top, currentTitle),
|
|
497
728
|
message: askTitle(ask),
|
|
498
729
|
notification: {
|
|
499
730
|
when: "blurred"
|
|
500
731
|
},
|
|
501
|
-
sound:
|
|
502
|
-
name: soundOf(ask),
|
|
503
|
-
when: "always"
|
|
504
|
-
}
|
|
732
|
+
sound: false
|
|
505
733
|
});
|
|
506
734
|
}
|
|
507
735
|
};
|
|
736
|
+
sound().catch(() => {
|
|
737
|
+
return;
|
|
738
|
+
});
|
|
508
739
|
const unlisten = ctx.data.listen(({
|
|
509
740
|
details
|
|
510
741
|
}) => {
|
|
511
|
-
if (
|
|
742
|
+
if (listing && follows.has(details.type) && !following)
|
|
743
|
+
following = setTimeout(() => {
|
|
744
|
+
following = undefined;
|
|
745
|
+
follow().catch(() => {
|
|
746
|
+
return;
|
|
747
|
+
});
|
|
748
|
+
}, SETTLE_MS);
|
|
749
|
+
if (details.type !== "permission.asked" && details.type !== "form.created")
|
|
750
|
+
return;
|
|
751
|
+
const data = details.data;
|
|
752
|
+
const eventSession = data?.sessionID ?? data?.form?.sessionID;
|
|
753
|
+
if (eventSession)
|
|
754
|
+
named.add(eventSession);
|
|
755
|
+
if (settling)
|
|
512
756
|
return;
|
|
513
757
|
settling = setTimeout(() => {
|
|
514
758
|
settling = undefined;
|
|
@@ -537,6 +781,7 @@ function addAsksInbox(ctx) {
|
|
|
537
781
|
open,
|
|
538
782
|
cleanup: () => {
|
|
539
783
|
clearTimeout(settling);
|
|
784
|
+
clearTimeout(following);
|
|
540
785
|
unlisten();
|
|
541
786
|
}
|
|
542
787
|
};
|
|
@@ -552,7 +797,7 @@ import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
|
552
797
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
553
798
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
554
799
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
555
|
-
import { createEffect, createResource, createRoot, createSignal, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
800
|
+
import { createEffect, createMemo, createResource, createRoot, createSignal, For, on, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
556
801
|
|
|
557
802
|
// src/tree/rpc.ts
|
|
558
803
|
import { Rpc as Rpc2 } from "@opencode/plugin/rpc";
|
|
@@ -563,20 +808,23 @@ var Summary = Schema2.Struct({
|
|
|
563
808
|
running: Count2,
|
|
564
809
|
waiting: Count2,
|
|
565
810
|
idle: Count2,
|
|
566
|
-
|
|
567
|
-
|
|
811
|
+
stalled: Schema2.optionalKey(Count2),
|
|
812
|
+
groups: Schema2.Array(Group)
|
|
568
813
|
});
|
|
569
814
|
var Wait = Schema2.Union([
|
|
570
815
|
Schema2.Struct({ type: Schema2.Literal("permission"), action: Schema2.String, resources: Schema2.Array(Schema2.String) }),
|
|
571
|
-
Schema2.Struct({ type: Schema2.Literal("question"), question: Schema2.String })
|
|
816
|
+
Schema2.Struct({ type: Schema2.Literal("question"), question: Schema2.String }),
|
|
817
|
+
Schema2.Struct({ type: Schema2.Literal("form"), title: Schema2.String })
|
|
572
818
|
]);
|
|
573
819
|
var Row = Schema2.Struct({
|
|
574
820
|
sessionID: Schema2.String,
|
|
575
821
|
parentID: Schema2.String,
|
|
576
822
|
title: Schema2.String,
|
|
577
823
|
depth: Count2,
|
|
578
|
-
state: Schema2.Literals(["running", "waiting", "idle", "gone"]),
|
|
824
|
+
state: Schema2.Literals(["running", "waiting", "idle", "stalled", "gone"]),
|
|
825
|
+
stalled: Schema2.optionalKey(Schema2.Number),
|
|
579
826
|
outcome: Schema2.optionalKey(Schema2.Literals(["succeeded", "failed", "interrupted"])),
|
|
827
|
+
stopped: Schema2.optionalKey(Schema2.Boolean),
|
|
580
828
|
report: Schema2.optionalKey(Schema2.Struct({ state: Schema2.Literals(["owed", "reported", "ended"]), status: Schema2.optionalKey(Schema2.String), progressed: Schema2.Boolean })),
|
|
581
829
|
group: Schema2.optionalKey(Schema2.Struct({ name: Schema2.String, standing: Schema2.String })),
|
|
582
830
|
isolated: Schema2.Boolean,
|
|
@@ -588,12 +836,17 @@ var Row = Schema2.Struct({
|
|
|
588
836
|
});
|
|
589
837
|
var Tree = Schema2.Struct({
|
|
590
838
|
rows: Schema2.Array(Row),
|
|
591
|
-
truncated: Schema2.optionalKey(Count2)
|
|
839
|
+
truncated: Schema2.optionalKey(Count2),
|
|
840
|
+
stalled: Schema2.optionalKey(Schema2.Number)
|
|
592
841
|
});
|
|
593
842
|
var Stopped = Schema2.Struct({
|
|
594
843
|
stopped: Schema2.Array(Schema2.Struct({ sessionID: Schema2.String, title: Schema2.String, outcome: Schema2.String })),
|
|
595
844
|
cancelled: Count2
|
|
596
845
|
});
|
|
846
|
+
var Forgotten = Schema2.Union([
|
|
847
|
+
Schema2.Struct({ outcome: Schema2.Literals(["forgotten", "removed", "gone"]) }),
|
|
848
|
+
Schema2.Struct({ outcome: Schema2.Literal("kept"), reason: Schema2.String })
|
|
849
|
+
]);
|
|
597
850
|
var treeRpc = Rpc2.define({
|
|
598
851
|
id: "courier",
|
|
599
852
|
methods: {
|
|
@@ -609,6 +862,11 @@ var treeRpc = Rpc2.define({
|
|
|
609
862
|
input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
|
|
610
863
|
output: Schema2.toStandardSchemaV1(Stopped),
|
|
611
864
|
errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
|
|
865
|
+
},
|
|
866
|
+
cleanup: {
|
|
867
|
+
input: Schema2.toStandardSchemaV1(Schema2.Struct({ sessionID: Schema2.String, by: Schema2.String })),
|
|
868
|
+
output: Schema2.toStandardSchemaV1(Forgotten),
|
|
869
|
+
errors: { refused: Schema2.toStandardSchemaV1(Schema2.Struct({})) }
|
|
612
870
|
}
|
|
613
871
|
},
|
|
614
872
|
events: {}
|
|
@@ -621,9 +879,19 @@ var SETTLE_MS2 = 250;
|
|
|
621
879
|
var PANEL_SETTLE_MS = 1000;
|
|
622
880
|
var PANEL = "courier-tree";
|
|
623
881
|
var TICK_MS = 1000;
|
|
624
|
-
function summaryLine(summary) {
|
|
625
|
-
const
|
|
626
|
-
|
|
882
|
+
function summaryLine(summary, maxWidth = Infinity) {
|
|
883
|
+
const groups = summary.groups.map((group) => `group ${group.name} ${group.reported}/${group.members}`);
|
|
884
|
+
const parts = [summary.waiting ? `${summary.waiting} waiting on you` : "", summary.stalled ? `${summary.stalled} stalled` : "", summary.running ? `${summary.running} running` : "", ...groups, summary.idle ? `${summary.idle} idle` : ""].filter(Boolean);
|
|
885
|
+
if (!parts.length)
|
|
886
|
+
return "";
|
|
887
|
+
const line = (kept) => {
|
|
888
|
+
const dropped = groups.filter((group) => parts.indexOf(group) >= kept).length;
|
|
889
|
+
return ["courier", parts.slice(0, kept).join(" \xB7 "), dropped ? `\xB7 +${counted(dropped, "group")}` : ""].filter(Boolean).join(" ");
|
|
890
|
+
};
|
|
891
|
+
for (let kept = parts.length;kept > 0; kept--)
|
|
892
|
+
if (line(kept).length <= maxWidth)
|
|
893
|
+
return line(kept);
|
|
894
|
+
return parts[0];
|
|
627
895
|
}
|
|
628
896
|
function duration(ms) {
|
|
629
897
|
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
@@ -649,35 +917,46 @@ function bar({
|
|
|
649
917
|
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}] ${reported}/${started} reported`;
|
|
650
918
|
}
|
|
651
919
|
function waitLine(wait) {
|
|
652
|
-
|
|
920
|
+
if (wait.type === "permission")
|
|
921
|
+
return `asks permission: ${[wait.action, ...wait.resources].join(" ")}`;
|
|
922
|
+
return wait.type === "form" ? `shows a form: ${wait.title}` : `asks: ${wait.question}`;
|
|
653
923
|
}
|
|
654
924
|
function statusLine(row) {
|
|
655
|
-
return [stateWord(row), reportWords(row.report), row.group ? `group ${row.group.name} ${row.group.standing}` : "", row.isolated ? "isolated" : ""].filter(Boolean).join(" \xB7 ");
|
|
925
|
+
return [stateWord(row), reportWords(row.report, row.stopped), row.group ? `group ${row.group.name} ${row.group.standing}` : "", row.isolated ? "isolated" : ""].filter(Boolean).join(" \xB7 ");
|
|
656
926
|
}
|
|
657
927
|
function stateWord(row) {
|
|
658
928
|
if (row.state === "waiting")
|
|
659
929
|
return "waiting on you";
|
|
930
|
+
if (row.state === "stalled")
|
|
931
|
+
return "stalled";
|
|
932
|
+
if (row.state === "idle" && row.stopped)
|
|
933
|
+
return "stopped";
|
|
660
934
|
if (row.state === "idle" && (row.outcome === "failed" || row.outcome === "interrupted"))
|
|
661
935
|
return row.outcome;
|
|
662
936
|
return row.state;
|
|
663
937
|
}
|
|
664
|
-
function reportWords(report) {
|
|
938
|
+
function reportWords(report, stopped) {
|
|
665
939
|
if (!report)
|
|
666
940
|
return "";
|
|
667
941
|
if (report.state === "owed")
|
|
668
942
|
return "owes a report";
|
|
669
943
|
if (report.state === "ended")
|
|
670
|
-
return "ended without a report";
|
|
944
|
+
return stopped ? "" : "ended without a report";
|
|
671
945
|
return ["reported", report.status].filter(Boolean).join(" ");
|
|
672
946
|
}
|
|
673
947
|
var counted = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
674
948
|
function activityLine(row, now) {
|
|
675
949
|
return [`ran ${duration((row.idle ?? now) - row.created)}`, row.tool ? `last ${row.tool.name} ${duration(now - row.tool.at)} ago` : "no tool call yet", row.cost !== undefined ? `$${row.cost.toFixed(2)}` : ""].filter(Boolean).join(" \xB7 ");
|
|
676
950
|
}
|
|
951
|
+
function stalledLine(since, now) {
|
|
952
|
+
return `a message waits in its inbox since ${duration(now - since)}, woken again in vain`;
|
|
953
|
+
}
|
|
677
954
|
function detailLines(row, rows, now) {
|
|
678
955
|
const started = parts(rows, row.sessionID);
|
|
679
956
|
if (row.waits.length)
|
|
680
957
|
return [...started.started ? [bar(started)] : [], ...row.waits.map(waitLine)];
|
|
958
|
+
if (row.stalled !== undefined)
|
|
959
|
+
return [...started.started ? [bar(started)] : [], stalledLine(row.stalled, now)];
|
|
681
960
|
if (started.started)
|
|
682
961
|
return [bar(started)];
|
|
683
962
|
return row.state === "gone" ? [] : [activityLine(row, now)];
|
|
@@ -690,8 +969,10 @@ function courierOf(ctx) {
|
|
|
690
969
|
location
|
|
691
970
|
};
|
|
692
971
|
}
|
|
972
|
+
var REREAD_MS = 15000;
|
|
693
973
|
function changes(ctx, types, settle) {
|
|
694
974
|
const [changed, setChanged] = createSignal(0);
|
|
975
|
+
const reread = setInterval(() => setChanged((count) => count + 1), REREAD_MS);
|
|
695
976
|
let settling;
|
|
696
977
|
const unlisten = ctx.data.listen(({
|
|
697
978
|
details
|
|
@@ -705,6 +986,7 @@ function changes(ctx, types, settle) {
|
|
|
705
986
|
});
|
|
706
987
|
return [changed, () => {
|
|
707
988
|
clearTimeout(settling);
|
|
989
|
+
clearInterval(reread);
|
|
708
990
|
unlisten();
|
|
709
991
|
}];
|
|
710
992
|
}
|
|
@@ -733,34 +1015,44 @@ function addTreeStatus(ctx, onWaiting) {
|
|
|
733
1015
|
location: location(sessionID)
|
|
734
1016
|
}).then((summary) => ({
|
|
735
1017
|
sessionID,
|
|
736
|
-
|
|
1018
|
+
summary,
|
|
737
1019
|
waiting: summary.waiting
|
|
738
1020
|
}), () => ({
|
|
739
1021
|
sessionID,
|
|
740
|
-
|
|
1022
|
+
summary: undefined,
|
|
741
1023
|
waiting: 0
|
|
742
1024
|
})));
|
|
743
|
-
const
|
|
1025
|
+
const shown = () => {
|
|
744
1026
|
const latest = read.state === "ready" || read.state === "refreshing" ? read.latest : undefined;
|
|
745
1027
|
return latest && latest.sessionID === input.sessionID ? latest : undefined;
|
|
746
1028
|
};
|
|
747
|
-
const
|
|
1029
|
+
const [width, setWidth] = createSignal(0);
|
|
1030
|
+
const present = () => shown()?.summary ? summaryLine(shown().summary) : "";
|
|
1031
|
+
const line = () => width() && shown()?.summary ? summaryLine(shown().summary, width() - 1) : "";
|
|
748
1032
|
const click = () => {
|
|
749
|
-
const latest =
|
|
1033
|
+
const latest = shown();
|
|
750
1034
|
if (latest?.waiting)
|
|
751
1035
|
onWaiting?.(latest.sessionID);
|
|
752
1036
|
};
|
|
753
1037
|
return _$createComponent(Show, {
|
|
754
1038
|
get when() {
|
|
755
|
-
return
|
|
1039
|
+
return present();
|
|
756
1040
|
},
|
|
757
1041
|
get children() {
|
|
758
|
-
var _el$ = _$createElement("
|
|
1042
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createTextNode(` `);
|
|
759
1043
|
_$insertNode(_el$, _el$2);
|
|
760
|
-
_$setProp(_el$, "
|
|
761
|
-
_$setProp(_el$, "
|
|
762
|
-
_$
|
|
763
|
-
_$
|
|
1044
|
+
_$setProp(_el$, "flexGrow", 1);
|
|
1045
|
+
_$setProp(_el$, "flexShrink", 1);
|
|
1046
|
+
_$setProp(_el$, "minWidth", 0);
|
|
1047
|
+
_$setProp(_el$, "onSizeChange", function() {
|
|
1048
|
+
if (this.width > 0)
|
|
1049
|
+
setWidth(this.width);
|
|
1050
|
+
});
|
|
1051
|
+
_$insertNode(_el$2, _el$3);
|
|
1052
|
+
_$setProp(_el$2, "wrapMode", "none");
|
|
1053
|
+
_$setProp(_el$2, "onMouseUp", click);
|
|
1054
|
+
_$insert(_el$2, line, null);
|
|
1055
|
+
_$effect((_$p) => _$setProp(_el$2, "fg", ctx.theme.text.muted, _$p));
|
|
764
1056
|
return _el$;
|
|
765
1057
|
}
|
|
766
1058
|
});
|
|
@@ -815,7 +1107,52 @@ async function confirmStop(ctx, courier, location, by, target) {
|
|
|
815
1107
|
}
|
|
816
1108
|
return true;
|
|
817
1109
|
}
|
|
1110
|
+
function forgottenLine(title, result) {
|
|
1111
|
+
const name = `"${oneLine(title)}"`;
|
|
1112
|
+
if (result.outcome === "kept")
|
|
1113
|
+
return `Kept ${name} and its worktree: ${result.reason}`;
|
|
1114
|
+
if (result.outcome === "removed")
|
|
1115
|
+
return `Forgot ${name} and removed its worktree`;
|
|
1116
|
+
return result.outcome === "gone" ? `Forgot ${name}; its worktree was already gone` : `Forgot ${name}`;
|
|
1117
|
+
}
|
|
1118
|
+
async function confirmForget(ctx, courier, location, by, target, done) {
|
|
1119
|
+
const what = target.isolated ? "Its worktree is removed unless it has uncommitted changes or commits found nowhere else." : "It leaves this tree and courier_children.";
|
|
1120
|
+
const confirmed = await ctx.ui.dialog.confirm({
|
|
1121
|
+
title: "Forget session",
|
|
1122
|
+
message: `Forget "${oneLine(target.title)}" (${target.sessionID})? ${what}`,
|
|
1123
|
+
label: {
|
|
1124
|
+
confirm: "Forget",
|
|
1125
|
+
cancel: "Keep"
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
if (!confirmed)
|
|
1129
|
+
return false;
|
|
1130
|
+
try {
|
|
1131
|
+
const result = await courier.cleanup({
|
|
1132
|
+
sessionID: target.sessionID,
|
|
1133
|
+
by
|
|
1134
|
+
}, {
|
|
1135
|
+
location: location(by)
|
|
1136
|
+
});
|
|
1137
|
+
ctx.ui.toast.show({
|
|
1138
|
+
title: "courier",
|
|
1139
|
+
message: forgottenLine(target.title, result),
|
|
1140
|
+
variant: result.outcome === "kept" ? "warning" : "success"
|
|
1141
|
+
});
|
|
1142
|
+
if (result.outcome !== "kept")
|
|
1143
|
+
done();
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
ctx.ui.toast.show({
|
|
1146
|
+
title: "courier forget",
|
|
1147
|
+
message: reason(error),
|
|
1148
|
+
variant: "error"
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
818
1153
|
var FOCUS_PROMPT = "pane.focus.left";
|
|
1154
|
+
var SPLIT_WIDTH = 80;
|
|
1155
|
+
var oneLine = (title) => title.replace(/[\r\n]+/g, " ");
|
|
819
1156
|
function TreePanel(props) {
|
|
820
1157
|
const {
|
|
821
1158
|
ctx,
|
|
@@ -855,10 +1192,18 @@ function TreePanel(props) {
|
|
|
855
1192
|
tree
|
|
856
1193
|
});
|
|
857
1194
|
});
|
|
858
|
-
createEffect(() =>
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1195
|
+
createEffect(() => {
|
|
1196
|
+
const {
|
|
1197
|
+
presentation,
|
|
1198
|
+
focused
|
|
1199
|
+
} = input;
|
|
1200
|
+
view.note(ctx.renderer.width > SPLIT_WIDTH ? {
|
|
1201
|
+
presentation,
|
|
1202
|
+
focused
|
|
1203
|
+
} : {
|
|
1204
|
+
focused
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
862
1207
|
onMount(() => {
|
|
863
1208
|
if (!view.refocus)
|
|
864
1209
|
return;
|
|
@@ -868,23 +1213,39 @@ function TreePanel(props) {
|
|
|
868
1213
|
const [now, setNow] = createSignal(Date.now());
|
|
869
1214
|
const tick = setInterval(() => setNow(Date.now()), TICK_MS);
|
|
870
1215
|
onCleanup(() => clearInterval(tick));
|
|
871
|
-
const [
|
|
1216
|
+
const [selectedID, setSelectedID] = createSignal();
|
|
872
1217
|
let placed = false;
|
|
873
1218
|
createEffect(() => {
|
|
874
|
-
const
|
|
875
|
-
if (
|
|
1219
|
+
const found = placed ? undefined : rows().find((row) => row.sessionID === input.sessionID);
|
|
1220
|
+
if (!found)
|
|
876
1221
|
return;
|
|
877
1222
|
placed = true;
|
|
878
|
-
|
|
1223
|
+
setSelectedID(found.sessionID);
|
|
879
1224
|
});
|
|
880
|
-
|
|
1225
|
+
let at = 0;
|
|
1226
|
+
const selected = createMemo(() => {
|
|
1227
|
+
const id = selectedID();
|
|
1228
|
+
const found = rows().findIndex((row) => row.sessionID === id);
|
|
1229
|
+
const index = found >= 0 ? found : Math.min(at, rows().length - 1);
|
|
1230
|
+
at = Math.max(index, 0);
|
|
1231
|
+
return index;
|
|
1232
|
+
});
|
|
1233
|
+
createEffect(() => {
|
|
1234
|
+
const row = rows()[selected()];
|
|
1235
|
+
if (row && row.sessionID !== selectedID())
|
|
1236
|
+
setSelectedID(row.sessionID);
|
|
1237
|
+
});
|
|
1238
|
+
const current = () => rows()[selected()];
|
|
881
1239
|
let scroll;
|
|
882
1240
|
createEffect(() => {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
scroll?.scrollChildIntoView(`courier-row-${index}`);
|
|
1241
|
+
if (selected() >= 0)
|
|
1242
|
+
scroll?.scrollChildIntoView(`courier-row-${selected()}`);
|
|
886
1243
|
});
|
|
887
|
-
const move = (by) =>
|
|
1244
|
+
const move = (by) => {
|
|
1245
|
+
const next = rows()[Math.max(0, Math.min(rows().length - 1, selected() + by))];
|
|
1246
|
+
if (next)
|
|
1247
|
+
setSelectedID(next.sessionID);
|
|
1248
|
+
};
|
|
888
1249
|
const open = () => {
|
|
889
1250
|
const row = current();
|
|
890
1251
|
if (row)
|
|
@@ -902,6 +1263,22 @@ function TreePanel(props) {
|
|
|
902
1263
|
under: reach(rows(), row.sessionID) - 1
|
|
903
1264
|
});
|
|
904
1265
|
};
|
|
1266
|
+
const forgetSelected = () => {
|
|
1267
|
+
const row = current();
|
|
1268
|
+
if (!row)
|
|
1269
|
+
return;
|
|
1270
|
+
if (row.state !== "gone")
|
|
1271
|
+
return ctx.ui.toast.show({
|
|
1272
|
+
title: "courier",
|
|
1273
|
+
message: `x forgets only a gone session; "${oneLine(row.title)}" is ${row.state}.`,
|
|
1274
|
+
variant: "warning"
|
|
1275
|
+
});
|
|
1276
|
+
confirmForget(ctx, courier, location, view.root, {
|
|
1277
|
+
sessionID: row.sessionID,
|
|
1278
|
+
title: row.title,
|
|
1279
|
+
isolated: row.isolated
|
|
1280
|
+
}, () => void refetch());
|
|
1281
|
+
};
|
|
905
1282
|
ctx.keymap.layer(() => ({
|
|
906
1283
|
priority: 10,
|
|
907
1284
|
enabled: () => input.focused,
|
|
@@ -935,6 +1312,11 @@ function TreePanel(props) {
|
|
|
935
1312
|
title: "Stop session and those under it",
|
|
936
1313
|
group: "Courier",
|
|
937
1314
|
run: stopSelected
|
|
1315
|
+
}, {
|
|
1316
|
+
bind: "x",
|
|
1317
|
+
title: "Forget gone session",
|
|
1318
|
+
group: "Courier",
|
|
1319
|
+
run: forgetSelected
|
|
938
1320
|
}, {
|
|
939
1321
|
bind: "r",
|
|
940
1322
|
title: "Refresh",
|
|
@@ -954,52 +1336,53 @@ function TreePanel(props) {
|
|
|
954
1336
|
}));
|
|
955
1337
|
const root = () => parts(rows(), view.root);
|
|
956
1338
|
const rowColor = (row) => {
|
|
1339
|
+
if (row.state === "stalled")
|
|
1340
|
+
return theme.text.feedback.error.base;
|
|
957
1341
|
if (row.state === "waiting")
|
|
958
1342
|
return theme.text.feedback.warning.base;
|
|
959
1343
|
return row.state === "gone" ? theme.text.muted : theme.text.base;
|
|
960
1344
|
};
|
|
961
1345
|
return (() => {
|
|
962
|
-
var _el$
|
|
963
|
-
_$insertNode(_el$3, _el$4);
|
|
964
|
-
_$insertNode(_el$3, _el$13);
|
|
965
|
-
_$insertNode(_el$3, _el$14);
|
|
966
|
-
_$setProp(_el$3, "flexDirection", "column");
|
|
967
|
-
_$setProp(_el$3, "flexGrow", 1);
|
|
968
|
-
_$setProp(_el$3, "paddingLeft", 1);
|
|
969
|
-
_$setProp(_el$3, "paddingRight", 1);
|
|
1346
|
+
var _el$4 = _$createElement("box"), _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$15 = _$createElement("scrollbox"), _el$16 = _$createElement("text");
|
|
970
1347
|
_$insertNode(_el$4, _el$5);
|
|
971
|
-
_$
|
|
972
|
-
_$
|
|
973
|
-
_$setProp(_el$4, "
|
|
974
|
-
_$
|
|
975
|
-
_$setProp(_el$
|
|
976
|
-
_$
|
|
1348
|
+
_$insertNode(_el$4, _el$15);
|
|
1349
|
+
_$insertNode(_el$4, _el$16);
|
|
1350
|
+
_$setProp(_el$4, "flexDirection", "column");
|
|
1351
|
+
_$setProp(_el$4, "flexGrow", 1);
|
|
1352
|
+
_$setProp(_el$4, "paddingLeft", 1);
|
|
1353
|
+
_$setProp(_el$4, "paddingRight", 1);
|
|
1354
|
+
_$insertNode(_el$5, _el$6);
|
|
1355
|
+
_$setProp(_el$5, "flexDirection", "row");
|
|
1356
|
+
_$setProp(_el$5, "flexShrink", 0);
|
|
1357
|
+
_$setProp(_el$5, "gap", 2);
|
|
1358
|
+
_$insertNode(_el$6, _$createTextNode(`courier tree`));
|
|
1359
|
+
_$setProp(_el$6, "wrapMode", "none");
|
|
1360
|
+
_$insert(_el$5, _$createComponent(Show, {
|
|
977
1361
|
get when() {
|
|
978
1362
|
return root().started;
|
|
979
1363
|
},
|
|
980
1364
|
get children() {
|
|
981
|
-
var _el$
|
|
982
|
-
_$setProp(_el$
|
|
983
|
-
_$insert(_el$
|
|
984
|
-
_$effect((_$p) => _$setProp(_el$
|
|
985
|
-
return _el$
|
|
1365
|
+
var _el$8 = _$createElement("text");
|
|
1366
|
+
_$setProp(_el$8, "wrapMode", "none");
|
|
1367
|
+
_$insert(_el$8, () => bar(root()));
|
|
1368
|
+
_$effect((_$p) => _$setProp(_el$8, "fg", theme.text.muted, _$p));
|
|
1369
|
+
return _el$8;
|
|
986
1370
|
}
|
|
987
1371
|
}), null);
|
|
988
|
-
_$insert(_el$
|
|
1372
|
+
_$insert(_el$5, _$createComponent(Show, {
|
|
989
1373
|
get when() {
|
|
990
1374
|
return latest()?.tree?.truncated;
|
|
991
1375
|
},
|
|
992
1376
|
get children() {
|
|
993
|
-
var _el$
|
|
994
|
-
_$insertNode(_el$
|
|
995
|
-
_$
|
|
996
|
-
_$
|
|
997
|
-
_$
|
|
998
|
-
|
|
999
|
-
return _el$8;
|
|
1377
|
+
var _el$9 = _$createElement("text"), _el$0 = _$createTextNode(` older left out`);
|
|
1378
|
+
_$insertNode(_el$9, _el$0);
|
|
1379
|
+
_$setProp(_el$9, "wrapMode", "none");
|
|
1380
|
+
_$insert(_el$9, () => latest()?.tree?.truncated, _el$0);
|
|
1381
|
+
_$effect((_$p) => _$setProp(_el$9, "fg", theme.text.feedback.warning.base, _$p));
|
|
1382
|
+
return _el$9;
|
|
1000
1383
|
}
|
|
1001
1384
|
}), null);
|
|
1002
|
-
_$insert(_el$
|
|
1385
|
+
_$insert(_el$4, _$createComponent(Show, {
|
|
1003
1386
|
get when() {
|
|
1004
1387
|
return latest()?.error;
|
|
1005
1388
|
},
|
|
@@ -1010,62 +1393,79 @@ function TreePanel(props) {
|
|
|
1010
1393
|
_$effect((_$p) => _$setProp(_el$1, "fg", theme.text.feedback.error.base, _$p));
|
|
1011
1394
|
return _el$1;
|
|
1012
1395
|
}
|
|
1013
|
-
}), _el$
|
|
1014
|
-
_$insert(_el$
|
|
1396
|
+
}), _el$15);
|
|
1397
|
+
_$insert(_el$4, _$createComponent(Show, {
|
|
1015
1398
|
get when() {
|
|
1016
|
-
return
|
|
1399
|
+
return latest()?.tree?.stalled;
|
|
1017
1400
|
},
|
|
1018
1401
|
get children() {
|
|
1019
|
-
var _el$11 = _$createElement("text");
|
|
1020
|
-
_$insertNode(_el$11,
|
|
1021
|
-
_$
|
|
1402
|
+
var _el$11 = _$createElement("text"), _el$12 = _$createTextNode(`This session is stalled: `);
|
|
1403
|
+
_$insertNode(_el$11, _el$12);
|
|
1404
|
+
_$insert(_el$11, () => stalledLine(latest().tree.stalled, now()), null);
|
|
1405
|
+
_$effect((_$p) => _$setProp(_el$11, "fg", theme.text.feedback.error.base, _$p));
|
|
1022
1406
|
return _el$11;
|
|
1023
1407
|
}
|
|
1024
|
-
}), _el$
|
|
1025
|
-
_$
|
|
1026
|
-
|
|
1027
|
-
|
|
1408
|
+
}), _el$15);
|
|
1409
|
+
_$insert(_el$4, _$createComponent(Show, {
|
|
1410
|
+
get when() {
|
|
1411
|
+
return _$memo(() => !!latest()?.tree)() && !rows().length;
|
|
1412
|
+
},
|
|
1413
|
+
get children() {
|
|
1414
|
+
var _el$13 = _$createElement("text");
|
|
1415
|
+
_$insertNode(_el$13, _$createTextNode(`No session started with courier_spawn under this one.`));
|
|
1416
|
+
_$effect((_$p) => _$setProp(_el$13, "fg", theme.text.muted, _$p));
|
|
1417
|
+
return _el$13;
|
|
1418
|
+
}
|
|
1419
|
+
}), _el$15);
|
|
1420
|
+
_$use((value) => scroll = value, _el$15);
|
|
1421
|
+
_$setProp(_el$15, "flexGrow", 1);
|
|
1422
|
+
_$insert(_el$15, _$createComponent(For, {
|
|
1028
1423
|
get each() {
|
|
1029
1424
|
return rows();
|
|
1030
1425
|
},
|
|
1031
1426
|
children: (row, index) => {
|
|
1032
|
-
const chosen = () => index() ===
|
|
1427
|
+
const chosen = () => index() === selected();
|
|
1033
1428
|
const indent = " ".repeat(row.depth - 1);
|
|
1034
1429
|
return (() => {
|
|
1035
|
-
var _el$
|
|
1036
|
-
_$insertNode(_el$
|
|
1037
|
-
_$setProp(_el$
|
|
1038
|
-
_$setProp(_el$
|
|
1039
|
-
_$insertNode(_el$
|
|
1040
|
-
_$insertNode(_el$
|
|
1041
|
-
_$setProp(_el$
|
|
1042
|
-
_$
|
|
1043
|
-
_$
|
|
1044
|
-
_$
|
|
1045
|
-
_$
|
|
1046
|
-
_$insert(_el$
|
|
1047
|
-
_$insert(_el$
|
|
1430
|
+
var _el$18 = _$createElement("box"), _el$19 = _$createElement("box"), _el$20 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` `);
|
|
1431
|
+
_$insertNode(_el$18, _el$19);
|
|
1432
|
+
_$setProp(_el$18, "flexDirection", "column");
|
|
1433
|
+
_$setProp(_el$18, "flexShrink", 0);
|
|
1434
|
+
_$insertNode(_el$19, _el$20);
|
|
1435
|
+
_$insertNode(_el$19, _el$21);
|
|
1436
|
+
_$setProp(_el$19, "flexDirection", "row");
|
|
1437
|
+
_$setProp(_el$19, "flexShrink", 0);
|
|
1438
|
+
_$setProp(_el$20, "wrapMode", "none");
|
|
1439
|
+
_$setProp(_el$20, "flexShrink", 1);
|
|
1440
|
+
_$setProp(_el$20, "minWidth", 0);
|
|
1441
|
+
_$insert(_el$20, indent, null);
|
|
1442
|
+
_$insert(_el$20, () => chosen() ? "\u25B8 " : " ", null);
|
|
1443
|
+
_$insert(_el$20, () => oneLine(row.title), null);
|
|
1444
|
+
_$insert(_el$20, () => row.sessionID === input.sessionID ? " (on screen)" : "", null);
|
|
1445
|
+
_$insertNode(_el$21, _el$22);
|
|
1446
|
+
_$setProp(_el$21, "wrapMode", "none");
|
|
1447
|
+
_$setProp(_el$21, "flexShrink", 0);
|
|
1448
|
+
_$insert(_el$21, () => statusLine(row), null);
|
|
1449
|
+
_$insert(_el$18, _$createComponent(For, {
|
|
1048
1450
|
get each() {
|
|
1049
1451
|
return detailLines(row, rows(), now());
|
|
1050
1452
|
},
|
|
1051
1453
|
children: (line) => (() => {
|
|
1052
|
-
var _el$
|
|
1053
|
-
_$insertNode(_el$
|
|
1054
|
-
_$setProp(_el$
|
|
1055
|
-
_$insert(_el$
|
|
1056
|
-
_$insert(_el$
|
|
1057
|
-
_$effect((_$p) => _$setProp(_el$
|
|
1058
|
-
return _el$
|
|
1454
|
+
var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` `);
|
|
1455
|
+
_$insertNode(_el$23, _el$24);
|
|
1456
|
+
_$setProp(_el$23, "wrapMode", "none");
|
|
1457
|
+
_$insert(_el$23, indent, _el$24);
|
|
1458
|
+
_$insert(_el$23, line, null);
|
|
1459
|
+
_$effect((_$p) => _$setProp(_el$23, "fg", theme.text.muted, _$p));
|
|
1460
|
+
return _el$23;
|
|
1059
1461
|
})()
|
|
1060
1462
|
}), null);
|
|
1061
1463
|
_$effect((_p$) => {
|
|
1062
|
-
var _v$3 = `courier-row-${index()}`, _v$4 = chosen() && input.focused ? theme.background.raised.high : undefined, _v$5 = rowColor(row), _v$6 =
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
_v$
|
|
1066
|
-
_v$
|
|
1067
|
-
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$17, "fg", _v$5, _p$.a));
|
|
1068
|
-
_v$6 !== _p$.o && (_p$.o = _$setProp(_el$19, "style", _v$6, _p$.o));
|
|
1464
|
+
var _v$3 = `courier-row-${index()}`, _v$4 = chosen() && input.focused ? theme.background.raised.high : undefined, _v$5 = rowColor(row), _v$6 = theme.text.muted;
|
|
1465
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$18, "id", _v$3, _p$.e));
|
|
1466
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$18, "backgroundColor", _v$4, _p$.t));
|
|
1467
|
+
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$20, "fg", _v$5, _p$.a));
|
|
1468
|
+
_v$6 !== _p$.o && (_p$.o = _$setProp(_el$21, "fg", _v$6, _p$.o));
|
|
1069
1469
|
return _p$;
|
|
1070
1470
|
}, {
|
|
1071
1471
|
e: undefined,
|
|
@@ -1073,23 +1473,23 @@ function TreePanel(props) {
|
|
|
1073
1473
|
a: undefined,
|
|
1074
1474
|
o: undefined
|
|
1075
1475
|
});
|
|
1076
|
-
return _el$
|
|
1476
|
+
return _el$18;
|
|
1077
1477
|
})();
|
|
1078
1478
|
}
|
|
1079
1479
|
}));
|
|
1080
|
-
_$insertNode(_el$
|
|
1081
|
-
_$setProp(_el$
|
|
1082
|
-
_$setProp(_el$
|
|
1480
|
+
_$insertNode(_el$16, _$createTextNode(`\u2191\u2193 select \xB7 enter open \xB7 s stop \xB7 x forget \xB7 r refresh \xB7 f fullscreen \xB7 q close`));
|
|
1481
|
+
_$setProp(_el$16, "wrapMode", "none");
|
|
1482
|
+
_$setProp(_el$16, "flexShrink", 0);
|
|
1083
1483
|
_$effect((_p$) => {
|
|
1084
1484
|
var _v$ = theme.text.base, _v$2 = theme.text.muted;
|
|
1085
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$
|
|
1086
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
1485
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$, _p$.e));
|
|
1486
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$16, "fg", _v$2, _p$.t));
|
|
1087
1487
|
return _p$;
|
|
1088
1488
|
}, {
|
|
1089
1489
|
e: undefined,
|
|
1090
1490
|
t: undefined
|
|
1091
1491
|
});
|
|
1092
|
-
return _el$
|
|
1492
|
+
return _el$4;
|
|
1093
1493
|
})();
|
|
1094
1494
|
}
|
|
1095
1495
|
var USAGE = "/courier tree opens the session tree; /courier stop [session] stops a session and those under it";
|
|
@@ -1187,11 +1587,11 @@ function addTreePanel(ctx) {
|
|
|
1187
1587
|
createEffect(() => {
|
|
1188
1588
|
const route = ctx.ui.router.current();
|
|
1189
1589
|
const sessionID = route.type === "session" ? route.sessionID : undefined;
|
|
1190
|
-
const
|
|
1590
|
+
const shown = ctx.ui.panel.current();
|
|
1191
1591
|
untrack(() => {
|
|
1192
|
-
if (!following ||
|
|
1592
|
+
if (!following || shown && shown.sessionID === sessionID || !following.at && following.to === sessionID)
|
|
1193
1593
|
return;
|
|
1194
|
-
if (!
|
|
1594
|
+
if (!shown && sessionID && following.at === sessionID)
|
|
1195
1595
|
following = undefined;
|
|
1196
1596
|
else if (!sessionID)
|
|
1197
1597
|
hide(following);
|
|
@@ -1237,11 +1637,29 @@ function addTreePanel(ctx) {
|
|
|
1237
1637
|
message: "Open a session first",
|
|
1238
1638
|
variant: "warning"
|
|
1239
1639
|
});
|
|
1240
|
-
const
|
|
1640
|
+
const read = await courier.tree({
|
|
1241
1641
|
sessionID: route.sessionID
|
|
1242
1642
|
}, {
|
|
1243
1643
|
location: location(route.sessionID)
|
|
1244
|
-
}).then((tree) =>
|
|
1644
|
+
}).then((tree) => ({
|
|
1645
|
+
rows: tree.rows
|
|
1646
|
+
}), (error) => ({
|
|
1647
|
+
rows: [],
|
|
1648
|
+
error
|
|
1649
|
+
}));
|
|
1650
|
+
const rows = read.rows;
|
|
1651
|
+
if (!target && "error" in read)
|
|
1652
|
+
return ctx.ui.toast.show({
|
|
1653
|
+
title: "courier stop",
|
|
1654
|
+
message: reason(read.error),
|
|
1655
|
+
variant: "error"
|
|
1656
|
+
});
|
|
1657
|
+
if (!target && !rows.length)
|
|
1658
|
+
return ctx.ui.toast.show({
|
|
1659
|
+
title: "courier stop",
|
|
1660
|
+
message: "No session started with courier_spawn under this one.",
|
|
1661
|
+
variant: "warning"
|
|
1662
|
+
});
|
|
1245
1663
|
const sessionID = target || await ctx.ui.dialog.select({
|
|
1246
1664
|
title: "Stop which session?",
|
|
1247
1665
|
options: rows.map((row2) => ({
|
|
@@ -1314,6 +1732,8 @@ function addTreeAttention(ctx) {
|
|
|
1314
1732
|
const from = typeof metadata.from === "string" ? metadata.from : "";
|
|
1315
1733
|
if (!group && !status)
|
|
1316
1734
|
return;
|
|
1735
|
+
if (!group && ctx.data.session.get(from)?.parentID !== undefined)
|
|
1736
|
+
return;
|
|
1317
1737
|
notified.add(inboxID);
|
|
1318
1738
|
if (notified.size > NOTIFIED_MAX)
|
|
1319
1739
|
notified.delete(notified.values().next().value);
|
|
@@ -1345,7 +1765,7 @@ function addTreeAttention(ctx) {
|
|
|
1345
1765
|
var tui_default = Plugin.define({
|
|
1346
1766
|
id: "courier.tui",
|
|
1347
1767
|
setup: (ctx) => {
|
|
1348
|
-
const inbox = addAsksInbox(ctx);
|
|
1768
|
+
const inbox = addAsksInbox(ctx, CHANGES);
|
|
1349
1769
|
const cleanups = [inbox.cleanup, addTreeStatus(ctx, (sessionID) => void inbox.open(sessionID)), addTreePanel(ctx), addTreeAttention(ctx)];
|
|
1350
1770
|
return () => {
|
|
1351
1771
|
for (const cleanup of cleanups.toReversed())
|