@paradow/ledge 0.17.0 → 0.18.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/dist/cli/{attach-EYIX7FZN.js → attach-I6TTCGNV.js} +12 -7
- package/dist/cli/{bridge-6UHCD76N.js → bridge-APNFBPNE.js} +2 -2
- package/dist/cli/chunk-3EIRKAX7.js +165 -0
- package/dist/cli/chunk-3HCID6NW.js +145 -0
- package/dist/cli/{chunk-JQZHXVIZ.js → chunk-43D6NMYH.js} +1 -1
- package/dist/cli/{daemon-QEJLAIBT.js → chunk-7IQ6FV2F.js} +6044 -7227
- package/dist/cli/{chunk-ELLXOV4Q.js → chunk-7ZAA75HG.js} +1 -1
- package/dist/cli/{chunk-5ZCHFGCV.js → chunk-BVBX222F.js} +9 -7
- package/dist/cli/{chunk-OVJL2QTN.js → chunk-FQZ3IX6E.js} +2 -2
- package/dist/cli/chunk-G6K2NSQS.js +6 -0
- package/dist/cli/{chunk-VVK4I6B5.js → chunk-GXFU4XAX.js} +1 -1
- package/dist/cli/chunk-HP3G2TYJ.js +1772 -0
- package/dist/cli/{chunk-NC5YAEJB.js → chunk-KKNE7BFZ.js} +55 -3
- package/dist/cli/{chunk-36KJUV62.js → chunk-QQZALYNI.js} +38 -12
- package/dist/cli/{chunk-VE4LGNXX.js → chunk-TRTS24PF.js} +23 -5
- package/dist/cli/{chunk-Q7YNLP66.js → chunk-WCS24ICX.js} +5 -3
- package/dist/cli/{chunk-LAUNHDTX.js → chunk-WJ6YMZTT.js} +2 -2
- package/dist/cli/chunk-Y5DJ5INP.js +60 -0
- package/dist/cli/{chunk-AQ25HFBP.js → chunk-ZUYOA55W.js} +5 -4
- package/dist/cli/daemon-SKC7VKYQ.js +2240 -0
- package/dist/cli/handover-UXISKYPS.js +16 -0
- package/dist/cli/{hook-AE5HC6AS.js → hook-5UAARBWU.js} +86 -25
- package/dist/cli/index.js +29 -15
- package/dist/cli/launch-T5FJ42XS.js +34 -0
- package/dist/cli/{send-2OZ3YZRV.js → send-NLVR6PL2.js} +5 -5
- package/dist/cli/{setup-2VM4GCDJ.js → setup-EEIXRAMU.js} +4 -4
- package/dist/cli/{status-3VJAO6UL.js → status-2OCYHOWZ.js} +3 -3
- package/dist/cli/{task-PMKNYP6M.js → task-AOOWHPMN.js} +5 -5
- package/dist/cli/team-TVNSVYWW.js +101 -0
- package/dist/cli/{tui-DWA2EDC2.js → tui-MQLMUUJB.js} +866 -113
- package/dist/cli/{uninstall-AKUWE33D.js → uninstall-UUN7LIIT.js} +4 -4
- package/dist/cli/{version-WOGCIZY3.js → version-AEN2CRT2.js} +1 -1
- package/dist/cli/who-52MENZTT.js +11 -0
- package/package.json +1 -1
- package/dist/cli/chunk-QD27W6W5.js +0 -1474
- package/dist/cli/chunk-V2H7UD2N.js +0 -6
- package/dist/cli/launch-B4G5OFBV.js +0 -27
- package/dist/cli/who-37TNDYVA.js +0 -11
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-QQZALYNI.js";
|
|
4
|
+
import "./chunk-7IQ6FV2F.js";
|
|
5
|
+
import "./chunk-3EIRKAX7.js";
|
|
6
|
+
import "./chunk-OLLQBK6N.js";
|
|
7
|
+
import "./chunk-KKNE7BFZ.js";
|
|
8
|
+
import "./chunk-3HCID6NW.js";
|
|
9
|
+
import "./chunk-TRTS24PF.js";
|
|
10
|
+
import "./chunk-GXFU4XAX.js";
|
|
6
11
|
import {
|
|
12
|
+
CONTEXT_NEAR,
|
|
13
|
+
CONTEXT_NEAR_MAX,
|
|
14
|
+
CONTEXT_NEAR_MIN,
|
|
7
15
|
ChatTurnSchema,
|
|
16
|
+
ContextPrefsSchema,
|
|
8
17
|
IslandPrefsSchema,
|
|
18
|
+
LEDGE_GIVER,
|
|
9
19
|
MachinesSchema,
|
|
10
20
|
NOTE_MAX,
|
|
11
21
|
NOTE_NOT_TYPED,
|
|
@@ -13,6 +23,7 @@ import {
|
|
|
13
23
|
PROTOCOL_PORT,
|
|
14
24
|
ProjectsSchema,
|
|
15
25
|
backendOf,
|
|
26
|
+
contextNear,
|
|
16
27
|
isArchived,
|
|
17
28
|
offers,
|
|
18
29
|
parseServerMessage,
|
|
@@ -20,13 +31,14 @@ import {
|
|
|
20
31
|
pickName,
|
|
21
32
|
projectOf,
|
|
22
33
|
proposalRows,
|
|
23
|
-
readDaemonToken
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import "./chunk-
|
|
34
|
+
readDaemonToken,
|
|
35
|
+
taskClosed
|
|
36
|
+
} from "./chunk-HP3G2TYJ.js";
|
|
37
|
+
import "./chunk-WJ6YMZTT.js";
|
|
38
|
+
import "./chunk-43D6NMYH.js";
|
|
27
39
|
import {
|
|
28
40
|
ledgeDir
|
|
29
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-7ZAA75HG.js";
|
|
30
42
|
|
|
31
43
|
// src/tui/index.tsx
|
|
32
44
|
import { render } from "ink";
|
|
@@ -226,7 +238,7 @@ var daemonStore = {
|
|
|
226
238
|
return;
|
|
227
239
|
}
|
|
228
240
|
if (m.type === "chat.step" || m.type === "chat.delta" || m.type === "chat.done" || m.type === "chat.error") {
|
|
229
|
-
const chat = state.chat.map((t) => t.qid !== m.qid ? t : m.type === "chat.step" ? { ...t, steps: [...t.steps, m.step] } : m.type === "chat.done" ? { ...t, answer: m.text, answeredAt: m.at, ...m.proposal ? { proposal: m.proposal } : {}, ...m.proposals ? { proposals: m.proposals } : {}, ...m.relay ? { relay: m.relay } : {}, ...m.closes ? { closes: m.closes } : {}, ...m.projectProposal ? { projectProposal: m.projectProposal } : {} } : m.type === "chat.error" ? { ...t, error: m.message } : t);
|
|
241
|
+
const chat = state.chat.map((t) => t.qid !== m.qid ? t : m.type === "chat.step" ? { ...t, steps: [...t.steps, m.step] } : m.type === "chat.done" ? { ...t, answer: m.text, answeredAt: m.at, ...m.proposal ? { proposal: m.proposal } : {}, ...m.proposals ? { proposals: m.proposals } : {}, ...m.relay ? { relay: m.relay } : {}, ...m.closes ? { closes: m.closes } : {}, ...m.projectProposal ? { projectProposal: m.projectProposal } : {}, ...m.team ? { team: m.team } : {}, ...m.tasks ? { tasks: m.tasks } : {} } : m.type === "chat.error" ? { ...t, error: m.message } : t);
|
|
230
242
|
const chatLive = m.type === "chat.delta" ? { qid: m.qid, text: (state.chatLive?.qid === m.qid ? state.chatLive.text : "") + m.text } : m.type === "chat.step" ? state.chatLive : null;
|
|
231
243
|
state = { ...state, chat, chatLive };
|
|
232
244
|
emit();
|
|
@@ -420,6 +432,18 @@ function useMachines(client) {
|
|
|
420
432
|
return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, client.fleet?.machines ?? emptySnapshot, emptySnapshot);
|
|
421
433
|
}
|
|
422
434
|
var nullSnapshot = () => null;
|
|
435
|
+
var noTasks = [];
|
|
436
|
+
var emptyTasks = () => noTasks;
|
|
437
|
+
function useTeams(client) {
|
|
438
|
+
return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, client.fleet?.teams ?? nullSnapshot, nullSnapshot);
|
|
439
|
+
}
|
|
440
|
+
var defaultNear = () => CONTEXT_NEAR;
|
|
441
|
+
function useContextNear(client) {
|
|
442
|
+
return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, () => client.fleet?.contextNear() ?? CONTEXT_NEAR, defaultNear);
|
|
443
|
+
}
|
|
444
|
+
function useTasks(client) {
|
|
445
|
+
return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, client.fleet?.tasks ?? emptyTasks, emptyTasks);
|
|
446
|
+
}
|
|
423
447
|
function useProjects(client) {
|
|
424
448
|
return useSyncExternalStore3(client.fleet?.subscribe ?? noopSubscribe, client.fleet?.projects ?? nullSnapshot, nullSnapshot);
|
|
425
449
|
}
|
|
@@ -493,8 +517,31 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
493
517
|
let source = "local";
|
|
494
518
|
let projects = null;
|
|
495
519
|
let island;
|
|
520
|
+
let contextNear2;
|
|
496
521
|
let mobile;
|
|
497
522
|
let mobileError;
|
|
523
|
+
let teams = null;
|
|
524
|
+
let tasks = /* @__PURE__ */ new Map();
|
|
525
|
+
let taskList = [];
|
|
526
|
+
const taskLists = /* @__PURE__ */ new Set();
|
|
527
|
+
let listSeq = 0;
|
|
528
|
+
const local = (id) => id === LEDGE_GIVER ? id : qualify("local", id);
|
|
529
|
+
const teamOut = (t) => ({ ...t, seats: t.seats.map((s) => ({ ...s, sessionId: s.sessionId === null ? null : qualify("local", s.sessionId) })) });
|
|
530
|
+
const taskOut = (t) => ({ ...t, giver: local(t.giver), owner: local(t.owner) });
|
|
531
|
+
const putTasks = (list) => {
|
|
532
|
+
for (const t of list) tasks.set(t.id, taskOut(t));
|
|
533
|
+
taskList = [...tasks.values()].sort((a, b) => a.at.localeCompare(b.at));
|
|
534
|
+
listeners.forEach((fn) => fn());
|
|
535
|
+
};
|
|
536
|
+
const askTasks = (p) => {
|
|
537
|
+
const ask = (extra) => {
|
|
538
|
+
const rid = `fleet-tasks-${++listSeq}`;
|
|
539
|
+
taskLists.add(rid);
|
|
540
|
+
p.client.send({ type: "task.cmd", rid, by: LEDGE_GIVER, op: "list", all: true, ...extra });
|
|
541
|
+
};
|
|
542
|
+
ask({});
|
|
543
|
+
for (const t of teams ?? []) if (t.state !== "dissolved") ask({ team: t.id });
|
|
544
|
+
};
|
|
498
545
|
let routing = null, resetAt = "", lastStatus = "connecting";
|
|
499
546
|
const emit2 = (m) => messages.forEach((fn) => fn(m));
|
|
500
547
|
const day = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -528,7 +575,16 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
528
575
|
const available = (p) => !!p && p.status === "open" && p.fresh && !!p.snapshot;
|
|
529
576
|
const status = () => [...peers.values()].some(available) ? "open" : [...peers.values()].some((p) => p.status === "connecting") ? "connecting" : "unreachable";
|
|
530
577
|
const stale = (p) => !!opts.keepStale && !!p && !available(p) && !!p.snapshot;
|
|
531
|
-
const session = (p, s) => ({
|
|
578
|
+
const session = (p, s) => ({
|
|
579
|
+
...s,
|
|
580
|
+
id: qualify(p.config.id, s.id),
|
|
581
|
+
machineId: p.config.id,
|
|
582
|
+
machineName: p.config.label,
|
|
583
|
+
...stale(p) ? { offline: true } : {},
|
|
584
|
+
// A relief and the one it relieves are on the same machine (context handover spec §3.3).
|
|
585
|
+
...s.handover?.to ? { handover: { ...s.handover, to: qualify(p.config.id, s.handover.to) } } : {},
|
|
586
|
+
...s.relieves ? { relieves: { ...s.relieves, id: qualify(p.config.id, s.relieves.id) } } : {}
|
|
587
|
+
});
|
|
532
588
|
const stateSessions = (p) => (stale(p) ? p.snapshot.sessions.map((s) => ({ ...s, offline: true })) : p.snapshot?.sessions ?? []).filter((s) => !isArchived(s));
|
|
533
589
|
function refresh() {
|
|
534
590
|
state2 = configs.map((c) => {
|
|
@@ -633,12 +689,40 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
633
689
|
listeners.forEach((fn) => fn());
|
|
634
690
|
return;
|
|
635
691
|
}
|
|
692
|
+
if (m.type === "context.prefs") {
|
|
693
|
+
if (machine !== "local") return;
|
|
694
|
+
contextNear2 = m.prefs.near;
|
|
695
|
+
listeners.forEach((fn) => fn());
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
636
698
|
if (m.type === "island") {
|
|
637
699
|
if (machine !== "local") return;
|
|
638
700
|
island = m.prefs;
|
|
639
701
|
listeners.forEach((fn) => fn());
|
|
640
702
|
return;
|
|
641
703
|
}
|
|
704
|
+
if (m.type === "teams" || m.type === "team.updated") {
|
|
705
|
+
if (machine !== "local") return;
|
|
706
|
+
const known2 = new Set((teams ?? []).map((t) => t.id));
|
|
707
|
+
teams = m.type === "teams" ? m.teams.map(teamOut) : [...(teams ?? []).filter((t) => t.id !== m.team.id), teamOut(m.team)].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
708
|
+
listeners.forEach((fn) => fn());
|
|
709
|
+
if (m.type === "team.updated" && !known2.has(m.team.id) && m.team.state !== "dissolved") {
|
|
710
|
+
const rid = `fleet-tasks-${++listSeq}`;
|
|
711
|
+
taskLists.add(rid);
|
|
712
|
+
p.client.send({ type: "task.cmd", rid, by: LEDGE_GIVER, op: "list", all: true, team: m.team.id });
|
|
713
|
+
}
|
|
714
|
+
if (m.type === "teams" && p.snapshot?.daemon?.teams) askTasks(p);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (m.type === "task.updated") {
|
|
718
|
+
if (machine === "local") putTasks([m.task]);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (m.type === "task.result" && taskLists.has(m.rid)) {
|
|
722
|
+
taskLists.delete(m.rid);
|
|
723
|
+
if (machine === "local" && m.tasks) putTasks(m.tasks);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
642
726
|
if (m.type === "mobile") {
|
|
643
727
|
if (machine !== "local") return;
|
|
644
728
|
mobile = m.state;
|
|
@@ -789,7 +873,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
789
873
|
const qid = globalQid(machine, m.qid), old = turns2.get(qid);
|
|
790
874
|
if (!old) return;
|
|
791
875
|
if (m.type === "chat.done") {
|
|
792
|
-
const raw = { ...old, qid: m.qid, answer: m.text, answeredAt: m.at, proposal: m.proposal, proposals: m.proposals, relay: m.relay, closes: m.closes, projectProposal: m.projectProposal };
|
|
876
|
+
const raw = { ...old, qid: m.qid, answer: m.text, answeredAt: m.at, proposal: m.proposal, proposals: m.proposals, relay: m.relay, closes: m.closes, projectProposal: m.projectProposal, team: m.team, tasks: m.tasks };
|
|
793
877
|
const t = mappedTurn(p, { ...raw, steps: [] });
|
|
794
878
|
turns2.set(qid, { ...t, steps: old.steps });
|
|
795
879
|
history();
|
|
@@ -835,6 +919,10 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
835
919
|
emit2({ ...m, ...m.id ? { id: qualify(machine, m.id) } : {}, message: configs.length > 1 ? `${p.config.label}: ${m.message}` : m.message });
|
|
836
920
|
return;
|
|
837
921
|
}
|
|
922
|
+
if (m.type === "session.handover.result") {
|
|
923
|
+
emit2(m);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
838
926
|
if (machine === "local") emit2(m);
|
|
839
927
|
}
|
|
840
928
|
function add(c) {
|
|
@@ -1038,6 +1126,25 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1038
1126
|
}
|
|
1039
1127
|
}
|
|
1040
1128
|
if (msg.type === "update.daemon") machine = "local";
|
|
1129
|
+
if (msg.type === "roles.list" || msg.type === "team.launch") {
|
|
1130
|
+
if (!("qid" in msg && msg.qid)) machine = msg.machineId ?? "local";
|
|
1131
|
+
const { machineId: _m, ...rest } = outgoing;
|
|
1132
|
+
outgoing = rest;
|
|
1133
|
+
}
|
|
1134
|
+
if (msg.type === "team.join" || msg.type === "team.leave" || msg.type === "team.resume" || msg.type === "team.dissolve" || msg.type === "team.save" || msg.type === "task.cmd" && !msg.qid) machine = "local";
|
|
1135
|
+
if (outgoing.type === "team.join" || outgoing.type === "team.leave") {
|
|
1136
|
+
const o = splitId(outgoing.session);
|
|
1137
|
+
if (o) {
|
|
1138
|
+
machine = o.machine;
|
|
1139
|
+
outgoing = { ...outgoing, session: o.id };
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (outgoing.type === "task.cmd") {
|
|
1143
|
+
const o = outgoing.to ? splitId(outgoing.to) : null;
|
|
1144
|
+
if (o && !outgoing.to.startsWith("@")) outgoing = { ...outgoing, to: o.id };
|
|
1145
|
+
const b = splitId(outgoing.by);
|
|
1146
|
+
if (b && outgoing.by !== LEDGE_GIVER) outgoing = { ...outgoing, by: b.id };
|
|
1147
|
+
}
|
|
1041
1148
|
if (msg.type === "snapshot.request") {
|
|
1042
1149
|
publish();
|
|
1043
1150
|
history();
|
|
@@ -1082,9 +1189,9 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1082
1189
|
configure(raw) {
|
|
1083
1190
|
const next2 = parseMachines(raw);
|
|
1084
1191
|
if (source === "daemon") {
|
|
1085
|
-
const
|
|
1086
|
-
if (
|
|
1087
|
-
|
|
1192
|
+
const local2 = peers.get("local").client;
|
|
1193
|
+
if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
|
|
1194
|
+
local2.send({ type: "machines.set", machines: toWire(next2) });
|
|
1088
1195
|
return;
|
|
1089
1196
|
}
|
|
1090
1197
|
localStorage.setItem(MACHINES_KEY, JSON.stringify(next2));
|
|
@@ -1133,30 +1240,40 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1133
1240
|
projects: () => projects,
|
|
1134
1241
|
setProjects(next2) {
|
|
1135
1242
|
const valid = ProjectsSchema.parse(next2);
|
|
1136
|
-
const
|
|
1137
|
-
if (
|
|
1243
|
+
const local2 = peers.get("local").client;
|
|
1244
|
+
if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
|
|
1138
1245
|
if (projects === null) throw Object.assign(new Error("Update the local Ledge daemon: it does not know projects"), { name: "DaemonOutdated" });
|
|
1139
|
-
|
|
1246
|
+
local2.send({ type: "projects.set", projects: valid });
|
|
1140
1247
|
},
|
|
1141
1248
|
island: () => island,
|
|
1142
1249
|
setIsland(next2) {
|
|
1143
1250
|
const valid = IslandPrefsSchema.parse(next2);
|
|
1144
|
-
const
|
|
1145
|
-
if (
|
|
1251
|
+
const local2 = peers.get("local").client;
|
|
1252
|
+
if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
|
|
1146
1253
|
if (island === void 0) throw Object.assign(new Error("Update the local Ledge daemon: it does not know the island settings"), { name: "DaemonOutdated" });
|
|
1147
|
-
|
|
1254
|
+
local2.send({ type: "island.set", prefs: valid });
|
|
1148
1255
|
},
|
|
1149
1256
|
mobile: () => mobile,
|
|
1150
1257
|
mobileError: () => mobileError,
|
|
1258
|
+
teams: () => teams,
|
|
1259
|
+
tasks: () => taskList,
|
|
1260
|
+
contextNear: () => contextNear2,
|
|
1261
|
+
setContextNear(near) {
|
|
1262
|
+
const valid = ContextPrefsSchema.parse({ near });
|
|
1263
|
+
const local2 = peers.get("local").client;
|
|
1264
|
+
if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
|
|
1265
|
+
if (contextNear2 === void 0) throw Object.assign(new Error("Update the local Ledge daemon: it does not know the context threshold"), { name: "DaemonOutdated" });
|
|
1266
|
+
local2.send({ type: "context.set", prefs: valid });
|
|
1267
|
+
},
|
|
1151
1268
|
sendMobile(msg) {
|
|
1152
|
-
const
|
|
1153
|
-
if (
|
|
1269
|
+
const local2 = peers.get("local").client;
|
|
1270
|
+
if (local2.status() !== "open") throw Object.assign(new Error("The local daemon is not connected"), { name: "DaemonOffline" });
|
|
1154
1271
|
if (mobile === void 0) throw Object.assign(new Error("Update the local Ledge daemon: it does not know mobile access"), { name: "DaemonOutdated" });
|
|
1155
1272
|
if (mobileError) {
|
|
1156
1273
|
mobileError = void 0;
|
|
1157
1274
|
listeners.forEach((fn) => fn());
|
|
1158
1275
|
}
|
|
1159
|
-
|
|
1276
|
+
local2.send(msg);
|
|
1160
1277
|
}
|
|
1161
1278
|
}
|
|
1162
1279
|
};
|
|
@@ -1168,7 +1285,7 @@ function createFleetClient(config, makeClient = createDaemonClient, opts = {}) {
|
|
|
1168
1285
|
var COLORS = Object.keys(PROJECT_COLORS);
|
|
1169
1286
|
|
|
1170
1287
|
// src/tui/App.tsx
|
|
1171
|
-
import { useCallback, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef8, useState as
|
|
1288
|
+
import { useCallback, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef8, useState as useState9 } from "react";
|
|
1172
1289
|
import { useApp, useInput as useInput5, useStdout } from "ink";
|
|
1173
1290
|
|
|
1174
1291
|
// src/tui/actions.ts
|
|
@@ -1484,7 +1601,13 @@ var fr = {
|
|
|
1484
1601
|
change: "changer",
|
|
1485
1602
|
scroll: "d\xE9filer",
|
|
1486
1603
|
space: "espace",
|
|
1487
|
-
redraw: "autre nom"
|
|
1604
|
+
redraw: "autre nom",
|
|
1605
|
+
save: "enregistrer l\u2019\xE9quipe",
|
|
1606
|
+
tasks: "t\xE2ches de l\u2019\xE9quipe",
|
|
1607
|
+
join: "rejoindre une \xE9quipe",
|
|
1608
|
+
resume: "reprendre l\u2019\xE9quipe",
|
|
1609
|
+
teamMode: "session ou \xE9quipe",
|
|
1610
|
+
handover: "passer le relais"
|
|
1488
1611
|
},
|
|
1489
1612
|
overview: {
|
|
1490
1613
|
yours: (n) => `\xC0 toi \xB7 ${n}`,
|
|
@@ -1506,7 +1629,23 @@ var fr = {
|
|
|
1506
1629
|
allow: "Autoriser",
|
|
1507
1630
|
deny: "Refuser",
|
|
1508
1631
|
always: "Toujours",
|
|
1509
|
-
denyWithNote: "Refuser en expliquant"
|
|
1632
|
+
denyWithNote: "Refuser en expliquant",
|
|
1633
|
+
common: "table commune",
|
|
1634
|
+
teamLine: (done, given) => `\xE9quipe \xB7 ${done}/${given}`,
|
|
1635
|
+
resting: "au repos \xB7 R pour reprendre",
|
|
1636
|
+
tasks: (team) => `T\xE2ches \xB7 ${team}`,
|
|
1637
|
+
noTask: "Aucune t\xE2che.",
|
|
1638
|
+
ledge: "Ledge",
|
|
1639
|
+
near: (name, pct) => `${name} \xB7 ctx ${pct} % \xB7 H rel\xE8ve`,
|
|
1640
|
+
reliefFailed: (name, why) => `La rel\xE8ve de ${name} a \xE9chou\xE9 : ${why}`,
|
|
1641
|
+
context: (pct) => `ctx ${pct} %`,
|
|
1642
|
+
relief: { briefing: "rel\xE8ve : bilan demand\xE9", launching: "rel\xE8ve : lancement\u2026", done: "relev\xE9e", failed: "rel\xE8ve \xE9chou\xE9e" },
|
|
1643
|
+
relievedBy: (name) => `relev\xE9e par ${name}`,
|
|
1644
|
+
relieves: (name) => `rel\xE8ve de ${name}`,
|
|
1645
|
+
ledgeTask: (owner, title2, state2) => `${owner} a ${state2 === "done" ? "fini" : state2 === "refused" ? "refus\xE9" : "abandonn\xE9"} \xAB ${title2} \xBB \xB7 t\xE2che de Ledge`,
|
|
1646
|
+
joinWhich: (name) => `${name} rejoint quelle \xE9quipe ? (\u2191\u2193 \u21B5, esc)`,
|
|
1647
|
+
noTeamHere: "Aucune \xE9quipe dans ce projet.",
|
|
1648
|
+
notInTeam: "Cette session n\u2019est dans aucune \xE9quipe."
|
|
1510
1649
|
},
|
|
1511
1650
|
session: {
|
|
1512
1651
|
you: "Vous",
|
|
@@ -1554,7 +1693,23 @@ var fr = {
|
|
|
1554
1693
|
sentAt: "envoy\xE9e",
|
|
1555
1694
|
closed: "ferm\xE9e",
|
|
1556
1695
|
offline: "Le daemon local est hors ligne : la question attendra.",
|
|
1557
|
-
steps: { sessions: "les sessions", pane: (n) => `le terminal de ${n}`, reply: (n) => `la r\xE9ponse de ${n}`, git: (n) => `l\u2019\xE9tat git de ${n}`, repo: (p) => p }
|
|
1696
|
+
steps: { sessions: "les sessions", pane: (n) => `le terminal de ${n}`, reply: (n) => `la r\xE9ponse de ${n}`, git: (n) => `l\u2019\xE9tat git de ${n}`, repo: (p) => p },
|
|
1697
|
+
operator: (s, m) => `Ledge \xB7 Op\xE9rateur \xB7 voit ${s} ${plural(s, "session", "sessions")}, ${m} ${plural(m, "machine", "machines")}`,
|
|
1698
|
+
teamCard: (name) => `\xC9quipe \xB7 ${name}`,
|
|
1699
|
+
taskCard: "T\xE2ches \xE0 confier",
|
|
1700
|
+
launchTeam: "Lancer l\u2019\xE9quipe",
|
|
1701
|
+
save: "Enregistrer",
|
|
1702
|
+
saved: "enregistr\xE9e",
|
|
1703
|
+
seats: "Si\xE8ges",
|
|
1704
|
+
table: "Table",
|
|
1705
|
+
newTable: (project) => `nouvelle table dans la pi\xE8ce ${project}`,
|
|
1706
|
+
autonomy: { ask: "demande", edits: "modifie sans demander", auto: "autonome" },
|
|
1707
|
+
autoWarning: (roles) => `${roles} travaillera sans demander.`,
|
|
1708
|
+
teamLaunched: (at) => `\u2713 \xC9quipe lanc\xE9e \xE0 ${at}`,
|
|
1709
|
+
give: "Confier",
|
|
1710
|
+
waitTeam: "active une fois l\u2019\xE9quipe lanc\xE9e",
|
|
1711
|
+
given: (owner, id, state2) => `\u2713 Confi\xE9e \xE0 ${owner} \xB7 ${id} \xB7 ${state2}`,
|
|
1712
|
+
taskState: { open: "\xE0 prendre", taken: "prise", done: "finie", refused: "refus\xE9e", dropped: "abandonn\xE9e" }
|
|
1558
1713
|
},
|
|
1559
1714
|
launch: {
|
|
1560
1715
|
title: "Nouvelle session",
|
|
@@ -1579,9 +1734,23 @@ var fr = {
|
|
|
1579
1734
|
thenAttach: "puis a pour s\u2019attacher \xE0 son terminal",
|
|
1580
1735
|
missing: { prompt: "\xC9crivez une instruction.", machine: "Aucune machine en ligne.", repos: "Choisissez un d\xE9p\xF4t ou un dossier." },
|
|
1581
1736
|
launching: "Lancement\u2026",
|
|
1582
|
-
launched: (name) => `\u2713 ${name} est lanc\xE9e
|
|
1737
|
+
launched: (name) => `\u2713 ${name} est lanc\xE9e`,
|
|
1738
|
+
teamTitle: "Nouvelle \xE9quipe",
|
|
1739
|
+
mode: "Mode",
|
|
1740
|
+
session: "session",
|
|
1741
|
+
team: "\xE9quipe",
|
|
1742
|
+
template: "Mod\xE8le",
|
|
1743
|
+
custom: "si\xE8ges choisis un par un",
|
|
1744
|
+
seats: "Si\xE8ges",
|
|
1745
|
+
teamName: "Nom",
|
|
1746
|
+
noRole: "aucun r\xF4le : \xE9cris ~/.ledge/roles/<id>.md",
|
|
1747
|
+
loadingRoles: "lecture des r\xF4les\u2026",
|
|
1748
|
+
teamPreview: (team, project) => `L\u2019\xE9quipe ${team} s\u2019installera \xE0 une nouvelle table de la pi\xE8ce ${project}.`,
|
|
1749
|
+
teamLaunched: (name) => `\u2713 \xC9quipe ${name} lanc\xE9e`,
|
|
1750
|
+
teamFailed: (why) => `\xC9quipe non lanc\xE9e : ${why}`,
|
|
1751
|
+
noSeat: "Choisissez au moins un si\xE8ge."
|
|
1583
1752
|
},
|
|
1584
|
-
settings: { title: "R\xE9glages", language: "Langue", saved: "R\xE9glage enregistr\xE9.", saveFailed: (m) => `R\xE9glage appliqu\xE9, mais non enregistr\xE9 : ${m}` },
|
|
1753
|
+
settings: { title: "R\xE9glages", language: "Langue", context: "Contexte max", contextHint: "au-del\xE0 : passer le relais", contextUnknown: "mettez \xE0 jour le daemon", contextSaved: (pct) => `Contexte max : ${pct} %`, saved: "R\xE9glage enregistr\xE9.", saveFailed: (m) => `R\xE9glage appliqu\xE9, mais non enregistr\xE9 : ${m}` },
|
|
1585
1754
|
help: {
|
|
1586
1755
|
title: "Aide",
|
|
1587
1756
|
global: "Partout",
|
|
@@ -1593,6 +1762,8 @@ var fr = {
|
|
|
1593
1762
|
screens: "changer d\u2019\xE9cran"
|
|
1594
1763
|
},
|
|
1595
1764
|
toast: {
|
|
1765
|
+
handover: (n) => `${n} \xE9crit son bilan : une nouvelle session prendra sa place`,
|
|
1766
|
+
handoverRefused: (why) => `Rel\xE8ve refus\xE9e : ${why}`,
|
|
1596
1767
|
accept: (n) => `\u2713 ${n} : autoris\xE9`,
|
|
1597
1768
|
deny: (n) => `\u2713 ${n} : refus\xE9`,
|
|
1598
1769
|
always: (n) => `\u2713 ${n} : toujours autoris\xE9`,
|
|
@@ -1609,7 +1780,10 @@ var fr = {
|
|
|
1609
1780
|
noTerminalOmp: (n) => `${n} est pilot\xE9 par Ledge via le protocole d\u2019OMP : pas de terminal \xE0 attacher.`,
|
|
1610
1781
|
noTerminal: (n) => `Pas de terminal pour ${n}.`,
|
|
1611
1782
|
attachFailed: (code) => `tmux s\u2019est arr\xEAt\xE9 avec le code ${code}.`,
|
|
1612
|
-
modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours."
|
|
1783
|
+
modelSwitch: "Ce daemon ne change pas le mod\xE8le d\u2019une session en cours.",
|
|
1784
|
+
joined: (n, team) => `${n} rejoint ${team}.`,
|
|
1785
|
+
resumed: (team) => `Reprise de l\u2019\xE9quipe ${team} demand\xE9e.`,
|
|
1786
|
+
saved: (path) => `\xC9quipe enregistr\xE9e : ${path}`
|
|
1613
1787
|
},
|
|
1614
1788
|
decisions: {
|
|
1615
1789
|
failed: (name, message) => `D\xE9cision non transmise \xE0 ${name} : ${message}`,
|
|
@@ -1671,7 +1845,13 @@ var en = {
|
|
|
1671
1845
|
change: "change",
|
|
1672
1846
|
scroll: "scroll",
|
|
1673
1847
|
space: "space",
|
|
1674
|
-
redraw: "another name"
|
|
1848
|
+
redraw: "another name",
|
|
1849
|
+
save: "save the team",
|
|
1850
|
+
tasks: "team tasks",
|
|
1851
|
+
join: "join a team",
|
|
1852
|
+
resume: "resume the team",
|
|
1853
|
+
teamMode: "session or team",
|
|
1854
|
+
handover: "hand over"
|
|
1675
1855
|
},
|
|
1676
1856
|
overview: {
|
|
1677
1857
|
yours: (n) => `Yours \xB7 ${n}`,
|
|
@@ -1693,7 +1873,23 @@ var en = {
|
|
|
1693
1873
|
allow: "Allow",
|
|
1694
1874
|
deny: "Deny",
|
|
1695
1875
|
always: "Always",
|
|
1696
|
-
denyWithNote: "Deny with a note"
|
|
1876
|
+
denyWithNote: "Deny with a note",
|
|
1877
|
+
common: "common table",
|
|
1878
|
+
teamLine: (done, given) => `team \xB7 ${done}/${given}`,
|
|
1879
|
+
resting: "resting \xB7 R to resume",
|
|
1880
|
+
tasks: (team) => `Tasks \xB7 ${team}`,
|
|
1881
|
+
noTask: "No task.",
|
|
1882
|
+
ledge: "Ledge",
|
|
1883
|
+
near: (name, pct) => `${name} \xB7 ctx ${pct} % \xB7 H hand over`,
|
|
1884
|
+
reliefFailed: (name, why) => `The relief of ${name} failed: ${why}`,
|
|
1885
|
+
context: (pct) => `ctx ${pct} %`,
|
|
1886
|
+
relief: { briefing: "relief: brief asked", launching: "relief: launching\u2026", done: "relieved", failed: "relief failed" },
|
|
1887
|
+
relievedBy: (name) => `relieved by ${name}`,
|
|
1888
|
+
relieves: (name) => `relieves ${name}`,
|
|
1889
|
+
ledgeTask: (owner, title2, state2) => `${owner} ${state2 === "done" ? "finished" : state2 === "refused" ? "refused" : "dropped"} \u201C${title2}\u201D \xB7 Ledge\u2019s task`,
|
|
1890
|
+
joinWhich: (name) => `${name} joins which team? (\u2191\u2193 \u21B5, esc)`,
|
|
1891
|
+
noTeamHere: "No team in this project.",
|
|
1892
|
+
notInTeam: "This session is in no team."
|
|
1697
1893
|
},
|
|
1698
1894
|
session: {
|
|
1699
1895
|
you: "You",
|
|
@@ -1741,7 +1937,23 @@ var en = {
|
|
|
1741
1937
|
sentAt: "sent",
|
|
1742
1938
|
closed: "closed",
|
|
1743
1939
|
offline: "The local daemon is offline: the question will wait.",
|
|
1744
|
-
steps: { sessions: "the sessions", pane: (n) => `${n}'s terminal`, reply: (n) => `${n}'s reply`, git: (n) => `${n}'s git state`, repo: (p) => p }
|
|
1940
|
+
steps: { sessions: "the sessions", pane: (n) => `${n}'s terminal`, reply: (n) => `${n}'s reply`, git: (n) => `${n}'s git state`, repo: (p) => p },
|
|
1941
|
+
operator: (s, m) => `Ledge \xB7 Operator \xB7 sees ${s} ${plural2(s, "session", "sessions")}, ${m} ${plural2(m, "machine", "machines")}`,
|
|
1942
|
+
teamCard: (name) => `Team \xB7 ${name}`,
|
|
1943
|
+
taskCard: "Tasks to give",
|
|
1944
|
+
launchTeam: "Launch the team",
|
|
1945
|
+
save: "Save",
|
|
1946
|
+
saved: "saved",
|
|
1947
|
+
seats: "Seats",
|
|
1948
|
+
table: "Table",
|
|
1949
|
+
newTable: (project) => `new table in the ${project} room`,
|
|
1950
|
+
autonomy: { ask: "asks", edits: "edits without asking", auto: "autonomous" },
|
|
1951
|
+
autoWarning: (roles) => `${roles} will work without asking.`,
|
|
1952
|
+
teamLaunched: (at) => `\u2713 Team launched at ${at}`,
|
|
1953
|
+
give: "Give",
|
|
1954
|
+
waitTeam: "active once the team is launched",
|
|
1955
|
+
given: (owner, id, state2) => `\u2713 Given to ${owner} \xB7 ${id} \xB7 ${state2}`,
|
|
1956
|
+
taskState: { open: "to take", taken: "taken", done: "done", refused: "refused", dropped: "dropped" }
|
|
1745
1957
|
},
|
|
1746
1958
|
launch: {
|
|
1747
1959
|
title: "New session",
|
|
@@ -1766,11 +1978,27 @@ var en = {
|
|
|
1766
1978
|
thenAttach: "then a to attach to its terminal",
|
|
1767
1979
|
missing: { prompt: "Write an instruction.", machine: "No machine online.", repos: "Pick a repository or a folder." },
|
|
1768
1980
|
launching: "Launching\u2026",
|
|
1769
|
-
launched: (name) => `\u2713 ${name} is started
|
|
1981
|
+
launched: (name) => `\u2713 ${name} is started`,
|
|
1982
|
+
teamTitle: "New team",
|
|
1983
|
+
mode: "Mode",
|
|
1984
|
+
session: "session",
|
|
1985
|
+
team: "team",
|
|
1986
|
+
template: "Template",
|
|
1987
|
+
custom: "seats chosen one by one",
|
|
1988
|
+
seats: "Seats",
|
|
1989
|
+
teamName: "Name",
|
|
1990
|
+
noRole: "no role: write ~/.ledge/roles/<id>.md",
|
|
1991
|
+
loadingRoles: "reading the roles\u2026",
|
|
1992
|
+
teamPreview: (team, project) => `Team ${team} will sit at a new table in the ${project} room.`,
|
|
1993
|
+
teamLaunched: (name) => `\u2713 Team ${name} launched`,
|
|
1994
|
+
teamFailed: (why) => `Team not launched: ${why}`,
|
|
1995
|
+
noSeat: "Choose at least one seat."
|
|
1770
1996
|
},
|
|
1771
|
-
settings: { title: "Settings", language: "Language", saved: "Setting saved.", saveFailed: (m) => `Setting applied, but not saved: ${m}` },
|
|
1997
|
+
settings: { title: "Settings", language: "Language", context: "Max context", contextHint: "past it: hand over", contextUnknown: "update the daemon", contextSaved: (pct) => `Max context: ${pct} %`, saved: "Setting saved.", saveFailed: (m) => `Setting applied, but not saved: ${m}` },
|
|
1772
1998
|
help: { title: "Help", global: "Everywhere", overview: "Overview", session: "Session", ask: "Ask Ledge", launch: "New session", settings: "Settings", screens: "switch screen" },
|
|
1773
1999
|
toast: {
|
|
2000
|
+
handover: (n) => `${n} is writing its brief: a fresh session will take its place`,
|
|
2001
|
+
handoverRefused: (why) => `Relief refused: ${why}`,
|
|
1774
2002
|
accept: (n) => `\u2713 ${n}: allowed`,
|
|
1775
2003
|
deny: (n) => `\u2713 ${n}: denied`,
|
|
1776
2004
|
always: (n) => `\u2713 ${n}: always allowed`,
|
|
@@ -1787,7 +2015,10 @@ var en = {
|
|
|
1787
2015
|
noTerminalOmp: (n) => `${n} is driven by Ledge over OMP's protocol: there is no terminal to attach to.`,
|
|
1788
2016
|
noTerminal: (n) => `No terminal for ${n}.`,
|
|
1789
2017
|
attachFailed: (code) => `tmux stopped with code ${code}.`,
|
|
1790
|
-
modelSwitch: "This daemon cannot change a running session\u2019s model."
|
|
2018
|
+
modelSwitch: "This daemon cannot change a running session\u2019s model.",
|
|
2019
|
+
joined: (n, team) => `${n} joins ${team}.`,
|
|
2020
|
+
resumed: (team) => `Asked to resume team ${team}.`,
|
|
2021
|
+
saved: (path) => `Team saved: ${path}`
|
|
1791
2022
|
},
|
|
1792
2023
|
decisions: {
|
|
1793
2024
|
failed: (name, message) => `Could not send the decision to ${name}: ${message}`,
|
|
@@ -1816,20 +2047,37 @@ function I18nProvider({ lang, children }) {
|
|
|
1816
2047
|
// src/tui/model/overview.ts
|
|
1817
2048
|
var listed = (sessions) => sessions.filter((s) => s.state !== "exited");
|
|
1818
2049
|
var machineOf = (s) => s.machineId ?? splitId(s.id)?.machine ?? "local";
|
|
1819
|
-
function overview(sessions, projects) {
|
|
2050
|
+
function overview(sessions, projects, teams = [], tasks = []) {
|
|
1820
2051
|
const live = listed(sessions);
|
|
1821
2052
|
const yours = live.filter(isPending).sort((a, b) => a.lastActivityAt.localeCompare(b.lastActivityAt) || a.id.localeCompare(b.id));
|
|
1822
2053
|
const nodes = /* @__PURE__ */ new Map();
|
|
1823
2054
|
const known2 = new Map((projects?.projects ?? []).map((p) => [p.id, p]));
|
|
1824
|
-
for (const p of projects?.projects ?? []) nodes.set(`p:${p.id}`, { key: `p:${p.id}`, label: p.name, color: p.color, branches: [], count: 0 });
|
|
2055
|
+
for (const p of projects?.projects ?? []) nodes.set(`p:${p.id}`, { key: `p:${p.id}`, label: p.name, color: p.color, branches: [], count: 0, teams: [] });
|
|
2056
|
+
const rooms = /* @__PURE__ */ new Map();
|
|
2057
|
+
for (const team of teams) {
|
|
2058
|
+
if (team.state === "dissolved") continue;
|
|
2059
|
+
const pid = projects ? projectOf({ machineId: team.machine, cwd: team.cwd }, projects) ?? team.project : team.project;
|
|
2060
|
+
const node = pid ? nodes.get(`p:${pid}`) : void 0;
|
|
2061
|
+
if (!node) continue;
|
|
2062
|
+
const mine = tasks.filter((x) => x.team === team.id && x.giver === LEDGE_GIVER);
|
|
2063
|
+
const row = { key: `${node.key}|team:${team.id}`, team, sessions: [], done: mine.filter((x) => x.state === "done").length, given: mine.length };
|
|
2064
|
+
node.teams.push(row);
|
|
2065
|
+
rooms.set(team.id, row);
|
|
2066
|
+
}
|
|
1825
2067
|
for (const s of live) {
|
|
1826
2068
|
const pid = projects ? projectOf({ machineId: machineOf(s), cwd: s.cwd }, projects) : null;
|
|
1827
2069
|
const key = pid && known2.has(pid) ? `p:${pid}` : `r:${machineOf(s)}:${s.repoPath ?? s.cwd}`;
|
|
1828
2070
|
let node = nodes.get(key);
|
|
1829
2071
|
if (!node) {
|
|
1830
|
-
node = { key, label: s.repo ?? s.project, branches: [], count: 0 };
|
|
2072
|
+
node = { key, label: s.repo ?? s.project, branches: [], count: 0, teams: [] };
|
|
1831
2073
|
nodes.set(key, node);
|
|
1832
2074
|
}
|
|
2075
|
+
const seat = s.team ? rooms.get(s.team) : void 0;
|
|
2076
|
+
if (seat && node.teams.includes(seat)) {
|
|
2077
|
+
seat.sessions.push(s);
|
|
2078
|
+
node.count += 1;
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
1833
2081
|
const branch = s.branch ?? "-";
|
|
1834
2082
|
let row = node.branches.find((b) => b.branch === branch);
|
|
1835
2083
|
if (!row) {
|
|
@@ -1839,8 +2087,8 @@ function overview(sessions, projects) {
|
|
|
1839
2087
|
row.sessions.push(s);
|
|
1840
2088
|
node.count += 1;
|
|
1841
2089
|
}
|
|
1842
|
-
const tree = [...nodes.values()].filter((n) => n.count > 0);
|
|
1843
|
-
const order = tree.flatMap((n) => n.branches.flatMap((b) => b.sessions.map((s) => s.id)));
|
|
2090
|
+
const tree = [...nodes.values()].filter((n) => n.count > 0 || n.teams.length > 0);
|
|
2091
|
+
const order = tree.flatMap((n) => [...n.branches.flatMap((b) => b.sessions.map((s) => s.id)), ...n.teams.flatMap((x) => x.sessions.map((s) => s.id))]);
|
|
1844
2092
|
return { yours, tree, order, machines: new Set(live.map(machineOf)).size };
|
|
1845
2093
|
}
|
|
1846
2094
|
var helperCount = (s) => liveHelpers(s).length;
|
|
@@ -1862,15 +2110,17 @@ function nextWaiting(yours, selected) {
|
|
|
1862
2110
|
const at = yours.findIndex((s) => s.id === selected);
|
|
1863
2111
|
return yours[(at + 1) % yours.length].id;
|
|
1864
2112
|
}
|
|
1865
|
-
function exchanges(sessions, max) {
|
|
2113
|
+
function exchanges(sessions, max, tasks = [], ledge = "Ledge") {
|
|
1866
2114
|
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
1867
|
-
return sessions.flatMap((s) => {
|
|
2115
|
+
return [...sessions.flatMap((s) => {
|
|
1868
2116
|
const m = s.lastMessage;
|
|
1869
2117
|
if (!m) return [];
|
|
1870
2118
|
const peer = m.peer ? byId.get(m.peer) ?? sessions.find((x) => splitId(x.id)?.id === m.peer) : void 0;
|
|
1871
2119
|
return [{ at: m.at, from: s.name, to: peer?.name ?? m.to, summary: m.summary }];
|
|
1872
|
-
}).sort((a, b) => b.at.localeCompare(a.at)).slice(0, max);
|
|
2120
|
+
}), ...tasks.filter((x) => x.giver === LEDGE_GIVER).map((x) => ({ at: x.at, from: ledge, to: x.ownerName, summary: x.title }))].sort((a, b) => b.at.localeCompare(a.at)).slice(0, max);
|
|
1873
2121
|
}
|
|
2122
|
+
var nearContext = (sessions, near) => sessions.filter((s) => s.state !== "exited" && !s.archivedAt && (s.handover ? s.handover.state === "failed" : contextNear(s.context, near)));
|
|
2123
|
+
var closedLedgeTasks = (tasks) => tasks.filter((x) => x.giver === LEDGE_GIVER && taskClosed(x) && !x.seen);
|
|
1874
2124
|
|
|
1875
2125
|
// src/tui/model/time.ts
|
|
1876
2126
|
function ago(iso, now, t) {
|
|
@@ -1914,6 +2164,8 @@ var title = (text) => `\x1B]0;${text.replace(/[\u0000-\u001f\u007f]/g, " ")}\x07
|
|
|
1914
2164
|
// src/tui/screens/Overview.tsx
|
|
1915
2165
|
import { useMemo as useMemo2 } from "react";
|
|
1916
2166
|
import { Box as Box6, Text as Text6, useWindowSize as useWindowSize2 } from "ink";
|
|
2167
|
+
import { randomUUID } from "crypto";
|
|
2168
|
+
import { useState as useState2 } from "react";
|
|
1917
2169
|
|
|
1918
2170
|
// ../ui/src/bots/poseFor.ts
|
|
1919
2171
|
var FROM_SESSION = {
|
|
@@ -2042,7 +2294,9 @@ function Button({ label, k, primary, off }) {
|
|
|
2042
2294
|
] });
|
|
2043
2295
|
}
|
|
2044
2296
|
function SessionCard({ s, note }) {
|
|
2045
|
-
const
|
|
2297
|
+
const tui = useTui();
|
|
2298
|
+
const { t, g } = tui;
|
|
2299
|
+
const threshold = useContextNear(tui.client);
|
|
2046
2300
|
const state2 = botState(s);
|
|
2047
2301
|
const helpers = liveHelpers(s);
|
|
2048
2302
|
const p = s.pending;
|
|
@@ -2059,7 +2313,13 @@ function SessionCard({ s, note }) {
|
|
|
2059
2313
|
" ",
|
|
2060
2314
|
TOOL_NAME[s.tool],
|
|
2061
2315
|
" \xB7 ",
|
|
2062
|
-
modelLabel(s, t.session.defaultModel)
|
|
2316
|
+
modelLabel(s, t.session.defaultModel)
|
|
2317
|
+
] }),
|
|
2318
|
+
s.context ? /* @__PURE__ */ jsxs3(Text3, { color: contextNear(s.context, threshold) ? C.yellow : C.dim, children: [
|
|
2319
|
+
" \xB7 ",
|
|
2320
|
+
t.overview.context(Math.round(s.context.used / s.context.window * 100))
|
|
2321
|
+
] }) : null,
|
|
2322
|
+
/* @__PURE__ */ jsxs3(Text3, { color: C.dim, children: [
|
|
2063
2323
|
" \xB7 ",
|
|
2064
2324
|
machineLabel(s),
|
|
2065
2325
|
s.branch ? ` \xB7 ${g.branch} ${s.branch}` : ""
|
|
@@ -2072,6 +2332,17 @@ function SessionCard({ s, note }) {
|
|
|
2072
2332
|
t.overview.readOnly
|
|
2073
2333
|
] }) : null
|
|
2074
2334
|
] }),
|
|
2335
|
+
s.handover || s.relieves ? /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
|
|
2336
|
+
s.handover ? /* @__PURE__ */ jsxs3(Text3, { color: s.handover.state === "failed" ? C.red : C.yellow, children: [
|
|
2337
|
+
" \u21C4 ",
|
|
2338
|
+
s.handover.state === "done" && s.handover.toName ? t.overview.relievedBy(s.handover.toName) : t.overview.relief[s.handover.state],
|
|
2339
|
+
s.handover.reason ? ` : ${s.handover.reason}` : ""
|
|
2340
|
+
] }) : null,
|
|
2341
|
+
s.relieves ? /* @__PURE__ */ jsxs3(Text3, { color: C.dim, children: [
|
|
2342
|
+
" \u21C4 ",
|
|
2343
|
+
t.overview.relieves(s.relieves.name)
|
|
2344
|
+
] }) : null
|
|
2345
|
+
] }) : null,
|
|
2075
2346
|
helpers.length > 0 ? /* @__PURE__ */ jsxs3(Text3, { color: C.violet, wrap: "truncate", children: [
|
|
2076
2347
|
" ",
|
|
2077
2348
|
helpers.map((h) => `${g.helper} ${h.kind} \xB7 ${oneLine(h.task)}`).join(" ")
|
|
@@ -2151,9 +2422,81 @@ function Overview() {
|
|
|
2151
2422
|
const { columns, rows: height } = useWindowSize2();
|
|
2152
2423
|
const wide = columns >= WIDE;
|
|
2153
2424
|
const left = wide ? Math.min(64, Math.max(44, Math.floor(columns * 0.42))) : columns;
|
|
2154
|
-
const
|
|
2425
|
+
const teams = useTeams(tui.client);
|
|
2426
|
+
const tasks = useTasks(tui.client);
|
|
2427
|
+
const view = useMemo2(() => overview(d.sessions, projects, teams ?? [], tasks), [d.sessions, projects, teams, tasks]);
|
|
2155
2428
|
const sel = selected?.id ?? null;
|
|
2429
|
+
const closed = closedLedgeTasks(tasks);
|
|
2430
|
+
const threshold = useContextNear(tui.client);
|
|
2431
|
+
const near = useMemo2(() => nearContext(d.sessions, threshold), [d.sessions, threshold]);
|
|
2432
|
+
const [tasksShown, setTasksShown] = useState2(true);
|
|
2433
|
+
const [joining, setJoining] = useState2(null);
|
|
2434
|
+
const node = selected ? view.tree.find((n) => n.branches.some((b) => b.sessions.includes(selected)) || n.teams.some((x) => x.sessions.includes(selected))) : void 0;
|
|
2435
|
+
const seatTeam = selected?.team ? teams?.find((x) => x.id === selected.team && x.state !== "dissolved") : void 0;
|
|
2436
|
+
const choices2 = node?.teams.map((x) => x.team).filter((x) => x.state !== "dissolved" && x.id !== selected?.team) ?? [];
|
|
2156
2437
|
useScreenKeys((input, key) => {
|
|
2438
|
+
if (joining) {
|
|
2439
|
+
if (key.escape) {
|
|
2440
|
+
setJoining(null);
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
if (input === "j" || key.downArrow) {
|
|
2444
|
+
setJoining({ index: (joining.index + 1) % Math.max(1, choices2.length) });
|
|
2445
|
+
return true;
|
|
2446
|
+
}
|
|
2447
|
+
if (input === "k" || key.upArrow) {
|
|
2448
|
+
setJoining({ index: (joining.index + choices2.length - 1) % Math.max(1, choices2.length) });
|
|
2449
|
+
return true;
|
|
2450
|
+
}
|
|
2451
|
+
if (key.return && selected && choices2[joining.index]) {
|
|
2452
|
+
const team = choices2[joining.index];
|
|
2453
|
+
const role = selected.role ?? team.seats.find((x) => x.sessionId === null)?.role;
|
|
2454
|
+
setJoining(null);
|
|
2455
|
+
if (!role) {
|
|
2456
|
+
tui.toast({ text: t.overview.noTeamHere, tone: "info" });
|
|
2457
|
+
return true;
|
|
2458
|
+
}
|
|
2459
|
+
tui.client.send({ type: "team.join", team: team.id, session: selected.id, role });
|
|
2460
|
+
tui.toast({ text: t.toast.joined(selected.name, team.name), tone: "ok" });
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2465
|
+
if (input === "J" && selected) {
|
|
2466
|
+
if (choices2.length) setJoining({ index: 0 });
|
|
2467
|
+
else tui.toast({ text: t.overview.noTeamHere, tone: "info" });
|
|
2468
|
+
return true;
|
|
2469
|
+
}
|
|
2470
|
+
if (input === "R") {
|
|
2471
|
+
const resting = (seatTeam?.state === "resting" ? seatTeam : void 0) ?? node?.teams.find((x) => x.team.state === "resting")?.team ?? view.tree.flatMap((n) => n.teams).find((x) => x.team.state === "resting")?.team;
|
|
2472
|
+
if (resting) {
|
|
2473
|
+
tui.client.send({ type: "team.resume", team: resting.id });
|
|
2474
|
+
tui.toast({ text: t.toast.resumed(resting.name), tone: "ok" });
|
|
2475
|
+
}
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
if (input === "T") {
|
|
2479
|
+
setTasksShown((v) => !v);
|
|
2480
|
+
return true;
|
|
2481
|
+
}
|
|
2482
|
+
if (input === "H") {
|
|
2483
|
+
const s = (selected && selected.source === "launcher" && selected.state !== "exited" && selected.tool !== "omp" && (!selected.handover || selected.handover.state === "failed") ? selected : void 0) ?? near.find((x) => x.handover?.state !== "failed") ?? near[0];
|
|
2484
|
+
if (!s) return true;
|
|
2485
|
+
const rid = randomUUID();
|
|
2486
|
+
const off = tui.client.onMessage((m) => {
|
|
2487
|
+
if (m.type === "session.handover.result" && m.rid === rid) {
|
|
2488
|
+
off();
|
|
2489
|
+
tui.toast(m.ok ? { text: t.toast.handover(s.name), tone: "ok" } : { text: t.toast.handoverRefused(m.reason ?? "?"), tone: "info" });
|
|
2490
|
+
}
|
|
2491
|
+
});
|
|
2492
|
+
setTimeout(off, 5e3);
|
|
2493
|
+
tui.client.send({ type: "session.handover", rid, id: s.id });
|
|
2494
|
+
return true;
|
|
2495
|
+
}
|
|
2496
|
+
if (input === "x" && closed[0]) {
|
|
2497
|
+
tui.client.send({ type: "task.cmd", rid: randomUUID(), by: LEDGE_GIVER, op: "seen", task: closed[0].id });
|
|
2498
|
+
return true;
|
|
2499
|
+
}
|
|
2157
2500
|
if (input === "j" || key.downArrow) {
|
|
2158
2501
|
select(step(view.order, sel, 1));
|
|
2159
2502
|
return true;
|
|
@@ -2188,17 +2531,25 @@ function Overview() {
|
|
|
2188
2531
|
}
|
|
2189
2532
|
return false;
|
|
2190
2533
|
});
|
|
2191
|
-
const tree = view.tree.flatMap((n) =>
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2534
|
+
const tree = view.tree.flatMap((n) => {
|
|
2535
|
+
const teamed = n.teams.length > 0;
|
|
2536
|
+
const common = n.branches.length > 0;
|
|
2537
|
+
return [
|
|
2538
|
+
{ kind: "project", key: n.key, label: n.label, color: projectColor(n.color), count: n.count },
|
|
2539
|
+
...teamed && common ? [{ kind: "common", key: `${n.key}|common` }] : [],
|
|
2540
|
+
...n.branches.flatMap((b) => [{ kind: "branch", key: `${n.key}|${b.branch}`, branch: b.branch, indent: teamed ? 4 : 2 }, ...b.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: teamed ? 2 : 0 }))]),
|
|
2541
|
+
...n.teams.flatMap((x) => [{ kind: "team", key: x.key, team: x.team, done: x.done, given: x.given }, ...x.sessions.map((s) => ({ kind: "session", key: s.id, s, indent: 2 }))])
|
|
2542
|
+
];
|
|
2543
|
+
});
|
|
2544
|
+
const yoursCount = view.yours.length + closed.length + near.length;
|
|
2545
|
+
const yoursHeight = Math.max(1, Math.min(yoursCount, 6));
|
|
2546
|
+
const joinHeight = joining && selected ? Math.max(1, choices2.length) + 2 : 0;
|
|
2547
|
+
const treeHeight = Math.max(3, height - 3 - (yoursHeight + 2) - joinHeight - 3);
|
|
2197
2548
|
const inner = left - 4, nameW = 7;
|
|
2198
2549
|
const stateW = Math.max(...Object.values(t.state).map((x) => x.length)) + 1;
|
|
2199
2550
|
const topicW = Math.max(6, inner - 6 - nameW - stateW - 3);
|
|
2200
2551
|
const askW = Math.max(6, inner - 2 - nameW - 9 - 7);
|
|
2201
|
-
const yoursBox = /* @__PURE__ */ jsx7(TitledBox, { title: t.overview.yours(
|
|
2552
|
+
const yoursBox = /* @__PURE__ */ jsx7(TitledBox, { title: t.overview.yours(yoursCount), color: yoursCount ? C.red : C.line, titleColor: yoursCount ? C.red : C.dim, height: yoursHeight + 2, children: yoursCount === 0 ? /* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.overview.nobodyWaits }) : [...view.yours.slice(0, yoursHeight).map((s) => {
|
|
2202
2553
|
const st = botState(s);
|
|
2203
2554
|
return /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", backgroundColor: s.id === sel ? C.sel : void 0, children: [
|
|
2204
2555
|
/* @__PURE__ */ jsxs5(Text6, { color: STATE_COLOR[st], children: [
|
|
@@ -2213,42 +2564,91 @@ function Overview() {
|
|
|
2213
2564
|
ago(s.lastActivityAt, now, t.time)
|
|
2214
2565
|
] })
|
|
2215
2566
|
] }, s.id);
|
|
2216
|
-
})
|
|
2567
|
+
}), ...closed.slice(0, Math.max(0, yoursHeight - view.yours.length)).map((x) => /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", children: [
|
|
2568
|
+
/* @__PURE__ */ jsxs5(Text6, { color: x.state === "done" ? C.green : C.yellow, children: [
|
|
2569
|
+
x.state === "done" ? g.ok : g.fail,
|
|
2570
|
+
" "
|
|
2571
|
+
] }),
|
|
2572
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.fg, children: t.overview.ledgeTask(x.ownerName, x.title, x.state) })
|
|
2573
|
+
] }, x.id)), ...near.slice(0, Math.max(0, yoursHeight - view.yours.length - closed.length)).map((s) => /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", backgroundColor: s.id === sel ? C.sel : void 0, children: [
|
|
2574
|
+
/* @__PURE__ */ jsxs5(Text6, { color: C.yellow, children: [
|
|
2575
|
+
s.handover?.state === "failed" ? g.fail : "\u25D4",
|
|
2576
|
+
" "
|
|
2577
|
+
] }),
|
|
2578
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.fg, children: s.handover?.state === "failed" ? t.overview.reliefFailed(s.name, s.handover.reason ?? "?") : t.overview.near(`${s.name}${s.role ? ` @${s.role}` : ""}`, Math.round((s.context?.used ?? 0) / (s.context?.window ?? 1) * 100)) })
|
|
2579
|
+
] }, `near:${s.id}`))] });
|
|
2217
2580
|
const treeBox = /* @__PURE__ */ jsx7(TitledBox, { title: t.overview.projects, flexGrow: 1, children: tree.length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.overview.noSession }) : windowed(tree, sel, treeHeight).map((r) => r.kind === "project" ? /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", children: [
|
|
2218
2581
|
/* @__PURE__ */ jsx7(Text6, { color: r.color, children: g.project }),
|
|
2219
2582
|
" ",
|
|
2220
2583
|
/* @__PURE__ */ jsx7(Text6, { color: C.fg, bold: true, children: r.label }),
|
|
2221
2584
|
" ",
|
|
2222
2585
|
/* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.overview.sessions(r.count) })
|
|
2223
|
-
] }, r.key) : r.kind === "
|
|
2586
|
+
] }, r.key) : r.kind === "common" ? /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", color: C.faint, children: [
|
|
2224
2587
|
" ",
|
|
2588
|
+
g.common,
|
|
2589
|
+
" ",
|
|
2590
|
+
t.overview.common
|
|
2591
|
+
] }, r.key) : r.kind === "team" ? /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", children: [
|
|
2592
|
+
/* @__PURE__ */ jsx7(Text6, { children: " " }),
|
|
2593
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.coral, children: g.team }),
|
|
2594
|
+
" ",
|
|
2595
|
+
/* @__PURE__ */ jsx7(Text6, { color: r.team.state === "resting" ? C.dim : C.fg, bold: true, children: r.team.name }),
|
|
2596
|
+
" ",
|
|
2597
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.faint, children: r.team.state === "resting" ? t.overview.resting : t.overview.teamLine(r.done, r.given) })
|
|
2598
|
+
] }, r.key) : r.kind === "branch" ? /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", color: C.faint, children: [
|
|
2599
|
+
" ".repeat(r.indent),
|
|
2225
2600
|
g.branch,
|
|
2226
2601
|
" ",
|
|
2227
2602
|
r.branch
|
|
2228
|
-
] }, r.key) : /* @__PURE__ */ jsx7(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW, stateW }, r.key)) });
|
|
2603
|
+
] }, r.key) : /* @__PURE__ */ jsx7(SessionRow, { s: r.s, on: r.s.id === sel, nameW, topicW: Math.max(6, topicW - r.indent - (r.s.role ? r.s.role.length + 2 : 0)), stateW, indent: r.indent }, r.key)) });
|
|
2229
2604
|
const keys = [
|
|
2230
2605
|
["j k", t.keys.navigate],
|
|
2231
2606
|
["\u21B5", t.keys.open],
|
|
2232
2607
|
...view.yours.length ? [["tab", t.keys.next], ["y n", t.keys.answer]] : [],
|
|
2233
2608
|
["a", t.keys.attach],
|
|
2609
|
+
...seatTeam ? [["T", t.keys.tasks]] : [],
|
|
2610
|
+
...choices2.length ? [["J", t.keys.join]] : [],
|
|
2611
|
+
...view.tree.some((n) => n.teams.some((x) => x.team.state === "resting")) ? [["R", t.keys.resume]] : [],
|
|
2612
|
+
...near.length ? [["H", t.keys.handover]] : [],
|
|
2234
2613
|
["/", t.keys.ask],
|
|
2235
2614
|
["+", t.keys.launch],
|
|
2236
2615
|
[",", t.keys.settings],
|
|
2237
2616
|
["?", t.keys.help],
|
|
2238
2617
|
["q", t.keys.quit]
|
|
2239
2618
|
];
|
|
2619
|
+
const teamTasks = seatTeam ? tasks.filter((x) => x.team === seatTeam.id && (!taskClosed(x) || x.updatedAt.slice(0, 10) === (/* @__PURE__ */ new Date()).toISOString().slice(0, 10))).slice(-4) : [];
|
|
2240
2620
|
return /* @__PURE__ */ jsx7(Frame, { crumb: tui.machines.map((m) => `${m.label} ${m.ready ? g.machine : "\u25CB"}`).join(" "), keys, children: /* @__PURE__ */ jsxs5(Box6, { flexGrow: 1, children: [
|
|
2241
2621
|
/* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", width: left, flexShrink: 0, children: [
|
|
2242
2622
|
yoursBox,
|
|
2623
|
+
joining && selected ? /* @__PURE__ */ jsx7(TitledBox, { title: t.overview.joinWhich(selected.name), color: C.coral, titleColor: C.coral, height: joinHeight, children: choices2.map((x, i) => /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", backgroundColor: i === joining.index ? C.sel : void 0, children: [
|
|
2624
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.coral, children: g.team }),
|
|
2625
|
+
" ",
|
|
2626
|
+
x.name
|
|
2627
|
+
] }, x.id)) }) : null,
|
|
2243
2628
|
treeBox
|
|
2244
2629
|
] }),
|
|
2245
2630
|
wide ? /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", flexGrow: 1, children: [
|
|
2246
2631
|
selected ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2247
2632
|
/* @__PURE__ */ jsx7(TitledBox, { title: `${selected.name} \xB7 ${oneLine(selected.topic ?? selected.title)}`, color: isPending(selected) ? C.red : C.line, titleColor: isPending(selected) ? C.red : C.dim, children: /* @__PURE__ */ jsx7(SessionCard, { s: selected, note: true }) }),
|
|
2633
|
+
seatTeam && tasksShown ? /* @__PURE__ */ jsx7(TitledBox, { title: t.overview.tasks(`${g.team} ${seatTeam.name}`), height: Math.max(3, teamTasks.length + 2), children: teamTasks.length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.overview.noTask }) : teamTasks.map((x) => /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", children: [
|
|
2634
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.faint, children: x.id }),
|
|
2635
|
+
" ",
|
|
2636
|
+
/* @__PURE__ */ jsx7(Text6, { color: C.fg, children: x.title }),
|
|
2637
|
+
" ",
|
|
2638
|
+
/* @__PURE__ */ jsxs5(Text6, { color: C.dim, children: [
|
|
2639
|
+
x.giverName,
|
|
2640
|
+
" ",
|
|
2641
|
+
g.arrow,
|
|
2642
|
+
" ",
|
|
2643
|
+
x.ownerName
|
|
2644
|
+
] }),
|
|
2645
|
+
" ",
|
|
2646
|
+
/* @__PURE__ */ jsx7(Text6, { color: x.state === "done" ? C.green : x.state === "taken" ? C.blue : C.faint, children: t.ask.taskState[x.state] })
|
|
2647
|
+
] }, x.id)) }) : null,
|
|
2248
2648
|
/* @__PURE__ */ jsx7(TitledBox, { title: t.overview.terminal(paneName(selected)), flexGrow: 1, children: /* @__PURE__ */ jsx7(Tail, { lines: d.previews.get(selected.id) ?? [], color: C.dim, empty: t.overview.noPreview, emptyColor: C.faint }) })
|
|
2249
2649
|
] }) : /* @__PURE__ */ jsx7(TitledBox, { title: t.tabs.session, flexGrow: 1, children: /* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.session.noSession }) }),
|
|
2250
2650
|
/* @__PURE__ */ jsx7(TitledBox, { title: t.overview.exchanges, height: 5, children: (() => {
|
|
2251
|
-
const list = exchanges(d.sessions, 3);
|
|
2651
|
+
const list = exchanges(d.sessions, 3, tasks, t.overview.ledge);
|
|
2252
2652
|
return list.length === 0 ? /* @__PURE__ */ jsx7(Text6, { color: C.faint, children: t.overview.noExchange }) : list.map((x, i) => /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", children: [
|
|
2253
2653
|
/* @__PURE__ */ jsx7(Text6, { color: C.faint, children: clock(x.at) }),
|
|
2254
2654
|
" ",
|
|
@@ -2264,18 +2664,23 @@ function Overview() {
|
|
|
2264
2664
|
] }) : null
|
|
2265
2665
|
] }) });
|
|
2266
2666
|
}
|
|
2267
|
-
function SessionRow({ s, on, nameW, topicW, stateW }) {
|
|
2667
|
+
function SessionRow({ s, on, nameW, topicW, stateW, indent = 0 }) {
|
|
2268
2668
|
const { t, g } = useTui();
|
|
2269
2669
|
const st = botState(s);
|
|
2270
2670
|
const n = helperCount(s);
|
|
2271
2671
|
return /* @__PURE__ */ jsxs5(Text6, { wrap: "truncate", backgroundColor: on ? C.sel : void 0, children: [
|
|
2272
2672
|
/* @__PURE__ */ jsx7(Text6, { color: on ? C.coral : C.faint, children: on ? "\u258C" : " " }),
|
|
2273
|
-
/* @__PURE__ */ jsx7(Text6, { children: "
|
|
2673
|
+
/* @__PURE__ */ jsx7(Text6, { children: " ".repeat(2 + indent) }),
|
|
2274
2674
|
/* @__PURE__ */ jsxs5(Text6, { color: s.offline ? C.faint : STATE_COLOR[st], children: [
|
|
2275
2675
|
g[st],
|
|
2276
2676
|
" "
|
|
2277
2677
|
] }),
|
|
2278
2678
|
/* @__PURE__ */ jsx7(Text6, { color: s.offline ? C.dim : C.fg, bold: true, children: fit(s.name, nameW) }),
|
|
2679
|
+
s.role ? /* @__PURE__ */ jsxs5(Text6, { color: C.violet, children: [
|
|
2680
|
+
"@",
|
|
2681
|
+
s.role,
|
|
2682
|
+
" "
|
|
2683
|
+
] }) : null,
|
|
2279
2684
|
/* @__PURE__ */ jsxs5(Text6, { color: C.dim, children: [
|
|
2280
2685
|
fit(oneLine(s.topic ?? s.title), topicW),
|
|
2281
2686
|
" "
|
|
@@ -2293,7 +2698,7 @@ function SessionRow({ s, on, nameW, topicW, stateW }) {
|
|
|
2293
2698
|
}
|
|
2294
2699
|
|
|
2295
2700
|
// src/tui/screens/Session.tsx
|
|
2296
|
-
import { useEffect as useEffect6, useMemo as useMemo3, useState as
|
|
2701
|
+
import { useEffect as useEffect6, useMemo as useMemo3, useState as useState5 } from "react";
|
|
2297
2702
|
import { Box as Box9, Text as Text10, useWindowSize as useWindowSize3 } from "ink";
|
|
2298
2703
|
|
|
2299
2704
|
// ../ui/src/thread/turns.ts
|
|
@@ -2454,18 +2859,18 @@ function threadLines(items, o) {
|
|
|
2454
2859
|
}
|
|
2455
2860
|
|
|
2456
2861
|
// src/tui/ui/ModelPicker.tsx
|
|
2457
|
-
import { useState as
|
|
2862
|
+
import { useState as useState4 } from "react";
|
|
2458
2863
|
import { Box as Box7, Text as Text7, useInput } from "ink";
|
|
2459
2864
|
|
|
2460
2865
|
// ../fleet/src/models.ts
|
|
2461
|
-
import { useEffect as useEffect4, useState as
|
|
2866
|
+
import { useEffect as useEffect4, useState as useState3 } from "react";
|
|
2462
2867
|
var IDLE = { status: "idle", models: [] };
|
|
2463
2868
|
var LOADING = { status: "loading", models: [] };
|
|
2464
2869
|
var known = /* @__PURE__ */ new Map();
|
|
2465
2870
|
var next = 0;
|
|
2466
2871
|
function useModels(client, machine, tool) {
|
|
2467
2872
|
const key = machine === null || tool === null ? null : `${machine}|${tool}`;
|
|
2468
|
-
const [answer, setAnswer] =
|
|
2873
|
+
const [answer, setAnswer] = useState3(null);
|
|
2469
2874
|
useEffect4(() => {
|
|
2470
2875
|
if (key === null || machine === null || tool === null || known.has(key)) return;
|
|
2471
2876
|
const requestId = `models-${++next}`;
|
|
@@ -2489,8 +2894,8 @@ function ModelPicker({ machine, tool, current, onPick, onCancel }) {
|
|
|
2489
2894
|
useEditing(true);
|
|
2490
2895
|
const models = useModels(client, machine, tool);
|
|
2491
2896
|
const rows = [{ id: null, label: t.launch.defaultModel, efforts: [], defaultEffort: void 0 }, ...models.models.map((m) => ({ id: m.id, label: m.label, efforts: m.efforts ?? [], defaultEffort: m.defaultEffort }))];
|
|
2492
|
-
const [at, setAt] =
|
|
2493
|
-
const [effort, setEffort] =
|
|
2897
|
+
const [at, setAt] = useState4(() => Math.max(0, rows.findIndex((r) => r.id === current.model)));
|
|
2898
|
+
const [effort, setEffort] = useState4(current.effort);
|
|
2494
2899
|
const row = rows[Math.min(at, rows.length - 1)];
|
|
2495
2900
|
useInput((input, key) => {
|
|
2496
2901
|
if (key.escape) {
|
|
@@ -2544,7 +2949,7 @@ function Lines({ lines, up = 0, onHeight }) {
|
|
|
2544
2949
|
const h = Math.max(1, height);
|
|
2545
2950
|
const back = Math.min(up, Math.max(0, lines.length - h));
|
|
2546
2951
|
const shown = lines.slice(Math.max(0, lines.length - h - back), lines.length - back);
|
|
2547
|
-
return /* @__PURE__ */ jsx9(Box8, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: shown.map((l, i) => /* @__PURE__ */ jsx9(Text8, { wrap: "truncate", children: l.map((x, k) => /* @__PURE__ */ jsx9(Text8, { color: x.color, bold: x.bold, children: x.text || (k === 0 ? " " : "") }, k)) }, i)) });
|
|
2952
|
+
return /* @__PURE__ */ jsx9(Box8, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: shown.map((l, i) => /* @__PURE__ */ jsx9(Box8, { flexShrink: 0, children: /* @__PURE__ */ jsx9(Text8, { wrap: "truncate", children: l.map((x, k) => /* @__PURE__ */ jsx9(Text8, { color: x.color, bold: x.bold, children: x.text || (k === 0 ? " " : "") }, k)) }) }, i)) });
|
|
2548
2953
|
}
|
|
2549
2954
|
|
|
2550
2955
|
// src/tui/ui/TextInput.tsx
|
|
@@ -2594,12 +2999,12 @@ function SessionScreen() {
|
|
|
2594
2999
|
function SessionView({ s }) {
|
|
2595
3000
|
const { client, d, t, g, now, actions, attach, toast, machines } = useTui();
|
|
2596
3001
|
const { columns } = useWindowSize3();
|
|
2597
|
-
const [mode, setMode] =
|
|
2598
|
-
const [draft, setDraft] =
|
|
2599
|
-
const [diffs, setDiffs] =
|
|
2600
|
-
const [unfolded, setUnfolded] =
|
|
2601
|
-
const [terminal, setTerminal] =
|
|
2602
|
-
const [up, setUp] =
|
|
3002
|
+
const [mode, setMode] = useState5("read");
|
|
3003
|
+
const [draft, setDraft] = useState5("");
|
|
3004
|
+
const [diffs, setDiffs] = useState5(false);
|
|
3005
|
+
const [unfolded, setUnfolded] = useState5(false);
|
|
3006
|
+
const [terminal, setTerminal] = useState5(false);
|
|
3007
|
+
const [up, setUp] = useState5(0);
|
|
2603
3008
|
useEditing(mode === "reply" || mode === "note");
|
|
2604
3009
|
const items = d.threads.get(s.id);
|
|
2605
3010
|
useEffect6(() => {
|
|
@@ -2608,7 +3013,7 @@ function SessionView({ s }) {
|
|
|
2608
3013
|
const width = Math.max(20, columns - 6);
|
|
2609
3014
|
const lines = useMemo3(() => items ? threadLines(items, { t, g, now, width, diffs, unfolded }) : [], [items, t, g, now, width, diffs, unfolded]);
|
|
2610
3015
|
const p = s.pending;
|
|
2611
|
-
const [view, setView] =
|
|
3016
|
+
const [view, setView] = useState5(10);
|
|
2612
3017
|
const maxUp = Math.max(0, lines.length - view);
|
|
2613
3018
|
const machine = splitId(s.id)?.machine ?? "local";
|
|
2614
3019
|
const canSwitch = machines.find((m) => m.id === machine)?.daemon?.modelSwitch === true;
|
|
@@ -2756,8 +3161,8 @@ function SessionView({ s }) {
|
|
|
2756
3161
|
}
|
|
2757
3162
|
|
|
2758
3163
|
// src/tui/screens/Ask.tsx
|
|
2759
|
-
import { randomUUID } from "crypto";
|
|
2760
|
-
import { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef7, useState as
|
|
3164
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3165
|
+
import { useEffect as useEffect7, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
|
|
2761
3166
|
import { Box as Box10, Text as Text11, useInput as useInput3, useWindowSize as useWindowSize4 } from "ink";
|
|
2762
3167
|
|
|
2763
3168
|
// src/tui/model/chat.ts
|
|
@@ -2780,6 +3185,8 @@ function chatLines(turns2, live, o) {
|
|
|
2780
3185
|
out.push([{ text: "" }]);
|
|
2781
3186
|
for (const l of wrap(text, width - 2)) out.push([{ text: " " }, { text: l, color: C.fg }]);
|
|
2782
3187
|
} else out.push([{ text: ` ${t.ask.reading}`, color: C.faint }]);
|
|
3188
|
+
if (turn.team?.teamId && turn.team.launchedAt) out.push([{ text: ` ${g.team} ${turn.team.name} \xB7 ${t.ask.teamLaunched(clock(turn.team.launchedAt))}`, color: C.green }]);
|
|
3189
|
+
for (const c of turn.tasks ?? []) if (c.taskId) out.push([{ text: ` ${c.title} \xB7 ${t.ask.given(c.ownerName ?? c.toName ?? c.to, c.taskId, t.ask.taskState[c.state ?? "open"])}`, color: C.green }]);
|
|
2783
3190
|
}
|
|
2784
3191
|
return out;
|
|
2785
3192
|
}
|
|
@@ -2787,6 +3194,9 @@ function openCards(turns2) {
|
|
|
2787
3194
|
const out = [];
|
|
2788
3195
|
for (const turn of [...turns2].reverse()) {
|
|
2789
3196
|
if (!turn.answeredAt) continue;
|
|
3197
|
+
if (turn.team && !turn.team.teamId) out.push({ kind: "team", qid: turn.qid });
|
|
3198
|
+
const tasks = (turn.tasks ?? []).map((c, i) => c.taskId ? -1 : i).filter((i) => i >= 0);
|
|
3199
|
+
if (tasks.length) out.push({ kind: "task", qid: turn.qid, rows: tasks });
|
|
2790
3200
|
const launch = proposalRows(turn).map((p, i) => p.launchedId ? -1 : i).filter((i) => i >= 0);
|
|
2791
3201
|
if (launch.length) out.push({ kind: "launch", qid: turn.qid, rows: launch });
|
|
2792
3202
|
if (turn.relay && turn.relay.status !== "sent") out.push({ kind: "relay", qid: turn.qid });
|
|
@@ -2802,9 +3212,9 @@ var CARD_MS = 2e4;
|
|
|
2802
3212
|
function Ask() {
|
|
2803
3213
|
const { client, d, machines, t, g, toast, go } = useTui();
|
|
2804
3214
|
const { columns } = useWindowSize4();
|
|
2805
|
-
const [draft, setDraft] =
|
|
2806
|
-
const [focus, setFocus] =
|
|
2807
|
-
const [busy, setBusy] =
|
|
3215
|
+
const [draft, setDraft] = useState6("");
|
|
3216
|
+
const [focus, setFocus] = useState6(-1);
|
|
3217
|
+
const [busy, setBusy] = useState6(null);
|
|
2808
3218
|
useEditing(focus === -1);
|
|
2809
3219
|
const cards = useMemo4(() => openCards(d.chat), [d.chat]);
|
|
2810
3220
|
useEffect7(() => {
|
|
@@ -2828,7 +3238,7 @@ function Ask() {
|
|
|
2828
3238
|
toast({ text: t.ask.offline, tone: "info" });
|
|
2829
3239
|
return;
|
|
2830
3240
|
}
|
|
2831
|
-
const qid = `q-${
|
|
3241
|
+
const qid = `q-${randomUUID2()}`;
|
|
2832
3242
|
daemonStore.beginTurn({ qid, question: text.trim(), askedAt: (/* @__PURE__ */ new Date()).toISOString(), steps: [] });
|
|
2833
3243
|
client.send({ type: "chat.ask", qid, text: text.trim() });
|
|
2834
3244
|
setDraft("");
|
|
@@ -2837,8 +3247,22 @@ function Ask() {
|
|
|
2837
3247
|
if (busy) return;
|
|
2838
3248
|
const turn = d.chat.find((x) => x.qid === card.qid);
|
|
2839
3249
|
if (!turn) return;
|
|
3250
|
+
if (card.kind === "task") {
|
|
3251
|
+
const row = card.rows.find((r) => {
|
|
3252
|
+
const c = turn.tasks[r];
|
|
3253
|
+
return !c.to.startsWith("@") || c.team || turn.team?.teamId;
|
|
3254
|
+
});
|
|
3255
|
+
if (row === void 0) {
|
|
3256
|
+
toast({ text: t.ask.waitTeam, tone: "info" });
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
setBusy({ qid: card.qid, turn, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now() });
|
|
3260
|
+
client.send({ type: "task.cmd", rid: randomUUID2(), by: LEDGE_GIVER, op: "give", qid: card.qid, row });
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
2840
3263
|
setBusy({ qid: card.qid, turn, errorSeq: d.errors.at(-1)?.seq ?? 0, at: Date.now() });
|
|
2841
|
-
if (card.kind === "launch")
|
|
3264
|
+
if (card.kind === "team") client.send({ type: "team.launch", rid: randomUUID2(), qid: card.qid });
|
|
3265
|
+
else if (card.kind === "launch") {
|
|
2842
3266
|
const first = proposalRows(turn)[card.rows[0]];
|
|
2843
3267
|
client.send({ type: "session.launch", tool: first.tool, cwd: first.cwd, ...first.prompt ? { prompt: first.prompt } : {}, qid: card.qid, rows: card.rows });
|
|
2844
3268
|
} else if (card.kind === "relay") client.send({ type: turn.relay?.status === "queued" ? "session.relay.cancel" : "session.relay", qid: card.qid });
|
|
@@ -2869,6 +3293,11 @@ function Ask() {
|
|
|
2869
3293
|
act(cards[focus]);
|
|
2870
3294
|
return true;
|
|
2871
3295
|
}
|
|
3296
|
+
if (input === "s" && cards[focus].kind === "team") {
|
|
3297
|
+
const team = d.chat.find((x) => x.qid === cards[focus].qid)?.team;
|
|
3298
|
+
if (team && !team.template && !team.savedPath) client.send({ type: "team.save", rid: randomUUID2(), qid: cards[focus].qid, name: team.name, seats: team.seats.map((x) => ({ role: x.role, tool: x.tool, ...x.model ? { model: x.model } : {}, ...x.effort ? { effort: x.effort } : {} })) });
|
|
3299
|
+
return true;
|
|
3300
|
+
}
|
|
2872
3301
|
if (key.escape) {
|
|
2873
3302
|
setFocus(-1);
|
|
2874
3303
|
return true;
|
|
@@ -2878,10 +3307,10 @@ function Ask() {
|
|
|
2878
3307
|
const sessions = listed(d.sessions).length;
|
|
2879
3308
|
const width = Math.max(20, columns - 6);
|
|
2880
3309
|
const lines = useMemo4(() => chatLines(d.chat, d.chatLive, { t, g, width }), [d.chat, d.chatLive, t, g, width]);
|
|
2881
|
-
return /* @__PURE__ */ jsxs9(Frame, { crumb: `${t.tabs.ask}`, keys: [["\u21B5", t.keys.send], ["tab", t.keys.nextCard], ["ctrl+n", t.keys.newChat], ["ctrl+c", t.keys.stopAnswer], ["esc", t.keys.back]], children: [
|
|
2882
|
-
/* @__PURE__ */ jsx12(TitledBox, { title: t.ask.
|
|
3310
|
+
return /* @__PURE__ */ jsxs9(Frame, { crumb: `${t.tabs.ask}`, keys: [["\u21B5", t.keys.send], ["tab", t.keys.nextCard], ["s", t.keys.save], ["ctrl+n", t.keys.newChat], ["ctrl+c", t.keys.stopAnswer], ["esc", t.keys.back]], children: [
|
|
3311
|
+
/* @__PURE__ */ jsx12(TitledBox, { title: t.ask.operator(sessions, Math.max(1, machines.length)), flexGrow: 1, children: lines.length === 0 ? /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.ask.empty }) : /* @__PURE__ */ jsx12(Lines, { lines }) }),
|
|
2883
3312
|
cards.slice(0, 2).map((c, i) => /* @__PURE__ */ jsx12(CardBox, { card: c, turn: d.chat.find((x) => x.qid === c.qid), focused: i === focus, busy: busy?.qid === c.qid }, `${c.kind}:${c.qid}`)),
|
|
2884
|
-
/* @__PURE__ */ jsx12(TitledBox, { title: t.ask.question, color: focus === -1 ? C.coral : C.line, children: /* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3313
|
+
/* @__PURE__ */ jsx12(TitledBox, { title: t.ask.question, flexShrink: 0, color: focus === -1 ? C.coral : C.line, children: /* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
2885
3314
|
/* @__PURE__ */ jsxs9(Text11, { color: C.coral, children: [
|
|
2886
3315
|
g.cursor,
|
|
2887
3316
|
" "
|
|
@@ -2895,9 +3324,67 @@ function Ask() {
|
|
|
2895
3324
|
function CardBox({ card, turn, focused, busy }) {
|
|
2896
3325
|
const { t, g } = useTui();
|
|
2897
3326
|
const color = focused ? C.coral : C.line;
|
|
3327
|
+
if (card.kind === "team") {
|
|
3328
|
+
const team = turn.team;
|
|
3329
|
+
const auto = team.seats.filter((x) => x.autonomy === "auto").map((x) => `@${x.role}`);
|
|
3330
|
+
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.teamCard(team.name), flexShrink: 0, color, titleColor: C.coral, children: [
|
|
3331
|
+
/* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3332
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.dim, children: t.ask.folder.padEnd(13) }),
|
|
3333
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.faint, children: team.cwd })
|
|
3334
|
+
] }),
|
|
3335
|
+
/* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3336
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.dim, children: t.ask.table.padEnd(13) }),
|
|
3337
|
+
g.team,
|
|
3338
|
+
" ",
|
|
3339
|
+
t.ask.newTable(team.projectName ?? team.project)
|
|
3340
|
+
] }),
|
|
3341
|
+
team.seats.map((x) => /* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3342
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.dim, children: "".padEnd(13) }),
|
|
3343
|
+
/* @__PURE__ */ jsxs9(Text11, { color: C.violet, children: [
|
|
3344
|
+
"@",
|
|
3345
|
+
x.role
|
|
3346
|
+
] }),
|
|
3347
|
+
" ",
|
|
3348
|
+
x.label,
|
|
3349
|
+
" ",
|
|
3350
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.faint, children: [TOOL_NAME[x.tool], x.model, x.effort].filter(Boolean).join(" \xB7 ") }),
|
|
3351
|
+
" ",
|
|
3352
|
+
/* @__PURE__ */ jsx12(Text11, { color: x.autonomy === "auto" ? C.yellow : C.faint, children: t.ask.autonomy[x.autonomy ?? "ask"] })
|
|
3353
|
+
] }, x.role)),
|
|
3354
|
+
auto.length ? /* @__PURE__ */ jsx12(Text11, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
|
|
3355
|
+
team.error ? /* @__PURE__ */ jsx12(Text11, { color: C.red, wrap: "truncate", children: team.error }) : null,
|
|
3356
|
+
/* @__PURE__ */ jsxs9(Box10, { columnGap: 2, children: [
|
|
3357
|
+
/* @__PURE__ */ jsx12(Button, { label: busy ? `${g.live} ${t.ask.launchTeam}` : t.ask.launchTeam, k: "\u21B5", primary: focused }),
|
|
3358
|
+
!team.template ? team.savedPath ? /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.ask.saved }) : /* @__PURE__ */ jsx12(Button, { label: t.ask.save, k: "s" }) : null,
|
|
3359
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.ask.nothingWithoutYou })
|
|
3360
|
+
] })
|
|
3361
|
+
] });
|
|
3362
|
+
}
|
|
3363
|
+
if (card.kind === "task") {
|
|
3364
|
+
const tasks = turn.tasks;
|
|
3365
|
+
const waits = !!turn.team && !turn.team.teamId;
|
|
3366
|
+
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.taskCard, flexShrink: 0, color, titleColor: C.violet, children: [
|
|
3367
|
+
card.rows.map((r) => {
|
|
3368
|
+
const c = tasks[r];
|
|
3369
|
+
return /* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
3370
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.fg, children: c.title }),
|
|
3371
|
+
" ",
|
|
3372
|
+
/* @__PURE__ */ jsx12(Text11, { color: C.faint, children: c.toName ?? c.to }),
|
|
3373
|
+
c.error ? /* @__PURE__ */ jsxs9(Text11, { color: C.red, children: [
|
|
3374
|
+
" ",
|
|
3375
|
+
c.error
|
|
3376
|
+
] }) : null
|
|
3377
|
+
] }, r);
|
|
3378
|
+
}),
|
|
3379
|
+
/* @__PURE__ */ jsxs9(Box10, { columnGap: 2, children: [
|
|
3380
|
+
/* @__PURE__ */ jsx12(Button, { label: busy ? `${g.live} ${t.ask.give}` : t.ask.give, k: "\u21B5", primary: focused && !waits }),
|
|
3381
|
+
waits ? /* @__PURE__ */ jsx12(Text11, { color: C.faint, children: t.ask.waitTeam }) : null
|
|
3382
|
+
] })
|
|
3383
|
+
] });
|
|
3384
|
+
}
|
|
2898
3385
|
if (card.kind === "launch") {
|
|
2899
3386
|
const rows = proposalRows(turn);
|
|
2900
|
-
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.launchCard, color, titleColor: C.coral, children: [
|
|
3387
|
+
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.launchCard, flexShrink: 0, color, titleColor: C.coral, children: [
|
|
2901
3388
|
card.rows.map((r) => {
|
|
2902
3389
|
const p = rows[r];
|
|
2903
3390
|
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
@@ -2927,7 +3414,7 @@ function CardBox({ card, turn, focused, busy }) {
|
|
|
2927
3414
|
}
|
|
2928
3415
|
if (card.kind === "relay") {
|
|
2929
3416
|
const r = turn.relay;
|
|
2930
|
-
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.relayCard(r.name), color, titleColor: C.violet, children: [
|
|
3417
|
+
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.relayCard(r.name), flexShrink: 0, color, titleColor: C.violet, children: [
|
|
2931
3418
|
/* @__PURE__ */ jsx12(Text11, { wrap: "truncate", color: C.fg, children: oneLine(r.text) }),
|
|
2932
3419
|
r.error ? /* @__PURE__ */ jsx12(Text11, { color: C.red, wrap: "truncate", children: r.error }) : null,
|
|
2933
3420
|
/* @__PURE__ */ jsxs9(Box10, { columnGap: 2, children: [
|
|
@@ -2937,7 +3424,7 @@ function CardBox({ card, turn, focused, busy }) {
|
|
|
2937
3424
|
] });
|
|
2938
3425
|
}
|
|
2939
3426
|
const closes = turn.closes ?? [];
|
|
2940
|
-
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.closeCard, color, titleColor: C.red, children: [
|
|
3427
|
+
return /* @__PURE__ */ jsxs9(TitledBox, { title: t.ask.closeCard, flexShrink: 0, color, titleColor: C.red, children: [
|
|
2941
3428
|
card.rows.map((r) => /* @__PURE__ */ jsxs9(Text11, { wrap: "truncate", children: [
|
|
2942
3429
|
/* @__PURE__ */ jsx12(Text11, { color: C.fg, children: closes[r].name }),
|
|
2943
3430
|
" ",
|
|
@@ -2952,7 +3439,7 @@ function CardBox({ card, turn, focused, busy }) {
|
|
|
2952
3439
|
}
|
|
2953
3440
|
|
|
2954
3441
|
// src/tui/screens/Launch.tsx
|
|
2955
|
-
import { useEffect as useEffect8, useMemo as useMemo5, useState as
|
|
3442
|
+
import { useEffect as useEffect8, useMemo as useMemo5, useState as useState7 } from "react";
|
|
2956
3443
|
import { Box as Box11, Text as Text12, useInput as useInput4 } from "ink";
|
|
2957
3444
|
|
|
2958
3445
|
// ../fleet/src/launch.ts
|
|
@@ -3020,19 +3507,24 @@ function launchMessage(f, name) {
|
|
|
3020
3507
|
}
|
|
3021
3508
|
|
|
3022
3509
|
// src/tui/screens/Launch.tsx
|
|
3023
|
-
import {
|
|
3510
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3511
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3024
3512
|
var FIELDS = ["prompt", "tool", "model", "effort", "project", "machine", "repos", "folder", "name"];
|
|
3025
3513
|
var TEXT = /* @__PURE__ */ new Set(["prompt", "folder"]);
|
|
3026
3514
|
var LAUNCH_MS = 2e4;
|
|
3027
3515
|
var cycle = (list, cur, by) => list[((Math.max(0, list.indexOf(cur)) + by) % list.length + list.length) % list.length];
|
|
3028
3516
|
function Launch() {
|
|
3517
|
+
const [team, setTeam] = useState7(false);
|
|
3518
|
+
return team ? /* @__PURE__ */ jsx13(TeamLaunch, { onSession: () => setTeam(false) }) : /* @__PURE__ */ jsx13(SessionLaunch, { onTeam: () => setTeam(true) });
|
|
3519
|
+
}
|
|
3520
|
+
function SessionLaunch({ onTeam }) {
|
|
3029
3521
|
const { client, d, machines, projects, t, g, toast, go, select } = useTui();
|
|
3030
3522
|
const list = projects?.projects ?? [];
|
|
3031
|
-
const [form, setForm] =
|
|
3032
|
-
const [field, setField] =
|
|
3033
|
-
const [folder, setFolder] =
|
|
3034
|
-
const [repoAt, setRepoAt] =
|
|
3035
|
-
const [launching, setLaunching] =
|
|
3523
|
+
const [form, setForm] = useState7(() => launchDefaults(list, machines, {}, "claude"));
|
|
3524
|
+
const [field, setField] = useState7("prompt");
|
|
3525
|
+
const [folder, setFolder] = useState7("");
|
|
3526
|
+
const [repoAt, setRepoAt] = useState7(0);
|
|
3527
|
+
const [launching, setLaunching] = useState7(null);
|
|
3036
3528
|
useEditing(true);
|
|
3037
3529
|
useEffect8(() => {
|
|
3038
3530
|
if (form.machine === null) {
|
|
@@ -3054,7 +3546,7 @@ function Launch() {
|
|
|
3054
3546
|
const withFolder = folder.trim() ? { ...form, repos: MULTI_REPO_TOOLS.has(form.tool) ? [folder.trim(), ...form.repos.filter((r) => r !== folder.trim())] : [folder.trim()] } : form;
|
|
3055
3547
|
const projectIdOf = (s) => projects ? projectOf(s, projects) : null;
|
|
3056
3548
|
const drawn = useMemo5(() => previewOf(withFolder, d.sessions, projectIdOf), [form.machine, form.project, d.sessions.length]);
|
|
3057
|
-
const [name, setName] =
|
|
3549
|
+
const [name, setName] = useState7(null);
|
|
3058
3550
|
const shownName = name ?? drawn.name;
|
|
3059
3551
|
const missing = missingOf(withFolder, machines);
|
|
3060
3552
|
useEffect8(() => {
|
|
@@ -3094,6 +3586,10 @@ function Launch() {
|
|
|
3094
3586
|
launch();
|
|
3095
3587
|
return;
|
|
3096
3588
|
}
|
|
3589
|
+
if (key.ctrl && input === "t") {
|
|
3590
|
+
onTeam();
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3097
3593
|
if (key.ctrl && input === "r") {
|
|
3098
3594
|
setName(pickName([...d.sessions.map((s) => s.name), shownName]));
|
|
3099
3595
|
return;
|
|
@@ -3143,8 +3639,27 @@ function Launch() {
|
|
|
3143
3639
|
x === cur ? /* @__PURE__ */ jsx13(Text12, { color: C.fg, bold: true, children: `${g.prev} ${label(x)} ${g.next}` }) : /* @__PURE__ */ jsx13(Text12, { color: color?.(x) ?? C.dim, children: label(x) }),
|
|
3144
3640
|
" "
|
|
3145
3641
|
] }, i)) });
|
|
3146
|
-
return /* @__PURE__ */ jsxs10(Frame, { crumb: t.launch.title, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+r", t.keys.redraw], ["esc", t.keys.cancel]], children: [
|
|
3642
|
+
return /* @__PURE__ */ jsxs10(Frame, { crumb: t.launch.title, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["ctrl+r", t.keys.redraw], ["esc", t.keys.cancel]], children: [
|
|
3147
3643
|
/* @__PURE__ */ jsxs10(TitledBox, { title: t.launch.title, flexGrow: 1, children: [
|
|
3644
|
+
/* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3645
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: " " }),
|
|
3646
|
+
/* @__PURE__ */ jsx13(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.launch.mode }) }),
|
|
3647
|
+
/* @__PURE__ */ jsxs10(Text12, { children: [
|
|
3648
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.coral, children: [
|
|
3649
|
+
g.radioOn,
|
|
3650
|
+
" ",
|
|
3651
|
+
t.launch.session
|
|
3652
|
+
] }),
|
|
3653
|
+
" ",
|
|
3654
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.dim, children: [
|
|
3655
|
+
g.radioOff,
|
|
3656
|
+
" ",
|
|
3657
|
+
t.launch.team
|
|
3658
|
+
] }),
|
|
3659
|
+
" ",
|
|
3660
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: "ctrl+t" })
|
|
3661
|
+
] })
|
|
3662
|
+
] }),
|
|
3148
3663
|
row("prompt", t.launch.instruction, /* @__PURE__ */ jsx13(TextInput, { value: form.prompt, onChange: (v) => setForm((f) => ({ ...f, prompt: v })), active: field === "prompt" })),
|
|
3149
3664
|
/* @__PURE__ */ jsx13(Text12, { children: " " }),
|
|
3150
3665
|
row("tool", t.launch.agent, /* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: tools.map((x) => /* @__PURE__ */ jsxs10(Text12, { color: x === form.tool ? C.coral : C.dim, children: [
|
|
@@ -3198,15 +3713,226 @@ function Launch() {
|
|
|
3198
3713
|
] })
|
|
3199
3714
|
] });
|
|
3200
3715
|
}
|
|
3716
|
+
var TEAM_FIELDS = ["project", "folder", "template", "seats", "name"];
|
|
3717
|
+
var AUTONOMY_COLOR = { ask: C.faint, edits: C.dim, auto: C.yellow };
|
|
3718
|
+
function TeamLaunch({ onSession }) {
|
|
3719
|
+
const { client, machines, projects, t, g, toast, go } = useTui();
|
|
3720
|
+
const list = projects?.projects ?? [];
|
|
3721
|
+
const [projectId, setProjectId] = useState7(list[0]?.id ?? null);
|
|
3722
|
+
const project = list.find((p) => p.id === projectId);
|
|
3723
|
+
const folders = useMemo5(() => {
|
|
3724
|
+
const own = (project?.repos ?? []).filter((r) => r.machine === "local").map((r) => r.path);
|
|
3725
|
+
return own.length ? own : (machines.find((m) => m.id === "local")?.recents ?? []).slice(0, 6).map((r) => r.cwd);
|
|
3726
|
+
}, [project, machines]);
|
|
3727
|
+
const [folderAt, setFolderAt] = useState7(0);
|
|
3728
|
+
const cwd = folders[folderAt] ?? folders[0] ?? null;
|
|
3729
|
+
const [field, setField] = useState7("template");
|
|
3730
|
+
const [defs, setDefs] = useState7(null);
|
|
3731
|
+
const [template, setTemplate] = useState7(null);
|
|
3732
|
+
const [seats, setSeats] = useState7([]);
|
|
3733
|
+
const [seatAt, setSeatAt] = useState7(0);
|
|
3734
|
+
const [name, setName] = useState7("");
|
|
3735
|
+
const [launching, setLaunching] = useState7(false);
|
|
3736
|
+
useEditing(true);
|
|
3737
|
+
useEffect8(() => {
|
|
3738
|
+
if (!cwd) {
|
|
3739
|
+
setDefs({ roles: [], templates: [] });
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
setDefs(null);
|
|
3743
|
+
const requestId = `tui-roles-${randomUUID3()}`;
|
|
3744
|
+
const off = client.onMessage((m) => {
|
|
3745
|
+
if (m.type !== "roles.listing" || m.requestId !== requestId) return;
|
|
3746
|
+
off();
|
|
3747
|
+
setDefs({ roles: m.roles, templates: m.templates });
|
|
3748
|
+
setSeats(m.roles.map((role) => ({ role, tool: role.tool ?? "claude", on: false })));
|
|
3749
|
+
setTemplate(m.templates[0]?.id ?? null);
|
|
3750
|
+
setName((n) => n || m.templates[0]?.name || project?.name || "");
|
|
3751
|
+
});
|
|
3752
|
+
client.send({ type: "roles.list", cwd, requestId });
|
|
3753
|
+
return off;
|
|
3754
|
+
}, [cwd]);
|
|
3755
|
+
const tpl = defs?.templates.find((x) => x.id === template);
|
|
3756
|
+
const chosen = tpl ? tpl.seats.map((x) => {
|
|
3757
|
+
const role = defs.roles.find((r) => r.id === x.role);
|
|
3758
|
+
return { id: x.role, tool: x.tool ?? role?.tool ?? "claude", autonomy: role?.autonomy ?? "ask" };
|
|
3759
|
+
}) : seats.filter((x) => x.on).map((x) => ({ id: x.role.id, tool: x.tool, autonomy: x.role.autonomy ?? "ask" }));
|
|
3760
|
+
const auto = chosen.filter((x) => x.autonomy === "auto").map((x) => `@${x.id}`);
|
|
3761
|
+
const teamName = name.trim() || tpl?.name || project?.name || "team";
|
|
3762
|
+
const launch = () => {
|
|
3763
|
+
if (launching || !cwd) return;
|
|
3764
|
+
if (!chosen.length) {
|
|
3765
|
+
toast({ text: t.launch.noSeat, tone: "info" });
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
const rid = randomUUID3();
|
|
3769
|
+
setLaunching(true);
|
|
3770
|
+
const off = client.onMessage((m) => {
|
|
3771
|
+
if (m.type !== "team.launched" && m.type !== "team.failed" || m.rid !== rid) return;
|
|
3772
|
+
off();
|
|
3773
|
+
setLaunching(false);
|
|
3774
|
+
if (m.type === "team.launched") {
|
|
3775
|
+
toast({ text: t.launch.teamLaunched(m.team.name), tone: "ok" });
|
|
3776
|
+
go("overview");
|
|
3777
|
+
} else toast({ text: t.launch.teamFailed(m.reason), tone: "error" });
|
|
3778
|
+
});
|
|
3779
|
+
client.send({ type: "team.launch", rid, cwd, name: teamName, ...tpl ? { template: tpl.id } : { seats: chosen.map((x) => ({ role: x.id, tool: x.tool })) } });
|
|
3780
|
+
};
|
|
3781
|
+
const tools = ["claude", "codex", "cursor", "omp"];
|
|
3782
|
+
const templates = [...(defs?.templates ?? []).map((x) => x.id), null];
|
|
3783
|
+
useInput4((input, key) => {
|
|
3784
|
+
if (key.escape) {
|
|
3785
|
+
go("overview");
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
if (key.ctrl && input === "s") {
|
|
3789
|
+
launch();
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
if (key.ctrl && input === "t") {
|
|
3793
|
+
onSession();
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
if (key.tab || key.downArrow || key.return) {
|
|
3797
|
+
setField((f) => cycle(TEAM_FIELDS, f, key.shift ? -1 : 1));
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
if (key.upArrow) {
|
|
3801
|
+
setField((f) => cycle(TEAM_FIELDS, f, -1));
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
if (field === "name") return;
|
|
3805
|
+
const by = key.rightArrow || input === "l" ? 1 : key.leftArrow || input === "h" ? -1 : 0;
|
|
3806
|
+
if (field === "project" && by) {
|
|
3807
|
+
const ids = list.map((p) => p.id);
|
|
3808
|
+
if (ids.length) {
|
|
3809
|
+
setProjectId((cur) => cycle(ids, cur ?? ids[0], by));
|
|
3810
|
+
setFolderAt(0);
|
|
3811
|
+
}
|
|
3812
|
+
} else if (field === "folder" && by && folders.length) setFolderAt((i) => (i + by + folders.length) % folders.length);
|
|
3813
|
+
else if (field === "template" && by) {
|
|
3814
|
+
const next2 = cycle(templates, template, by);
|
|
3815
|
+
setTemplate(next2);
|
|
3816
|
+
const x = defs?.templates.find((y) => y.id === next2);
|
|
3817
|
+
if (x) setName(x.name);
|
|
3818
|
+
} else if (field === "seats" && !tpl) {
|
|
3819
|
+
if (input === "j") setSeatAt((i) => seats.length ? (i + 1) % seats.length : 0);
|
|
3820
|
+
else if (input === "k") setSeatAt((i) => seats.length ? (i + seats.length - 1) % seats.length : 0);
|
|
3821
|
+
else if (input === " ") setSeats((xs) => xs.map((x, i) => i === seatAt ? { ...x, on: !x.on } : x));
|
|
3822
|
+
else if (by) setSeats((xs) => xs.map((x, i) => i === seatAt ? { ...x, tool: cycle(tools, x.tool, by) } : x));
|
|
3823
|
+
}
|
|
3824
|
+
});
|
|
3825
|
+
const row = (f, label, value) => /* @__PURE__ */ jsxs10(Box11, { backgroundColor: field === f ? C.sel : void 0, children: [
|
|
3826
|
+
/* @__PURE__ */ jsx13(Text12, { color: field === f ? C.coral : C.faint, children: field === f ? "\u258C" : " " }),
|
|
3827
|
+
/* @__PURE__ */ jsx13(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx13(Text12, { color: C.dim, children: label }) }),
|
|
3828
|
+
/* @__PURE__ */ jsx13(Box11, { flexGrow: 1, overflow: "hidden", flexDirection: "column", children: value })
|
|
3829
|
+
] }, f);
|
|
3830
|
+
return /* @__PURE__ */ jsxs10(Frame, { crumb: t.launch.teamTitle, keys: [["tab", t.keys.nextField], ["\u2190 \u2192", t.keys.choose], [t.keys.space, t.keys.tick], ["ctrl+s", t.keys.start], ["ctrl+t", t.keys.teamMode], ["esc", t.keys.cancel]], children: [
|
|
3831
|
+
/* @__PURE__ */ jsxs10(TitledBox, { title: t.launch.teamTitle, flexGrow: 1, children: [
|
|
3832
|
+
/* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3833
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: " " }),
|
|
3834
|
+
/* @__PURE__ */ jsx13(Box11, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsx13(Text12, { color: C.dim, children: t.launch.mode }) }),
|
|
3835
|
+
/* @__PURE__ */ jsxs10(Text12, { children: [
|
|
3836
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.dim, children: [
|
|
3837
|
+
g.radioOff,
|
|
3838
|
+
" ",
|
|
3839
|
+
t.launch.session
|
|
3840
|
+
] }),
|
|
3841
|
+
" ",
|
|
3842
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.coral, children: [
|
|
3843
|
+
g.radioOn,
|
|
3844
|
+
" ",
|
|
3845
|
+
t.launch.team
|
|
3846
|
+
] }),
|
|
3847
|
+
" ",
|
|
3848
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.faint, children: "ctrl+t" })
|
|
3849
|
+
] })
|
|
3850
|
+
] }),
|
|
3851
|
+
row("project", t.launch.project, /* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: list.length === 0 ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.launch.none }) : list.map((p) => /* @__PURE__ */ jsxs10(Text12, { color: p.id === projectId ? C.fg : projectColor(p.color), bold: p.id === projectId, children: [
|
|
3852
|
+
p.id === projectId ? `${g.prev} ${p.name} ${g.next}` : p.name,
|
|
3853
|
+
" "
|
|
3854
|
+
] }, p.id)) })),
|
|
3855
|
+
row("folder", t.launch.folder, /* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: cwd ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
3856
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.fg, children: cwd }),
|
|
3857
|
+
folders.length > 1 ? /* @__PURE__ */ jsxs10(Text12, { color: C.faint, children: [
|
|
3858
|
+
" (",
|
|
3859
|
+
folderAt + 1,
|
|
3860
|
+
"/",
|
|
3861
|
+
folders.length,
|
|
3862
|
+
")"
|
|
3863
|
+
] }) : null
|
|
3864
|
+
] }) : /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.launch.none }) })),
|
|
3865
|
+
row("template", t.launch.template, defs === null ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.launch.loadingRoles }) : /* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: templates.map((x) => /* @__PURE__ */ jsxs10(Text12, { color: x === template ? C.fg : C.dim, bold: x === template, children: [
|
|
3866
|
+
x === template ? `${g.prev} ${x ?? t.launch.custom} ${g.next}` : x ?? t.launch.custom,
|
|
3867
|
+
" "
|
|
3868
|
+
] }, x ?? "-")) })),
|
|
3869
|
+
row("seats", t.launch.seats, tpl ? /* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs10(Text12, { children: [
|
|
3870
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.violet, children: [
|
|
3871
|
+
"@",
|
|
3872
|
+
x.id
|
|
3873
|
+
] }),
|
|
3874
|
+
" ",
|
|
3875
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: TOOL_NAME[x.tool] }),
|
|
3876
|
+
" "
|
|
3877
|
+
] }, x.id)) }) : seats.length === 0 ? /* @__PURE__ */ jsx13(Text12, { color: C.faint, children: t.launch.noRole }) : /* @__PURE__ */ jsx13(Fragment4, { children: seats.map((x, i) => /* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", backgroundColor: field === "seats" && i === seatAt ? "#3a3532" : void 0, children: [
|
|
3878
|
+
/* @__PURE__ */ jsx13(Text12, { color: x.on ? C.green : C.faint, children: x.on ? g.boxOn : g.boxOff }),
|
|
3879
|
+
" ",
|
|
3880
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.violet, children: [
|
|
3881
|
+
"@",
|
|
3882
|
+
x.role.id
|
|
3883
|
+
] }),
|
|
3884
|
+
" ",
|
|
3885
|
+
x.role.label,
|
|
3886
|
+
" ",
|
|
3887
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.dim, children: [
|
|
3888
|
+
g.prev,
|
|
3889
|
+
" ",
|
|
3890
|
+
TOOL_NAME[x.tool],
|
|
3891
|
+
" ",
|
|
3892
|
+
g.next
|
|
3893
|
+
] }),
|
|
3894
|
+
" ",
|
|
3895
|
+
/* @__PURE__ */ jsx13(Text12, { color: AUTONOMY_COLOR[x.role.autonomy ?? "ask"], children: t.ask.autonomy[x.role.autonomy ?? "ask"] })
|
|
3896
|
+
] }, x.role.id)) })),
|
|
3897
|
+
row("name", t.launch.teamName, /* @__PURE__ */ jsx13(TextInput, { value: name, onChange: setName, active: field === "name", placeholder: teamName }))
|
|
3898
|
+
] }),
|
|
3899
|
+
/* @__PURE__ */ jsxs10(TitledBox, { title: t.launch.preview, color: C.coral, titleColor: C.coral, children: [
|
|
3900
|
+
/* @__PURE__ */ jsxs10(Text12, { wrap: "truncate", children: [
|
|
3901
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.coral, children: g.team }),
|
|
3902
|
+
" ",
|
|
3903
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.fg, bold: true, children: t.launch.teamPreview(teamName, project?.name ?? cwd ?? "-") })
|
|
3904
|
+
] }),
|
|
3905
|
+
/* @__PURE__ */ jsx13(Text12, { wrap: "truncate", children: chosen.map((x) => /* @__PURE__ */ jsxs10(Text12, { children: [
|
|
3906
|
+
/* @__PURE__ */ jsxs10(Text12, { color: C.violet, children: [
|
|
3907
|
+
"@",
|
|
3908
|
+
x.id
|
|
3909
|
+
] }),
|
|
3910
|
+
" ",
|
|
3911
|
+
/* @__PURE__ */ jsx13(Text12, { color: C.dim, children: TOOL_NAME[x.tool] }),
|
|
3912
|
+
" ",
|
|
3913
|
+
/* @__PURE__ */ jsx13(Text12, { color: AUTONOMY_COLOR[x.autonomy], children: t.ask.autonomy[x.autonomy] }),
|
|
3914
|
+
" "
|
|
3915
|
+
] }, x.id)) }),
|
|
3916
|
+
auto.length ? /* @__PURE__ */ jsx13(Text12, { color: C.yellow, wrap: "truncate", children: t.ask.autoWarning(auto.join(", ")) }) : null,
|
|
3917
|
+
/* @__PURE__ */ jsxs10(Box11, { marginTop: 1, columnGap: 2, children: [
|
|
3918
|
+
/* @__PURE__ */ jsx13(Button, { label: launching ? t.launch.launching : t.ask.launchTeam, k: "ctrl+s", primary: true }),
|
|
3919
|
+
/* @__PURE__ */ jsx13(Button, { label: t.launch.cancel, k: "esc" })
|
|
3920
|
+
] })
|
|
3921
|
+
] })
|
|
3922
|
+
] });
|
|
3923
|
+
}
|
|
3201
3924
|
|
|
3202
3925
|
// src/tui/screens/Settings.tsx
|
|
3203
|
-
import { useState as
|
|
3926
|
+
import { useState as useState8 } from "react";
|
|
3204
3927
|
import { Box as Box12, Text as Text13 } from "ink";
|
|
3205
3928
|
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3206
3929
|
function Settings() {
|
|
3207
|
-
const
|
|
3208
|
-
const
|
|
3209
|
-
const
|
|
3930
|
+
const tui = useTui();
|
|
3931
|
+
const { t, g, lang, setLang } = tui;
|
|
3932
|
+
const near = useContextNear(tui.client);
|
|
3933
|
+
const known2 = tui.client.fleet?.contextNear() !== void 0;
|
|
3934
|
+
const rows = [{ id: "lang", label: t.settings.language }, { id: "context", label: t.settings.context }];
|
|
3935
|
+
const [at, setAt] = useState8(0);
|
|
3210
3936
|
useScreenKeys((input, key) => {
|
|
3211
3937
|
if (key.upArrow || input === "k") {
|
|
3212
3938
|
setAt((i) => (i - 1 + rows.length) % rows.length);
|
|
@@ -3219,16 +3945,35 @@ function Settings() {
|
|
|
3219
3945
|
const by = key.rightArrow || input === "l" || input === " " || key.return ? 1 : key.leftArrow || input === "h" ? -1 : 0;
|
|
3220
3946
|
if (!by) return false;
|
|
3221
3947
|
if (rows[at].id === "lang") setLang(LANGS[(LANGS.indexOf(lang) + by + LANGS.length) % LANGS.length]);
|
|
3948
|
+
else if (known2) {
|
|
3949
|
+
const pct = Math.min(CONTEXT_NEAR_MAX * 100, Math.max(CONTEXT_NEAR_MIN * 100, Math.round(near * 100) + by * 5));
|
|
3950
|
+
try {
|
|
3951
|
+
tui.client.fleet?.setContextNear(pct / 100);
|
|
3952
|
+
tui.toast({ text: t.settings.contextSaved(pct), tone: "ok" });
|
|
3953
|
+
} catch (e) {
|
|
3954
|
+
tui.toast({ text: e.message, tone: "info" });
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3222
3957
|
return true;
|
|
3223
3958
|
});
|
|
3224
3959
|
return /* @__PURE__ */ jsx14(Frame, { crumb: t.settings.title, keys: [["\u2191 \u2193", t.keys.navigate], ["\u2190 \u2192", t.keys.change], ["esc", t.keys.back]], children: /* @__PURE__ */ jsx14(TitledBox, { title: t.settings.title, flexGrow: 1, children: rows.map((r, i) => /* @__PURE__ */ jsxs11(Box12, { backgroundColor: i === at ? C.sel : void 0, children: [
|
|
3225
3960
|
/* @__PURE__ */ jsx14(Text13, { color: i === at ? C.coral : C.faint, children: i === at ? "\u258C" : " " }),
|
|
3226
3961
|
/* @__PURE__ */ jsx14(Box12, { width: 16, children: /* @__PURE__ */ jsx14(Text13, { color: C.dim, children: r.label }) }),
|
|
3227
|
-
/* @__PURE__ */ jsx14(Text13, { children: LANGS.map((l) => /* @__PURE__ */ jsxs11(Text13, { color: l === lang ? C.fg : C.dim, bold: l === lang, children: [
|
|
3962
|
+
r.id === "lang" ? /* @__PURE__ */ jsx14(Text13, { children: LANGS.map((l) => /* @__PURE__ */ jsxs11(Text13, { color: l === lang ? C.fg : C.dim, bold: l === lang, children: [
|
|
3228
3963
|
l === lang ? `${g.radioOn} ` : `${g.radioOff} `,
|
|
3229
3964
|
t.lang[l],
|
|
3230
3965
|
" "
|
|
3231
|
-
] }, l)) })
|
|
3966
|
+
] }, l)) }) : /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
3967
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.fg, bold: true, children: [
|
|
3968
|
+
"\u25C2 ",
|
|
3969
|
+
Math.round(near * 100),
|
|
3970
|
+
" % \u25B8"
|
|
3971
|
+
] }),
|
|
3972
|
+
/* @__PURE__ */ jsxs11(Text13, { color: C.faint, children: [
|
|
3973
|
+
" ",
|
|
3974
|
+
known2 ? t.settings.contextHint : t.settings.contextUnknown
|
|
3975
|
+
] })
|
|
3976
|
+
] })
|
|
3232
3977
|
] }, r.id)) }) });
|
|
3233
3978
|
}
|
|
3234
3979
|
|
|
@@ -3259,7 +4004,7 @@ function Help() {
|
|
|
3259
4004
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
3260
4005
|
var TOAST_MS = 5e3;
|
|
3261
4006
|
function App(props) {
|
|
3262
|
-
const [lang, setLangState] =
|
|
4007
|
+
const [lang, setLangState] = useState9(props.lang);
|
|
3263
4008
|
return /* @__PURE__ */ jsx16(I18nProvider, { lang, children: /* @__PURE__ */ jsx16(Shell, { ...props, lang, setLang: setLangState }) });
|
|
3264
4009
|
}
|
|
3265
4010
|
function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs, env, run }) {
|
|
@@ -3271,14 +4016,16 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
3271
4016
|
const d = useDaemon();
|
|
3272
4017
|
const machines = useMachines(client);
|
|
3273
4018
|
const projects = useProjects(client);
|
|
4019
|
+
const teams = useTeams(client);
|
|
4020
|
+
const tasks = useTasks(client);
|
|
3274
4021
|
const fleet = client.fleet;
|
|
3275
4022
|
const t = MESSAGES[lang];
|
|
3276
|
-
const [screen, setScreen] =
|
|
3277
|
-
const [back, setBack] =
|
|
3278
|
-
const [selectedId, setSelectedId] =
|
|
3279
|
-
const [lastToast, setToast] =
|
|
3280
|
-
const [editing, setEditingState] =
|
|
3281
|
-
const [now, setNow] =
|
|
4023
|
+
const [screen, setScreen] = useState9("overview");
|
|
4024
|
+
const [back, setBack] = useState9("overview");
|
|
4025
|
+
const [selectedId, setSelectedId] = useState9(null);
|
|
4026
|
+
const [lastToast, setToast] = useState9(null);
|
|
4027
|
+
const [editing, setEditingState] = useState9(false);
|
|
4028
|
+
const [now, setNow] = useState9(() => Date.now());
|
|
3282
4029
|
const keys = useRef8(null);
|
|
3283
4030
|
useEffect9(() => {
|
|
3284
4031
|
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
@@ -3291,7 +4038,7 @@ function Shell({ client, lang, setLang: setLangState, saveLang, notify, glyphs,
|
|
|
3291
4038
|
}, [lastToast]);
|
|
3292
4039
|
const toast = useCallback((x) => setToast(x), []);
|
|
3293
4040
|
const setEditing = useCallback((on) => setEditingState(on), []);
|
|
3294
|
-
const view = useMemo6(() => overview(d.sessions, projects), [d.sessions, projects]);
|
|
4041
|
+
const view = useMemo6(() => overview(d.sessions, projects, teams ?? [], tasks), [d.sessions, projects, teams, tasks]);
|
|
3295
4042
|
const order = useRef8([]);
|
|
3296
4043
|
useEffect9(() => {
|
|
3297
4044
|
setSelectedId((cur) => cur === null && view.yours[0] ? view.yours[0].id : keepSelection(view.order, cur, order.current));
|
|
@@ -3444,7 +4191,10 @@ var UNICODE = {
|
|
|
3444
4191
|
prev: "\u25C2",
|
|
3445
4192
|
next: "\u25B8",
|
|
3446
4193
|
ellipsis: "\u2026",
|
|
3447
|
-
bar: "\u2502"
|
|
4194
|
+
bar: "\u2502",
|
|
4195
|
+
/** A team's table and the common table (roles spec §3.9). */
|
|
4196
|
+
team: "\u25A3",
|
|
4197
|
+
common: "\u25AD"
|
|
3448
4198
|
};
|
|
3449
4199
|
var ASCII = {
|
|
3450
4200
|
approval: "!",
|
|
@@ -3475,7 +4225,9 @@ var ASCII = {
|
|
|
3475
4225
|
prev: "<",
|
|
3476
4226
|
next: ">",
|
|
3477
4227
|
ellipsis: "...",
|
|
3478
|
-
bar: "|"
|
|
4228
|
+
bar: "|",
|
|
4229
|
+
team: "[T]",
|
|
4230
|
+
common: "[ ]"
|
|
3479
4231
|
};
|
|
3480
4232
|
function glyphsFor(env) {
|
|
3481
4233
|
if (env.LEDGE_TUI_ASCII === "1") return ASCII;
|
|
@@ -3538,7 +4290,8 @@ async function runTui(args) {
|
|
|
3538
4290
|
client.connect();
|
|
3539
4291
|
const app = render(
|
|
3540
4292
|
/* @__PURE__ */ jsx17(App, { client, lang, saveLang: (next2) => saveSettings({ ...loadSettings(), lang: next2 }), notify: parsed.notify, glyphs: glyphsFor(process.env), env: process.env }),
|
|
3541
|
-
|
|
4293
|
+
// Incremental: a key rewrites the lines it changed, not the whole screen erased then drawn again (the flicker).
|
|
4294
|
+
{ alternateScreen: true, exitOnCtrlC: false, maxFps: 30, incrementalRendering: true }
|
|
3542
4295
|
);
|
|
3543
4296
|
const stop = () => app.unmount();
|
|
3544
4297
|
process.once("SIGTERM", stop);
|