tmux-ide 2.9.0-beta.47 → 2.9.0-beta.49
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/bin/cli.js +2764 -2425
- package/package.json +2 -2
- package/packages/contracts/src/cohesion-fixture.ts +2 -0
- package/packages/contracts/src/fleet-catalog.ts +2 -0
- package/packages/contracts/src/index.ts +2 -0
- package/packages/contracts/src/pane-team.ts +32 -0
- package/packages/daemon/dist/command-center/discovery.js +12 -2
- package/packages/daemon/dist/command-center/resources/agent-graph-overlay.js +2 -2
- package/packages/daemon/dist/command-center/resources/application-shell.js +22 -15
- package/packages/daemon/dist/command-center/resources/fleet-catalog.js +8 -4
- package/packages/daemon/dist/command-center/resources/fleet-resource-route.js +2 -2
- package/packages/daemon/dist/lib/daemon-embed.js +24 -3
- package/packages/daemon/dist/pane-team.js +28 -0
- package/packages/daemon/dist/terminal/attachments/agent-status-probe.js +33 -1
- package/packages/daemon/dist/terminal/attachments/claude-team-names.js +146 -0
- package/packages/daemon/dist/terminal/attachments/manual-pane-team.js +30 -0
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +1 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +1 -0
- package/packages/daemon/dist/terminal/protocol/pane-display-name.js +40 -11
- package/packages/daemon/dist/terminal/protocol/session-descriptor-discovery.js +4 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +17 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +3 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +3 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-new-agent-owner.js +86 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-command-owner.js +5 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-input.js +1 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-team-groups.js +22 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +24 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +7 -5
- package/packages/daemon/dist/tui/mirror/ui/agent-row.jsx +2 -2
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +6 -0
- package/packages/daemon/src/command-center/discovery.ts +18 -2
- package/packages/daemon/src/command-center/resources/agent-graph-overlay.ts +2 -1
- package/packages/daemon/src/command-center/resources/application-shell.ts +24 -18
- package/packages/daemon/src/command-center/resources/fleet-catalog.ts +11 -3
- package/packages/daemon/src/command-center/resources/fleet-resource-route.ts +5 -3
- package/packages/daemon/src/command-center/server.ts +1 -1
- package/packages/daemon/src/lib/daemon-embed.ts +31 -4
- package/packages/daemon/src/pane-team.ts +42 -0
- package/packages/daemon/src/terminal/attachments/agent-status-probe.ts +46 -1
- package/packages/daemon/src/terminal/attachments/claude-team-names.ts +201 -0
- package/packages/daemon/src/terminal/attachments/manual-pane-team.ts +31 -0
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +2 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +1 -0
- package/packages/daemon/src/terminal/mirror/trusted-inventory.ts +1 -0
- package/packages/daemon/src/terminal/protocol/pane-display-name.ts +51 -13
- package/packages/daemon/src/terminal/protocol/session-descriptor-discovery.ts +4 -4
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +19 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +3 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +5 -1
- package/packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog.tsx +97 -0
- package/packages/daemon/src/tui/mirror/runtime/application-new-agent-owner.ts +89 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-command-owner.ts +6 -0
- package/packages/daemon/src/tui/mirror/runtime/application-palette-input.ts +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +5 -4
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +23 -1
- package/packages/daemon/src/tui/mirror/runtime/application-team-groups.ts +40 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +27 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +10 -5
- package/packages/daemon/src/tui/mirror/ui/agent-row.tsx +7 -3
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +7 -0
|
@@ -2914,6 +2914,7 @@ export class SessionChannel {
|
|
|
2914
2914
|
active: descriptor.paneActive && descriptor.windowActive,
|
|
2915
2915
|
role: descriptor.role,
|
|
2916
2916
|
name: descriptor.name,
|
|
2917
|
+
...(descriptor.nameSource ? { nameSource: descriptor.nameSource } : {}),
|
|
2917
2918
|
type: descriptor.type,
|
|
2918
2919
|
missionStamp: descriptor.missionStamp,
|
|
2919
2920
|
nativePaneBirthId: descriptor.nativePaneBirthId ?? null,
|
|
@@ -86,7 +86,14 @@ const GENERIC_SHELLS = new Set([
|
|
|
86
86
|
const GENERIC_TITLES = new Set(["shell", "terminal", "tmux"]);
|
|
87
87
|
function boundedName(value) {
|
|
88
88
|
const name = value?.trim() ?? "";
|
|
89
|
-
return name.length > 0 &&
|
|
89
|
+
return name.length > 0 &&
|
|
90
|
+
name.length <= 80 &&
|
|
91
|
+
[...name].every((c) => {
|
|
92
|
+
const code = c.codePointAt(0);
|
|
93
|
+
return code > 31 && (code < 127 || code > 159);
|
|
94
|
+
})
|
|
95
|
+
? name
|
|
96
|
+
: null;
|
|
90
97
|
}
|
|
91
98
|
function commandBasename(value) {
|
|
92
99
|
const command = boundedName(value);
|
|
@@ -110,20 +117,20 @@ export function memorablePaneName(seed) {
|
|
|
110
117
|
const second = stableHash(`${seed}:noun`);
|
|
111
118
|
return `${ADJECTIVES[first % ADJECTIVES.length]}-${NOUNS[second % NOUNS.length]}`;
|
|
112
119
|
}
|
|
113
|
-
function
|
|
120
|
+
export function meaningfulPaneTitle(value, currentCommand, hostName) {
|
|
114
121
|
const title = boundedName(value);
|
|
115
122
|
if (!title || GENERIC_TITLES.has(title.toLowerCase()))
|
|
116
123
|
return null;
|
|
124
|
+
if (["claude", "claude code", "codex", "node", "bun", currentCommand?.toLowerCase()].includes(title.toLowerCase()))
|
|
125
|
+
return null;
|
|
117
126
|
if (title.startsWith("/") || title.startsWith("~") || title.includes("@"))
|
|
118
127
|
return null;
|
|
119
128
|
// Interactive shells commonly publish the machine hostname as their tmux
|
|
120
129
|
// title. It identifies the computer, not the work happening in this pane,
|
|
121
130
|
// so keep the memorable pane identity until a real foreground process runs.
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
(hostName &&
|
|
126
|
-
[hostName, hostName.split(".")[0]].some((host) => host?.toLowerCase() === title.toLowerCase()))))
|
|
131
|
+
if (/^[a-z0-9][a-z0-9.-]*\.[a-z0-9-]{2,}$/iu.test(title) ||
|
|
132
|
+
(hostName &&
|
|
133
|
+
[hostName, hostName.split(".")[0]].some((host) => host?.toLowerCase() === title.toLowerCase())))
|
|
127
134
|
return null;
|
|
128
135
|
return title;
|
|
129
136
|
}
|
|
@@ -131,23 +138,45 @@ export function resolvePaneDisplayName(input) {
|
|
|
131
138
|
const configuredName = boundedName(input.configuredName);
|
|
132
139
|
const configuredSource = input.configuredNameSource?.trim().toLowerCase() ?? "";
|
|
133
140
|
const generatedName = memorablePaneName(input.semanticPaneId);
|
|
141
|
+
const automaticHarnessName = [
|
|
142
|
+
"claude",
|
|
143
|
+
"claude code",
|
|
144
|
+
"codex",
|
|
145
|
+
"node",
|
|
146
|
+
"bun",
|
|
147
|
+
input.currentCommand?.toLowerCase(),
|
|
148
|
+
].includes(configuredName?.toLowerCase() ?? "");
|
|
134
149
|
const legacyConfiguredName = configuredName &&
|
|
135
150
|
configuredName !== generatedName &&
|
|
151
|
+
!automaticHarnessName &&
|
|
136
152
|
!GENERIC_TITLES.has(configuredName.toLowerCase())
|
|
137
153
|
? configuredName
|
|
138
154
|
: null;
|
|
139
155
|
if (configuredName && configuredSource === "manual")
|
|
140
156
|
return { name: configuredName, source: "manual" };
|
|
141
|
-
|
|
157
|
+
const teamName = boundedName(input.teamMemberName);
|
|
158
|
+
if (teamName)
|
|
159
|
+
return { name: teamName, source: "agent" };
|
|
160
|
+
const reportedName = boundedName(input.agentDisplayName);
|
|
161
|
+
if (reportedName &&
|
|
162
|
+
!["claude", "claude code", "codex", input.currentCommand?.toLowerCase()].includes(reportedName.toLowerCase()))
|
|
163
|
+
return { name: reportedName, source: "agent" };
|
|
164
|
+
if (configuredName &&
|
|
165
|
+
!automaticHarnessName &&
|
|
166
|
+
(configuredSource === "agent" || input.paneType === "agent"))
|
|
142
167
|
return { name: configuredName, source: "agent" };
|
|
143
168
|
if (legacyConfiguredName && configuredSource !== "generated")
|
|
144
169
|
return { name: legacyConfiguredName, source: "manual" };
|
|
145
170
|
const command = commandBasename(input.currentCommand);
|
|
146
|
-
|
|
147
|
-
return { name: command, source: "process" };
|
|
148
|
-
const title = meaningfulTitle(input.title, command, input.hostName);
|
|
171
|
+
const title = meaningfulPaneTitle(input.title, command, input.hostName);
|
|
149
172
|
if (title)
|
|
150
173
|
return { name: title, source: "title" };
|
|
174
|
+
if (reportedName)
|
|
175
|
+
return { name: reportedName, source: "agent" };
|
|
176
|
+
if (configuredName && configuredSource === "agent")
|
|
177
|
+
return { name: configuredName, source: "agent" };
|
|
178
|
+
if (command && !GENERIC_SHELLS.has(command.toLowerCase()))
|
|
179
|
+
return { name: command, source: "process" };
|
|
151
180
|
return {
|
|
152
181
|
name: configuredName && configuredSource === "generated" ? configuredName : generatedName,
|
|
153
182
|
source: "generated",
|
|
@@ -26,6 +26,7 @@ export const SESSION_PANE_DESCRIPTOR_FORMAT = [
|
|
|
26
26
|
"#{window_panes}",
|
|
27
27
|
"#{session_windows}",
|
|
28
28
|
"#{pane_birth_id}",
|
|
29
|
+
"#{qa:@tmux_ide_name_source}",
|
|
29
30
|
].join("\t");
|
|
30
31
|
/** Decode the escapes emitted by tmux's `qa` format modifier. */
|
|
31
32
|
export function decodeTmuxArgument(value) {
|
|
@@ -99,9 +100,9 @@ export function parseSessionPaneDescriptorReply(lines) {
|
|
|
99
100
|
continue;
|
|
100
101
|
}
|
|
101
102
|
const encoded = utf8Line.split("\t");
|
|
102
|
-
if (encoded.length !== 21)
|
|
103
|
+
if (encoded.length !== 21 && encoded.length !== 22)
|
|
103
104
|
continue;
|
|
104
|
-
const [runtimePaneId = "", semanticPaneId = "", role = "", type = "", currentCommand = "", cwd = "", windowIndexRaw = "", windowName = "", windowId = "", title = "", runtimeSessionId = "", paneIndexRaw = "", name = "", missionStamp = "", paneActiveRaw = "", windowActiveRaw = "", semanticWindowId = "", sessionName = "", windowPaneCountRaw = "", sessionWindowCountRaw = "", nativePaneBirthId = "",] = encoded.map(decodeTmuxArgument);
|
|
105
|
+
const [runtimePaneId = "", semanticPaneId = "", role = "", type = "", currentCommand = "", cwd = "", windowIndexRaw = "", windowName = "", windowId = "", title = "", runtimeSessionId = "", paneIndexRaw = "", name = "", missionStamp = "", paneActiveRaw = "", windowActiveRaw = "", semanticWindowId = "", sessionName = "", windowPaneCountRaw = "", sessionWindowCountRaw = "", nativePaneBirthId = "", nameSource = "",] = encoded.map(decodeTmuxArgument);
|
|
105
106
|
if (!/^%(?:0|[1-9][0-9]*)$/u.test(runtimePaneId) || runtimePaneId.length > 32)
|
|
106
107
|
continue;
|
|
107
108
|
if (!/^\$(?:0|[1-9][0-9]*)$/u.test(runtimeSessionId) || runtimeSessionId.length > 32)
|
|
@@ -154,9 +155,7 @@ export function parseSessionPaneDescriptorReply(lines) {
|
|
|
154
155
|
semanticWindowId: nonempty(semanticWindowId),
|
|
155
156
|
paneIndex,
|
|
156
157
|
name: nonempty(name),
|
|
157
|
-
|
|
158
|
-
// stays wire-compatible. Generated names are recognized deterministically.
|
|
159
|
-
nameSource: null,
|
|
158
|
+
nameSource: ["manual", "agent", "generated"].includes(nameSource) ? nameSource : null,
|
|
160
159
|
missionStamp: nonempty(missionStamp),
|
|
161
160
|
nativePaneBirthId: nonempty(nativePaneBirthId),
|
|
162
161
|
paneActive: paneActiveRaw === "1",
|
|
@@ -189,7 +189,7 @@ export function HomeAgentRoster(props) {
|
|
|
189
189
|
{(key) => {
|
|
190
190
|
const row = () => byKey().get(key);
|
|
191
191
|
return (<box height={rowHeight()} width={width()} flexShrink={0} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
192
|
-
<AgentRow surface="canvas" theme={props.theme} id={`home-agent:${key}`} name={row().name} context={[row().machineLabel, row().serverLabel, row().sessionName]
|
|
192
|
+
<AgentRow surface="canvas" theme={props.theme} id={`home-agent:${key}`} name={row().name} team={row().team} context={[row().machineLabel, row().serverLabel, row().sessionName]
|
|
193
193
|
.filter(Boolean)
|
|
194
194
|
.join(" · ")} width={width()} compact={rowHeight() === 1} interaction={props.interactionForAgent?.(row())} paneName={props.paneName} activity={row().activity} attention={row().attention} unavailable={row().paneId === null || stale(row())} selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} onOpen={(source) => {
|
|
195
195
|
if (!props.inputActive || row().paneId === null || stale(row()))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createApplicationNewAgentOwner } from "./application-new-agent-owner.js";
|
|
1
2
|
import { createEffect, createSignal, onCleanup, untrack } from "solid-js";
|
|
2
3
|
import { createApplicationHomeAgentObserver, } from "./application-home-agent-observer.js";
|
|
3
4
|
import { createHomeAgentSelectionOwner } from "./application-home-agent-selection.js";
|
|
@@ -156,12 +157,23 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
156
157
|
sessionOwner: () => options.sessionOwner(),
|
|
157
158
|
selectPane: options.interaction.selectPane,
|
|
158
159
|
});
|
|
160
|
+
const newAgent = createApplicationNewAgentOwner({
|
|
161
|
+
targetKey: () => JSON.stringify([
|
|
162
|
+
applicationGenerationNavigationKey(options.sessionOwner()?.snapshot() ?? null),
|
|
163
|
+
options.sessionOwner()?.snapshot()?.connection?.workspaceName,
|
|
164
|
+
options.sessionOwner()?.sessionName(),
|
|
165
|
+
]),
|
|
166
|
+
workspace: () => options.sessionOwner()?.snapshot()?.connection?.workspaceName ?? null,
|
|
167
|
+
create: (draft) => options.interaction.newAgent(draft.name, draft.harness),
|
|
168
|
+
setNote: options.setNote,
|
|
169
|
+
});
|
|
159
170
|
const paneRename = createApplicationPaneRenameOwner(options.interaction.renamePane, options.setNote);
|
|
160
171
|
const homeAgents = createApplicationHomeAgentsOwner({
|
|
161
172
|
catalog: options.catalog.snapshot,
|
|
162
173
|
active: () => !options.fleetHome && options.activeSurface() === "home",
|
|
163
174
|
inputActive: () => options.activeSurface() === "home" &&
|
|
164
175
|
!paneRename.draft() &&
|
|
176
|
+
!newAgent.draft() &&
|
|
165
177
|
!options.appearanceOpen?.() &&
|
|
166
178
|
!options.shell().semantic?.focus.palette.open &&
|
|
167
179
|
!options.shell().localPaletteOpen &&
|
|
@@ -222,13 +234,16 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
222
234
|
return null;
|
|
223
235
|
if (options.sessionOwner()?.snapshot()?.status !== "live")
|
|
224
236
|
return "Open a live session first";
|
|
225
|
-
return command !== "new-window" && !options.focusedPane()
|
|
237
|
+
return command !== "new-window" && command !== "new-agent" && !options.focusedPane()
|
|
238
|
+
? "Select a live pane first"
|
|
239
|
+
: null;
|
|
226
240
|
},
|
|
227
241
|
activeSurface: options.activeSurface,
|
|
228
242
|
binding: options.binding,
|
|
229
243
|
commandSource: applicationPaletteCommandSource,
|
|
230
244
|
setSurface: options.setSurface,
|
|
231
245
|
setNote: options.setNote,
|
|
246
|
+
openNewAgent: newAgent.begin,
|
|
232
247
|
openAppearance: options.openAppearance,
|
|
233
248
|
toggleSidebar: options.toggleSidebar,
|
|
234
249
|
openSessions: options.openSessions,
|
|
@@ -245,6 +260,7 @@ export function createApplicationHomeNavigationOwner(options) {
|
|
|
245
260
|
return {
|
|
246
261
|
homeAgents: options.fleetHome ? { ...homeAgents, presentation: options.fleetHome } : homeAgents,
|
|
247
262
|
paneRename,
|
|
263
|
+
newAgent,
|
|
248
264
|
paletteCommands,
|
|
249
265
|
paletteCommandList,
|
|
250
266
|
openAgent,
|
|
@@ -30,6 +30,7 @@ export function projectHomeAgentRows(session, shell) {
|
|
|
30
30
|
windowId: shell.resource.terminalInventory?.resources.find((resource) => resource.attachability.status === "available" &&
|
|
31
31
|
resource.attachability.semanticPaneId === agent.paneId)?.windowResourceId ?? null,
|
|
32
32
|
name: agent.name,
|
|
33
|
+
...(agent.team ? { team: agent.team } : {}),
|
|
33
34
|
harness: agent.harness,
|
|
34
35
|
activity: agent.activity,
|
|
35
36
|
attention: agent.attention,
|
|
@@ -41,6 +41,7 @@ export function createApplicationHomeExperience(options) {
|
|
|
41
41
|
paletteCommands: navigation.paletteCommands,
|
|
42
42
|
blocked: () => options.paletteModalOpen() ||
|
|
43
43
|
!!navigation.paneRename.draft() ||
|
|
44
|
+
!!navigation.newAgent.draft() ||
|
|
44
45
|
paletteOpen() ||
|
|
45
46
|
appearance.pickerOpen() ||
|
|
46
47
|
machines.switching() ||
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { groupApplicationTeamRows } from "./application-team-groups.js";
|
|
1
2
|
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
2
3
|
import { createHomeAgentSelectionOwner } from "./application-home-agent-selection.js";
|
|
3
4
|
export function projectHomeFleet(catalog, groups, filter) {
|
|
@@ -52,6 +53,7 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
52
53
|
const matching = query
|
|
53
54
|
? rows.filter((row) => [
|
|
54
55
|
row.name,
|
|
56
|
+
row.team?.name,
|
|
55
57
|
row.harness,
|
|
56
58
|
row.projectName,
|
|
57
59
|
row.sessionName,
|
|
@@ -67,7 +69,7 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
67
69
|
? "loading"
|
|
68
70
|
: "unavailable"
|
|
69
71
|
: "live",
|
|
70
|
-
rows: matching,
|
|
72
|
+
rows: groupApplicationTeamRows(matching),
|
|
71
73
|
observedSessions,
|
|
72
74
|
totalSessions,
|
|
73
75
|
loadingSessions,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { groupApplicationTeamRows } from "./application-team-groups.js";
|
|
1
2
|
import { CHROME_ACTIONS, SIDEBAR_ACTIONS } from "../workspace/application-action-descriptions.js";
|
|
2
3
|
import { AgentRow } from "../ui/agent-row.jsx";
|
|
3
4
|
import { fleetHostColor } from "./fleet-presentation.js";
|
|
@@ -45,7 +46,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
45
46
|
: row.session?.name === props.model.activeSessionName());
|
|
46
47
|
const preferenceKey = (group) => group.environmentId ?? group.id;
|
|
47
48
|
const rows = createMemo(() => [
|
|
48
|
-
...props.model.groups().flatMap((group) => (group.agents ?? []).map((agent) => ({
|
|
49
|
+
...props.model.groups().flatMap((group) => groupApplicationTeamRows(group.agents ?? []).map((agent) => ({
|
|
49
50
|
key: JSON.stringify([
|
|
50
51
|
group.id,
|
|
51
52
|
"agent",
|
|
@@ -374,7 +375,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
374
375
|
: row.group.state === "ready"
|
|
375
376
|
? undefined
|
|
376
377
|
: connectionDetail(row.group)} selected={Boolean(row.session && active(row))} focused={focused() && row.key === selectedKey()} onActivate={(source) => activate(row, source)}/>}>
|
|
377
|
-
{(agent) => (<AgentRow theme={props.theme} id={`machine:${row.key}`} name={agent().name} context={[
|
|
378
|
+
{(agent) => (<AgentRow theme={props.theme} id={`machine:${row.key}`} name={agent().name} team={agent().team} context={[
|
|
378
379
|
row.group.label,
|
|
379
380
|
row.group.sessions.find((session) => session.server?.serverId === agent().server?.serverId)?.serverLabel,
|
|
380
381
|
friendlySessionLabel(agent().sessionName),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createSignal } from "solid-js";
|
|
2
|
+
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
3
|
+
/** Owns one reviewed workspace target; reconnects never retarget a launch. */
|
|
4
|
+
export function createApplicationNewAgentOwner(options) {
|
|
5
|
+
const [draft, setDraft] = createSignal(null);
|
|
6
|
+
const [busy, setBusy] = createSignal(false);
|
|
7
|
+
const [error, setError] = createSignal(null);
|
|
8
|
+
let target = "";
|
|
9
|
+
const cancel = () => {
|
|
10
|
+
if (!busy())
|
|
11
|
+
setDraft(null);
|
|
12
|
+
};
|
|
13
|
+
const submit = () => {
|
|
14
|
+
const current = draft();
|
|
15
|
+
if (!current || !current.name.trim() || busy() || error())
|
|
16
|
+
return;
|
|
17
|
+
if (target !== options.targetKey()) {
|
|
18
|
+
setError("Workspace changed. Close this dialog and open New agent again.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setBusy(true);
|
|
22
|
+
const expectedTarget = target;
|
|
23
|
+
void Promise.resolve()
|
|
24
|
+
.then(() => {
|
|
25
|
+
if (options.targetKey() !== expectedTarget)
|
|
26
|
+
throw new Error("Workspace changed");
|
|
27
|
+
return options.create({ ...current, name: current.name.trim() });
|
|
28
|
+
})
|
|
29
|
+
.then((message) => {
|
|
30
|
+
options.setNote(message);
|
|
31
|
+
setDraft(null);
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
setError("Creation was not confirmed. Check the workspace before trying again.");
|
|
35
|
+
})
|
|
36
|
+
.finally(() => setBusy(false));
|
|
37
|
+
};
|
|
38
|
+
const harness = (value) => {
|
|
39
|
+
const current = draft();
|
|
40
|
+
if (current && !busy())
|
|
41
|
+
setDraft({ ...current, harness: value });
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
draft,
|
|
45
|
+
busy,
|
|
46
|
+
error,
|
|
47
|
+
cancel,
|
|
48
|
+
submit,
|
|
49
|
+
harness,
|
|
50
|
+
begin() {
|
|
51
|
+
const workspace = options.workspace();
|
|
52
|
+
if (!workspace || busy())
|
|
53
|
+
return;
|
|
54
|
+
target = options.targetKey();
|
|
55
|
+
setError(null);
|
|
56
|
+
setDraft({ name: "", harness: "claude", workspace });
|
|
57
|
+
},
|
|
58
|
+
handleKey(event) {
|
|
59
|
+
const current = draft();
|
|
60
|
+
if (!current)
|
|
61
|
+
return false;
|
|
62
|
+
if (busy())
|
|
63
|
+
return true;
|
|
64
|
+
if (event.name.toLowerCase() === "tab" && event.eventType !== "release") {
|
|
65
|
+
harness(current.harness === "claude" ? "codex" : "claude");
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const action = applicationPaneRenameKeyAction(event, current.name);
|
|
69
|
+
if (action.kind === "cancel")
|
|
70
|
+
cancel();
|
|
71
|
+
else if (action.kind === "submit")
|
|
72
|
+
submit();
|
|
73
|
+
else if (action.kind === "update")
|
|
74
|
+
setDraft({ ...current, name: action.value });
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
handlePaste(bytes) {
|
|
78
|
+
const current = draft();
|
|
79
|
+
if (!current)
|
|
80
|
+
return false;
|
|
81
|
+
if (!busy())
|
|
82
|
+
setDraft({ ...current, name: applicationPaneRenamePaste(current.name, bytes) });
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -57,6 +57,11 @@ export function createApplicationPaletteCommandOwner(options) {
|
|
|
57
57
|
options.setNote(unavailable);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
+
if (command === "new-agent") {
|
|
61
|
+
setOpen(false, source);
|
|
62
|
+
options.openNewAgent?.();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
60
65
|
if (typeof command === "object" && command.fleet) {
|
|
61
66
|
if (command.fleet.disabled || !options.openFleet) {
|
|
62
67
|
options.setNote("That machine or session is unavailable.");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { paneTeamGroupKey, } from "@tmux-ide/contracts";
|
|
2
|
+
export function applicationTeamKey(row) {
|
|
3
|
+
return row.team
|
|
4
|
+
? paneTeamGroupKey(row.team, row.machineId ?? "local", row.server?.serverId ?? "default", row.server?.generation ?? row.daemonInstanceId ?? "")
|
|
5
|
+
: null;
|
|
6
|
+
}
|
|
7
|
+
/** Group by membership, never by name, activity, or current tmux window. Stable within groups. */
|
|
8
|
+
export function groupApplicationTeamRows(rows) {
|
|
9
|
+
const groups = new Map();
|
|
10
|
+
const ungrouped = [];
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
const key = applicationTeamKey(row);
|
|
13
|
+
if (key === null) {
|
|
14
|
+
ungrouped.push(row);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const group = groups.get(key) ?? [];
|
|
18
|
+
group.push(row);
|
|
19
|
+
groups.set(key, group);
|
|
20
|
+
}
|
|
21
|
+
return [...groups.values()].flat().concat(ungrouped);
|
|
22
|
+
}
|
package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js
CHANGED
|
@@ -1125,6 +1125,30 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1125
1125
|
return `split unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
1126
1126
|
}
|
|
1127
1127
|
},
|
|
1128
|
+
async newAgent(name, harness) {
|
|
1129
|
+
const active = options.generation();
|
|
1130
|
+
const workspaceName = active?.connection?.workspaceName;
|
|
1131
|
+
if (active?.status !== "live" || !active.client || !workspaceName)
|
|
1132
|
+
throw new Error("Open a live workspace first");
|
|
1133
|
+
const response = await active.client.dispatch({
|
|
1134
|
+
kind: "owner-action",
|
|
1135
|
+
name: "workspace.pane.create",
|
|
1136
|
+
input: {
|
|
1137
|
+
workspaceName,
|
|
1138
|
+
kind: "agent",
|
|
1139
|
+
displayTitle: name,
|
|
1140
|
+
harnessProfileId: harness,
|
|
1141
|
+
role: "implementer",
|
|
1142
|
+
placement: { kind: "window" },
|
|
1143
|
+
},
|
|
1144
|
+
operationId: createOperationId(),
|
|
1145
|
+
});
|
|
1146
|
+
if (options.generation() !== active ||
|
|
1147
|
+
response.kind !== "owner-action" ||
|
|
1148
|
+
response.result === null)
|
|
1149
|
+
throw new Error("Agent creation was not confirmed");
|
|
1150
|
+
return `Created ${name}`;
|
|
1151
|
+
},
|
|
1128
1152
|
async newWindow() {
|
|
1129
1153
|
const active = options.generation();
|
|
1130
1154
|
const workspaceName = active?.connection?.workspaceName ?? null;
|
|
@@ -35,11 +35,13 @@ export function terminalPaneChromeLabel(paneId, focused, width, indicator, displ
|
|
|
35
35
|
}
|
|
36
36
|
export function terminalPaneDisplayTitle(paneId, indicator, displayName, displayNameSource) {
|
|
37
37
|
const presentedName = displayName?.trim() || paneId;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
if (indicator?.nameResolved && indicator.name.trim())
|
|
39
|
+
return indicator.name.trim();
|
|
40
|
+
if (displayNameSource === "manual" ||
|
|
41
|
+
displayNameSource === "agent" ||
|
|
42
|
+
displayNameSource === "title")
|
|
43
|
+
return presentedName;
|
|
44
|
+
return indicator?.name.trim() || presentedName;
|
|
43
45
|
}
|
|
44
46
|
export function terminalWindowTitle(window) {
|
|
45
47
|
return window.windowName ?? window.semanticWindowId ?? "window";
|
|
@@ -40,7 +40,7 @@ export function AgentRow(props) {
|
|
|
40
40
|
event.stopPropagation();
|
|
41
41
|
props.onOpen("mouse");
|
|
42
42
|
}}>
|
|
43
|
-
<NavigationRow surface={props.surface} theme={props.theme} id={props.id} width={props.width} label={props.name} marker={receipt()
|
|
43
|
+
<NavigationRow surface={props.surface} theme={props.theme} id={props.id} width={props.width} label={props.compact && props.team ? `${props.team.name} / ${props.name}` : props.name} marker={receipt()
|
|
44
44
|
? receiptMarker()
|
|
45
45
|
: props.activity === "idle" && !props.attention && !props.unavailable
|
|
46
46
|
? " "
|
|
@@ -53,7 +53,7 @@ export function AgentRow(props) {
|
|
|
53
53
|
: status()?.label} selected={props.selected} focused={props.focused} hovered={props.hovered} disabled={props.unavailable} tone={status()?.tone === "blocked" ? "warning" : "neutral"} onActivate={props.onOpen}/>
|
|
54
54
|
<Show when={!props.compact}>
|
|
55
55
|
<text height={1} width={props.width} fg={props.selected ? palette().foreground : props.theme.roles.text.muted}>
|
|
56
|
-
{clipTerminal(` ${props.context}`, props.width)}
|
|
56
|
+
{clipTerminal(` ${[props.team?.name, props.context].filter(Boolean).join(" · ")}`, props.width)}
|
|
57
57
|
</text>
|
|
58
58
|
</Show>
|
|
59
59
|
</box>);
|
|
@@ -8,6 +8,12 @@ export const PALETTE_REFERENCE_COMMANDS = {
|
|
|
8
8
|
};
|
|
9
9
|
/** Presentation only: execution remains in the existing application owners. */
|
|
10
10
|
export function applicationCommandDescription(command) {
|
|
11
|
+
if (command === "new-agent")
|
|
12
|
+
return {
|
|
13
|
+
id: command,
|
|
14
|
+
label: "New agent…",
|
|
15
|
+
detail: "Named Claude or Codex agent in this workspace",
|
|
16
|
+
};
|
|
11
17
|
if (command === "help")
|
|
12
18
|
return { id: command, label: "Using tmux-ide", detail: "Help and getting started" };
|
|
13
19
|
if (command === "hide-sidebar" || command === "show-sidebar")
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readManualPaneTeam } from "../terminal/attachments/manual-pane-team.ts";
|
|
2
|
+
import type { PaneTeamMembership } from "@tmux-ide/contracts";
|
|
3
|
+
import { decodeTmuxArgument } from "../terminal/protocol/session-descriptor-discovery.ts";
|
|
1
4
|
import type { NativePaneIdentity } from "@tmux-ide/contracts";
|
|
2
5
|
import { nativePaneIdentity } from "../lib/native-pane-identity.ts";
|
|
3
6
|
import type { InteractionPaneEndpoint } from "@tmux-ide/contracts";
|
|
@@ -223,6 +226,10 @@ export function readAdoptedSessionNames(runTmux: TmuxRunner = _tmuxRunner): stri
|
|
|
223
226
|
|
|
224
227
|
/** One live pane, with the raw agent-authority options gathered for the fleet. */
|
|
225
228
|
export interface FleetPaneFacts {
|
|
229
|
+
readonly team?: PaneTeamMembership;
|
|
230
|
+
readonly title?: string;
|
|
231
|
+
readonly name?: string | null;
|
|
232
|
+
readonly nameSource?: string | null;
|
|
226
233
|
readonly nativeIdentity?: NativePaneIdentity | null;
|
|
227
234
|
readonly nativePaneBirthId?: string | null;
|
|
228
235
|
readonly interactionEndpoint?: Extract<InteractionPaneEndpoint, { kind: "pane" }> | null;
|
|
@@ -237,6 +244,7 @@ export interface FleetPaneFacts {
|
|
|
237
244
|
readonly agentStateRaw: string | null;
|
|
238
245
|
readonly agentStatusTextRaw: string | null;
|
|
239
246
|
readonly agentDisplayNameRaw: string | null;
|
|
247
|
+
readonly teamMemberName?: string;
|
|
240
248
|
/** Durable `@agent_hint` agent identity, or null when the pane has none. */
|
|
241
249
|
readonly agentHintRaw: string | null;
|
|
242
250
|
}
|
|
@@ -271,6 +279,10 @@ const FLEET_PANE_FORMAT = [
|
|
|
271
279
|
"#{@agent_display_name}",
|
|
272
280
|
"#{@agent_hint}",
|
|
273
281
|
"#{pane_birth_id}",
|
|
282
|
+
"#{qa:pane_title}",
|
|
283
|
+
"#{qa:@ide_name}",
|
|
284
|
+
"#{qa:@tmux_ide_name_source}",
|
|
285
|
+
"#{@tmux_ide_team}",
|
|
274
286
|
FLEET_LINE_SENTINEL,
|
|
275
287
|
].join(FLEET_FIELD_SEPARATOR);
|
|
276
288
|
|
|
@@ -316,8 +328,8 @@ export function readAdoptedFleet(
|
|
|
316
328
|
if (!line) continue;
|
|
317
329
|
const fields = line.split(FLEET_FIELD_SEPARATOR);
|
|
318
330
|
// session, pane, semantic pane, incarnation pid, active, command, path, state,
|
|
319
|
-
// statusText, displayName, hint, sentinel
|
|
320
|
-
if (fields.length
|
|
331
|
+
// statusText, displayName, hint, birth ID, quoted title/name/source, sentinel
|
|
332
|
+
if (![16, 17].includes(fields.length) || fields.at(-1) !== FLEET_LINE_SENTINEL) continue;
|
|
321
333
|
const sessionName = fields[0]!;
|
|
322
334
|
if (!adoptedSet.has(sessionName)) continue;
|
|
323
335
|
const runtimePaneId = fields[1]!;
|
|
@@ -330,6 +342,10 @@ export function readAdoptedFleet(
|
|
|
330
342
|
panesBySession.set(sessionName, panes);
|
|
331
343
|
}
|
|
332
344
|
panes.push({
|
|
345
|
+
team: fields.length === 17 ? readManualPaneTeam(fields[15], incarnation) : undefined,
|
|
346
|
+
nameSource: emptyToNull(decodeTmuxArgument(fields[14]!)),
|
|
347
|
+
title: decodeTmuxArgument(fields[12]!),
|
|
348
|
+
name: emptyToNull(decodeTmuxArgument(fields[13]!)),
|
|
333
349
|
runtimePaneId,
|
|
334
350
|
semanticPaneId: emptyToNull(fields[2]!),
|
|
335
351
|
incarnation,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isAgentPane,
|
|
19
19
|
paneIdentities,
|
|
20
20
|
resolveAgentPresentation,
|
|
21
|
+
resolvedAgentLabel,
|
|
21
22
|
type ApplicationShellSessionFacts,
|
|
22
23
|
} from "./application-shell.ts";
|
|
23
24
|
|
|
@@ -143,7 +144,7 @@ export function projectApplicationShellAgentGraphOverlay(
|
|
|
143
144
|
status: nodeStatus(presentation.detectStatus),
|
|
144
145
|
statusSource: presentation.statusSource,
|
|
145
146
|
attention: presentation.attention,
|
|
146
|
-
label: nodeLabel(
|
|
147
|
+
label: nodeLabel(resolvedAgentLabel(pane, presentation, index)),
|
|
147
148
|
});
|
|
148
149
|
nodeWindowIds.add(windowId);
|
|
149
150
|
if (pane.role === "lead") leadWindowIds.push(windowId);
|