pilotswarm 0.5.37 → 0.5.39
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/mcp/dist/src/tools/agent-packages.d.ts.map +1 -1
- package/mcp/dist/src/tools/agent-packages.js +34 -14
- package/mcp/dist/src/tools/agent-packages.js.map +1 -1
- package/package.json +3 -3
- package/tui/src/node-sdk-transport.js +113 -18
- package/ui/core/src/controller.js +196 -49
- package/ui/core/src/layout.js +10 -0
- package/ui/core/src/reducer.js +265 -20
- package/ui/core/src/selectors.js +132 -10
- package/ui/core/src/state.js +90 -2
- package/ui/core/src/themes/index.js +1 -1
- package/ui/react/src/web-app.js +1097 -220
- package/web/auth/config.js +3 -0
- package/web/auth/index.js +8 -1
- package/web/auth/normalize/proxy.js +48 -0
- package/web/auth/providers/proxy.js +141 -0
- package/web/dist/assets/{abnfDiagram-VRR7QNED-DlvPH-8O.js → abnfDiagram-VRR7QNED-Hsly1fgf.js} +1 -1
- package/web/dist/assets/{arc-r1liz5e5.js → arc-DiQLL9vM.js} +1 -1
- package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-Bci5dNH3.js → architectureDiagram-ZJ3FMSHR-B8ry8WNZ.js} +1 -1
- package/web/dist/assets/{blockDiagram-677ZJIJ3-Bug32AXp.js → blockDiagram-677ZJIJ3-Z4PcukIo.js} +1 -1
- package/web/dist/assets/{c4Diagram-LMCZKHZV-DwxSFqgE.js → c4Diagram-LMCZKHZV-Cahkc0gc.js} +1 -1
- package/web/dist/assets/channel-BHLBPE50.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-B_dbI_xg.js → chunk-2Q5K7J3B-CtZuWH4J.js} +1 -1
- package/web/dist/assets/{chunk-32BRIVSS-BkF68W-R.js → chunk-32BRIVSS-C0M390-k.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-BSbNIbJu.js → chunk-5VM5RSS4-ClvX9u5j.js} +1 -1
- package/web/dist/assets/{chunk-EX3LRPZG-Blv_Iqa2.js → chunk-EX3LRPZG-BVAet5KG.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-Dk_dBFKd.js → chunk-JWPE2WC7-BccmZmrr.js} +1 -1
- package/web/dist/assets/{chunk-MOJQB5TN-DqNFBrfw.js → chunk-MOJQB5TN-BN5MKe94.js} +1 -1
- package/web/dist/assets/{chunk-RYQCIY6F-Dlv7Svpb.js → chunk-RYQCIY6F-DfWw-ACP.js} +1 -1
- package/web/dist/assets/{chunk-V7JOEXUC-DozGikwu.js → chunk-V7JOEXUC-BgUEoLnN.js} +1 -1
- package/web/dist/assets/{chunk-VR4S4FIN-CmkqvGT4.js → chunk-VR4S4FIN-k1eKfFNA.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-DyByai5N.js → chunk-XXDRQBXY-DPyV10yR.js} +1 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-rj9D2TSL.js +1 -0
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-rj9D2TSL.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-Bv-gRVvA.js → cose-bilkent-JH36ORCC-Bt_rpukK.js} +1 -1
- package/web/dist/assets/{cynefin-VYW2F7L2-DRnAsyZU.js → cynefin-VYW2F7L2-Z8eMzFMv.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-TSTJHNR4-B1fnUtjo.js → cynefinDiagram-TSTJHNR4-BJSNawFi.js} +1 -1
- package/web/dist/assets/{dagre-VKFMJZFB-DM6-QENB.js → dagre-VKFMJZFB-D6dNl8jc.js} +1 -1
- package/web/dist/assets/{diagram-FQU43EPY-BPxxnLdU.js → diagram-FQU43EPY-Cy1nbGsU.js} +1 -1
- package/web/dist/assets/{diagram-G47NLZAW-BgtJT3qF.js → diagram-G47NLZAW-CmrbuA_d.js} +1 -1
- package/web/dist/assets/{diagram-NH7WQ7WH-uekeIzsy.js → diagram-NH7WQ7WH-PoLNQE2j.js} +1 -1
- package/web/dist/assets/{diagram-OA4YK3LP-BVy3JZWE.js → diagram-OA4YK3LP-BZwNFcSV.js} +1 -1
- package/web/dist/assets/{diagram-WEI45ONY-BWdjUr32.js → diagram-WEI45ONY-pKw0b_fN.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DD4fwhNK.js → ebnfDiagram-CCIWWBDH-UNctuQCV.js} +1 -1
- package/web/dist/assets/{erDiagram-Q63AITRT-BllD_6Kh.js → erDiagram-Q63AITRT-DxIETMif.js} +1 -1
- package/web/dist/assets/{flowDiagram-23GEKE2U-QEYKkAUN.js → flowDiagram-23GEKE2U-C06IdNKV.js} +1 -1
- package/web/dist/assets/{ganttDiagram-NO4QXBWP-uZcfKTSW.js → ganttDiagram-NO4QXBWP-fU7aGyP1.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-XPdbPaH-.js → gitGraphDiagram-IHSO6WYX-9UKkPJm6.js} +1 -1
- package/web/dist/assets/index-B8nGlHwb.js +24 -0
- package/web/dist/assets/index-DtL0lbis.css +1 -0
- package/web/dist/assets/{infoDiagram-FWYZ7A6U-DXCnQOJs.js → infoDiagram-FWYZ7A6U-C6F06SQJ.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-687_4FBy.js → ishikawaDiagram-FXEZZL3T-CB-O9NfO.js} +1 -1
- package/web/dist/assets/{journeyDiagram-5HDEW3XC-GXlKuda8.js → journeyDiagram-5HDEW3XC-B-8MOf-H.js} +1 -1
- package/web/dist/assets/{kanban-definition-HUTT4EX6-C38-2DvN.js → kanban-definition-HUTT4EX6-DX0XA3hI.js} +1 -1
- package/web/dist/assets/{linear-lGPXiBcz.js → linear-D60PrjTW.js} +1 -1
- package/web/dist/assets/{mermaid.core-C_neEtab.js → mermaid.core-bonzwapf.js} +4 -4
- package/web/dist/assets/{mindmap-definition-LN4V7U3C-BaJ9tpEG.js → mindmap-definition-LN4V7U3C-Y-6Dum33.js} +1 -1
- package/web/dist/assets/{papaparse.min-rDwKYbBf.js → papaparse.min-Cas1JfRG.js} +1 -1
- package/web/dist/assets/{pegDiagram-2B236MQR-BdXJLYgY.js → pegDiagram-2B236MQR-_paO0yDw.js} +1 -1
- package/web/dist/assets/{pieDiagram-ENE6RG2P-DU19t6fM.js → pieDiagram-ENE6RG2P-xT1_Gpn4.js} +1 -1
- package/web/dist/assets/pilotswarm-BT6tOknQ.js +96 -0
- package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CBkOdPp0.js → quadrantDiagram-ABIIQ3AL-1usdLFOF.js} +1 -1
- package/web/dist/assets/{railroadDiagram-RFXS5EU6-D2DMcGDw.js → railroadDiagram-RFXS5EU6-R3lwHOz9.js} +1 -1
- package/web/dist/assets/{requirementDiagram-TGXJPOKE-Bx9tF1pr.js → requirementDiagram-TGXJPOKE-BL8J1__Z.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-HTMAVEWB-DcLlRfsi.js → sankeyDiagram-HTMAVEWB-Dz6--0pl.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-xr6UVRBO.js → sequenceDiagram-DBY2YBRQ-CG2Xpkct.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-0fUkSXmg.js → sizeCapture-X5ZJPWSS-BPbYfjxj.js} +1 -1
- package/web/dist/assets/{stateDiagram-2N3HPSRC-BWgJGGpg.js → stateDiagram-2N3HPSRC-46x_y_aA.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-C2tnolSo.js +1 -0
- package/web/dist/assets/{swimlanes-5IMT3BWC-CZKya4_i.js → swimlanes-5IMT3BWC-SXdMo4zJ.js} +2 -2
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-Dh6Sn__t.js +8 -0
- package/web/dist/assets/{timeline-definition-FHXFAJF6-Df8BpeAu.js → timeline-definition-FHXFAJF6-CGgT-b38.js} +1 -1
- package/web/dist/assets/{vennDiagram-L72KCM5P-ChX5bjT6.js → vennDiagram-L72KCM5P-BGS3xBa_.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-EHGQE667-Dr48BgaJ.js → wardleyDiagram-EHGQE667-m2U3wEZJ.js} +1 -1
- package/web/dist/assets/{xychartDiagram-FW5EYKEG-Cia_MFfc.js → xychartDiagram-FW5EYKEG-DI6tQysg.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/runtime.js +30 -7
- package/web/dist/assets/channel-jVpSYat3.js +0 -1
- package/web/dist/assets/classDiagram-OUVF2IWQ-C78zSajE.js +0 -1
- package/web/dist/assets/classDiagram-v2-EOCWNBFH-C78zSajE.js +0 -1
- package/web/dist/assets/index-BuDSoRIL.css +0 -1
- package/web/dist/assets/index-D1itTxvB.js +0 -24
- package/web/dist/assets/pilotswarm-BNKGG936.js +0 -96
- package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DmAC8DdB.js +0 -1
- package/web/dist/assets/swimlanesDiagram-G3AALYLV-CVYx20_X.js +0 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UI_COMMANDS, FOCUS_REGIONS, INSPECTOR_TABS, cycleValue } from "./commands.js";
|
|
2
|
+
import { canvasKey as canvasPrefKey } from "./state.js";
|
|
2
3
|
import { parseAgentSourceLink } from "./repo-links.js";
|
|
3
4
|
import { importPackageFilesFromLink, readImportedPackageName } from "./repo-import.js";
|
|
4
5
|
import {
|
|
@@ -50,6 +51,22 @@ import {
|
|
|
50
51
|
import { findArtifactEntry } from "./state.js";
|
|
51
52
|
import { getTheme, listThemes } from "./themes/index.js";
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The copy selector for a package LIST row: which same-named copy it is.
|
|
56
|
+
* Scope shadowing means one name can be a shared package AND a user copy at
|
|
57
|
+
* once; the row's scope (+ owner, for admins looking at someone else's user
|
|
58
|
+
* copy) is what disambiguates every read and action on it.
|
|
59
|
+
*/
|
|
60
|
+
export function packageRowSelector(row) {
|
|
61
|
+
if (!row) return null;
|
|
62
|
+
return {
|
|
63
|
+
...(row.scope === "shared" || row.scope === "user" ? { scope: row.scope } : {}),
|
|
64
|
+
...(row.scope === "user" && row.owner?.provider && row.owner?.subject
|
|
65
|
+
? { owner: { provider: row.owner.provider, subject: row.owner.subject } }
|
|
66
|
+
: {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
const ORCHESTRATION_STATS_REFRESH_MS = 20_000;
|
|
54
71
|
const SESSION_STATS_REFRESH_MS = 20_000;
|
|
55
72
|
const FLEET_STATS_REFRESH_MS = 30_000;
|
|
@@ -2414,6 +2431,17 @@ export class PilotSwarmUiController {
|
|
|
2414
2431
|
if (pendingGroupRows) {
|
|
2415
2432
|
this.dispatch({ type: "sessions/groupsLoaded", groups: pendingGroupRows, sessions });
|
|
2416
2433
|
}
|
|
2434
|
+
// Canvas summaries ride the list rows (session_canvases join) — seed
|
|
2435
|
+
// the store so rows mark on cold load, before any session is selected
|
|
2436
|
+
// or any live event arrives. Never regresses what live events already
|
|
2437
|
+
// know; see the canvas/seed reducer.
|
|
2438
|
+
const canvasSeed = [];
|
|
2439
|
+
for (const row of sessions) {
|
|
2440
|
+
for (const c of Array.isArray(row?.canvases) ? row.canvases : []) {
|
|
2441
|
+
canvasSeed.push({ sessionId: row.sessionId, slot: c.slot, name: c.name, latestRev: c.latestRev, sizeBytes: c.sizeBytes });
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
if (canvasSeed.length) this.dispatch({ type: "canvas/seed", entries: canvasSeed });
|
|
2417
2445
|
this.dispatch({ type: "sessions/loaded", sessions });
|
|
2418
2446
|
this.refreshOpenSessionOwnerFilterModal();
|
|
2419
2447
|
const selected = this.getState().sessions.activeSessionId;
|
|
@@ -3050,27 +3078,39 @@ export class PilotSwarmUiController {
|
|
|
3050
3078
|
}
|
|
3051
3079
|
}
|
|
3052
3080
|
|
|
3053
|
-
/**
|
|
3054
|
-
|
|
3055
|
-
|
|
3081
|
+
/**
|
|
3082
|
+
* Select a package: loads its detail and workspace tree.
|
|
3083
|
+
*
|
|
3084
|
+
* `selector` = { scope, owner? } from the clicked ROW. One name can be
|
|
3085
|
+
* two packages at once (scope shadowing: a shared copy plus a user
|
|
3086
|
+
* copy), and without the selector every read here resolved "own copy
|
|
3087
|
+
* first" — both rows showed the same history, and actions could land on
|
|
3088
|
+
* the copy the user was not looking at.
|
|
3089
|
+
*/
|
|
3090
|
+
async selectAdminPackage(name, selector = null) {
|
|
3091
|
+
this.dispatch({ type: "admin/packages/select", name, selector });
|
|
3056
3092
|
if (!name) return;
|
|
3093
|
+
// Selection token minted by the reducer just now; every response from
|
|
3094
|
+
// this selection carries it so a slow response for another COPY of
|
|
3095
|
+
// the same name (scope shadowing) can never land on this one.
|
|
3096
|
+
const seq = this.getState().admin?.packages?.selectionSeq ?? 0;
|
|
3057
3097
|
await Promise.all([
|
|
3058
3098
|
(async () => {
|
|
3059
3099
|
try {
|
|
3060
|
-
const detail = await this.transport.getAgentPackage(name);
|
|
3100
|
+
const detail = await this.transport.getAgentPackage(name, selector);
|
|
3061
3101
|
if (!detail) throw new Error(`package "${name}" not found`);
|
|
3062
|
-
this.dispatch({ type: "admin/packages/detail/loaded", name, detail });
|
|
3102
|
+
this.dispatch({ type: "admin/packages/detail/loaded", name, seq, detail });
|
|
3063
3103
|
} catch (error) {
|
|
3064
|
-
this.dispatch({ type: "admin/packages/detail/loadFailed", name, error: error?.message || String(error) });
|
|
3104
|
+
this.dispatch({ type: "admin/packages/detail/loadFailed", name, seq, error: error?.message || String(error) });
|
|
3065
3105
|
}
|
|
3066
3106
|
})(),
|
|
3067
3107
|
(async () => {
|
|
3068
3108
|
try {
|
|
3069
|
-
const tree = await this.transport.getAgentPackageTree(name);
|
|
3109
|
+
const tree = await this.transport.getAgentPackageTree(name, null, selector);
|
|
3070
3110
|
// A slower tree for a PREVIOUS selection must not clobber
|
|
3071
3111
|
// the current package's workspace (stale-response race).
|
|
3072
|
-
if (this.getState().admin?.packages?.
|
|
3073
|
-
this.dispatch({ type: "admin/packages/tree/loaded", name, tree });
|
|
3112
|
+
if ((this.getState().admin?.packages?.selectionSeq ?? 0) !== seq) return;
|
|
3113
|
+
this.dispatch({ type: "admin/packages/tree/loaded", name, seq, tree });
|
|
3074
3114
|
// The package's own CHANGELOG, when it ships one. Loaded
|
|
3075
3115
|
// alongside the tree rather than on demand because it is
|
|
3076
3116
|
// the first thing a reviewer wants: what changed, why, and
|
|
@@ -3079,11 +3119,12 @@ export class PilotSwarmUiController {
|
|
|
3079
3119
|
if (hasChangelog && typeof this.transport.getAgentPackageFile === "function") {
|
|
3080
3120
|
void (async () => {
|
|
3081
3121
|
try {
|
|
3082
|
-
const file = await this.transport.getAgentPackageFile(name, null, "CHANGELOG.md");
|
|
3083
|
-
if (this.getState().admin?.packages?.
|
|
3122
|
+
const file = await this.transport.getAgentPackageFile(name, null, "CHANGELOG.md", selector);
|
|
3123
|
+
if ((this.getState().admin?.packages?.selectionSeq ?? 0) !== seq) return;
|
|
3084
3124
|
this.dispatch({
|
|
3085
3125
|
type: "admin/packages/changelog/loaded",
|
|
3086
3126
|
name,
|
|
3127
|
+
seq,
|
|
3087
3128
|
content: typeof file === "string" ? file : (file?.content ?? file?.text ?? ""),
|
|
3088
3129
|
});
|
|
3089
3130
|
} catch {
|
|
@@ -3092,13 +3133,13 @@ export class PilotSwarmUiController {
|
|
|
3092
3133
|
}
|
|
3093
3134
|
})();
|
|
3094
3135
|
} else {
|
|
3095
|
-
this.dispatch({ type: "admin/packages/changelog/loaded", name, content: null });
|
|
3136
|
+
this.dispatch({ type: "admin/packages/changelog/loaded", name, seq, content: null });
|
|
3096
3137
|
}
|
|
3097
3138
|
// Default preview: plugin.json (always present in a valid package).
|
|
3098
3139
|
const first = tree?.files?.find((f) => f.path === "plugin.json") ?? tree?.files?.[0];
|
|
3099
3140
|
if (first) void this.selectAdminPackageFile(first.path);
|
|
3100
3141
|
} catch (error) {
|
|
3101
|
-
this.dispatch({ type: "admin/packages/tree/loadFailed", name, error: error?.message || String(error) });
|
|
3142
|
+
this.dispatch({ type: "admin/packages/tree/loadFailed", name, seq, error: error?.message || String(error) });
|
|
3102
3143
|
}
|
|
3103
3144
|
})(),
|
|
3104
3145
|
]);
|
|
@@ -3109,14 +3150,22 @@ export class PilotSwarmUiController {
|
|
|
3109
3150
|
const pkgs = this.getState().admin?.packages;
|
|
3110
3151
|
if (!pkgs?.list?.length) return;
|
|
3111
3152
|
// Same shared→user projection the settings tree renders, so j/k walks
|
|
3112
|
-
// the list in VISUAL order regardless of transport ordering.
|
|
3113
|
-
|
|
3153
|
+
// the list in VISUAL order regardless of transport ordering. Rows are
|
|
3154
|
+
// COPIES, not names: a shadowed name appears once per scope.
|
|
3155
|
+
const rows = [
|
|
3114
3156
|
...pkgs.list.filter((p) => p.scope === "shared"),
|
|
3115
3157
|
...pkgs.list.filter((p) => p.scope !== "shared"),
|
|
3116
|
-
]
|
|
3117
|
-
const
|
|
3118
|
-
const
|
|
3119
|
-
|
|
3158
|
+
];
|
|
3159
|
+
const keyOf = (row) => `${row.name}\u0001${row.scope ?? ""}\u0001${row.owner?.subject ?? ""}`;
|
|
3160
|
+
const selectedKey = pkgs.selectedName
|
|
3161
|
+
? `${pkgs.selectedName}\u0001${pkgs.selectedSelector?.scope ?? ""}\u0001${pkgs.selectedSelector?.owner?.subject ?? ""}`
|
|
3162
|
+
: null;
|
|
3163
|
+
let index = rows.findIndex((row) => keyOf(row) === selectedKey);
|
|
3164
|
+
if (index < 0) index = rows.findIndex((row) => row.name === pkgs.selectedName);
|
|
3165
|
+
const next = rows[Math.min(rows.length - 1, Math.max(0, (index < 0 ? (delta > 0 ? -1 : 0) : index) + delta))];
|
|
3166
|
+
if (next && (next.name !== pkgs.selectedName || keyOf(next) !== selectedKey)) {
|
|
3167
|
+
await this.selectAdminPackage(next.name, packageRowSelector(next));
|
|
3168
|
+
}
|
|
3120
3169
|
}
|
|
3121
3170
|
|
|
3122
3171
|
toggleAdminPackageDir(dir) {
|
|
@@ -3127,12 +3176,17 @@ export class PilotSwarmUiController {
|
|
|
3127
3176
|
const pkgs = this.getState().admin?.packages;
|
|
3128
3177
|
const name = pkgs?.selectedName;
|
|
3129
3178
|
if (!name || !filePath) return;
|
|
3179
|
+
// Tag the preview with the current selection token so a slow file for
|
|
3180
|
+
// one copy can't render under another copy of the same name (their
|
|
3181
|
+
// file paths are identical — both ship plugin.json).
|
|
3182
|
+
const seq = pkgs?.selectionSeq ?? 0;
|
|
3183
|
+
const selector = pkgs?.selectedSelector ?? null;
|
|
3130
3184
|
this.dispatch({ type: "admin/packages/file/loading", path: filePath });
|
|
3131
3185
|
try {
|
|
3132
|
-
const file = await this.transport.getAgentPackageFile(name, null, filePath);
|
|
3133
|
-
this.dispatch({ type: "admin/packages/file/loaded", file });
|
|
3186
|
+
const file = await this.transport.getAgentPackageFile(name, null, filePath, selector);
|
|
3187
|
+
this.dispatch({ type: "admin/packages/file/loaded", seq, file });
|
|
3134
3188
|
} catch (error) {
|
|
3135
|
-
this.dispatch({ type: "admin/packages/file/loadFailed", path: filePath, error: error?.message || String(error) });
|
|
3189
|
+
this.dispatch({ type: "admin/packages/file/loadFailed", seq, path: filePath, error: error?.message || String(error) });
|
|
3136
3190
|
}
|
|
3137
3191
|
}
|
|
3138
3192
|
|
|
@@ -3145,32 +3199,53 @@ export class PilotSwarmUiController {
|
|
|
3145
3199
|
async runAdminPackageAction(kind, arg = null) {
|
|
3146
3200
|
const pkgs = this.getState().admin?.packages;
|
|
3147
3201
|
const name = pkgs?.selectedName;
|
|
3202
|
+
const selector = pkgs?.selectedSelector ?? null;
|
|
3148
3203
|
if (!name) return;
|
|
3149
3204
|
this.dispatch({ type: "admin/packages/action/pending", action: kind });
|
|
3150
3205
|
try {
|
|
3151
3206
|
switch (kind) {
|
|
3152
3207
|
case "promote":
|
|
3153
|
-
await this.transport.setAgentPackageScope(name, "shared");
|
|
3208
|
+
await this.transport.setAgentPackageScope(name, "shared", selector);
|
|
3154
3209
|
break;
|
|
3155
3210
|
case "demote":
|
|
3156
|
-
await this.transport.setAgentPackageScope(name, "user");
|
|
3211
|
+
await this.transport.setAgentPackageScope(name, "user", selector);
|
|
3212
|
+
break;
|
|
3213
|
+
// Publish the selected copy's version (arg, default active)
|
|
3214
|
+
// into the same-named package in the OTHER scope. THE update
|
|
3215
|
+
// path when the shared name already exists — promote can only
|
|
3216
|
+
// move a row to an unused name.
|
|
3217
|
+
case "republish":
|
|
3218
|
+
await this.transport.republishAgentPackageVersion(
|
|
3219
|
+
name, arg, selector?.scope === "shared" ? "user" : "shared",
|
|
3220
|
+
);
|
|
3157
3221
|
break;
|
|
3158
3222
|
case "pin":
|
|
3159
|
-
await this.transport.pinAgentPackageVersion(name, arg);
|
|
3223
|
+
await this.transport.pinAgentPackageVersion(name, arg, selector);
|
|
3160
3224
|
break;
|
|
3161
3225
|
case "enable":
|
|
3162
3226
|
case "disable":
|
|
3163
|
-
await this.transport.setAgentPackageEnabled(name, kind === "enable");
|
|
3227
|
+
await this.transport.setAgentPackageEnabled(name, kind === "enable", selector);
|
|
3164
3228
|
break;
|
|
3165
3229
|
case "delete":
|
|
3166
|
-
await this.transport.deleteAgentPackage(name);
|
|
3230
|
+
await this.transport.deleteAgentPackage(name, selector);
|
|
3167
3231
|
break;
|
|
3168
3232
|
default:
|
|
3169
3233
|
throw new Error(`unknown package action: ${kind}`);
|
|
3170
3234
|
}
|
|
3171
3235
|
this.dispatch({ type: "admin/packages/action/done" });
|
|
3172
3236
|
await this.refreshAdminAgentPackages();
|
|
3173
|
-
|
|
3237
|
+
// Re-select the copy that still exists. promote/demote MOVE the row
|
|
3238
|
+
// to the other scope, so the pre-action selector now points at a
|
|
3239
|
+
// copy that is gone — re-selecting it would error the pane with
|
|
3240
|
+
// "package not found". Every other action leaves the copy in place.
|
|
3241
|
+
if (kind !== "delete") {
|
|
3242
|
+
const nextSelector = kind === "promote"
|
|
3243
|
+
? { scope: "shared", ...(selector?.owner ? { owner: selector.owner } : {}) }
|
|
3244
|
+
: kind === "demote"
|
|
3245
|
+
? { scope: "user", ...(selector?.owner ? { owner: selector.owner } : {}) }
|
|
3246
|
+
: selector;
|
|
3247
|
+
await this.selectAdminPackage(name, nextSelector);
|
|
3248
|
+
}
|
|
3174
3249
|
} catch (error) {
|
|
3175
3250
|
this.dispatch({ type: "admin/packages/action/failed", error: error?.message || String(error) });
|
|
3176
3251
|
}
|
|
@@ -4254,9 +4329,13 @@ export class PilotSwarmUiController {
|
|
|
4254
4329
|
applyCanvasUpdate(sessionId, event) {
|
|
4255
4330
|
const rev = Number(event?.data?.rev);
|
|
4256
4331
|
if (!sessionId || !Number.isFinite(rev) || rev <= 0) return false;
|
|
4332
|
+
const rawSlot = Number(event?.data?.slot);
|
|
4333
|
+
const slot = Number.isInteger(rawSlot) && rawSlot >= 1 && rawSlot <= 5 ? rawSlot : 1;
|
|
4257
4334
|
this.dispatch({
|
|
4258
4335
|
type: "canvas/updated",
|
|
4259
4336
|
sessionId,
|
|
4337
|
+
slot,
|
|
4338
|
+
...(typeof event?.data?.name === "string" ? { name: event.data.name } : {}),
|
|
4260
4339
|
rev,
|
|
4261
4340
|
note: typeof event?.data?.note === "string" ? event.data.note : "",
|
|
4262
4341
|
sizeBytes: event?.data?.sizeBytes,
|
|
@@ -4265,7 +4344,9 @@ export class PilotSwarmUiController {
|
|
|
4265
4344
|
|
|
4266
4345
|
const state = this.getState();
|
|
4267
4346
|
if (sessionId !== state.sessions.activeSessionId) return true;
|
|
4268
|
-
|
|
4347
|
+
// The opt-out is PER SLOT: leaving one canvas by hand must not
|
|
4348
|
+
// silence a different canvas's first draw.
|
|
4349
|
+
if (state.canvas.prefs[canvasPrefKey(sessionId, slot)]?.optedOut) return true;
|
|
4269
4350
|
const createdAt = event?.createdAt ? Date.parse(event.createdAt) : Number.NaN;
|
|
4270
4351
|
if (Number.isFinite(createdAt) && Date.now() - createdAt > PRESENTED_ARTIFACT_FRESHNESS_MS) {
|
|
4271
4352
|
return true;
|
|
@@ -4274,7 +4355,7 @@ export class PilotSwarmUiController {
|
|
|
4274
4355
|
// when the mode is already "canvas", because a phone can be off its
|
|
4275
4356
|
// canvas tab while the mode state still says canvas — the tick is
|
|
4276
4357
|
// what brings the tab back. On desktop the repeat dispatch is a no-op.
|
|
4277
|
-
this.dispatch({ type: "canvas/flip", sessionId });
|
|
4358
|
+
this.dispatch({ type: "canvas/flip", sessionId, slot });
|
|
4278
4359
|
return true;
|
|
4279
4360
|
}
|
|
4280
4361
|
|
|
@@ -4337,29 +4418,55 @@ export class PilotSwarmUiController {
|
|
|
4337
4418
|
return;
|
|
4338
4419
|
}
|
|
4339
4420
|
try {
|
|
4421
|
+
// A 30-event window, not 1: five interleaved slots mean the top
|
|
4422
|
+
// event only describes the most recently drawn slot. Latest per
|
|
4423
|
+
// slot wins; every drawn slot gets its snapshot.
|
|
4340
4424
|
const rows = await this.transport.getSessionEventsBefore(
|
|
4341
|
-
sessionId, Number.MAX_SAFE_INTEGER,
|
|
4425
|
+
sessionId, Number.MAX_SAFE_INTEGER, 30, ["session.canvas_updated"],
|
|
4342
4426
|
);
|
|
4343
|
-
const
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
rev
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4427
|
+
const bySlot = new Map();
|
|
4428
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
4429
|
+
const slot = Number(row?.data?.slot) || 1;
|
|
4430
|
+
const rev = Number(row?.data?.rev) || 0;
|
|
4431
|
+
if (!bySlot.has(slot) || rev > (Number(bySlot.get(slot)?.data?.rev) || 0)) bySlot.set(slot, row);
|
|
4432
|
+
}
|
|
4433
|
+
// Always at least a slot-1 snapshot (rev 0 marks snapshotLoaded
|
|
4434
|
+
// so this does not respin).
|
|
4435
|
+
if (!bySlot.has(1)) bySlot.set(1, null);
|
|
4436
|
+
let anyDrawn = false;
|
|
4437
|
+
for (const [slot, top] of bySlot) {
|
|
4438
|
+
const rev = Number(top?.data?.rev) || 0;
|
|
4439
|
+
if (rev > 0) anyDrawn = true;
|
|
4440
|
+
this.dispatch({
|
|
4441
|
+
type: "canvas/snapshot",
|
|
4442
|
+
sessionId,
|
|
4443
|
+
slot,
|
|
4444
|
+
rev,
|
|
4445
|
+
...(typeof top?.data?.name === "string" ? { name: top.data.name } : {}),
|
|
4446
|
+
note: typeof top?.data?.note === "string" ? top.data.note : "",
|
|
4447
|
+
sizeBytes: top?.data?.sizeBytes,
|
|
4448
|
+
responseContract: top?.data?.responseContract,
|
|
4449
|
+
});
|
|
4450
|
+
}
|
|
4451
|
+
// The latest data tick per slot is that page's cold-load state —
|
|
4452
|
+
// replay it so a freshly loaded shell reconstructs without the
|
|
4453
|
+
// agent.
|
|
4454
|
+
if (anyDrawn) {
|
|
4355
4455
|
const dataRows = await this.transport.getSessionEventsBefore(
|
|
4356
|
-
sessionId, Number.MAX_SAFE_INTEGER,
|
|
4456
|
+
sessionId, Number.MAX_SAFE_INTEGER, 30, ["session.canvas_data"],
|
|
4357
4457
|
).catch(() => []);
|
|
4358
|
-
const
|
|
4359
|
-
|
|
4458
|
+
const tickBySlot = new Map();
|
|
4459
|
+
for (const row of Array.isArray(dataRows) ? dataRows : []) {
|
|
4460
|
+
const slot = Number(row?.data?.slot) || 1;
|
|
4461
|
+
const dr = Number(row?.data?.dataRev) || 0;
|
|
4462
|
+
if (!tickBySlot.has(slot) || dr > (Number(tickBySlot.get(slot)?.data?.dataRev) || 0)) tickBySlot.set(slot, row);
|
|
4463
|
+
}
|
|
4464
|
+
for (const [slot, tick] of tickBySlot) {
|
|
4465
|
+
if (!tick?.data?.dataRev) continue;
|
|
4360
4466
|
this.dispatch({
|
|
4361
4467
|
type: "canvas/data",
|
|
4362
4468
|
sessionId,
|
|
4469
|
+
slot,
|
|
4363
4470
|
dataRev: Number(tick.data.dataRev),
|
|
4364
4471
|
payload: tick.data.payload,
|
|
4365
4472
|
note: typeof tick.data.note === "string" ? tick.data.note : "",
|
|
@@ -4371,6 +4478,27 @@ export class PilotSwarmUiController {
|
|
|
4371
4478
|
}
|
|
4372
4479
|
}
|
|
4373
4480
|
|
|
4481
|
+
/**
|
|
4482
|
+
* An agent asked to PRESENT an already-drawn canvas — no bytes, no rev,
|
|
4483
|
+
* nothing marked unseen. Same guards as a draw's auto-flip: only the
|
|
4484
|
+
* active session, only a fresh event (a reconnect replay must not yank
|
|
4485
|
+
* the view), and the user's manual dismissal of that slot still wins.
|
|
4486
|
+
*/
|
|
4487
|
+
applyCanvasPresented(sessionId, event) {
|
|
4488
|
+
const rawSlot = Number(event?.data?.slot);
|
|
4489
|
+
const slot = Number.isInteger(rawSlot) && rawSlot >= 1 && rawSlot <= 5 ? rawSlot : 1;
|
|
4490
|
+
if (!sessionId) return false;
|
|
4491
|
+
const state = this.getState();
|
|
4492
|
+
if (sessionId !== state.sessions.activeSessionId) return false;
|
|
4493
|
+
if (state.canvas.prefs[canvasPrefKey(sessionId, slot)]?.optedOut) return false;
|
|
4494
|
+
const createdAt = event?.createdAt ? Date.parse(event.createdAt) : Number.NaN;
|
|
4495
|
+
if (Number.isFinite(createdAt) && Date.now() - createdAt > PRESENTED_ARTIFACT_FRESHNESS_MS) {
|
|
4496
|
+
return false;
|
|
4497
|
+
}
|
|
4498
|
+
this.dispatch({ type: "canvas/flip", sessionId, slot });
|
|
4499
|
+
return true;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4374
4502
|
mergeSessionEvent(sessionId, event) {
|
|
4375
4503
|
if (!sessionId || !event) return false;
|
|
4376
4504
|
const state = this.getState();
|
|
@@ -4429,10 +4557,14 @@ export class PilotSwarmUiController {
|
|
|
4429
4557
|
if (event.eventType === "session.canvas_updated") {
|
|
4430
4558
|
this.applyCanvasUpdate(sessionId, event);
|
|
4431
4559
|
}
|
|
4560
|
+
if (event.eventType === "session.canvas_presented") {
|
|
4561
|
+
this.applyCanvasPresented(sessionId, event);
|
|
4562
|
+
}
|
|
4432
4563
|
if (event.eventType === "session.canvas_data") {
|
|
4433
4564
|
this.dispatch({
|
|
4434
4565
|
type: "canvas/data",
|
|
4435
4566
|
sessionId,
|
|
4567
|
+
slot: Number(event?.data?.slot) || 1,
|
|
4436
4568
|
dataRev: Number(event?.data?.dataRev),
|
|
4437
4569
|
payload: event?.data?.payload,
|
|
4438
4570
|
note: typeof event?.data?.note === "string" ? event.data.note : "",
|
|
@@ -7156,7 +7288,14 @@ export class PilotSwarmUiController {
|
|
|
7156
7288
|
}
|
|
7157
7289
|
|
|
7158
7290
|
setFocus(focusRegion) {
|
|
7159
|
-
|
|
7291
|
+
// Carry the raw request alongside the normalized region: on a phone a
|
|
7292
|
+
// workspace region (sessions/chat) normalizes to inspector, and the
|
|
7293
|
+
// mobile follow-focus effect must not read that as "open diagnostics".
|
|
7294
|
+
this.dispatch({
|
|
7295
|
+
type: "ui/focus",
|
|
7296
|
+
focusRegion: normalizeFocusRegion(focusRegion, this.getCurrentLayout()),
|
|
7297
|
+
requestedFocusRegion: focusRegion,
|
|
7298
|
+
});
|
|
7160
7299
|
}
|
|
7161
7300
|
|
|
7162
7301
|
focusNext() {
|
|
@@ -7449,7 +7588,15 @@ export class PilotSwarmUiController {
|
|
|
7449
7588
|
|
|
7450
7589
|
applyPaneVisualScrollOffset(pane, offset, options = {}, state = this.getState()) {
|
|
7451
7590
|
const maxOffset = this.getPaneMaxScrollOffset(pane, state);
|
|
7452
|
-
|
|
7591
|
+
// fromViewport: the offset was measured off the real DOM (scrollTop /
|
|
7592
|
+
// row height), which the browser has already bounded. Clamping it
|
|
7593
|
+
// against the CONTROLLER's max — counted in TUI render-metric rows, a
|
|
7594
|
+
// different unit — teleported the pane on the first wheel tick: one
|
|
7595
|
+
// 100px notch re-applied as metric-max × 16px, thousands of pixels
|
|
7596
|
+
// away. The two row spaces must never clamp each other.
|
|
7597
|
+
const nextOffset = options.fromViewport
|
|
7598
|
+
? Math.max(0, Number(offset) || 0)
|
|
7599
|
+
: Math.max(0, Math.min(Number(offset) || 0, maxOffset));
|
|
7453
7600
|
if (this.paneUsesStickyBottomFollow(pane, state)) {
|
|
7454
7601
|
const followBottom = options.followBottom !== undefined
|
|
7455
7602
|
? Boolean(options.followBottom)
|
|
@@ -7468,7 +7615,7 @@ export class PilotSwarmUiController {
|
|
|
7468
7615
|
updatePaneScrollFromViewport(pane, offset, options = {}) {
|
|
7469
7616
|
const state = this.getState();
|
|
7470
7617
|
if (this.paneUsesStickyBottomFollow(pane, state)) {
|
|
7471
|
-
this.applyPaneVisualScrollOffset(pane, offset, options, state);
|
|
7618
|
+
this.applyPaneVisualScrollOffset(pane, offset, { ...options, fromViewport: true }, state);
|
|
7472
7619
|
return;
|
|
7473
7620
|
}
|
|
7474
7621
|
this.scrollPaneTo(pane, offset);
|
package/ui/core/src/layout.js
CHANGED
|
@@ -57,6 +57,16 @@ export function getMaxSessionPaneHeight(totalHeight, bodyHeight) {
|
|
|
57
57
|
return Math.max(MIN_SESSION_PANE_HEIGHT, Math.min(maxByChatMinimum, maxByWindow));
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export function getDiagnosticsSplitAdjustBounds(columnHeight, dividerHeight) {
|
|
61
|
+
const height = Math.max(0, Number(columnHeight) || 0);
|
|
62
|
+
const divider = clamp(Math.max(0, Number(dividerHeight) || 0), 0, height);
|
|
63
|
+
const minAdjust = -(height / 2);
|
|
64
|
+
return {
|
|
65
|
+
minAdjust,
|
|
66
|
+
maxAdjust: Math.max(minAdjust, (height / 2) - divider),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
export function computeLegacyLayout(viewport, paneAdjust = 0, promptRows = 1, sessionPaneAdjust = 0, activityPaneAdjust = 0, fullscreenPane = null) {
|
|
61
71
|
const safeViewport = normalizeViewport(viewport);
|
|
62
72
|
const totalWidth = safeViewport.width;
|