tmux-ide 2.9.0-beta.48 → 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 +2055 -1907
- 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 +4 -1
- package/packages/daemon/dist/command-center/resources/application-shell.js +2 -1
- package/packages/daemon/dist/command-center/resources/fleet-catalog.js +2 -0
- package/packages/daemon/dist/lib/daemon-embed.js +7 -5
- package/packages/daemon/dist/pane-team.js +28 -0
- package/packages/daemon/dist/terminal/attachments/agent-status-probe.js +21 -1
- package/packages/daemon/dist/terminal/attachments/claude-team-names.js +22 -6
- package/packages/daemon/dist/terminal/attachments/manual-pane-team.js +30 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.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-team-groups.js +22 -0
- package/packages/daemon/dist/tui/mirror/ui/agent-row.jsx +2 -2
- package/packages/daemon/src/command-center/discovery.ts +6 -1
- package/packages/daemon/src/command-center/resources/application-shell.ts +4 -1
- package/packages/daemon/src/command-center/resources/fleet-catalog.ts +2 -0
- package/packages/daemon/src/lib/daemon-embed.ts +7 -5
- package/packages/daemon/src/pane-team.ts +42 -0
- package/packages/daemon/src/terminal/attachments/agent-status-probe.ts +29 -1
- package/packages/daemon/src/terminal/attachments/claude-team-names.ts +43 -8
- package/packages/daemon/src/terminal/attachments/manual-pane-team.ts +31 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +1 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +3 -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-team-groups.ts +40 -0
- package/packages/daemon/src/tui/mirror/ui/agent-row.tsx +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.0-beta.
|
|
3
|
+
"version": "2.9.0-beta.49",
|
|
4
4
|
"description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"postinstall": "node scripts/postinstall.js",
|
|
65
65
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
66
66
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
67
|
-
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
|
|
67
|
+
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/agent-team-renderer.test.tsx",
|
|
68
68
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
69
69
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
70
70
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PaneTeamMembershipSchemaZ } from "./pane-team.ts";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { CommandIdSchemaZ } from "./commands.ts";
|
|
3
4
|
import {
|
|
@@ -42,6 +43,7 @@ const SessionSidebarItemSchemaZ = z
|
|
|
42
43
|
|
|
43
44
|
const AgentSidebarItemSchemaZ = z
|
|
44
45
|
.object({
|
|
46
|
+
team: PaneTeamMembershipSchemaZ.optional(),
|
|
45
47
|
id: SemanticProductIdSchemaZ,
|
|
46
48
|
name: LabelSchemaZ,
|
|
47
49
|
harness: z.enum(["codex", "claude-code", "custom"]),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PaneTeamMembershipSchemaZ } from "./pane-team.ts";
|
|
1
2
|
import {
|
|
2
3
|
InteractionPaneEndpointSchemaZ,
|
|
3
4
|
NativePaneIdentitySchemaZ,
|
|
@@ -121,6 +122,7 @@ export type FleetAgentHarness = z.infer<typeof FleetAgentHarnessSchemaZ>;
|
|
|
121
122
|
export const FleetCatalogAgentEntryV1SchemaZ = z
|
|
122
123
|
.object({
|
|
123
124
|
agentId: FleetAgentIdSchemaZ,
|
|
125
|
+
team: PaneTeamMembershipSchemaZ.optional(),
|
|
124
126
|
name: FleetLabelSchemaZ,
|
|
125
127
|
harness: FleetAgentHarnessSchemaZ,
|
|
126
128
|
activity: AgentActivitySchemaZ,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/** Presentation membership; never grants control of a pane or a harness team. */
|
|
4
|
+
export const PaneTeamMembershipSchemaZ = z
|
|
5
|
+
.object({
|
|
6
|
+
id: z.string().regex(/^team\.[a-zA-Z0-9_-]{16,64}$/u),
|
|
7
|
+
name: z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(80)
|
|
11
|
+
.refine(
|
|
12
|
+
(value) =>
|
|
13
|
+
value === value.trim() &&
|
|
14
|
+
Array.from(value).every((character) => {
|
|
15
|
+
const code = character.codePointAt(0)!;
|
|
16
|
+
return code > 31 && (code < 127 || code > 159);
|
|
17
|
+
}),
|
|
18
|
+
),
|
|
19
|
+
source: z.enum(["manual", "claude-code"]),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
export type PaneTeamMembership = z.infer<typeof PaneTeamMembershipSchemaZ>;
|
|
23
|
+
|
|
24
|
+
/** Scope imported identities to their machine and live server, not session/layout. */
|
|
25
|
+
export function paneTeamGroupKey(
|
|
26
|
+
team: PaneTeamMembership,
|
|
27
|
+
machineId: string,
|
|
28
|
+
serverId: string,
|
|
29
|
+
generation: string,
|
|
30
|
+
): string {
|
|
31
|
+
return JSON.stringify([machineId, serverId, generation, team.source, team.id]);
|
|
32
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readManualPaneTeam } from "../terminal/attachments/manual-pane-team.js";
|
|
1
2
|
import { decodeTmuxArgument } from "../terminal/protocol/session-descriptor-discovery.js";
|
|
2
3
|
import { nativePaneIdentity } from "../lib/native-pane-identity.js";
|
|
3
4
|
import { liveSessionIdForNativeIdentity } from "../terminal/protocol/live-session-identity.js";
|
|
@@ -201,6 +202,7 @@ const FLEET_PANE_FORMAT = [
|
|
|
201
202
|
"#{qa:pane_title}",
|
|
202
203
|
"#{qa:@ide_name}",
|
|
203
204
|
"#{qa:@tmux_ide_name_source}",
|
|
205
|
+
"#{@tmux_ide_team}",
|
|
204
206
|
FLEET_LINE_SENTINEL,
|
|
205
207
|
].join(FLEET_FIELD_SEPARATOR);
|
|
206
208
|
function emptyToNull(value) {
|
|
@@ -239,7 +241,7 @@ export function readAdoptedFleet(registry = getDefaultWorkspaceRegistry(), runTm
|
|
|
239
241
|
const fields = line.split(FLEET_FIELD_SEPARATOR);
|
|
240
242
|
// session, pane, semantic pane, incarnation pid, active, command, path, state,
|
|
241
243
|
// statusText, displayName, hint, birth ID, quoted title/name/source, sentinel
|
|
242
|
-
if (fields.length
|
|
244
|
+
if (![16, 17].includes(fields.length) || fields.at(-1) !== FLEET_LINE_SENTINEL)
|
|
243
245
|
continue;
|
|
244
246
|
const sessionName = fields[0];
|
|
245
247
|
if (!adoptedSet.has(sessionName))
|
|
@@ -256,6 +258,7 @@ export function readAdoptedFleet(registry = getDefaultWorkspaceRegistry(), runTm
|
|
|
256
258
|
panesBySession.set(sessionName, panes);
|
|
257
259
|
}
|
|
258
260
|
panes.push({
|
|
261
|
+
team: fields.length === 17 ? readManualPaneTeam(fields[15], incarnation) : undefined,
|
|
259
262
|
nameSource: emptyToNull(decodeTmuxArgument(fields[14])),
|
|
260
263
|
title: decodeTmuxArgument(fields[12]),
|
|
261
264
|
name: emptyToNull(decodeTmuxArgument(fields[13])),
|
|
@@ -254,7 +254,7 @@ export function resolvedAgentLabel(pane, presentation, index) {
|
|
|
254
254
|
}
|
|
255
255
|
const AGENT_STATE_STAMP = /^(?:working|blocked|done|idle):\d+$/u;
|
|
256
256
|
export function isAgentPane(pane) {
|
|
257
|
-
if (pane.teamMemberName)
|
|
257
|
+
if (pane.teamMemberName || pane.team)
|
|
258
258
|
return true;
|
|
259
259
|
// A well-formed @agent_state stamp IS the agent contract: any pane that
|
|
260
260
|
// self-reports is an agent pane, even when its command is a bare shell and
|
|
@@ -416,6 +416,7 @@ function projectApplicationShellResourceV1Core(session, paneIds, nowSec) {
|
|
|
416
416
|
// labels such as `Terminal` with the canonical harness name.
|
|
417
417
|
name: resolvedAgentLabel(pane, presentation, index),
|
|
418
418
|
harness: harnessForPane(pane),
|
|
419
|
+
...(pane.team ? { team: pane.team } : {}),
|
|
419
420
|
activity: presentation.activity,
|
|
420
421
|
paneId,
|
|
421
422
|
attention: presentation.attention,
|
|
@@ -106,6 +106,7 @@ function toPresentationPane(pane, index) {
|
|
|
106
106
|
agentStatusTextRaw: pane.agentStatusTextRaw,
|
|
107
107
|
agentDisplayNameRaw: pane.agentDisplayNameRaw,
|
|
108
108
|
teamMemberName: pane.teamMemberName,
|
|
109
|
+
team: pane.team,
|
|
109
110
|
// Authority-only: the fleet never scrapes an unopened session. `null` (not
|
|
110
111
|
// `undefined`) keeps `resolveAgentPresentation` on the ground-truth path
|
|
111
112
|
// while its scrape verdict resolves to `unknown` without any capture.
|
|
@@ -126,6 +127,7 @@ function projectSession(session, nowSec, remainingAgentBudget) {
|
|
|
126
127
|
agentId: fleetAgentIdForPane(session.name, pane),
|
|
127
128
|
name: fleetLabel(resolvedAgentLabel(presentationPane, presentation, index), `Agent ${index + 1}`),
|
|
128
129
|
harness: harnessForPane(presentationPane),
|
|
130
|
+
...(pane.team ? { team: pane.team } : {}),
|
|
129
131
|
activity: presentation.activity,
|
|
130
132
|
attention: presentation.attention,
|
|
131
133
|
statusSource: presentation.statusSource,
|
|
@@ -7,7 +7,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import { createClaudeTeamMembershipReader } from "../terminal/attachments/claude-team-names.js";
|
|
11
11
|
import { PaneSourceDiscovery } from "./pane-source-discovery.js";
|
|
12
12
|
import { createBackgroundNativeCapture } from "./background-native-capture.js";
|
|
13
13
|
import { createOwnedViewerAdapterFactory } from "./owned-viewer-factory.js";
|
|
@@ -709,7 +709,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
709
709
|
// observe one daemon-generation authority rather than whichever server a
|
|
710
710
|
// caller's ambient TMUX/PATH happens to select.
|
|
711
711
|
const tmuxAuthority = resolveWorkspacePaneTmuxAuthority();
|
|
712
|
-
const
|
|
712
|
+
const readTeamMemberships = createClaudeTeamMembershipReader(join(resolveRuntimeNamespace().claudeDir, "teams"));
|
|
713
713
|
const catalogTmuxRunner = createPinnedWorkspaceTmuxRunner(tmuxAuthority);
|
|
714
714
|
const initialTmuxProof = captureTmuxServerProof(catalogTmuxRunner);
|
|
715
715
|
const initialNativeIdentityParts = initialTmuxProof
|
|
@@ -1148,7 +1148,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1148
1148
|
},
|
|
1149
1149
|
agentStatusProbeFactory: ({ run }) => createTmuxAgentStatusProbe({
|
|
1150
1150
|
run,
|
|
1151
|
-
|
|
1151
|
+
readTeamMemberships,
|
|
1152
1152
|
captureNative: (pane, signal) => backgroundCapture?.({
|
|
1153
1153
|
paneId: pane.runtimePaneId,
|
|
1154
1154
|
nativeIdentity: pane.nativeIdentity ?? null,
|
|
@@ -1393,7 +1393,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1393
1393
|
: null, () => observationSelector?.nativeServerEpoch ?? null);
|
|
1394
1394
|
if (!fleet)
|
|
1395
1395
|
return null;
|
|
1396
|
-
const names = await
|
|
1396
|
+
const names = await readTeamMemberships(fleet.flatMap((session) => session.panes.map((pane) => ({
|
|
1397
1397
|
runtimePaneId: pane.runtimePaneId,
|
|
1398
1398
|
pid: pane.incarnation,
|
|
1399
1399
|
}))));
|
|
@@ -1401,7 +1401,9 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1401
1401
|
...session,
|
|
1402
1402
|
panes: session.panes.map((pane) => {
|
|
1403
1403
|
const name = names.get(pane.runtimePaneId);
|
|
1404
|
-
return name
|
|
1404
|
+
return name
|
|
1405
|
+
? { ...pane, teamMemberName: name.name, team: pane.team ?? name.team }
|
|
1406
|
+
: pane;
|
|
1405
1407
|
}),
|
|
1406
1408
|
}));
|
|
1407
1409
|
},
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { runtimeTmuxArgs } from "./lib/runtime-namespace.js";
|
|
4
|
+
import { manualPaneTeamStamp, PANE_TEAM_OPTION } from "./terminal/attachments/manual-pane-team.js";
|
|
5
|
+
/** Explicit membership only. Never launches, stops, or messages an agent. */
|
|
6
|
+
export function assignPaneTeam(options, run = (args) => execFileSync("tmux", args, { encoding: "utf8", timeout: 5000 })) {
|
|
7
|
+
if (!/^%\d+$/u.test(options.paneId))
|
|
8
|
+
throw new Error("Use an exact pane ID such as %3");
|
|
9
|
+
if (options.socketPath && options.socketName)
|
|
10
|
+
throw new Error("Choose one tmux socket selector");
|
|
11
|
+
if (options.socketPath && !isAbsolute(options.socketPath))
|
|
12
|
+
throw new Error("Socket path must be absolute");
|
|
13
|
+
if (options.socketName && !/^[a-zA-Z0-9_.-]+$/u.test(options.socketName))
|
|
14
|
+
throw new Error("Invalid socket name");
|
|
15
|
+
const args = (command) => options.socketPath
|
|
16
|
+
? ["-S", options.socketPath, ...command]
|
|
17
|
+
: options.socketName
|
|
18
|
+
? ["-L", options.socketName, ...command]
|
|
19
|
+
: runtimeTmuxArgs(command);
|
|
20
|
+
const pid = Number(run(args(["display-message", "-p", "-t", options.paneId, "#{pane_pid}"])).trim());
|
|
21
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
22
|
+
throw new Error("Pane is no longer available");
|
|
23
|
+
const stamp = options.name === null ? null : manualPaneTeamStamp(options.name, pid);
|
|
24
|
+
run(args(stamp === null
|
|
25
|
+
? ["set-option", "-p", "-u", "-t", options.paneId, PANE_TEAM_OPTION]
|
|
26
|
+
: ["set-option", "-p", "-t", options.paneId, PANE_TEAM_OPTION, stamp]));
|
|
27
|
+
return { paneId: options.paneId, team: options.name, source: stamp === null ? null : "manual" };
|
|
28
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readManualPaneTeam } from "./manual-pane-team.js";
|
|
1
2
|
/**
|
|
2
3
|
* Agent-status probe for the application-shell inventory — the IO half of the
|
|
3
4
|
* desktop projection's ground-truth agent detection.
|
|
@@ -40,6 +41,7 @@ const AGENT_OPTIONS_FORMAT = [
|
|
|
40
41
|
"#{@agent_display_name}",
|
|
41
42
|
"#{@agent_hint}",
|
|
42
43
|
"#{pane_pid}",
|
|
44
|
+
"#{@tmux_ide_team}",
|
|
43
45
|
AGENT_LINE_SENTINEL,
|
|
44
46
|
].join(AGENT_FIELD_SEPARATOR);
|
|
45
47
|
/** Trailing non-empty lines captured for the scrape fallback. */
|
|
@@ -69,7 +71,7 @@ function parseAgentOptions(stdout) {
|
|
|
69
71
|
continue;
|
|
70
72
|
const fields = line.split(AGENT_FIELD_SEPARATOR);
|
|
71
73
|
// paneId, state, statusText, displayName, hint, pid, sentinel
|
|
72
|
-
if (fields.length
|
|
74
|
+
if (![7, 8].includes(fields.length) || fields.at(-1) !== AGENT_LINE_SENTINEL)
|
|
73
75
|
continue;
|
|
74
76
|
const runtimePaneId = fields[0];
|
|
75
77
|
if (!RUNTIME_PANE_ID.test(runtimePaneId))
|
|
@@ -77,6 +79,7 @@ function parseAgentOptions(stdout) {
|
|
|
77
79
|
const pidText = fields[5];
|
|
78
80
|
const pid = /^[0-9]+$/u.test(pidText) ? Number(pidText) : null;
|
|
79
81
|
result.set(runtimePaneId, {
|
|
82
|
+
team: fields.length === 8 ? readManualPaneTeam(fields[6], pid) : undefined,
|
|
80
83
|
stateRaw: emptyToNull(fields[1]),
|
|
81
84
|
statusTextRaw: emptyToNull(fields[2]),
|
|
82
85
|
displayNameRaw: emptyToNull(fields[3]),
|
|
@@ -166,6 +169,7 @@ export function createTmuxAgentStatusProbe(deps) {
|
|
|
166
169
|
const candidates = [];
|
|
167
170
|
const emit = (pane, raw, scrape, agentKind) => {
|
|
168
171
|
facts.set(pane.runtimePaneId, {
|
|
172
|
+
...(raw?.team ? { team: raw.team } : {}),
|
|
169
173
|
agentKind,
|
|
170
174
|
agentStateRaw: raw?.stateRaw ?? null,
|
|
171
175
|
agentStatusTextRaw: raw?.statusTextRaw ?? null,
|
|
@@ -280,6 +284,22 @@ export function createTmuxAgentStatusProbe(deps) {
|
|
|
280
284
|
});
|
|
281
285
|
emit(pane, raw, verdict, manifest.id);
|
|
282
286
|
}
|
|
287
|
+
if (deps.readTeamMemberships) {
|
|
288
|
+
const memberships = await deps.readTeamMemberships(input.panes.flatMap((pane) => {
|
|
289
|
+
const pid = options.get(pane.runtimePaneId)?.pid;
|
|
290
|
+
return pid ? [{ runtimePaneId: pane.runtimePaneId, pid }] : [];
|
|
291
|
+
}));
|
|
292
|
+
throwIfAborted(signal);
|
|
293
|
+
for (const [paneId, member] of memberships) {
|
|
294
|
+
const fact = facts.get(paneId);
|
|
295
|
+
if (fact)
|
|
296
|
+
facts.set(paneId, {
|
|
297
|
+
...fact,
|
|
298
|
+
teamMemberName: member.name,
|
|
299
|
+
team: fact.team ?? member.team,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
283
303
|
if (deps.readTeamNames) {
|
|
284
304
|
const names = await deps.readTeamNames(input.panes.flatMap((pane) => {
|
|
285
305
|
const pid = options.get(pane.runtimePaneId)?.pid;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
/** Read-only compatibility adapter for Claude Code's runtime team metadata.
|
|
2
3
|
* Names are presentation evidence, never agent identity or permission authority.
|
|
3
4
|
*/
|
|
@@ -53,12 +54,15 @@ function matchesProcess(command, member) {
|
|
|
53
54
|
}
|
|
54
55
|
return true;
|
|
55
56
|
}
|
|
56
|
-
export function
|
|
57
|
+
export function resolveClaudeTeamMember(pane, members, processes) {
|
|
57
58
|
// A native pane id alone cannot bind a team: different servers reuse %N.
|
|
58
59
|
// Its live shell PID scopes the match to this host and this pane's subtree.
|
|
59
60
|
const subtree = subtreeEntries(processes, pane.pid);
|
|
60
61
|
const matches = members.filter((m) => m.paneId === pane.runtimePaneId && subtree.some((p) => matchesProcess(p.command, m)));
|
|
61
|
-
return matches.length === 1 ? matches[0]
|
|
62
|
+
return matches.length === 1 ? matches[0] : null;
|
|
63
|
+
}
|
|
64
|
+
export function resolveClaudeTeamName(pane, members, processes) {
|
|
65
|
+
return resolveClaudeTeamMember(pane, members, processes)?.name ?? null;
|
|
62
66
|
}
|
|
63
67
|
async function readMembers(directory) {
|
|
64
68
|
try {
|
|
@@ -94,7 +98,7 @@ async function readMembers(directory) {
|
|
|
94
98
|
return [];
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
|
-
export function
|
|
101
|
+
export function createClaudeTeamMembershipReader(directory, deps = {}) {
|
|
98
102
|
const now = deps.now ?? Date.now;
|
|
99
103
|
let snapshot = null;
|
|
100
104
|
let pending = null;
|
|
@@ -121,10 +125,22 @@ export function createClaudeTeamNameReader(directory, deps = {}) {
|
|
|
121
125
|
const current = snapshot;
|
|
122
126
|
const names = new Map();
|
|
123
127
|
for (const pane of panes) {
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
names.set(pane.runtimePaneId,
|
|
128
|
+
const member = resolveClaudeTeamMember(pane, current.members, current.processes);
|
|
129
|
+
if (member)
|
|
130
|
+
names.set(pane.runtimePaneId, {
|
|
131
|
+
name: member.name,
|
|
132
|
+
team: {
|
|
133
|
+
id: `team.${createHash("sha256").update(member.team).digest("hex").slice(0, 32)}`,
|
|
134
|
+
name: member.team.slice(0, 80),
|
|
135
|
+
source: "claude-code",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
127
138
|
}
|
|
128
139
|
return names;
|
|
129
140
|
};
|
|
130
141
|
}
|
|
142
|
+
/** Compatibility API for callers interested only in the shared display name. */
|
|
143
|
+
export function createClaudeTeamNameReader(directory, deps = {}) {
|
|
144
|
+
const read = createClaudeTeamMembershipReader(directory, deps);
|
|
145
|
+
return async (panes) => new Map([...(await read(panes))].map(([id, value]) => [id, value.name]));
|
|
146
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { PaneTeamMembershipSchemaZ } from "@tmux-ide/contracts";
|
|
3
|
+
export const PANE_TEAM_OPTION = "@tmux_ide_team";
|
|
4
|
+
/** Pane-local metadata survives layout moves, but never a replacement process. */
|
|
5
|
+
export function manualPaneTeamStamp(name, pid) {
|
|
6
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
7
|
+
throw new Error("Invalid pane process");
|
|
8
|
+
const team = PaneTeamMembershipSchemaZ.parse({
|
|
9
|
+
id: `team.${createHash("sha256").update(name).digest("hex").slice(0, 32)}`,
|
|
10
|
+
name,
|
|
11
|
+
source: "manual",
|
|
12
|
+
});
|
|
13
|
+
return `v1.${Buffer.from(JSON.stringify({ version: 1, pid, team })).toString("base64url")}`;
|
|
14
|
+
}
|
|
15
|
+
export function readManualPaneTeam(raw, pid) {
|
|
16
|
+
if (!raw || raw.length > 2048 || !pid)
|
|
17
|
+
return undefined;
|
|
18
|
+
try {
|
|
19
|
+
if (!/^v1\.[A-Za-z0-9_-]+$/u.test(raw))
|
|
20
|
+
return undefined;
|
|
21
|
+
const value = JSON.parse(Buffer.from(raw.slice(3), "base64url").toString("utf8"));
|
|
22
|
+
if (value.version !== 1 || value.pid !== pid)
|
|
23
|
+
return undefined;
|
|
24
|
+
const parsed = PaneTeamMembershipSchemaZ.safeParse(value.team);
|
|
25
|
+
return parsed.success && parsed.data.source === "manual" ? parsed.data : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -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()))
|
|
@@ -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,
|
|
@@ -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,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
|
+
}
|
|
@@ -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>);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readManualPaneTeam } from "../terminal/attachments/manual-pane-team.ts";
|
|
2
|
+
import type { PaneTeamMembership } from "@tmux-ide/contracts";
|
|
1
3
|
import { decodeTmuxArgument } from "../terminal/protocol/session-descriptor-discovery.ts";
|
|
2
4
|
import type { NativePaneIdentity } from "@tmux-ide/contracts";
|
|
3
5
|
import { nativePaneIdentity } from "../lib/native-pane-identity.ts";
|
|
@@ -224,6 +226,7 @@ export function readAdoptedSessionNames(runTmux: TmuxRunner = _tmuxRunner): stri
|
|
|
224
226
|
|
|
225
227
|
/** One live pane, with the raw agent-authority options gathered for the fleet. */
|
|
226
228
|
export interface FleetPaneFacts {
|
|
229
|
+
readonly team?: PaneTeamMembership;
|
|
227
230
|
readonly title?: string;
|
|
228
231
|
readonly name?: string | null;
|
|
229
232
|
readonly nameSource?: string | null;
|
|
@@ -279,6 +282,7 @@ const FLEET_PANE_FORMAT = [
|
|
|
279
282
|
"#{qa:pane_title}",
|
|
280
283
|
"#{qa:@ide_name}",
|
|
281
284
|
"#{qa:@tmux_ide_name_source}",
|
|
285
|
+
"#{@tmux_ide_team}",
|
|
282
286
|
FLEET_LINE_SENTINEL,
|
|
283
287
|
].join(FLEET_FIELD_SEPARATOR);
|
|
284
288
|
|
|
@@ -325,7 +329,7 @@ export function readAdoptedFleet(
|
|
|
325
329
|
const fields = line.split(FLEET_FIELD_SEPARATOR);
|
|
326
330
|
// session, pane, semantic pane, incarnation pid, active, command, path, state,
|
|
327
331
|
// statusText, displayName, hint, birth ID, quoted title/name/source, sentinel
|
|
328
|
-
if (fields.length
|
|
332
|
+
if (![16, 17].includes(fields.length) || fields.at(-1) !== FLEET_LINE_SENTINEL) continue;
|
|
329
333
|
const sessionName = fields[0]!;
|
|
330
334
|
if (!adoptedSet.has(sessionName)) continue;
|
|
331
335
|
const runtimePaneId = fields[1]!;
|
|
@@ -338,6 +342,7 @@ export function readAdoptedFleet(
|
|
|
338
342
|
panesBySession.set(sessionName, panes);
|
|
339
343
|
}
|
|
340
344
|
panes.push({
|
|
345
|
+
team: fields.length === 17 ? readManualPaneTeam(fields[15], incarnation) : undefined,
|
|
341
346
|
nameSource: emptyToNull(decodeTmuxArgument(fields[14]!)),
|
|
342
347
|
title: decodeTmuxArgument(fields[12]!),
|
|
343
348
|
name: emptyToNull(decodeTmuxArgument(fields[13]!)),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PaneTeamMembership } from "@tmux-ide/contracts";
|
|
1
2
|
import { resolvePaneDisplayName } from "../../terminal/protocol/pane-display-name.ts";
|
|
2
3
|
import type { NativePaneIdentity } from "@tmux-ide/contracts";
|
|
3
4
|
import { hostname } from "node:os";
|
|
@@ -72,6 +73,7 @@ export interface ApplicationShellPanePresentationFacts {
|
|
|
72
73
|
/** Raw `@agent_display_name`; sanitized + freshness-gated in the pure layer. */
|
|
73
74
|
readonly agentDisplayNameRaw?: string | null;
|
|
74
75
|
readonly teamMemberName?: string;
|
|
76
|
+
readonly team?: PaneTeamMembership;
|
|
75
77
|
/**
|
|
76
78
|
* Screen-scrape fallback verdict the discovery layer resolved for panes
|
|
77
79
|
* WITHOUT fresh authority. `null` means authority was fresh (scrape skipped);
|
|
@@ -431,7 +433,7 @@ export function resolvedAgentLabel(
|
|
|
431
433
|
const AGENT_STATE_STAMP = /^(?:working|blocked|done|idle):\d+$/u;
|
|
432
434
|
|
|
433
435
|
export function isAgentPane(pane: ApplicationShellPanePresentationFacts): boolean {
|
|
434
|
-
if (pane.teamMemberName) return true;
|
|
436
|
+
if (pane.teamMemberName || pane.team) return true;
|
|
435
437
|
// A well-formed @agent_state stamp IS the agent contract: any pane that
|
|
436
438
|
// self-reports is an agent pane, even when its command is a bare shell and
|
|
437
439
|
// no @ide_type/role metadata exists. Staleness only affects the status, not
|
|
@@ -630,6 +632,7 @@ function projectApplicationShellResourceV1Core(
|
|
|
630
632
|
// labels such as `Terminal` with the canonical harness name.
|
|
631
633
|
name: resolvedAgentLabel(pane, presentation, index),
|
|
632
634
|
harness: harnessForPane(pane),
|
|
635
|
+
...(pane.team ? { team: pane.team } : {}),
|
|
633
636
|
activity: presentation.activity,
|
|
634
637
|
paneId,
|
|
635
638
|
attention: presentation.attention,
|
|
@@ -136,6 +136,7 @@ function toPresentationPane(
|
|
|
136
136
|
agentStatusTextRaw: pane.agentStatusTextRaw,
|
|
137
137
|
agentDisplayNameRaw: pane.agentDisplayNameRaw,
|
|
138
138
|
teamMemberName: pane.teamMemberName,
|
|
139
|
+
team: pane.team,
|
|
139
140
|
// Authority-only: the fleet never scrapes an unopened session. `null` (not
|
|
140
141
|
// `undefined`) keeps `resolveAgentPresentation` on the ground-truth path
|
|
141
142
|
// while its scrape verdict resolves to `unknown` without any capture.
|
|
@@ -163,6 +164,7 @@ function projectSession(
|
|
|
163
164
|
`Agent ${index + 1}`,
|
|
164
165
|
),
|
|
165
166
|
harness: harnessForPane(presentationPane),
|
|
167
|
+
...(pane.team ? { team: pane.team } : {}),
|
|
166
168
|
activity: presentation.activity,
|
|
167
169
|
attention: presentation.attention,
|
|
168
170
|
statusSource: presentation.statusSource,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { createClaudeTeamMembershipReader } from "../terminal/attachments/claude-team-names.ts";
|
|
3
3
|
import { PaneSourceDiscovery } from "./pane-source-discovery.ts";
|
|
4
4
|
import { createBackgroundNativeCapture } from "./background-native-capture.ts";
|
|
5
5
|
import { createOwnedViewerAdapterFactory } from "./owned-viewer-factory.ts";
|
|
@@ -1131,7 +1131,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1131
1131
|
// observe one daemon-generation authority rather than whichever server a
|
|
1132
1132
|
// caller's ambient TMUX/PATH happens to select.
|
|
1133
1133
|
const tmuxAuthority = resolveWorkspacePaneTmuxAuthority();
|
|
1134
|
-
const
|
|
1134
|
+
const readTeamMemberships = createClaudeTeamMembershipReader(
|
|
1135
1135
|
join(resolveRuntimeNamespace().claudeDir, "teams"),
|
|
1136
1136
|
);
|
|
1137
1137
|
const catalogTmuxRunner = createPinnedWorkspaceTmuxRunner(tmuxAuthority);
|
|
@@ -1628,7 +1628,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1628
1628
|
agentStatusProbeFactory: ({ run }) =>
|
|
1629
1629
|
createTmuxAgentStatusProbe({
|
|
1630
1630
|
run,
|
|
1631
|
-
|
|
1631
|
+
readTeamMemberships,
|
|
1632
1632
|
captureNative: (pane, signal) =>
|
|
1633
1633
|
backgroundCapture?.(
|
|
1634
1634
|
{
|
|
@@ -1923,7 +1923,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1923
1923
|
() => observationSelector?.nativeServerEpoch ?? null,
|
|
1924
1924
|
);
|
|
1925
1925
|
if (!fleet) return null;
|
|
1926
|
-
const names = await
|
|
1926
|
+
const names = await readTeamMemberships(
|
|
1927
1927
|
fleet.flatMap((session) =>
|
|
1928
1928
|
session.panes.map((pane) => ({
|
|
1929
1929
|
runtimePaneId: pane.runtimePaneId,
|
|
@@ -1935,7 +1935,9 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1935
1935
|
...session,
|
|
1936
1936
|
panes: session.panes.map((pane) => {
|
|
1937
1937
|
const name = names.get(pane.runtimePaneId);
|
|
1938
|
-
return name
|
|
1938
|
+
return name
|
|
1939
|
+
? { ...pane, teamMemberName: name.name, team: pane.team ?? name.team }
|
|
1940
|
+
: pane;
|
|
1939
1941
|
}),
|
|
1940
1942
|
}));
|
|
1941
1943
|
},
|