@sealant/mend 0.35.0 → 0.36.0-next.602
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/{chunk-SMH65F6Z.js → chunk-5MPE4D2O.js} +559 -4
- package/dist/{dashboard-MGR4YUFD.js → dashboard-AVABQC2T.js} +225 -98
- package/dist/main.js +2559 -530
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +7 -7
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect-pi.1 +28 -0
- package/man/mend-connect.1 +5 -3
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-repo.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-folder-create.1 +1 -1
- package/man/mend-folder-list.1 +1 -1
- package/man/mend-folder-push.1 +1 -1
- package/man/mend-folder-rm.1 +1 -1
- package/man/mend-git-author.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-invite.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-land.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-members.1 +1 -1
- package/man/mend-memory-import.1 +23 -0
- package/man/mend-memory-rm.1 +15 -0
- package/man/mend-memory-show.1 +15 -0
- package/man/mend-memory.1 +32 -0
- package/man/mend-models.1 +22 -0
- package/man/mend-operator-exposure.1 +1 -1
- package/man/mend-operator-gate.1 +1 -1
- package/man/mend-operator-grant-owner.1 +1 -1
- package/man/mend-operator-org-create.1 +1 -1
- package/man/mend-operator-org-invite-owner.1 +1 -1
- package/man/mend-operator-org-list.1 +1 -1
- package/man/mend-operator-org-rename.1 +1 -1
- package/man/mend-operator-reset-link.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-pull.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-secrets-add.1 +24 -0
- package/man/mend-secrets-rm.1 +14 -0
- package/man/mend-secrets.1 +18 -0
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +1 -1
- package/man/mend-server-setup.1 +27 -3
- package/man/mend-server-start.1 +1 -1
- package/man/mend-server-status.1 +5 -3
- package/man/mend-server-stop.1 +3 -3
- package/man/mend-server-upgrade.1 +7 -2
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-session-share.1 +2 -2
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-snake.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-uninstall.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees-rm.1 +29 -0
- package/man/mend-worktrees.1 +2 -2
- package/man/mend.1 +35 -5
- package/package.json +21 -39
|
@@ -9,13 +9,15 @@ import {
|
|
|
9
9
|
cwdFacts,
|
|
10
10
|
eventFamilies,
|
|
11
11
|
isPendingId,
|
|
12
|
+
launchPhaseOf,
|
|
12
13
|
matchProjectByCwd,
|
|
13
14
|
mayStillBeWorking,
|
|
14
15
|
normalizeProjectName,
|
|
15
16
|
pendingId,
|
|
16
17
|
repositoryCloneUrlIssue,
|
|
17
|
-
servicesHoldLine
|
|
18
|
-
|
|
18
|
+
servicesHoldLine,
|
|
19
|
+
withoutLaunchPhase
|
|
20
|
+
} from "./chunk-5MPE4D2O.js";
|
|
19
21
|
|
|
20
22
|
// src/dashboard.tsx
|
|
21
23
|
import { CliRenderEvents, createCliRenderer } from "@opentui/core";
|
|
@@ -127,8 +129,9 @@ var deriveProjects = (data) => (data?.projects ?? []).map((project) => {
|
|
|
127
129
|
const detail = data?.details.get(project.id);
|
|
128
130
|
const sessions = detail?.sessions ?? [];
|
|
129
131
|
const live = sessions.filter((s) => LIVE_STATUSES.has(s.status)).length;
|
|
132
|
+
const stopping = sessions.filter((s) => s.status === "stopping").length;
|
|
130
133
|
const open = (detail?.annotations ?? []).reduce((sum, a) => sum + a.openComments, 0);
|
|
131
|
-
return { project, total: sessions.length, live, open };
|
|
134
|
+
return { project, total: sessions.length, live, stopping, open };
|
|
132
135
|
});
|
|
133
136
|
var bySessionRecency = (a, b) => {
|
|
134
137
|
const aLive = LIVE_STATUSES.has(a.status) ? 1 : 0;
|
|
@@ -142,7 +145,10 @@ var toSessionItem = (data, detail) => (session) => ({
|
|
|
142
145
|
services: data.servicesBySession.get(session.id) ?? [],
|
|
143
146
|
processes: data.processesBySession.get(session.id) ?? []
|
|
144
147
|
});
|
|
145
|
-
var isDeadEnd = (session) =>
|
|
148
|
+
var isDeadEnd = (session) => (
|
|
149
|
+
// A stopping session may still be saving, or kept for recovery: never hidden (story S23).
|
|
150
|
+
!LIVE_STATUSES.has(session.status) && session.status !== "stopping" && session.hasTranscript === false
|
|
151
|
+
);
|
|
146
152
|
var deriveWorktrees = (data, projectId) => {
|
|
147
153
|
if (data === void 0 || projectId === null) return [];
|
|
148
154
|
const detail = data.details.get(projectId);
|
|
@@ -199,13 +205,13 @@ var deriveHarnesses = (resuming) => {
|
|
|
199
205
|
{
|
|
200
206
|
harness: null,
|
|
201
207
|
label: resuming.harness,
|
|
202
|
-
hint: "same harness \
|
|
208
|
+
hint: "same harness \xB7 native resume, conversation intact"
|
|
203
209
|
},
|
|
204
210
|
...others.map(
|
|
205
211
|
(harness) => ({
|
|
206
212
|
harness,
|
|
207
213
|
label: harness,
|
|
208
|
-
hint: harness === "shell" ? "a bash in the worktree \
|
|
214
|
+
hint: harness === "shell" ? "a bash in the worktree \xB7 resume either agent from inside" : "the conversation crosses as a distilled prompt"
|
|
209
215
|
})
|
|
210
216
|
)
|
|
211
217
|
];
|
|
@@ -214,7 +220,7 @@ var deriveHarnesses = (resuming) => {
|
|
|
214
220
|
(harness) => ({
|
|
215
221
|
harness,
|
|
216
222
|
label: harness,
|
|
217
|
-
hint: harness === "shell" ? "a plain bash session \
|
|
223
|
+
hint: harness === "shell" ? "a plain bash session \xB7 new worktree, recorded" : `mend ${harness} \xB7 new worktree, recorded session`
|
|
218
224
|
})
|
|
219
225
|
);
|
|
220
226
|
};
|
|
@@ -233,11 +239,81 @@ var groupActivityAt = (group) => group.sessions.reduce(
|
|
|
233
239
|
group.createdAt
|
|
234
240
|
);
|
|
235
241
|
var foldGroupStatus = (group) => {
|
|
236
|
-
const statuses = group.sessions.map((item) => item.session.status);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return
|
|
242
|
+
const statuses = new Set(group.sessions.map((item) => item.session.status));
|
|
243
|
+
for (const word of ["waiting", "running", "starting", "stopping", "idle"]) {
|
|
244
|
+
if (statuses.has(word)) return word;
|
|
245
|
+
}
|
|
246
|
+
return "settled";
|
|
247
|
+
};
|
|
248
|
+
var liveCountWords = (live, stopping) => {
|
|
249
|
+
const words = [
|
|
250
|
+
...live > 0 ? [`${live} live`] : [],
|
|
251
|
+
...stopping > 0 ? [`${stopping} stopping`] : []
|
|
252
|
+
];
|
|
253
|
+
return words.length === 0 ? "settled" : words.join(" \xB7 ");
|
|
254
|
+
};
|
|
255
|
+
var elapsedWords = (iso, now = Date.now()) => {
|
|
256
|
+
const ms = now - new Date(iso).getTime();
|
|
257
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
258
|
+
const minutes = Math.floor(ms / 6e4);
|
|
259
|
+
if (minutes < 1) return "<1m";
|
|
260
|
+
if (minutes < 60) return `${minutes}m`;
|
|
261
|
+
const hours = Math.floor(minutes / 60);
|
|
262
|
+
if (hours < 24) return `${hours}h`;
|
|
263
|
+
return `${Math.floor(hours / 24)}d`;
|
|
264
|
+
};
|
|
265
|
+
var startingWordsOf = (session) => {
|
|
266
|
+
if (isPendingId(session.id)) return "launching";
|
|
267
|
+
const phase = launchPhaseOf(session.summary);
|
|
268
|
+
if (phase === null) return "launching";
|
|
269
|
+
switch (phase.kind) {
|
|
270
|
+
case "waiting-previous":
|
|
271
|
+
return phase.words.endsWith("is saving") ? "waiting for the previous save" : "waiting for the previous session";
|
|
272
|
+
case "booting":
|
|
273
|
+
return "booting";
|
|
274
|
+
case "preparing":
|
|
275
|
+
return "preparing the workspace";
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var startingExplanationOf = (session) => {
|
|
279
|
+
const phase = isPendingId(session.id) ? null : launchPhaseOf(session.summary);
|
|
280
|
+
if (phase === null) return "launching";
|
|
281
|
+
switch (phase.kind) {
|
|
282
|
+
case "waiting-previous":
|
|
283
|
+
return phase.words.endsWith("is saving") ? "the previous session in this worktree is still saving \xB7 this one starts from its save" : phase.words;
|
|
284
|
+
case "booting":
|
|
285
|
+
return "the workspace is booting \xB7 the agent starts when it is up";
|
|
286
|
+
case "preparing":
|
|
287
|
+
return "the workspace has no runtime yet \xB7 after an update, building its image takes about 8 minutes";
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var sessionTimeWords = (item, now = Date.now()) => {
|
|
291
|
+
const { session } = item;
|
|
292
|
+
if (session.status === "starting" || session.status === "stopping") return null;
|
|
293
|
+
const agent = item.annotation?.currentAgent ?? null;
|
|
294
|
+
const created = elapsedWords(session.createdAt, now);
|
|
295
|
+
const createdWords = created === null ? null : `created ${agoWords(created)}`;
|
|
296
|
+
if (LIVE_STATUSES.has(session.status)) {
|
|
297
|
+
if (agent === null || agent.createdAt === void 0) return createdWords;
|
|
298
|
+
if (agent.exitedAt !== null) {
|
|
299
|
+
const ended2 = elapsedWords(agent.exitedAt, now);
|
|
300
|
+
return ended2 === null ? createdWords : `agent ended ${agoWords(ended2)}`;
|
|
301
|
+
}
|
|
302
|
+
const up = elapsedWords(agent.createdAt, now);
|
|
303
|
+
if (up === null) return createdWords;
|
|
304
|
+
if (agent.kind === "agent-external") return `activity seen ${agoWords(up)}`;
|
|
305
|
+
return session.harness === "shell" ? `shell up ${up}` : `up ${up}`;
|
|
306
|
+
}
|
|
307
|
+
const settledAt = session.settledAt ?? null;
|
|
308
|
+
const ended = settledAt === null ? null : elapsedWords(settledAt, now);
|
|
309
|
+
return ended === null ? createdWords : `ended ${agoWords(ended)}`;
|
|
310
|
+
};
|
|
311
|
+
var agoWords = (elapsed) => elapsed === "<1m" ? "just now" : `${elapsed} ago`;
|
|
312
|
+
var sessionStateWords = (item, now = Date.now()) => {
|
|
313
|
+
const hold = sessionHold(item);
|
|
314
|
+
if (hold !== null) return hold;
|
|
315
|
+
if (item.session.status === "starting") return startingWordsOf(item.session);
|
|
316
|
+
return sessionTimeWords(item, now);
|
|
241
317
|
};
|
|
242
318
|
var liveShellOf = (processes) => processes.findLast((process2) => process2.kind === "shell" && process2.exitedAt === null) ?? null;
|
|
243
319
|
var liveProtocolOf = (processes) => processes.find((process2) => process2.kind === "agent-protocol" && process2.exitedAt === null) ?? null;
|
|
@@ -276,7 +352,14 @@ var markSessionStopped = (data, sessionId) => {
|
|
|
276
352
|
return {
|
|
277
353
|
...mapWorkbenchSessions(
|
|
278
354
|
data,
|
|
279
|
-
(session) =>
|
|
355
|
+
(session) => (
|
|
356
|
+
// Asked, not observed (story S28): `stopping` until the server says how it ended. A stop
|
|
357
|
+
// leaves Services running, so a session with Services reads as stopped with what holds it.
|
|
358
|
+
session.id === sessionId ? {
|
|
359
|
+
...session,
|
|
360
|
+
status: (data.servicesBySession.get(sessionId) ?? []).length > 0 ? "stopped" : "stopping"
|
|
361
|
+
} : session
|
|
362
|
+
)
|
|
280
363
|
),
|
|
281
364
|
processesBySession
|
|
282
365
|
};
|
|
@@ -290,6 +373,13 @@ var AGENT_WORKING = /* @__PURE__ */ new Set(["starting", "running", "waiting"]);
|
|
|
290
373
|
var sessionHold = (item) => {
|
|
291
374
|
const capture = captureLineOf(item.session);
|
|
292
375
|
if (capture !== null) return capture;
|
|
376
|
+
if (item.session.status === "stopping") {
|
|
377
|
+
const agent = item.annotation?.currentAgent ?? null;
|
|
378
|
+
return agent !== null && agent.exitedAt !== null ? "agent ended \xB7 workspace end not confirmed" : "workspace end not confirmed";
|
|
379
|
+
}
|
|
380
|
+
return servicesHoldOf(item);
|
|
381
|
+
};
|
|
382
|
+
var servicesHoldOf = (item) => {
|
|
293
383
|
const agent = item.annotation?.currentAgent ?? null;
|
|
294
384
|
const agentProcessLive = item.processes.some(
|
|
295
385
|
(process2) => process2.kind !== "shell" && process2.exitedAt === null
|
|
@@ -300,6 +390,24 @@ var sessionHold = (item) => {
|
|
|
300
390
|
liveServices: Math.max(item.annotation?.liveServices ?? 0, item.services.length)
|
|
301
391
|
});
|
|
302
392
|
};
|
|
393
|
+
var sessionRowFacts = (item, width, now = Date.now()) => {
|
|
394
|
+
const agents = item.processes.filter((process2) => process2.kind !== "shell").length;
|
|
395
|
+
const shells = item.processes.filter((process2) => process2.kind === "shell").length;
|
|
396
|
+
const services = item.services.length;
|
|
397
|
+
const state = sessionStateWords(item, now);
|
|
398
|
+
const stateWidth = width > 40 ? width - 9 : width - 2;
|
|
399
|
+
return fitHints(
|
|
400
|
+
[
|
|
401
|
+
...state === null ? [] : [cut(state, stateWidth)],
|
|
402
|
+
item.session.harness,
|
|
403
|
+
...shells > 0 ? [`${shells} shell`] : [],
|
|
404
|
+
...agents > 0 ? [`${agents} agent`] : [],
|
|
405
|
+
...services > 0 && servicesHoldOf(item) === null ? [`${services} service`] : []
|
|
406
|
+
],
|
|
407
|
+
width
|
|
408
|
+
);
|
|
409
|
+
};
|
|
410
|
+
var cut = (text, width) => text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
303
411
|
var removeWorktreeGroup = (data, projectId, group) => {
|
|
304
412
|
const memberIds = new Set(group.sessions.map((item) => item.session.id));
|
|
305
413
|
const detail = data.details.get(projectId);
|
|
@@ -344,12 +452,12 @@ var filterBranches = (branches, query) => {
|
|
|
344
452
|
};
|
|
345
453
|
var baseStepNotice = (state) => {
|
|
346
454
|
if (state.branchError !== null) {
|
|
347
|
-
return `branches unreadable \
|
|
455
|
+
return `branches unreadable \xB7 ${state.branchError} \xB7 enter uses the default`;
|
|
348
456
|
}
|
|
349
457
|
if (state.branches === null) return "reading branches\u2026";
|
|
350
|
-
if (state.branches.length === 0) return "no branches read \
|
|
458
|
+
if (state.branches.length === 0) return "no branches read \xB7 enter uses the default branch";
|
|
351
459
|
if (filterBranches(state.branches, state.query).length === 0) {
|
|
352
|
-
return "no branch matches \
|
|
460
|
+
return "no branch matches \xB7 enter uses the default branch";
|
|
353
461
|
}
|
|
354
462
|
return null;
|
|
355
463
|
};
|
|
@@ -2155,7 +2263,9 @@ var STATUS_COLOR = {
|
|
|
2155
2263
|
idle: INK_2,
|
|
2156
2264
|
completed: FAINT,
|
|
2157
2265
|
failed: ERROR,
|
|
2158
|
-
stopped: FAINT
|
|
2266
|
+
stopped: FAINT,
|
|
2267
|
+
stopping: INK_2,
|
|
2268
|
+
settled: FAINT
|
|
2159
2269
|
};
|
|
2160
2270
|
var AGENT_LIVE_STATUSES = /* @__PURE__ */ new Set(["starting", "running", "waiting"]);
|
|
2161
2271
|
var timeAgo = (iso) => {
|
|
@@ -2168,7 +2278,13 @@ var timeAgo = (iso) => {
|
|
|
2168
2278
|
if (hours < 24) return `${hours}h ago`;
|
|
2169
2279
|
return `${Math.floor(hours / 24)}d ago`;
|
|
2170
2280
|
};
|
|
2171
|
-
var
|
|
2281
|
+
var lastSessionWords = (group) => {
|
|
2282
|
+
const at = group.sessions.length === 0 ? group.createdAt : groupActivityAt(group);
|
|
2283
|
+
const elapsed = elapsedWords(at);
|
|
2284
|
+
if (elapsed === null) return [];
|
|
2285
|
+
const ago = elapsed === "<1m" ? "just now" : `${elapsed} ago`;
|
|
2286
|
+
return [group.sessions.length === 0 ? `created ${ago}` : `last session created ${ago}`];
|
|
2287
|
+
};
|
|
2172
2288
|
var fit = (text, width) => text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}\u2026`;
|
|
2173
2289
|
var COLUMN_TITLE = {
|
|
2174
2290
|
projects: "projects",
|
|
@@ -2234,7 +2350,7 @@ var ProjectRow = ({
|
|
|
2234
2350
|
return /* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: selected ? WASH : "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
2235
2351
|
/* @__PURE__ */ jsx3(Gutter, { selected }),
|
|
2236
2352
|
/* @__PURE__ */ jsx3("span", { fg: INK, children: fit(item.project.name, nameWidth).padEnd(nameWidth + 1) }),
|
|
2237
|
-
/* @__PURE__ */ jsx3("span", { fg: item.live > 0 ? MUTED : FAINT, children: item.live > 0 ? `${item.live}/${item.total}` : String(item.total) })
|
|
2353
|
+
/* @__PURE__ */ jsx3("span", { fg: item.live + item.stopping > 0 ? MUTED : FAINT, children: item.live + item.stopping > 0 ? `${item.live + item.stopping}/${item.total}` : String(item.total) })
|
|
2238
2354
|
] }) });
|
|
2239
2355
|
};
|
|
2240
2356
|
var WorktreeRow = ({
|
|
@@ -2242,18 +2358,16 @@ var WorktreeRow = ({
|
|
|
2242
2358
|
selected,
|
|
2243
2359
|
width
|
|
2244
2360
|
}) => {
|
|
2245
|
-
const
|
|
2246
|
-
const
|
|
2247
|
-
const status = live ? folded : "settled";
|
|
2248
|
-
const color = live ? STATUS_COLOR[folded] ?? MUTED : FAINT;
|
|
2361
|
+
const status = foldGroupStatus(group);
|
|
2362
|
+
const color = STATUS_COLOR[status] ?? MUTED;
|
|
2249
2363
|
const open = group.annotation?.openComments ?? 0;
|
|
2250
2364
|
const sessions = group.sessions.length;
|
|
2251
2365
|
const nameWidth = Math.max(6, width - 5 - status.length);
|
|
2252
2366
|
const factWidth = Math.max(8, width - 3);
|
|
2253
2367
|
const facts = fitHints(
|
|
2254
2368
|
[
|
|
2255
|
-
fit(groupBaseLabel(group), factWidth),
|
|
2256
|
-
|
|
2369
|
+
fit(`from ${groupBaseLabel(group)}`, Math.max(12, Math.floor(factWidth / 2))),
|
|
2370
|
+
...lastSessionWords(group),
|
|
2257
2371
|
...open > 0 ? [`${open} open`] : [],
|
|
2258
2372
|
...sessions === 0 ? ["empty"] : sessions === 1 ? [] : [`${sessions} sessions`]
|
|
2259
2373
|
],
|
|
@@ -2273,25 +2387,12 @@ var SessionRow = ({
|
|
|
2273
2387
|
selected,
|
|
2274
2388
|
width
|
|
2275
2389
|
}) => {
|
|
2276
|
-
const { session
|
|
2390
|
+
const { session } = item;
|
|
2277
2391
|
const status = session.status;
|
|
2278
2392
|
const color = STATUS_COLOR[status] ?? MUTED;
|
|
2279
|
-
const agents = processes.filter((process2) => process2.kind !== "shell").length;
|
|
2280
|
-
const shells = processes.filter((process2) => process2.kind === "shell").length;
|
|
2281
2393
|
const nameWidth = Math.max(6, width - 5 - status.length);
|
|
2282
2394
|
const factWidth = Math.max(8, width - 3);
|
|
2283
|
-
const
|
|
2284
|
-
const facts = fitHints(
|
|
2285
|
-
[
|
|
2286
|
-
...hold === null ? [] : [hold],
|
|
2287
|
-
session.harness,
|
|
2288
|
-
shortAge(session.createdAt),
|
|
2289
|
-
...shells > 0 ? [`${shells} shell`] : [],
|
|
2290
|
-
...agents > 0 ? [`${agents} agent`] : [],
|
|
2291
|
-
...hold === null && services.length > 0 ? [`${services.length} service`] : []
|
|
2292
|
-
],
|
|
2293
|
-
factWidth
|
|
2294
|
-
);
|
|
2395
|
+
const facts = sessionRowFacts(item, factWidth);
|
|
2295
2396
|
return /* @__PURE__ */ jsxs3("box", { flexShrink: 0, flexDirection: "column", backgroundColor: selected ? WASH : "transparent", children: [
|
|
2296
2397
|
/* @__PURE__ */ jsx3("box", { height: 1, flexShrink: 0, backgroundColor: "transparent", children: /* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
2297
2398
|
/* @__PURE__ */ jsx3(Gutter, { selected }),
|
|
@@ -2341,7 +2442,10 @@ var SessionFacts = ({
|
|
|
2341
2442
|
const { session, annotation, services } = item;
|
|
2342
2443
|
const color = STATUS_COLOR[session.status] ?? MUTED;
|
|
2343
2444
|
const hold = sessionHold(item);
|
|
2344
|
-
const
|
|
2445
|
+
const starting = session.status === "starting";
|
|
2446
|
+
const phase = starting ? startingWordsOf(session) : null;
|
|
2447
|
+
const summary = (starting ? withoutLaunchPhase(session.summary) : session.summary)?.split("\n")[0] ?? null;
|
|
2448
|
+
const when = starting ? null : sessionTimeWords(item);
|
|
2345
2449
|
const change = annotation ?? group?.annotation;
|
|
2346
2450
|
const lines = [
|
|
2347
2451
|
/* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
@@ -2352,6 +2456,10 @@ var SessionFacts = ({
|
|
|
2352
2456
|
/* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
2353
2457
|
/* @__PURE__ */ jsx3("span", { children: " " }),
|
|
2354
2458
|
/* @__PURE__ */ jsx3("span", { fg: color, children: session.status }),
|
|
2459
|
+
phase === null ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
2460
|
+
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
|
|
2461
|
+
/* @__PURE__ */ jsx3("span", { fg: INK_2, children: phase })
|
|
2462
|
+
] }),
|
|
2355
2463
|
hold === null ? null : /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
2356
2464
|
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
|
|
2357
2465
|
/* @__PURE__ */ jsx3("span", { fg: INK_2, children: hold })
|
|
@@ -2360,7 +2468,11 @@ var SessionFacts = ({
|
|
|
2360
2468
|
/* @__PURE__ */ jsx3("span", { fg: MUTED, children: session.branch }),
|
|
2361
2469
|
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: session.baseSha === "" ? "" : ` vs ${session.baseRef ?? session.baseSha.slice(0, 12)}` })
|
|
2362
2470
|
] }, "status"),
|
|
2363
|
-
/* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children:
|
|
2471
|
+
/* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: [
|
|
2472
|
+
...timeAgo(session.createdAt) === "" ? [] : [`created ${timeAgo(session.createdAt)}`],
|
|
2473
|
+
isPendingId(session.id) ? "provisioning" : session.id.slice(0, 8),
|
|
2474
|
+
...when === null || when.startsWith("created ") ? [] : [when]
|
|
2475
|
+
].join(" \xB7 ").replace(/^/, " ") }, "started"),
|
|
2364
2476
|
/* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
2365
2477
|
/* @__PURE__ */ jsx3("span", { children: " " }),
|
|
2366
2478
|
services.length === 0 ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "no services running" }) : services.slice(0, 2).map((service, index) => {
|
|
@@ -2409,7 +2521,7 @@ var StatusLine = ({
|
|
|
2409
2521
|
}
|
|
2410
2522
|
return void 0;
|
|
2411
2523
|
}, [busy, status]);
|
|
2412
|
-
const text = busy === null ? status !== null && Date.now() - status.at < 5e3 ? ` ${status.text}` : "" : ` ${busy} ${Math.max(0, Math.round((now - busyStarted) / 1e3))}s`;
|
|
2524
|
+
const text = busy === null ? status !== null && Date.now() - status.at < 5e3 ? ` ${status.text}` : "" : ` ${busy} ${Math.max(0, Math.round((now - busyStarted) / 1e3))}s elapsed`;
|
|
2413
2525
|
return /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: text });
|
|
2414
2526
|
};
|
|
2415
2527
|
var errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -2421,7 +2533,7 @@ var startedOrStarting = async (start, starting) => {
|
|
|
2421
2533
|
throw error;
|
|
2422
2534
|
}
|
|
2423
2535
|
};
|
|
2424
|
-
var startedLine = (session) => session.status === "starting" ? `still starting \xB7 ${sessionDisplayName(session)} \
|
|
2536
|
+
var startedLine = (session) => session.status === "starting" ? `still starting \xB7 ${sessionDisplayName(session)} \xB7 a attaches once the row reads running` : `started \xB7 ${sessionDisplayName(session)} \xB7 a attaches`;
|
|
2425
2537
|
var keepSpanVisible = (scroll, top, height, viewRows) => {
|
|
2426
2538
|
if (scroll === null || viewRows <= 0) return;
|
|
2427
2539
|
const viewH = Math.max(1, viewRows);
|
|
@@ -2563,9 +2675,9 @@ var App = ({ ctx, onQuit }) => {
|
|
|
2563
2675
|
return share.subscribe((event) => {
|
|
2564
2676
|
if (event === null) return;
|
|
2565
2677
|
if (event.kind === "sign-requested") {
|
|
2566
|
-
say(`\u270E signature requested
|
|
2678
|
+
say(`\u270E signature requested \xB7 ${event.context} \xB7 touch your key if it blinks`);
|
|
2567
2679
|
} else if (event.kind === "signed") say(`\u2713 signed (${event.seconds.toFixed(1)}s)`);
|
|
2568
|
-
else if (event.kind === "not-signed") say(`\u2717 not signed \
|
|
2680
|
+
else if (event.kind === "not-signed") say(`\u2717 not signed \xB7 ${event.message}`);
|
|
2569
2681
|
});
|
|
2570
2682
|
}, [ctx.agentShare]);
|
|
2571
2683
|
useEffect3(() => {
|
|
@@ -2622,7 +2734,7 @@ ${tunneled}
|
|
|
2622
2734
|
const liveProtocol = liveProtocolOf(detail.processes);
|
|
2623
2735
|
const existing = liveShellOf(detail.processes);
|
|
2624
2736
|
if (liveProtocol !== null) {
|
|
2625
|
-
process.stdout.write(`taking over from the protocol session \
|
|
2737
|
+
process.stdout.write(`taking over from the protocol session \xB7 same conversation\u2026
|
|
2626
2738
|
|
|
2627
2739
|
`);
|
|
2628
2740
|
await ctx.api("POST", `/sessions/${session.id}/handoff`, { to: "pty" });
|
|
@@ -2632,12 +2744,12 @@ ${tunneled}
|
|
|
2632
2744
|
"POST",
|
|
2633
2745
|
`/sessions/${session.id}/shell`
|
|
2634
2746
|
);
|
|
2635
|
-
process.stdout.write(`no live terminal \
|
|
2747
|
+
process.stdout.write(`no live terminal \xB7 opened a shell in the workspace
|
|
2636
2748
|
|
|
2637
2749
|
`);
|
|
2638
2750
|
outcome = await ctx.attachTty(session.id, "shell", shell.id);
|
|
2639
2751
|
} else {
|
|
2640
|
-
process.stdout.write(`no live terminal \
|
|
2752
|
+
process.stdout.write(`no live terminal \xB7 rejoining the open shell
|
|
2641
2753
|
|
|
2642
2754
|
`);
|
|
2643
2755
|
outcome = await ctx.attachTty(session.id, "shell", existing.id);
|
|
@@ -2651,7 +2763,7 @@ ${tunneled}
|
|
|
2651
2763
|
lockRef.current = false;
|
|
2652
2764
|
}
|
|
2653
2765
|
say(
|
|
2654
|
-
outcome === "unavailable" ? "attach unavailable \
|
|
2766
|
+
outcome === "unavailable" ? "attach unavailable \xB7 could not connect" : outcome === "no-answer" ? `no answer \xB7 the server did not open ${short}'s terminal; it keeps running \xB7 a tries again` : outcome === "detached" || outcome === "interrupted" ? `detached \xB7 ${short} keeps running` : outcome === "dropped" ? `disconnected \xB7 ${short} \xB7 refreshing session status` : `terminal ended \xB7 ${short} \xB7 refreshing session status`
|
|
2655
2767
|
);
|
|
2656
2768
|
refetch();
|
|
2657
2769
|
};
|
|
@@ -2665,8 +2777,9 @@ ${tunneled}
|
|
|
2665
2777
|
};
|
|
2666
2778
|
const launchMutation = useMutation2({
|
|
2667
2779
|
mutationFn: async (vars) => {
|
|
2668
|
-
|
|
2669
|
-
|
|
2780
|
+
if (HARNESS_COMMANDS[vars.harness] === void 0) {
|
|
2781
|
+
throw new Error(`unknown harness "${vars.harness}"`);
|
|
2782
|
+
}
|
|
2670
2783
|
const session = await ctx.api("POST", `/projects/${vars.projectId}/sessions`, {
|
|
2671
2784
|
harness: vars.harness,
|
|
2672
2785
|
label: null,
|
|
@@ -2674,7 +2787,7 @@ ${tunneled}
|
|
|
2674
2787
|
base: vars.base
|
|
2675
2788
|
});
|
|
2676
2789
|
return startedOrStarting(
|
|
2677
|
-
ctx.api("POST", `/sessions/${session.id}/launch`, {
|
|
2790
|
+
ctx.api("POST", `/sessions/${session.id}/launch`, {}),
|
|
2678
2791
|
session
|
|
2679
2792
|
);
|
|
2680
2793
|
},
|
|
@@ -2695,7 +2808,7 @@ ${tunneled}
|
|
|
2695
2808
|
);
|
|
2696
2809
|
setWorktreeKey(vars.pendingKey);
|
|
2697
2810
|
selectSession(vars.pendingKey);
|
|
2698
|
-
setBusy(`provisioning ${vars.harness} workspace \
|
|
2811
|
+
setBusy(`provisioning ${vars.harness} workspace \xB7`);
|
|
2699
2812
|
setBusyStarted(Date.now());
|
|
2700
2813
|
},
|
|
2701
2814
|
onError: (error, vars) => {
|
|
@@ -2736,7 +2849,7 @@ ${tunneled}
|
|
|
2736
2849
|
);
|
|
2737
2850
|
selectSession(vars.session.id);
|
|
2738
2851
|
setBusy(
|
|
2739
|
-
`resuming ${sessionDisplayName(vars.session)} \
|
|
2852
|
+
`resuming ${sessionDisplayName(vars.session)} \xB7 a fresh workspace restores the saved state \xB7`
|
|
2740
2853
|
);
|
|
2741
2854
|
setBusyStarted(Date.now());
|
|
2742
2855
|
},
|
|
@@ -2751,7 +2864,7 @@ ${tunneled}
|
|
|
2751
2864
|
);
|
|
2752
2865
|
setBusy(null);
|
|
2753
2866
|
say(
|
|
2754
|
-
resumed.status === "starting" ? `still resuming \xB7 ${sessionDisplayName(resumed)} \
|
|
2867
|
+
resumed.status === "starting" ? `still resuming \xB7 ${sessionDisplayName(resumed)} \xB7 a attaches once the row reads running` : `resumed \xB7 ${sessionDisplayName(resumed)} \xB7 a attaches`
|
|
2755
2868
|
);
|
|
2756
2869
|
},
|
|
2757
2870
|
onSettled: (_data, _error, vars) => {
|
|
@@ -2768,7 +2881,7 @@ ${tunneled}
|
|
|
2768
2881
|
{
|
|
2769
2882
|
mode: "mend-key",
|
|
2770
2883
|
label: "mend-key",
|
|
2771
|
-
hint: "the machine's Mend deploy key \
|
|
2884
|
+
hint: "the machine's Mend deploy key \xB7 add its public half on the git host"
|
|
2772
2885
|
},
|
|
2773
2886
|
{
|
|
2774
2887
|
mode: "bridge",
|
|
@@ -2784,7 +2897,7 @@ ${tunneled}
|
|
|
2784
2897
|
}),
|
|
2785
2898
|
onMutate: (offer) => {
|
|
2786
2899
|
setAdoptOffer(null);
|
|
2787
|
-
setBusy(`adopting ${offer.name} \
|
|
2900
|
+
setBusy(`adopting ${offer.name} \xB7 cloning into the store \xB7`);
|
|
2788
2901
|
setBusyStarted(Date.now());
|
|
2789
2902
|
},
|
|
2790
2903
|
onError: (error) => {
|
|
@@ -2798,7 +2911,7 @@ ${tunneled}
|
|
|
2798
2911
|
return;
|
|
2799
2912
|
}
|
|
2800
2913
|
const project = result.project;
|
|
2801
|
-
say(`adopted \xB7 ${project.name} \
|
|
2914
|
+
say(`adopted \xB7 ${project.name} \xB7 w starts a worktree`);
|
|
2802
2915
|
setProjectKey(project.id);
|
|
2803
2916
|
setWorktreeKey(null);
|
|
2804
2917
|
setSessionKey(null);
|
|
@@ -2833,7 +2946,7 @@ ${tunneled}
|
|
|
2833
2946
|
},
|
|
2834
2947
|
onSuccess: (result, session) => {
|
|
2835
2948
|
say(
|
|
2836
|
-
`stopped ${result.stopped} service${result.stopped === 1 ? "" : "s"} \xB7 ${sessionDisplayName(session)} \
|
|
2949
|
+
`stopped ${result.stopped} service${result.stopped === 1 ? "" : "s"} \xB7 ${sessionDisplayName(session)} \xB7 the workspace ends once nothing is live`
|
|
2837
2950
|
);
|
|
2838
2951
|
},
|
|
2839
2952
|
onError: (error) => {
|
|
@@ -2847,7 +2960,7 @@ ${tunneled}
|
|
|
2847
2960
|
onMutate: async (session) => {
|
|
2848
2961
|
await queryClient.cancelQueries({ queryKey: WORKBENCH_KEY });
|
|
2849
2962
|
patchWorkbench((current) => markSessionStopped(current, session.id));
|
|
2850
|
-
say(`stopped \xB7 ${sessionDisplayName(session)} \
|
|
2963
|
+
say(`stopped \xB7 ${sessionDisplayName(session)} \xB7 the record and review remain`);
|
|
2851
2964
|
},
|
|
2852
2965
|
onError: (error) => {
|
|
2853
2966
|
say(errorText(error));
|
|
@@ -2857,14 +2970,15 @@ ${tunneled}
|
|
|
2857
2970
|
});
|
|
2858
2971
|
const launchInWorktreeMutation = useMutation2({
|
|
2859
2972
|
mutationFn: async (vars) => {
|
|
2860
|
-
|
|
2861
|
-
|
|
2973
|
+
if (HARNESS_COMMANDS[vars.harness] === void 0) {
|
|
2974
|
+
throw new Error(`unknown harness "${vars.harness}"`);
|
|
2975
|
+
}
|
|
2862
2976
|
const session = await ctx.api("POST", `/worktrees/${vars.worktreeId}/sessions`, {
|
|
2863
2977
|
harness: vars.harness,
|
|
2864
2978
|
label: null
|
|
2865
2979
|
});
|
|
2866
2980
|
return startedOrStarting(
|
|
2867
|
-
ctx.api("POST", `/sessions/${session.id}/launch`, {
|
|
2981
|
+
ctx.api("POST", `/sessions/${session.id}/launch`, {}),
|
|
2868
2982
|
session
|
|
2869
2983
|
);
|
|
2870
2984
|
},
|
|
@@ -2944,7 +3058,7 @@ ${tunneled}
|
|
|
2944
3058
|
refetch();
|
|
2945
3059
|
},
|
|
2946
3060
|
onSuccess: (_result, session) => {
|
|
2947
|
-
say(`removed \xB7 ${sessionDisplayName(session)} \
|
|
3061
|
+
say(`removed \xB7 ${sessionDisplayName(session)} \xB7 the worktree remains`);
|
|
2948
3062
|
refetch();
|
|
2949
3063
|
},
|
|
2950
3064
|
onSettled: settleRefetch
|
|
@@ -2956,7 +3070,7 @@ ${tunneled}
|
|
|
2956
3070
|
if (group === null || group.sessions.some((item) => isPendingId(item.session.id))) return;
|
|
2957
3071
|
if (group.live > 0) {
|
|
2958
3072
|
say(
|
|
2959
|
-
`${group.live} session${group.live === 1 ? "" : "s"} live \
|
|
3073
|
+
`${group.live} session${group.live === 1 ? "" : "s"} live \xB7 stop them first (\u21E7K) \xB7 ${group.name}`
|
|
2960
3074
|
);
|
|
2961
3075
|
return;
|
|
2962
3076
|
}
|
|
@@ -2967,13 +3081,13 @@ ${tunneled}
|
|
|
2967
3081
|
}
|
|
2968
3082
|
setRemoveArmed(`wt:${group.key}`);
|
|
2969
3083
|
const facts = group.sessions.length === 1 ? "its session and change go with it" : `${group.sessions.length} sessions and the change go with it`;
|
|
2970
|
-
say(`press \u21E7D again to remove worktree \xB7 ${group.name} \
|
|
3084
|
+
say(`press \u21E7D again to remove worktree \xB7 ${group.name} \xB7 ${facts}`);
|
|
2971
3085
|
return;
|
|
2972
3086
|
}
|
|
2973
3087
|
const session = selectedSession;
|
|
2974
3088
|
if (session === null || isPendingId(session.id)) return;
|
|
2975
3089
|
if (AGENT_LIVE_STATUSES.has(session.status)) {
|
|
2976
|
-
say(`the agent is still working \
|
|
3090
|
+
say(`the agent is still working \xB7 stop it first (\u21E7K) \xB7 ${sessionDisplayName(session)}`);
|
|
2977
3091
|
return;
|
|
2978
3092
|
}
|
|
2979
3093
|
if (removeArmed === session.id && confirmationVisible("press \u21E7D again")) {
|
|
@@ -2983,7 +3097,7 @@ ${tunneled}
|
|
|
2983
3097
|
}
|
|
2984
3098
|
setRemoveArmed(session.id);
|
|
2985
3099
|
say(
|
|
2986
|
-
`press \u21E7D again to remove session \xB7 ${sessionDisplayName(session)} \
|
|
3100
|
+
`press \u21E7D again to remove session \xB7 ${sessionDisplayName(session)} \xB7 its record goes, the worktree stays`
|
|
2987
3101
|
);
|
|
2988
3102
|
};
|
|
2989
3103
|
const armStop = () => {
|
|
@@ -2992,7 +3106,7 @@ ${tunneled}
|
|
|
2992
3106
|
if (group === null) return;
|
|
2993
3107
|
const live = group.sessions.filter((item) => LIVE_STATUSES.has(item.session.status));
|
|
2994
3108
|
if (live.length === 0) {
|
|
2995
|
-
say("nothing to stop \
|
|
3109
|
+
say("nothing to stop \xB7 the worktree is settled");
|
|
2996
3110
|
return;
|
|
2997
3111
|
}
|
|
2998
3112
|
if (stopArmed === `wt:${group.key}` && confirmationVisible("press \u21E7K again")) {
|
|
@@ -3008,7 +3122,7 @@ ${tunneled}
|
|
|
3008
3122
|
}
|
|
3009
3123
|
const session = selectedSession;
|
|
3010
3124
|
if (session === null || isPendingId(session.id)) return;
|
|
3011
|
-
const hold = selectedItem === null ? null :
|
|
3125
|
+
const hold = selectedItem === null ? null : servicesHoldOf(selectedItem);
|
|
3012
3126
|
if (hold !== null) {
|
|
3013
3127
|
if (stopArmed === `svc:${session.id}` && confirmationVisible("press \u21E7K again")) {
|
|
3014
3128
|
setStopArmed(null);
|
|
@@ -3016,11 +3130,16 @@ ${tunneled}
|
|
|
3016
3130
|
return;
|
|
3017
3131
|
}
|
|
3018
3132
|
setStopArmed(`svc:${session.id}`);
|
|
3019
|
-
say(`press \u21E7K again to stop the services \xB7 ${sessionDisplayName(session)} \
|
|
3133
|
+
say(`press \u21E7K again to stop the services \xB7 ${sessionDisplayName(session)} \xB7 ${hold}`);
|
|
3134
|
+
return;
|
|
3135
|
+
}
|
|
3136
|
+
if (session.status === "stopping") {
|
|
3137
|
+
const saving = selectedItem === null ? null : sessionHold(selectedItem);
|
|
3138
|
+
say(`already stopping${saving === null ? "" : ` \xB7 ${saving}`}`);
|
|
3020
3139
|
return;
|
|
3021
3140
|
}
|
|
3022
3141
|
if (!LIVE_STATUSES.has(session.status)) {
|
|
3023
|
-
say("nothing to stop \
|
|
3142
|
+
say("nothing to stop \xB7 the session is settled");
|
|
3024
3143
|
return;
|
|
3025
3144
|
}
|
|
3026
3145
|
if (stopArmed === session.id && confirmationVisible("press \u21E7K again")) {
|
|
@@ -3072,7 +3191,7 @@ ${tunneled}
|
|
|
3072
3191
|
if (creatingRef.current?.projectId !== projectId || request !== branchRequest.current)
|
|
3073
3192
|
return;
|
|
3074
3193
|
const message = errorText(error);
|
|
3075
|
-
say(`could not read branches \
|
|
3194
|
+
say(`could not read branches \xB7 ${message}`);
|
|
3076
3195
|
setCreating(
|
|
3077
3196
|
(current) => current?.projectId === projectId ? { ...current, branches: [], branchError: message } : current
|
|
3078
3197
|
);
|
|
@@ -3107,7 +3226,7 @@ ${tunneled}
|
|
|
3107
3226
|
const gateKey = `launch:${current.projectId}:${current.name}`;
|
|
3108
3227
|
if (!gate.take(gateKey)) {
|
|
3109
3228
|
setCreating(null);
|
|
3110
|
-
say("that worktree is already starting \
|
|
3229
|
+
say("that worktree is already starting \xB7 wait for it to finish before starting another");
|
|
3111
3230
|
return;
|
|
3112
3231
|
}
|
|
3113
3232
|
setCreating(null);
|
|
@@ -3138,11 +3257,11 @@ ${tunneled}
|
|
|
3138
3257
|
return;
|
|
3139
3258
|
case "starting":
|
|
3140
3259
|
say(
|
|
3141
|
-
`still starting \xB7 ${sessionDisplayName(plan.session)} \
|
|
3260
|
+
`still starting \xB7 ${sessionDisplayName(plan.session)} \xB7 a attaches once the row reads running`
|
|
3142
3261
|
);
|
|
3143
3262
|
return;
|
|
3144
3263
|
case "settled":
|
|
3145
|
-
say(`settled \xB7 ${sessionDisplayName(plan.session)} \
|
|
3264
|
+
say(`settled \xB7 ${sessionDisplayName(plan.session)} \xB7 r resumes it, \u21E7D removes it`);
|
|
3146
3265
|
return;
|
|
3147
3266
|
case "stopping":
|
|
3148
3267
|
say(
|
|
@@ -3150,10 +3269,10 @@ ${tunneled}
|
|
|
3150
3269
|
);
|
|
3151
3270
|
return;
|
|
3152
3271
|
case "pending":
|
|
3153
|
-
say("still provisioning \
|
|
3272
|
+
say("still provisioning \xB7 the row fills in when the workspace answers");
|
|
3154
3273
|
return;
|
|
3155
3274
|
case "none":
|
|
3156
|
-
say("no session selected \
|
|
3275
|
+
say("no session selected \xB7 n starts one");
|
|
3157
3276
|
return;
|
|
3158
3277
|
}
|
|
3159
3278
|
};
|
|
@@ -3168,13 +3287,13 @@ ${tunneled}
|
|
|
3168
3287
|
openPicker(plan.session);
|
|
3169
3288
|
return;
|
|
3170
3289
|
case "live":
|
|
3171
|
-
say(`already live \xB7 ${sessionDisplayName(plan.session)} \
|
|
3290
|
+
say(`already live \xB7 ${sessionDisplayName(plan.session)} \xB7 a attaches`);
|
|
3172
3291
|
return;
|
|
3173
3292
|
case "pending":
|
|
3174
|
-
say("still provisioning \
|
|
3293
|
+
say("still provisioning \xB7 nothing to resume yet");
|
|
3175
3294
|
return;
|
|
3176
3295
|
case "none":
|
|
3177
|
-
say("no session selected \
|
|
3296
|
+
say("no session selected \xB7 n starts one");
|
|
3178
3297
|
return;
|
|
3179
3298
|
}
|
|
3180
3299
|
};
|
|
@@ -3399,7 +3518,7 @@ ${tunneled}
|
|
|
3399
3518
|
}
|
|
3400
3519
|
if (key.ctrl === true) return;
|
|
3401
3520
|
if (focus === "projects" && ["attach", "resume", "stop", "remove", "rename", "openWeb", "review"].includes(verb ?? "")) {
|
|
3402
|
-
say("select a session first \
|
|
3521
|
+
say("select a session first \xB7 \u2192 opens worktrees");
|
|
3403
3522
|
return;
|
|
3404
3523
|
}
|
|
3405
3524
|
switch (verb) {
|
|
@@ -3452,7 +3571,7 @@ ${tunneled}
|
|
|
3452
3571
|
const session = selectedSession;
|
|
3453
3572
|
if (session === null) return;
|
|
3454
3573
|
if (isPendingId(session.id)) {
|
|
3455
|
-
say("still provisioning \
|
|
3574
|
+
say("still provisioning \xB7 nothing to review yet");
|
|
3456
3575
|
return;
|
|
3457
3576
|
}
|
|
3458
3577
|
const target = reviewTargetForSession(
|
|
@@ -3472,6 +3591,7 @@ ${tunneled}
|
|
|
3472
3591
|
}
|
|
3473
3592
|
});
|
|
3474
3593
|
const liveTotal = projectItems.reduce((sum, item) => sum + item.live, 0);
|
|
3594
|
+
const stoppingTotal = projectItems.reduce((sum, item) => sum + item.stopping, 0);
|
|
3475
3595
|
const columnTitle = (column) => {
|
|
3476
3596
|
if (column === "worktrees") {
|
|
3477
3597
|
return selectedProject === null ? "worktrees" : `worktrees \xB7 ${worktreeGroups.length}${selectedProject.live > 0 ? ` \xB7 ${selectedProject.live} live` : ""}`;
|
|
@@ -3485,7 +3605,7 @@ ${tunneled}
|
|
|
3485
3605
|
}
|
|
3486
3606
|
return COLUMN_TITLE[column];
|
|
3487
3607
|
};
|
|
3488
|
-
const pickerTitle = picker === null ? "" : picker.worktree === void 0 ? picker.session === null ? "new session \
|
|
3608
|
+
const pickerTitle = picker === null ? "" : picker.worktree === void 0 ? picker.session === null ? "new session \xB7 pick a harness" : `resume ${sessionDisplayName(picker.session)} \xB7 pick a harness` : `new session in ${picker.worktree.name} \xB7 pick a harness`;
|
|
3489
3609
|
const creatingHeight = 2 + 1 + 1 + 6 + 1 + deriveHarnesses(null).length;
|
|
3490
3610
|
const footerText = adoptOffer === null ? editing === null ? creating === null ? picker === null ? ` ${fitHints(verbHints(focus), Math.max(12, terminalCols - 2))}` : " \u2191\u2193 move \xB7 enter start \xB7 esc cancel" : creating.step === "name" ? " enter continue \xB7 esc cancel" : creating.step === "base" ? " type to filter \xB7 \u2191\u2193 move \xB7 enter choose base \xB7 esc back" : " \u2191\u2193 move \xB7 enter launch \xB7 esc back" : " enter save \xB7 esc cancel" : " enter adopt \xB7 \u2190\u2192 auth mode \xB7 esc not now";
|
|
3491
3611
|
const loadFailure = data === void 0 && failureReason !== null ? errorText(failureReason) : null;
|
|
@@ -3513,11 +3633,11 @@ ${tunneled}
|
|
|
3513
3633
|
const sectionFact = (section) => {
|
|
3514
3634
|
if (section === "projects") {
|
|
3515
3635
|
if (selectedProject === null) return "";
|
|
3516
|
-
return selectedProject.live
|
|
3636
|
+
return liveCountWords(selectedProject.live, selectedProject.stopping);
|
|
3517
3637
|
}
|
|
3518
3638
|
if (section === "worktrees") {
|
|
3519
3639
|
if (selectedGroup === null) return "";
|
|
3520
|
-
return
|
|
3640
|
+
return foldGroupStatus(selectedGroup);
|
|
3521
3641
|
}
|
|
3522
3642
|
return selectedSession?.status ?? "";
|
|
3523
3643
|
};
|
|
@@ -3604,7 +3724,7 @@ ${tunneled}
|
|
|
3604
3724
|
},
|
|
3605
3725
|
group.key
|
|
3606
3726
|
)),
|
|
3607
|
-
data !== void 0 && worktreeGroups.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no worktrees \
|
|
3727
|
+
data !== void 0 && worktreeGroups.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no worktrees \xB7 w starts one" }) : null
|
|
3608
3728
|
]
|
|
3609
3729
|
}
|
|
3610
3730
|
)
|
|
@@ -3639,10 +3759,10 @@ ${tunneled}
|
|
|
3639
3759
|
data !== void 0 && sessionItems.length === 0 ? /* @__PURE__ */ jsx3(
|
|
3640
3760
|
EmptyNote,
|
|
3641
3761
|
{
|
|
3642
|
-
text: selectedGroup === null ? "no worktree selected" : "no sessions \
|
|
3762
|
+
text: selectedGroup === null ? "no worktree selected" : "no sessions \xB7 n starts one"
|
|
3643
3763
|
}
|
|
3644
3764
|
) : null,
|
|
3645
|
-
loadFailure === null ? null : /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: ` ${loadFailure} \
|
|
3765
|
+
loadFailure === null ? null : /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: ` ${loadFailure} \xB7 retrying` })
|
|
3646
3766
|
]
|
|
3647
3767
|
}
|
|
3648
3768
|
)
|
|
@@ -3651,7 +3771,7 @@ ${tunneled}
|
|
|
3651
3771
|
);
|
|
3652
3772
|
}
|
|
3653
3773
|
};
|
|
3654
|
-
const renderDetail = () => /* @__PURE__ */ jsx3(Pane, { title: paneTitle("detail", detailWidth + 2), focused: focus === "detail", grow: true, children: selectedItem === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no session selected \
|
|
3774
|
+
const renderDetail = () => /* @__PURE__ */ jsx3(Pane, { title: paneTitle("detail", detailWidth + 2), focused: focus === "detail", grow: true, children: selectedItem === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no session selected \xB7 n starts one" }) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
3655
3775
|
/* @__PURE__ */ jsx3(
|
|
3656
3776
|
SessionFacts,
|
|
3657
3777
|
{
|
|
@@ -3666,10 +3786,10 @@ ${tunneled}
|
|
|
3666
3786
|
/* @__PURE__ */ jsxs3("text", { height: 1, bg: "transparent", children: [
|
|
3667
3787
|
/* @__PURE__ */ jsx3("span", { children: " " }),
|
|
3668
3788
|
/* @__PURE__ */ jsx3("span", { fg: INK_2, children: "starting" }),
|
|
3669
|
-
/* @__PURE__ */ jsx3("span", { fg: FAINT, children:
|
|
3789
|
+
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: ` \xB7 ${selectedSession === null ? "launching" : startingExplanationOf(selectedSession)}` })
|
|
3670
3790
|
] }),
|
|
3671
3791
|
snakeShown ? /* @__PURE__ */ jsx3(SnakeBoard, { handle: snake, focused: focus === "detail" }) : /* @__PURE__ */ jsx3(EmptyNote, { text: "snake is put away \xB7 space brings it back" })
|
|
3672
|
-
] }) : previewSessionId === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "provisioning \
|
|
3792
|
+
] }) : previewSessionId === null ? /* @__PURE__ */ jsx3(EmptyNote, { text: "provisioning \xB7 no record yet" }) : selectedSession?.harness === "shell" ? /* @__PURE__ */ jsx3(EmptyNote, { text: "shell \xB7 no conversation record; a attaches if live" }) : transcript.isPending ? /* @__PURE__ */ jsx3(EmptyNote, { text: "reading the record\u2026" }) : transcript.error === null ? previewView.lines.length === 0 ? /* @__PURE__ */ jsx3(EmptyNote, { text: "no conversation recorded yet" }) : previewView.lines.map((line, index) => /* @__PURE__ */ jsx3(PreviewRow, { line }, `${index}-${line.kind}`)) : /* @__PURE__ */ jsx3("text", { height: 1, fg: INK_2, bg: "transparent", children: ` could not read the record \xB7 ${errorText(transcript.error)}` }) })
|
|
3673
3793
|
] }) });
|
|
3674
3794
|
const offNav = layout.offscreen.filter(isNavSection);
|
|
3675
3795
|
const detailOffscreen = layout.offscreen.includes("detail");
|
|
@@ -3688,6 +3808,13 @@ ${tunneled}
|
|
|
3688
3808
|
liveTotal,
|
|
3689
3809
|
" live"
|
|
3690
3810
|
] }),
|
|
3811
|
+
stoppingTotal > 0 ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
3812
|
+
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
|
|
3813
|
+
/* @__PURE__ */ jsxs3("span", { fg: INK_2, children: [
|
|
3814
|
+
stoppingTotal,
|
|
3815
|
+
" stopping"
|
|
3816
|
+
] })
|
|
3817
|
+
] }) : null,
|
|
3691
3818
|
gate.count() > 0 ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
3692
3819
|
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: " \xB7 " }),
|
|
3693
3820
|
/* @__PURE__ */ jsxs3("span", { fg: INK_2, children: [
|
|
@@ -3784,7 +3911,7 @@ ${tunneled}
|
|
|
3784
3911
|
border: true,
|
|
3785
3912
|
borderStyle: "rounded",
|
|
3786
3913
|
borderColor: ACCENT,
|
|
3787
|
-
title: ` label \
|
|
3914
|
+
title: ` label \xB7 ${editing.harness} ${editing.id.slice(0, 8)} `,
|
|
3788
3915
|
titleAlignment: "left",
|
|
3789
3916
|
backgroundColor: SURFACE,
|
|
3790
3917
|
height: 3,
|
|
@@ -3856,7 +3983,7 @@ ${tunneled}
|
|
|
3856
3983
|
border: true,
|
|
3857
3984
|
borderStyle: "rounded",
|
|
3858
3985
|
borderColor: ACCENT,
|
|
3859
|
-
title: ` new worktree \
|
|
3986
|
+
title: ` new worktree \xB7 ${selectedProject?.project.name ?? "project"} `,
|
|
3860
3987
|
titleAlignment: "left",
|
|
3861
3988
|
backgroundColor: SURFACE,
|
|
3862
3989
|
flexDirection: "column",
|
|
@@ -3904,7 +4031,7 @@ ${tunneled}
|
|
|
3904
4031
|
{
|
|
3905
4032
|
focused: true,
|
|
3906
4033
|
value: "",
|
|
3907
|
-
placeholder: "type to filter
|
|
4034
|
+
placeholder: "type to filter \xB7 enter takes the highlighted branch, or the default when empty",
|
|
3908
4035
|
backgroundColor: SURFACE,
|
|
3909
4036
|
focusedBackgroundColor: SURFACE,
|
|
3910
4037
|
textColor: INK,
|
|
@@ -3950,7 +4077,7 @@ ${tunneled}
|
|
|
3950
4077
|
/* @__PURE__ */ jsx3(Gutter, { selected: highlighted }),
|
|
3951
4078
|
/* @__PURE__ */ jsx3("span", { fg: active ? INK : FAINT, children: branch.name.slice(0, 40).padEnd(41) }),
|
|
3952
4079
|
/* @__PURE__ */ jsx3("span", { fg: FAINT, children: branch.sha.slice(0, 8).padEnd(10) }),
|
|
3953
|
-
/* @__PURE__ */ jsx3("span", { fg: active ? MUTED : FAINT, children:
|
|
4080
|
+
/* @__PURE__ */ jsx3("span", { fg: active ? MUTED : FAINT, children: timeAgo(branch.committedAt).padEnd(9) }),
|
|
3954
4081
|
branch.isDefault ? /* @__PURE__ */ jsx3("span", { fg: FAINT, children: "default" }) : null
|
|
3955
4082
|
] })
|
|
3956
4083
|
},
|
|
@@ -3983,7 +4110,7 @@ ${tunneled}
|
|
|
3983
4110
|
]
|
|
3984
4111
|
}
|
|
3985
4112
|
),
|
|
3986
|
-
loadFailure === null ? /* @__PURE__ */ jsx3(StatusLine, { busy, busyStarted, status }) : /* @__PURE__ */ jsx3("text", { height: 1, fg: ERROR, bg: "transparent", children: ` could not load workbench \
|
|
4113
|
+
loadFailure === null ? /* @__PURE__ */ jsx3(StatusLine, { busy, busyStarted, status }) : /* @__PURE__ */ jsx3("text", { height: 1, fg: ERROR, bg: "transparent", children: ` could not load workbench \xB7 ${loadFailure} \xB7 retrying` }),
|
|
3987
4114
|
/* @__PURE__ */ jsx3("text", { height: 1, fg: FAINT, bg: "transparent", children: footerText })
|
|
3988
4115
|
] });
|
|
3989
4116
|
};
|