@sealant/mend 0.36.0-next.643 → 0.36.0-next.646

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.
Files changed (91) hide show
  1. package/README.md +4 -1
  2. package/dist/{chunk-2TDRYRGO.js → chunk-ASHAV3UV.js} +259 -0
  3. package/dist/{dashboard-2HF3KTTU.js → dashboard-4J2WMT3G.js} +134 -17
  4. package/dist/main.js +646 -461
  5. package/man/mend-accounts.1 +1 -1
  6. package/man/mend-adopt.1 +1 -1
  7. package/man/mend-attach.1 +1 -1
  8. package/man/mend-codex.1 +3 -1
  9. package/man/mend-completions.1 +1 -1
  10. package/man/mend-connect-pi.1 +1 -1
  11. package/man/mend-connect.1 +3 -1
  12. package/man/mend-continue.1 +1 -1
  13. package/man/mend-doctor.1 +1 -1
  14. package/man/mend-dotfiles-repo.1 +1 -1
  15. package/man/mend-dotfiles-sync.1 +1 -1
  16. package/man/mend-dotfiles.1 +1 -1
  17. package/man/mend-env-cluster.1 +1 -1
  18. package/man/mend-env-load.1 +1 -1
  19. package/man/mend-env-show.1 +1 -1
  20. package/man/mend-folder-create.1 +1 -1
  21. package/man/mend-folder-list.1 +1 -1
  22. package/man/mend-folder-push.1 +1 -1
  23. package/man/mend-folder-rm.1 +1 -1
  24. package/man/mend-git-author.1 +1 -1
  25. package/man/mend-help.1 +1 -1
  26. package/man/mend-invite.1 +1 -1
  27. package/man/mend-keys-init.1 +1 -1
  28. package/man/mend-keys-mode.1 +1 -1
  29. package/man/mend-keys-share.1 +1 -1
  30. package/man/mend-keys-show.1 +1 -1
  31. package/man/mend-land.1 +1 -1
  32. package/man/mend-login.1 +1 -1
  33. package/man/mend-logout.1 +1 -1
  34. package/man/mend-man.1 +1 -1
  35. package/man/mend-members.1 +1 -1
  36. package/man/mend-memory-import.1 +1 -1
  37. package/man/mend-memory-rm.1 +1 -1
  38. package/man/mend-memory-show.1 +1 -1
  39. package/man/mend-memory.1 +1 -1
  40. package/man/mend-models.1 +1 -1
  41. package/man/mend-operator-exposure.1 +1 -1
  42. package/man/mend-operator-gate.1 +1 -1
  43. package/man/mend-operator-grant-owner.1 +1 -1
  44. package/man/mend-operator-org-create.1 +1 -1
  45. package/man/mend-operator-org-invite-owner.1 +1 -1
  46. package/man/mend-operator-org-list.1 +1 -1
  47. package/man/mend-operator-org-rename.1 +1 -1
  48. package/man/mend-operator-reset-link.1 +1 -1
  49. package/man/mend-pair.1 +1 -1
  50. package/man/mend-projects.1 +1 -1
  51. package/man/mend-pull.1 +1 -1
  52. package/man/mend-refresh.1 +1 -1
  53. package/man/mend-rejoin.1 +1 -1
  54. package/man/mend-resume.1 +1 -1
  55. package/man/mend-run.1 +1 -1
  56. package/man/mend-secrets-add.1 +1 -1
  57. package/man/mend-secrets-rm.1 +1 -1
  58. package/man/mend-secrets.1 +1 -1
  59. package/man/mend-server-logs.1 +1 -1
  60. package/man/mend-server-restart.1 +1 -1
  61. package/man/mend-server-setup.1 +1 -1
  62. package/man/mend-server-start.1 +1 -1
  63. package/man/mend-server-status.1 +1 -1
  64. package/man/mend-server-stop.1 +1 -1
  65. package/man/mend-server-upgrade.1 +1 -1
  66. package/man/mend-server.1 +1 -1
  67. package/man/mend-service-add.1 +1 -1
  68. package/man/mend-service-connect.1 +1 -1
  69. package/man/mend-service-init.1 +1 -1
  70. package/man/mend-service-list.1 +1 -1
  71. package/man/mend-service-logs.1 +1 -1
  72. package/man/mend-service-restart.1 +1 -1
  73. package/man/mend-service-run.1 +1 -1
  74. package/man/mend-service-stop.1 +1 -1
  75. package/man/mend-session-share.1 +13 -3
  76. package/man/mend-sessions.1 +3 -1
  77. package/man/mend-shell.1 +1 -1
  78. package/man/mend-skills-push.1 +1 -1
  79. package/man/mend-skills.1 +1 -1
  80. package/man/mend-snake.1 +1 -1
  81. package/man/mend-ssh-setup.1 +1 -1
  82. package/man/mend-ssh.1 +1 -1
  83. package/man/mend-stop.1 +1 -1
  84. package/man/mend-ui.1 +1 -1
  85. package/man/mend-uninstall.1 +1 -1
  86. package/man/mend-version.1 +1 -1
  87. package/man/mend-workspace-replace.1 +24 -0
  88. package/man/mend-worktrees-rm.1 +1 -1
  89. package/man/mend-worktrees.1 +1 -1
  90. package/man/mend.1 +5 -2
  91. package/package.json +2 -2
package/README.md CHANGED
@@ -171,6 +171,8 @@ mend refresh [project] fetch origin's branches into the store
171
171
  mend land <session> [--branch b] [--no-pr] [--title t] | --check
172
172
  push the change to origin, open or update its pull request
173
173
  mend pull <session> [--force] fetch the change into this clone as mend/<name>
174
+ mend workspace replace <session> [--yes]
175
+ replace a workspace that started before Mend 0.36
174
176
 
175
177
  services
176
178
  mend service run|add|connect|list|logs|restart|stop|init
@@ -193,7 +195,8 @@ mend members who belongs to your organization, and thei
193
195
  mend invite [--role member|owner] [--email <address>] [--days <n>]
194
196
  print a one-time invitation link
195
197
  mend folder list|create|push|rm your organization's folders
196
- mend session share <session> on|off shared control: let everyone who can see a session steer it
198
+ mend session share <session> on|off [--yes]
199
+ shared control: each turn runs on its sender's login
197
200
 
198
201
  this machine
199
202
  mend operator org list|create|rename|invite-owner
@@ -28038,6 +28038,12 @@ var NativeIngestCursor = Schema_exports.Struct({
28038
28038
  lastEntryUuid: Schema_exports.NullOr(Schema_exports.String),
28039
28039
  lineCount: Schema_exports.Int
28040
28040
  });
28041
+ var LivePerson = class extends Schema_exports.Class("LivePerson")({
28042
+ accountId: Schema_exports.String,
28043
+ /** Their name as the instance knows it. */
28044
+ name: Schema_exports.String
28045
+ }) {
28046
+ };
28041
28047
  var Session = class extends Schema_exports.Class("Session")({
28042
28048
  id: SessionId,
28043
28049
  projectId: ProjectId,
@@ -28126,6 +28132,24 @@ var Session = class extends Schema_exports.Class("Session")({
28126
28132
  Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)),
28127
28133
  Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
28128
28134
  ),
28135
+ /**
28136
+ * The people with a process live in the session's executor as their own user, in a per-person
28137
+ * executor (docs/adr/0016, decision 13): filled by the API's session list and view only, in the
28138
+ * same query; empty everywhere else, in a shared executor, and from older servers.
28139
+ */
28140
+ livePeople: Schema_exports.Array(LivePerson).pipe(
28141
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed([])),
28142
+ Schema_exports.withConstructorDefault(Effect_exports.succeed([]))
28143
+ ),
28144
+ /**
28145
+ * The session's executor waits to be replaced (docs/adr/0016, decision 14): `marked` or
28146
+ * `retiring`; null otherwise. Read with `livePeople`, in the same query, so a client asks for the
28147
+ * retirement's detail only when there is one. Null everywhere else and from older servers.
28148
+ */
28149
+ workspaceRetirement: Schema_exports.NullOr(Schema_exports.Literals(["marked", "retiring"])).pipe(
28150
+ Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null)),
28151
+ Schema_exports.withConstructorDefault(Effect_exports.succeed(null))
28152
+ ),
28129
28153
  /**
28130
28154
  * Whether the harness left a conversation behind — a transcript Mend captured at settle or
28131
28155
  * found in the live harness home. False is a dead end: nothing to resume, nothing to hand
@@ -28382,6 +28406,62 @@ var LinuxIdentity = class extends Schema_exports.Class("LinuxIdentity")({
28382
28406
  uid: Schema_exports.Number
28383
28407
  }) {
28384
28408
  };
28409
+ var WorkspaceRetirementStopKind = Schema_exports.Literals([
28410
+ "terminal",
28411
+ "shell",
28412
+ "service",
28413
+ "turn",
28414
+ "process",
28415
+ "container",
28416
+ "unchecked"
28417
+ ]);
28418
+ var WorkspaceRetirementStop = class extends Schema_exports.Class(
28419
+ "WorkspaceRetirementStop"
28420
+ )({
28421
+ kind: WorkspaceRetirementStopKind,
28422
+ /**
28423
+ * What it is, as observed: a session's label, a Service's name, a process's command name and
28424
+ * pid, a container's name, or why something could not be checked. Empty for a process or a
28425
+ * container when the viewer is not the change's owner: they see its kind only.
28426
+ */
28427
+ label: Schema_exports.String
28428
+ }) {
28429
+ };
28430
+ var WorkspaceRetirementState = Schema_exports.Literals(["marked", "retiring"]);
28431
+ var WorkspaceRetirement = class extends Schema_exports.Class("WorkspaceRetirement")({
28432
+ state: WorkspaceRetirementState,
28433
+ /** The executor started before Mend 0.36 (no layout recorded for its launch). */
28434
+ preRelease: Schema_exports.Boolean,
28435
+ /** The account whose launch made the executor: the only one whose joins and turns it takes. */
28436
+ launcher: Schema_exports.NullOr(Schema_exports.String),
28437
+ /** What would stop if it were replaced now; empty when nothing would. */
28438
+ stops: Schema_exports.Array(WorkspaceRetirementStop),
28439
+ /** Why the last automatic replacement did not go ahead, as observed; null when none was tried. */
28440
+ reason: Schema_exports.NullOr(Schema_exports.String),
28441
+ /**
28442
+ * When what runs in the executor was last checked beyond Mend's own records (processes Mend did
28443
+ * not start, the sidecar's containers); null when only Mend's records were read.
28444
+ */
28445
+ checkedAt: Schema_exports.NullOr(Timestamp),
28446
+ /**
28447
+ * What the viewer was shown, as one token: "Replace this workspace now" names it, so Mend ends
28448
+ * nothing that was not listed (the replacement is refused if more would stop now).
28449
+ */
28450
+ fingerprint: Schema_exports.String,
28451
+ /** The viewer may replace it now: they own the change, and no agent turn is in flight. */
28452
+ canReplace: Schema_exports.Boolean
28453
+ }) {
28454
+ };
28455
+ var PreReleaseMemory = class extends Schema_exports.Class("PreReleaseMemory")({
28456
+ /** The capture it read, by its place on the worktree's chain. */
28457
+ captureN: Schema_exports.Number,
28458
+ provisional: Schema_exports.Boolean,
28459
+ creditedTo: Schema_exports.NullOr(Schema_exports.String),
28460
+ decidedBy: Schema_exports.Literals(["home-record", "only-person", "nobody", "nothing"]),
28461
+ /** Memory paths credited to nobody: two or more people's sessions shared the home. */
28462
+ notCredited: Schema_exports.Array(Schema_exports.String)
28463
+ }) {
28464
+ };
28385
28465
 
28386
28466
  // ../../packages/domain/src/workbench/host-user-namespaces.ts
28387
28467
  var HOST_USER_NAMESPACE_FILES = [
@@ -29095,6 +29175,54 @@ var SlackLinkedMentionJob = Schema_exports.Struct({
29095
29175
  linkedAt: Schema_exports.String
29096
29176
  });
29097
29177
 
29178
+ // ../../packages/domain/src/workbench/shared-workspace.ts
29179
+ var listed = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1] ?? ""}`;
29180
+ var joinWorktreeLine = (others) => others.length <= 1 ? `${others[0] ?? "Another person"}'s session is running in this worktree. You share its workspace: everything you run runs as you, on your own logins, but either of you can read the other's files, logins included.` : `${listed(others)}'s sessions are running in this worktree. You share its workspace: everything you run runs as you, on your own logins, but any of you can read the others' files, logins included.`;
29181
+ var JOIN_SHARED_HOME_LINE = "Another person's session is running in this worktree, in a workspace that shares one home: what you start there runs on the logins and Git identity of whoever started that workspace, not yours.";
29182
+ var sharedWorkspaceLine = (livePeople, viewer) => {
29183
+ if (livePeople.length < 2) return null;
29184
+ const others = livePeople.filter((person) => person.accountId !== viewer).map((p) => p.name);
29185
+ if (others.length === livePeople.length) {
29186
+ return `Shared workspace: ${listed(others)} \xB7 each runs as themselves \xB7 ${others.length === 2 ? "either can read the other's files" : "any of them can read the others' files"}.`;
29187
+ }
29188
+ return others.length === 1 && livePeople.length === 2 ? `Shared workspace with ${others[0]} \xB7 each of you runs as yourself \xB7 either of you can read the other's files.` : `Shared workspace with ${listed(others)} \xB7 each of you runs as yourself \xB7 any of you can read the others' files.`;
29189
+ };
29190
+ var SHARED_CONTROL_LINE = "Each turn runs on its sender's login. From now until this session ends, the agent uses no one's personal memory or instructions. The conversation so far, including what your agent loaded before, becomes visible to whoever steers.";
29191
+ var SHARED_CONTROL_LINE_OWNER_LOGINS = "Each turn runs on your provider logins and Git access, whoever sends it.";
29192
+ var sharedControlLine = (turnsOnSendersLogin) => turnsOnSendersLogin ? SHARED_CONTROL_LINE : SHARED_CONTROL_LINE_OWNER_LOGINS;
29193
+ var sharedControlConfirm = (turnsOnSendersLogin) => ({
29194
+ title: "Turn on shared control?",
29195
+ body: turnsOnSendersLogin ? SHARED_CONTROL_LINE : `Everyone who can see this project can send turns, answer approvals and interrupt. ${SHARED_CONTROL_LINE_OWNER_LOGINS}`,
29196
+ confirm: "Turn on",
29197
+ cancel: "Keep it off"
29198
+ });
29199
+ var workspaceRetirementLine = (retirement, launcherName) => {
29200
+ if (retirement.state === "retiring") {
29201
+ return "Replacing this workspace so that each person runs as themselves \xB7 nothing new starts until it has been saved and replaced";
29202
+ }
29203
+ const started = retirement.preRelease ? "This workspace started before Mend 0.36 and shares one home" : "This workspace shares one home";
29204
+ const reason = retirement.reason === null ? "" : ` \xB7 ${retirement.reason}`;
29205
+ return `${started} \xB7 it takes only ${launcherName}'s sessions and turns until it is replaced${reason}`;
29206
+ };
29207
+ var STOP_WORDS = {
29208
+ terminal: "terminal session (ends resumable)",
29209
+ shell: "shell",
29210
+ service: "Service started by hand",
29211
+ turn: "agent turn in flight",
29212
+ process: "process Mend did not start",
29213
+ container: "running container",
29214
+ unchecked: "could not check"
29215
+ };
29216
+ var REPLACE_WORKSPACE_ACTION = "Replace this workspace now";
29217
+ var timeOf3 = (at) => `${at.toISOString().slice(11, 16)} UTC`;
29218
+ var retirementStopLines = (retirement) => {
29219
+ const checked = retirement.checkedAt === null ? "Checked: Mend's records only \xB7 processes Mend did not start and running containers not checked yet" : `Checked at ${timeOf3(retirement.checkedAt)}: Mend's records, the processes in the workspace and its running containers`;
29220
+ const found = retirement.stops.length === 0 ? ["Found nothing that would stop."] : retirement.stops.map(
29221
+ (stop) => stop.label === "" ? STOP_WORDS[stop.kind] : `${STOP_WORDS[stop.kind]} \xB7 ${stop.label}`
29222
+ );
29223
+ return [checked, ...found, "Mend starts the launching session's mend.toml Services again."];
29224
+ };
29225
+
29098
29226
  // ../../packages/domain/src/workbench/steering.ts
29099
29227
  var ConversationWaitWork = Schema_exports.Struct({
29100
29228
  kind: Schema_exports.Literals([
@@ -29547,6 +29675,120 @@ var GATEWAY_STATUSES = /* @__PURE__ */ new Set([
29547
29675
  ]);
29548
29676
  var mayStillBeWorking = (error) => error instanceof MendRequestError && (error.kind === "timeout" || error.kind === "dropped" || error.kind === "http" && error.status !== null && GATEWAY_STATUSES.has(error.status));
29549
29677
 
29678
+ // src/shared-workspace.ts
29679
+ var checkedAtOf = (retirement) => {
29680
+ if (retirement.checkedAt === null) return null;
29681
+ const at = new Date(retirement.checkedAt);
29682
+ return Number.isNaN(at.getTime()) ? null : at;
29683
+ };
29684
+ var retirementEvidenceLines = (retirement) => retirementStopLines({ stops: retirement.stops, checkedAt: checkedAtOf(retirement) });
29685
+ var workspaceReadsOf = (session) => ({
29686
+ waiting: (session.livePeople ?? []).length > 0 && session.sharedControlEnabledAt !== void 0 && session.sharedControlEnabledAt !== null,
29687
+ retirement: session.workspaceRetirement !== void 0 && session.workspaceRetirement !== null
29688
+ });
29689
+ var hasPersonFacts = (sessions) => sessions.some(
29690
+ (session) => (session.livePeople ?? []).length > 0 || session.workspaceRetirement !== void 0 && session.workspaceRetirement !== null
29691
+ );
29692
+ var worktreeListsPeople = (sessions, worktreeId) => sessions.some(
29693
+ (session) => session.worktreeId === worktreeId && (session.livePeople ?? []).length > 0
29694
+ );
29695
+ var othersLiveInWorktree = (sessions, worktreeId, viewer) => {
29696
+ if (viewer === null) return [];
29697
+ const names = /* @__PURE__ */ new Map();
29698
+ for (const session of sessions) {
29699
+ if (session.worktreeId !== worktreeId) continue;
29700
+ for (const person of session.livePeople ?? []) {
29701
+ if (person.accountId !== viewer) names.set(person.accountId, person.name);
29702
+ }
29703
+ }
29704
+ return [...names.values()];
29705
+ };
29706
+ var worktreeJoinLine = (sessions, worktreeId, viewer) => {
29707
+ const others = othersLiveInWorktree(sessions, worktreeId, viewer);
29708
+ if (others.length > 0) return joinWorktreeLine(others);
29709
+ if (viewer === null || worktreeListsPeople(sessions, worktreeId)) return null;
29710
+ const anotherLive = sessions.some(
29711
+ (session) => session.worktreeId === worktreeId && session.status !== void 0 && LIVE_STATUSES.has(session.status) && session.ownerUserId !== void 0 && session.ownerUserId !== null && session.ownerUserId !== viewer
29712
+ );
29713
+ return anotherLive ? JOIN_SHARED_HOME_LINE : null;
29714
+ };
29715
+ var launcherNameOf = (launcher, livePeople, members) => livePeople.find((person) => person.accountId === launcher)?.name ?? members.find((member) => member.userId === launcher)?.name ?? "its launcher";
29716
+ var sessionWorkspaceLines = (facts) => {
29717
+ const { retirement } = facts;
29718
+ return {
29719
+ shared: sharedWorkspaceLine(facts.livePeople, facts.viewer),
29720
+ waiting: facts.wait?.line ?? null,
29721
+ retirement: retirement === null ? null : workspaceRetirementLine(
29722
+ retirement,
29723
+ launcherNameOf(retirement.launcher, facts.livePeople, facts.members)
29724
+ ),
29725
+ // Only the change's owner replaces it, and only while it is marked: a retiring one is
29726
+ // already being replaced.
29727
+ replace: retirement === null || !retirement.canReplace || retirement.state !== "marked" ? null : {
29728
+ action: REPLACE_WORKSPACE_ACTION,
29729
+ command: `mend workspace replace ${facts.sessionId.slice(0, 8)}`,
29730
+ stops: retirementEvidenceLines(retirement)
29731
+ }
29732
+ };
29733
+ };
29734
+ var workspaceLineList = (lines) => [
29735
+ ...lines.shared === null ? [] : [lines.shared],
29736
+ ...lines.waiting === null ? [] : [lines.waiting],
29737
+ ...lines.retirement === null ? [] : [lines.retirement],
29738
+ ...lines.replace === null ? [] : [
29739
+ `${lines.replace.action} \xB7 ${lines.replace.command}`,
29740
+ ...lines.replace.stops.map((stop) => ` ${stop}`)
29741
+ ]
29742
+ ];
29743
+ var wrapWords = (text, width) => {
29744
+ const limit = Math.max(8, width);
29745
+ const lines = [];
29746
+ let current = "";
29747
+ for (const raw of text.split(/\s+/)) {
29748
+ if (raw === "") continue;
29749
+ let word = raw;
29750
+ while (word.length > limit) {
29751
+ if (current !== "") {
29752
+ lines.push(current);
29753
+ current = "";
29754
+ }
29755
+ lines.push(word.slice(0, limit));
29756
+ word = word.slice(limit);
29757
+ }
29758
+ if (current === "") current = word;
29759
+ else if (current.length + 1 + word.length <= limit) current = `${current} ${word}`;
29760
+ else {
29761
+ lines.push(current);
29762
+ current = word;
29763
+ }
29764
+ }
29765
+ if (current !== "") lines.push(current);
29766
+ return lines;
29767
+ };
29768
+ var confirmPlan = (args2, interactive) => args2.includes("--yes") || args2.includes("-y") ? "confirmed" : interactive ? "ask" : "refuse";
29769
+ var sharedControlConfirmLines = (turnsOnSendersLogin) => {
29770
+ const confirm = sharedControlConfirm(turnsOnSendersLogin);
29771
+ return [confirm.title, confirm.body];
29772
+ };
29773
+ var sharedControlQuestion = (turnsOnSendersLogin) => {
29774
+ const confirm = sharedControlConfirm(turnsOnSendersLogin);
29775
+ return `${confirm.confirm.toLowerCase()}? (n: ${confirm.cancel.toLowerCase()})`;
29776
+ };
29777
+ var replaceConfirmLines = (retirement) => [
29778
+ `${REPLACE_WORKSPACE_ACTION}?`,
29779
+ ...retirementEvidenceLines(retirement).map((stop) => ` ${stop}`)
29780
+ ];
29781
+ var askYesNo = async (question) => {
29782
+ const readline = await import("node:readline/promises");
29783
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
29784
+ try {
29785
+ const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
29786
+ return answer === "y" || answer === "yes";
29787
+ } finally {
29788
+ rl.close();
29789
+ }
29790
+ };
29791
+
29550
29792
  // src/workbench-events.ts
29551
29793
  var createSseParser = () => {
29552
29794
  let buffer = "";
@@ -29625,6 +29867,10 @@ export {
29625
29867
  servicesHoldLine,
29626
29868
  validateSecretFilePath,
29627
29869
  terminalReadOnlyLine,
29870
+ sharedControlLine,
29871
+ sharedControlConfirm,
29872
+ workspaceRetirementLine,
29873
+ REPLACE_WORKSPACE_ACTION,
29628
29874
  WORKTREE_REMOVAL_FORCE_HINT,
29629
29875
  HARNESS_COMMANDS,
29630
29876
  pendingId,
@@ -29654,6 +29900,19 @@ export {
29654
29900
  noAnswerError,
29655
29901
  GATEWAY_STATUSES,
29656
29902
  mayStillBeWorking,
29903
+ workspaceReadsOf,
29904
+ hasPersonFacts,
29905
+ worktreeListsPeople,
29906
+ worktreeJoinLine,
29907
+ launcherNameOf,
29908
+ sessionWorkspaceLines,
29909
+ workspaceLineList,
29910
+ wrapWords,
29911
+ confirmPlan,
29912
+ sharedControlConfirmLines,
29913
+ sharedControlQuestion,
29914
+ replaceConfirmLines,
29915
+ askYesNo,
29657
29916
  createSseParser,
29658
29917
  eventFamilies
29659
29918
  };
@@ -8,6 +8,7 @@ import {
8
8
  createSseParser,
9
9
  cwdFacts,
10
10
  eventFamilies,
11
+ hasPersonFacts,
11
12
  isPendingId,
12
13
  launchPhaseOf,
13
14
  matchProjectByCwd,
@@ -16,8 +17,13 @@ import {
16
17
  pendingId,
17
18
  repositoryCloneUrlIssue,
18
19
  servicesHoldLine,
19
- withoutLaunchPhase
20
- } from "./chunk-2TDRYRGO.js";
20
+ sessionWorkspaceLines,
21
+ withoutLaunchPhase,
22
+ workspaceLineList,
23
+ workspaceReadsOf,
24
+ worktreeJoinLine,
25
+ wrapWords
26
+ } from "./chunk-ASHAV3UV.js";
21
27
 
22
28
  // src/dashboard.tsx
23
29
  import { CliRenderEvents, createCliRenderer } from "@opentui/core";
@@ -541,6 +547,51 @@ var fitHints = (hints, width) => {
541
547
  return kept.length === 0 ? "\u2026" : `${kept.join(" \xB7 ")} \u2026`;
542
548
  };
543
549
  var clampIndex = (length, index) => length === 0 ? 0 : Math.max(0, Math.min(length - 1, index));
550
+ var VIEWER_KEY = ["viewer"];
551
+ var viewerNeeded = (data) => data !== void 0 && [...data.details.values()].some((detail) => hasPersonFacts(detail.sessions));
552
+ var fetchViewer = async (ctx) => {
553
+ const [view, members] = await Promise.all([
554
+ ctx.api("GET", "/organization").catch(() => null),
555
+ ctx.api("GET", "/organization/members").catch(() => [])
556
+ ]);
557
+ return { userId: view?.userId ?? null, members };
558
+ };
559
+ var WORKSPACE_FACTS_KEY = (sessionId, reads) => [
560
+ "workspace-facts",
561
+ sessionId,
562
+ reads.waiting,
563
+ reads.retirement
564
+ ];
565
+ var fetchWorkspaceFacts = async (api, sessionId, reads) => {
566
+ const [wait, retirement] = await Promise.all([
567
+ reads.waiting ? api("GET", `/sessions/${sessionId}/waiting`).catch(() => null) : null,
568
+ reads.retirement ? api(
569
+ "GET",
570
+ `/sessions/${sessionId}/workspace-retirement`
571
+ ).catch(() => null) : null
572
+ ]);
573
+ return { wait, retirement };
574
+ };
575
+ var workspaceFactRows = (session, viewer, facts, width) => workspaceLineList(
576
+ sessionWorkspaceLines({
577
+ sessionId: session.id,
578
+ livePeople: session.livePeople ?? [],
579
+ viewer: viewer?.userId ?? null,
580
+ wait: facts?.wait ?? null,
581
+ retirement: facts?.retirement ?? null,
582
+ members: viewer?.members ?? []
583
+ })
584
+ ).flatMap((line) => {
585
+ const indent = line.startsWith(" ") ? " " : "";
586
+ return wrapWords(line, width - indent.length).map((row) => `${indent}${row}`);
587
+ });
588
+ var worktreeJoinRows = (data, projectId, worktree, viewerId, width) => {
589
+ const detail = data?.details.get(projectId);
590
+ const id = "id" in worktree ? worktree.id : detail?.worktrees?.find((candidate) => candidate.name === worktree.name)?.id;
591
+ if (detail === void 0 || id === void 0) return [];
592
+ const line = worktreeJoinLine(detail.sessions, id, viewerId);
593
+ return line === null ? [] : wrapWords(line, width);
594
+ };
544
595
  var KEY_BINDINGS = [
545
596
  {
546
597
  verb: "moveUp",
@@ -2445,7 +2496,8 @@ var SessionFacts = ({
2445
2496
  group,
2446
2497
  item,
2447
2498
  rows,
2448
- tunnels
2499
+ tunnels,
2500
+ workspaceRows
2449
2501
  }) => {
2450
2502
  const { session, annotation, services } = item;
2451
2503
  const color = STATUS_COLOR[session.status] ?? MUTED;
@@ -2476,6 +2528,18 @@ var SessionFacts = ({
2476
2528
  /* @__PURE__ */ jsx3("span", { fg: MUTED, children: session.branch }),
2477
2529
  /* @__PURE__ */ jsx3("span", { fg: FAINT, children: session.baseSha === "" ? "" : ` vs ${session.baseRef ?? session.baseSha.slice(0, 12)}` })
2478
2530
  ] }, "status"),
2531
+ // Who else is live here, what a turn waits for, a workspace waiting to be replaced: right
2532
+ // under the status, before the facts a short pane may drop.
2533
+ ...workspaceRows.map((row, index) => /* @__PURE__ */ jsx3(
2534
+ "text",
2535
+ {
2536
+ height: 1,
2537
+ bg: "transparent",
2538
+ fg: row.startsWith(" ") ? MUTED : INK_2,
2539
+ children: ` ${row}`
2540
+ },
2541
+ `workspace-${index}`
2542
+ )),
2479
2543
  /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: [
2480
2544
  ...timeAgo(session.createdAt) === "" ? [] : [`created ${timeAgo(session.createdAt)}`],
2481
2545
  isPendingId(session.id) ? "provisioning" : session.id.slice(0, 8),
@@ -2638,6 +2702,28 @@ var App = ({ ctx, onQuit }) => {
2638
2702
  refetchInterval: previewLive ? 8e3 : false,
2639
2703
  retry: 1
2640
2704
  });
2705
+ const viewer = useQuery2({
2706
+ queryKey: VIEWER_KEY,
2707
+ queryFn: () => fetchViewer(ctx),
2708
+ enabled: viewerNeeded(data),
2709
+ staleTime: Number.POSITIVE_INFINITY,
2710
+ retry: false
2711
+ });
2712
+ const workspaceReads = selectedSession === null ? { waiting: false, retirement: false } : workspaceReadsOf(selectedSession);
2713
+ const workspaceFacts = useQuery2({
2714
+ queryKey: WORKSPACE_FACTS_KEY(previewSessionId ?? "none", workspaceReads),
2715
+ queryFn: () => fetchWorkspaceFacts(ctx.api, previewSessionId ?? "", workspaceReads),
2716
+ enabled: previewSessionId !== null && previewLive && detailWidth > 0 && (workspaceReads.waiting || workspaceReads.retirement),
2717
+ staleTime: 3e3,
2718
+ refetchInterval: previewLive ? 8e3 : false,
2719
+ retry: 1
2720
+ });
2721
+ const workspaceRows = selectedSession === null || !previewLive || detailWidth === 0 ? [] : workspaceFactRows(
2722
+ selectedSession,
2723
+ viewer.data,
2724
+ workspaceFacts.data,
2725
+ Math.max(16, detailWidth - 4)
2726
+ );
2641
2727
  const preview = useMemo2(
2642
2728
  () => transcript.data === void 0 ? [] : previewLines(transcript.data, {
2643
2729
  width: Math.max(16, detailWidth - 6),
@@ -2646,7 +2732,10 @@ var App = ({ ctx, onQuit }) => {
2646
2732
  [transcript.data, detailWidth]
2647
2733
  );
2648
2734
  const [previewOffset, setPreviewOffset] = useState3(0);
2649
- const factRows = Math.max(0, Math.min(SESSION_FACT_ROWS, layout.detailRows - 2));
2735
+ const factRows = Math.max(
2736
+ 0,
2737
+ Math.min(SESSION_FACT_ROWS + workspaceRows.length, layout.detailRows - 2)
2738
+ );
2650
2739
  const showFactRule = factRows > 0 && layout.detailRows - factRows > 1;
2651
2740
  const waiting = selectedSession !== null && (selectedSession.status === "starting" || isPendingId(selectedSession.id));
2652
2741
  const snake = useSnake({
@@ -3614,6 +3703,22 @@ ${tunneled}
3614
3703
  return COLUMN_TITLE[column];
3615
3704
  };
3616
3705
  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`;
3706
+ const modalWidth = Math.min(74, terminalCols - 4);
3707
+ const pickerJoinRows = picker?.worktree?.id == null || selectedProject === null ? [] : worktreeJoinRows(
3708
+ data,
3709
+ selectedProject.project.id,
3710
+ { id: picker.worktree.id },
3711
+ viewer.data?.userId ?? null,
3712
+ modalWidth - 4
3713
+ );
3714
+ const pickerJoinHeight = pickerJoinRows.length === 0 ? 0 : pickerJoinRows.length + 1;
3715
+ const creatingJoinRows = creating === null || !creating.joins ? [] : worktreeJoinRows(
3716
+ data,
3717
+ creating.projectId,
3718
+ { name: creating.name },
3719
+ viewer.data?.userId ?? null,
3720
+ modalWidth - 7
3721
+ );
3617
3722
  const creatingHeight = 2 + 1 + 1 + 6 + 1 + deriveHarnesses(null).length;
3618
3723
  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";
3619
3724
  const loadFailure = data === void 0 && failureReason !== null ? errorText(failureReason) : null;
@@ -3786,7 +3891,8 @@ ${tunneled}
3786
3891
  group: selectedGroup,
3787
3892
  item: selectedItem,
3788
3893
  rows: factRows,
3789
- tunnels: openTunnels
3894
+ tunnels: openTunnels,
3895
+ workspaceRows
3790
3896
  }
3791
3897
  ),
3792
3898
  showFactRule ? /* @__PURE__ */ jsx3("text", { height: 1, bg: "transparent", fg: FAINT, children: ` ${"\u2500".repeat(Math.max(4, detailWidth - 4))}` }) : null,
@@ -3881,15 +3987,18 @@ ${tunneled}
3881
3987
  ]
3882
3988
  }
3883
3989
  ) : null,
3884
- picker === null ? null : /* @__PURE__ */ jsx3(
3990
+ picker === null ? null : /* @__PURE__ */ jsxs3(
3885
3991
  "box",
3886
3992
  {
3887
3993
  position: "absolute",
3888
3994
  zIndex: 12,
3889
3995
  left: Math.max(1, Math.floor((terminalCols - Math.min(74, terminalCols - 4)) / 2)),
3890
- top: Math.max(1, Math.floor((terminalRows - (2 + pickerItems.length)) / 2)),
3996
+ top: Math.max(
3997
+ 1,
3998
+ Math.floor((terminalRows - (2 + pickerItems.length + pickerJoinHeight)) / 2)
3999
+ ),
3891
4000
  width: Math.min(74, terminalCols - 4),
3892
- height: 2 + pickerItems.length,
4001
+ height: 2 + pickerItems.length + pickerJoinHeight,
3893
4002
  border: true,
3894
4003
  borderStyle: "rounded",
3895
4004
  borderColor: ACCENT,
@@ -3897,15 +4006,19 @@ ${tunneled}
3897
4006
  titleAlignment: "left",
3898
4007
  backgroundColor: SURFACE,
3899
4008
  flexDirection: "column",
3900
- children: pickerItems.map((item, index) => /* @__PURE__ */ jsx3(
3901
- HarnessRow,
3902
- {
3903
- item,
3904
- selected: index === pickerIndex,
3905
- background: SURFACE
3906
- },
3907
- String(item.harness)
3908
- ))
4009
+ children: [
4010
+ pickerJoinRows.map((row, index) => /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, fg: INK_2, children: ` ${row}` }, `join-${index}`)),
4011
+ pickerJoinRows.length === 0 ? null : /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, children: " " }),
4012
+ pickerItems.map((item, index) => /* @__PURE__ */ jsx3(
4013
+ HarnessRow,
4014
+ {
4015
+ item,
4016
+ selected: index === pickerIndex,
4017
+ background: SURFACE
4018
+ },
4019
+ String(item.harness)
4020
+ ))
4021
+ ]
3909
4022
  }
3910
4023
  ),
3911
4024
  editing === null ? null : /* @__PURE__ */ jsx3(
@@ -4061,6 +4174,10 @@ ${tunneled}
4061
4174
  const branch = creating.joins || creating.branches === null ? void 0 : filterBranches(creating.branches, creating.query)[index];
4062
4175
  const active = creating.step === "base";
4063
4176
  if (branch === void 0) {
4177
+ const joinRow = creatingJoinRows[index];
4178
+ if (joinRow !== void 0) {
4179
+ return /* @__PURE__ */ jsx3("text", { height: 1, bg: SURFACE, fg: INK_2, children: ` ${joinRow}` }, `slot-${index}`);
4180
+ }
4064
4181
  const notice = creating.joins ? null : baseStepNotice(creating);
4065
4182
  return /* @__PURE__ */ jsx3(
4066
4183
  "text",