pilotswarm 0.5.0 → 0.5.2
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/session-id.d.ts +13 -0
- package/mcp/dist/src/session-id.d.ts.map +1 -0
- package/mcp/dist/src/session-id.js +15 -0
- package/mcp/dist/src/session-id.js.map +1 -0
- package/mcp/dist/src/tools/artifacts.d.ts.map +1 -1
- package/mcp/dist/src/tools/artifacts.js +5 -4
- package/mcp/dist/src/tools/artifacts.js.map +1 -1
- package/mcp/dist/src/tools/observability.d.ts.map +1 -1
- package/mcp/dist/src/tools/observability.js +2 -1
- package/mcp/dist/src/tools/observability.js.map +1 -1
- package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
- package/mcp/dist/src/tools/sessions.js +9 -8
- package/mcp/dist/src/tools/sessions.js.map +1 -1
- package/mcp/dist/src/tools/turn-control.d.ts.map +1 -1
- package/mcp/dist/src/tools/turn-control.js +5 -4
- package/mcp/dist/src/tools/turn-control.js.map +1 -1
- package/package.json +3 -3
- package/tui/plugins/.mcp.json +1 -7
- package/tui/src/app.js +36 -5
- package/tui/src/node-sdk-transport.js +17 -0
- package/ui/core/src/commands.js +6 -0
- package/ui/core/src/controller.js +193 -10
- package/ui/core/src/reducer.js +118 -1
- package/ui/core/src/selectors.js +428 -21
- package/ui/core/src/state.js +17 -0
- package/ui/react/src/chat-status.js +22 -0
- package/ui/react/src/components.js +68 -6
- package/ui/react/src/web-app.js +67 -13
- package/web/dist/assets/index-BA1Gd5ZC.js +24 -0
- package/web/dist/assets/index-Jr_Wn0fg.css +1 -0
- package/web/dist/assets/pilotswarm-C6iSDexX.js +90 -0
- package/web/dist/assets/{react-IRUjK5Iz.js → react-CqzaLZob.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/runtime.js +23 -2
- package/web/dist/assets/index-B8ge3rzb.js +0 -24
- package/web/dist/assets/index-DXRIxmIv.css +0 -1
- package/web/dist/assets/pilotswarm-Dt1qY6hD.js +0 -90
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
getFocusRightTarget,
|
|
19
19
|
getPromptInputRows,
|
|
20
20
|
MIN_SESSION_PANE_HEIGHT,
|
|
21
|
+
MIN_CHAT_PANE_HEIGHT,
|
|
22
|
+
MIN_ACTIVITY_PANE_HEIGHT,
|
|
23
|
+
MIN_INSPECTOR_PANE_HEIGHT,
|
|
24
|
+
DEFAULT_ACTIVITY_PANE_RATIO,
|
|
21
25
|
normalizeFocusRegion,
|
|
22
26
|
} from "./layout.js";
|
|
23
27
|
import { parseTerminalMarkupRuns } from "./formatting.js";
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
selectActiveArtifactLinks,
|
|
26
30
|
selectActiveHttpLinks,
|
|
27
31
|
selectActivityPane,
|
|
32
|
+
selectLiveActivityLines,
|
|
28
33
|
selectChatLines,
|
|
29
34
|
selectFileBrowserItems,
|
|
30
35
|
selectFileSessionIdsForScope,
|
|
@@ -2155,6 +2160,7 @@ export class PilotSwarmUiController {
|
|
|
2155
2160
|
try {
|
|
2156
2161
|
const profile = await this.transport.getCurrentUserProfile();
|
|
2157
2162
|
this.dispatch({ type: "admin/profile/loaded", profile });
|
|
2163
|
+
await this.refreshSystemGhcpKeyStatus(profile);
|
|
2158
2164
|
return profile || null;
|
|
2159
2165
|
} catch (error) {
|
|
2160
2166
|
this.dispatch({ type: "admin/profile/loadFailed", error: error?.message || String(error) });
|
|
@@ -2162,6 +2168,35 @@ export class PilotSwarmUiController {
|
|
|
2162
2168
|
}
|
|
2163
2169
|
}
|
|
2164
2170
|
|
|
2171
|
+
/**
|
|
2172
|
+
* Load the SYSTEM user's Copilot key status (admins only). Quietly a
|
|
2173
|
+
* no-op when the caller is not admin or the transport predates the
|
|
2174
|
+
* feature, so the Admin Console degrades to the per-user-only view.
|
|
2175
|
+
*/
|
|
2176
|
+
async refreshSystemGhcpKeyStatus(profile = null) {
|
|
2177
|
+
const effective = profile || this.getState().admin?.profile || null;
|
|
2178
|
+
if (!effective?.isAdmin) return null;
|
|
2179
|
+
if (typeof this.transport.getSystemGitHubCopilotKeyStatus !== "function") return null;
|
|
2180
|
+
this.dispatch({ type: "admin/systemGhcpKey/loading" });
|
|
2181
|
+
try {
|
|
2182
|
+
const status = await this.transport.getSystemGitHubCopilotKeyStatus();
|
|
2183
|
+
this.dispatch({ type: "admin/systemGhcpKey/loaded", status });
|
|
2184
|
+
return status || null;
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
this.dispatch({ type: "admin/systemGhcpKey/loadFailed", error: error?.message || String(error) });
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
/**
|
|
2192
|
+
* Admin-only: switch the key editor between "your key" and
|
|
2193
|
+
* "System key" (the key ownerless system sessions run on).
|
|
2194
|
+
*/
|
|
2195
|
+
setAdminGhcpKeyStoreAsSystem(value) {
|
|
2196
|
+
if (value && !this.getState().admin?.profile?.isAdmin) return;
|
|
2197
|
+
this.dispatch({ type: "admin/ghcpKey/setSystemTarget", value: Boolean(value) });
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2165
2200
|
beginAdminEditGhcpKey() {
|
|
2166
2201
|
this.dispatch({ type: "admin/ghcpKey/beginEdit", draft: "" });
|
|
2167
2202
|
}
|
|
@@ -2213,8 +2248,12 @@ export class PilotSwarmUiController {
|
|
|
2213
2248
|
* use `clearAdminGhcpKey()` for that.
|
|
2214
2249
|
*/
|
|
2215
2250
|
async saveAdminGhcpKey() {
|
|
2216
|
-
|
|
2217
|
-
|
|
2251
|
+
const storeAsSystem = Boolean(this.getState().admin.ghcpKey.storeAsSystem);
|
|
2252
|
+
const setter = storeAsSystem ? "setSystemGitHubCopilotKey" : "setCurrentUserGitHubCopilotKey";
|
|
2253
|
+
if (typeof this.transport[setter] !== "function") {
|
|
2254
|
+
this.dispatch({ type: "admin/ghcpKey/saveFailed", error: storeAsSystem
|
|
2255
|
+
? "This deployment does not support System keys yet."
|
|
2256
|
+
: "Admin Console is not available on this transport." });
|
|
2218
2257
|
return;
|
|
2219
2258
|
}
|
|
2220
2259
|
const draft = String(this.getState().admin.ghcpKey.draft || "").trim();
|
|
@@ -2224,8 +2263,13 @@ export class PilotSwarmUiController {
|
|
|
2224
2263
|
}
|
|
2225
2264
|
this.dispatch({ type: "admin/ghcpKey/saving" });
|
|
2226
2265
|
try {
|
|
2227
|
-
|
|
2228
|
-
|
|
2266
|
+
if (storeAsSystem) {
|
|
2267
|
+
const status = await this.transport.setSystemGitHubCopilotKey({ key: draft });
|
|
2268
|
+
this.dispatch({ type: "admin/systemGhcpKey/saved", status });
|
|
2269
|
+
} else {
|
|
2270
|
+
const profile = await this.transport.setCurrentUserGitHubCopilotKey({ key: draft });
|
|
2271
|
+
this.dispatch({ type: "admin/ghcpKey/saved", profile });
|
|
2272
|
+
}
|
|
2229
2273
|
} catch (error) {
|
|
2230
2274
|
this.dispatch({ type: "admin/ghcpKey/saveFailed", error: error?.message || String(error) });
|
|
2231
2275
|
}
|
|
@@ -2236,14 +2280,23 @@ export class PilotSwarmUiController {
|
|
|
2236
2280
|
* worker's env-supplied default token.
|
|
2237
2281
|
*/
|
|
2238
2282
|
async clearAdminGhcpKey() {
|
|
2239
|
-
|
|
2240
|
-
|
|
2283
|
+
const storeAsSystem = Boolean(this.getState().admin.ghcpKey.storeAsSystem);
|
|
2284
|
+
const setter = storeAsSystem ? "setSystemGitHubCopilotKey" : "setCurrentUserGitHubCopilotKey";
|
|
2285
|
+
if (typeof this.transport[setter] !== "function") {
|
|
2286
|
+
this.dispatch({ type: "admin/ghcpKey/saveFailed", error: storeAsSystem
|
|
2287
|
+
? "This deployment does not support System keys yet."
|
|
2288
|
+
: "Admin Console is not available on this transport." });
|
|
2241
2289
|
return;
|
|
2242
2290
|
}
|
|
2243
2291
|
this.dispatch({ type: "admin/ghcpKey/saving" });
|
|
2244
2292
|
try {
|
|
2245
|
-
|
|
2246
|
-
|
|
2293
|
+
if (storeAsSystem) {
|
|
2294
|
+
const status = await this.transport.setSystemGitHubCopilotKey({ key: null });
|
|
2295
|
+
this.dispatch({ type: "admin/systemGhcpKey/saved", status });
|
|
2296
|
+
} else {
|
|
2297
|
+
const profile = await this.transport.setCurrentUserGitHubCopilotKey({ key: null });
|
|
2298
|
+
this.dispatch({ type: "admin/ghcpKey/saved", profile });
|
|
2299
|
+
}
|
|
2247
2300
|
} catch (error) {
|
|
2248
2301
|
this.dispatch({ type: "admin/ghcpKey/saveFailed", error: error?.message || String(error) });
|
|
2249
2302
|
}
|
|
@@ -2883,6 +2936,10 @@ export class PilotSwarmUiController {
|
|
|
2883
2936
|
this.exitPendingPromptEdit({ restoreDraft: true });
|
|
2884
2937
|
}
|
|
2885
2938
|
this.dispatch({ type: "sessions/selected", sessionId });
|
|
2939
|
+
// Sequence-tab turn selection/expansion is per-session; clear it on
|
|
2940
|
+
// switch so a stale turn index doesn't carry into another session.
|
|
2941
|
+
this.dispatch({ type: "ui/sequenceExpandedTurns", turns: [] });
|
|
2942
|
+
this.dispatch({ type: "ui/sequenceSelectedTurn", turn: null });
|
|
2886
2943
|
}
|
|
2887
2944
|
if (this.getState().sessions.byId[sessionId]?.isGroup) {
|
|
2888
2945
|
this.detachActiveSession();
|
|
@@ -3534,6 +3591,19 @@ export class PilotSwarmUiController {
|
|
|
3534
3591
|
await this.refreshSessions();
|
|
3535
3592
|
}
|
|
3536
3593
|
|
|
3594
|
+
openHelpModal() {
|
|
3595
|
+
this.dispatch({
|
|
3596
|
+
type: "ui/modal",
|
|
3597
|
+
modal: {
|
|
3598
|
+
type: "help",
|
|
3599
|
+
title: "Keybindings",
|
|
3600
|
+
selectedIndex: 0,
|
|
3601
|
+
previousFocus: this.getState().ui.focusRegion,
|
|
3602
|
+
},
|
|
3603
|
+
});
|
|
3604
|
+
this.dispatch({ type: "ui/status", text: "Keybindings — ? or Esc to close" });
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3537
3607
|
openThemePicker() {
|
|
3538
3608
|
const themes = listThemes().map((theme) => ({
|
|
3539
3609
|
id: theme.id,
|
|
@@ -4160,7 +4230,13 @@ export class PilotSwarmUiController {
|
|
|
4160
4230
|
|
|
4161
4231
|
moveModalSelection(delta) {
|
|
4162
4232
|
const modal = this.getState().ui.modal;
|
|
4163
|
-
if (!modal
|
|
4233
|
+
if (!modal) return;
|
|
4234
|
+
if (modal.type === "help") {
|
|
4235
|
+
const current = Math.max(0, Number(modal.selectedIndex) || 0);
|
|
4236
|
+
this.dispatch({ type: "ui/modalSelection", index: Math.max(0, current + delta) });
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
if (!Array.isArray(modal.items) || modal.items.length === 0) return;
|
|
4164
4240
|
if (modal.type === "logFilter" || modal.type === "filesFilter" || modal.type === "historyFormat") {
|
|
4165
4241
|
const currentPaneIndex = Math.max(0, Math.min(Number(modal.selectedIndex) || 0, modal.items.length - 1));
|
|
4166
4242
|
const selected = modal.items[currentPaneIndex];
|
|
@@ -4216,6 +4292,10 @@ export class PilotSwarmUiController {
|
|
|
4216
4292
|
async confirmModal() {
|
|
4217
4293
|
const modal = this.getState().ui.modal;
|
|
4218
4294
|
if (!modal) return;
|
|
4295
|
+
if (modal.type === "help") {
|
|
4296
|
+
this.closeModal();
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4219
4299
|
if (modal.type === "confirm") {
|
|
4220
4300
|
const previousFocus = modal.previousFocus;
|
|
4221
4301
|
this.dispatch({ type: "ui/modal", modal: null });
|
|
@@ -4782,6 +4862,88 @@ export class PilotSwarmUiController {
|
|
|
4782
4862
|
}
|
|
4783
4863
|
}
|
|
4784
4864
|
|
|
4865
|
+
// Focus-aware vertical resize: [ / ] grow or shrink whichever pane is
|
|
4866
|
+
// focused. Sessions and chat share the left-column vertical split;
|
|
4867
|
+
// inspector and activity share the right-column vertical split. A positive
|
|
4868
|
+
// delta always grows the focused pane at the expense of its sibling.
|
|
4869
|
+
resizeFocusedPane(delta) {
|
|
4870
|
+
const focus = this.getState().ui.focusRegion;
|
|
4871
|
+
const layoutState = this.getState().ui.layout || {};
|
|
4872
|
+
const bodyHeight = this.getCurrentLayout().bodyHeight ?? (layoutState.viewportHeight ?? 40);
|
|
4873
|
+
const totalHeight = layoutState.viewportHeight ?? 40;
|
|
4874
|
+
// Bound the adjust so BOTH panes stay strictly ABOVE their collapse
|
|
4875
|
+
// thresholds (the collapse check is `<=`, hence the +1). A keyboard
|
|
4876
|
+
// resize then never hides a pane — which would drop it from the Tab
|
|
4877
|
+
// cycle — and never lands on a dead plateau. A positive delta grows the
|
|
4878
|
+
// focused pane.
|
|
4879
|
+
const clamp = (current, signed, min, max) => {
|
|
4880
|
+
const hi = Math.max(min, max);
|
|
4881
|
+
return Math.max(min, Math.min(hi, (current || 0) + signed));
|
|
4882
|
+
};
|
|
4883
|
+
if (focus === FOCUS_REGIONS.SESSIONS || focus === FOCUS_REGIONS.CHAT) {
|
|
4884
|
+
const base = getBaseSessionPaneHeight(bodyHeight);
|
|
4885
|
+
const minH = MIN_SESSION_PANE_HEIGHT + 1;
|
|
4886
|
+
const maxH = Math.min(getMaxSessionPaneHeight(totalHeight, bodyHeight), bodyHeight - MIN_CHAT_PANE_HEIGHT - 1);
|
|
4887
|
+
const signed = focus === FOCUS_REGIONS.CHAT ? -delta : delta;
|
|
4888
|
+
const next = clamp(layoutState.sessionPaneAdjust, signed, minH - base, maxH - base);
|
|
4889
|
+
this.dispatch({ type: "ui/sessionPaneAdjust", sessionPaneAdjust: next });
|
|
4890
|
+
} else if (focus === FOCUS_REGIONS.INSPECTOR || focus === FOCUS_REGIONS.ACTIVITY) {
|
|
4891
|
+
const base = Math.max(MIN_ACTIVITY_PANE_HEIGHT, Math.floor(bodyHeight * DEFAULT_ACTIVITY_PANE_RATIO));
|
|
4892
|
+
const minH = MIN_ACTIVITY_PANE_HEIGHT + 1;
|
|
4893
|
+
const maxH = bodyHeight - MIN_INSPECTOR_PANE_HEIGHT - 1;
|
|
4894
|
+
const signed = focus === FOCUS_REGIONS.INSPECTOR ? -delta : delta;
|
|
4895
|
+
const next = clamp(layoutState.activityPaneAdjust, signed, minH - base, maxH - base);
|
|
4896
|
+
this.dispatch({ type: "ui/activityPaneAdjust", activityPaneAdjust: next });
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
|
|
4900
|
+
getActiveSequenceCompletedTurns() {
|
|
4901
|
+
const state = this.getState();
|
|
4902
|
+
const sid = state.sessions.activeSessionId;
|
|
4903
|
+
if (!sid) return [];
|
|
4904
|
+
const history = state.history.bySessionId?.get?.(sid);
|
|
4905
|
+
const turns = [];
|
|
4906
|
+
const seen = new Set();
|
|
4907
|
+
for (const event of history?.events || []) {
|
|
4908
|
+
if (event?.eventType !== "session.turn_completed") continue;
|
|
4909
|
+
const turn = Number(event?.data?.turnIndex ?? event?.data?.iteration);
|
|
4910
|
+
if (!Number.isFinite(turn) || seen.has(turn)) continue;
|
|
4911
|
+
seen.add(turn);
|
|
4912
|
+
turns.push(turn);
|
|
4913
|
+
}
|
|
4914
|
+
turns.sort((a, b) => a - b);
|
|
4915
|
+
return turns;
|
|
4916
|
+
}
|
|
4917
|
+
|
|
4918
|
+
// Move the sequence-tab turn cursor among completed turns. First press with
|
|
4919
|
+
// no selection lands on the latest turn; subsequent presses step.
|
|
4920
|
+
moveSequenceSelection(delta) {
|
|
4921
|
+
const turns = this.getActiveSequenceCompletedTurns();
|
|
4922
|
+
if (turns.length === 0) return;
|
|
4923
|
+
const at = turns.indexOf(Number(this.getState().ui.sequenceSelectedTurn));
|
|
4924
|
+
const index = at === -1
|
|
4925
|
+
? turns.length - 1
|
|
4926
|
+
: Math.max(0, Math.min(turns.length - 1, at + delta));
|
|
4927
|
+
this.dispatch({ type: "ui/sequenceSelectedTurn", turn: turns[index] });
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
toggleSequenceTurnExpanded() {
|
|
4931
|
+
const turns = this.getActiveSequenceCompletedTurns();
|
|
4932
|
+
if (turns.length === 0) return;
|
|
4933
|
+
let selected = Number(this.getState().ui.sequenceSelectedTurn);
|
|
4934
|
+
if (!turns.includes(selected)) {
|
|
4935
|
+
selected = turns[turns.length - 1];
|
|
4936
|
+
this.dispatch({ type: "ui/sequenceSelectedTurn", turn: selected });
|
|
4937
|
+
}
|
|
4938
|
+
const expanded = Array.isArray(this.getState().ui.sequenceExpandedTurns)
|
|
4939
|
+
? this.getState().ui.sequenceExpandedTurns.map(Number)
|
|
4940
|
+
: [];
|
|
4941
|
+
const next = expanded.includes(selected)
|
|
4942
|
+
? expanded.filter((turn) => turn !== selected)
|
|
4943
|
+
: [...expanded, selected];
|
|
4944
|
+
this.dispatch({ type: "ui/sequenceExpandedTurns", turns: next });
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4785
4947
|
nextInspectorTab() {
|
|
4786
4948
|
const current = this.getState().ui.inspectorTab;
|
|
4787
4949
|
const inspectorTab = cycleValue(INSPECTOR_TABS, current, 1);
|
|
@@ -5058,7 +5220,10 @@ export class PilotSwarmUiController {
|
|
|
5058
5220
|
|
|
5059
5221
|
const contentWidth = Math.max(20, layout.leftWidth - 4);
|
|
5060
5222
|
const contentHeight = Math.max(1, layout.chatPaneHeight - 2);
|
|
5061
|
-
const lines =
|
|
5223
|
+
const lines = [
|
|
5224
|
+
...selectChatLines(state, contentWidth),
|
|
5225
|
+
...selectLiveActivityLines(state),
|
|
5226
|
+
];
|
|
5062
5227
|
const bottomStickyLines = selectOutboxOverlayLines(state, contentWidth);
|
|
5063
5228
|
const bottomStickyHeight = Math.min(
|
|
5064
5229
|
Math.max(0, Math.floor(contentHeight * 0.34)),
|
|
@@ -6030,9 +6195,27 @@ export class PilotSwarmUiController {
|
|
|
6030
6195
|
case UI_COMMANDS.SHRINK_SESSION_PANE:
|
|
6031
6196
|
this.adjustSessionPaneSplit(-2);
|
|
6032
6197
|
return;
|
|
6198
|
+
case UI_COMMANDS.GROW_FOCUSED_PANE:
|
|
6199
|
+
this.resizeFocusedPane(2);
|
|
6200
|
+
return;
|
|
6201
|
+
case UI_COMMANDS.SHRINK_FOCUSED_PANE:
|
|
6202
|
+
this.resizeFocusedPane(-2);
|
|
6203
|
+
return;
|
|
6033
6204
|
case UI_COMMANDS.OPEN_ARTIFACT_PICKER:
|
|
6034
6205
|
await this.openArtifactPicker();
|
|
6035
6206
|
return;
|
|
6207
|
+
case UI_COMMANDS.OPEN_HELP:
|
|
6208
|
+
this.openHelpModal();
|
|
6209
|
+
return;
|
|
6210
|
+
case UI_COMMANDS.SEQUENCE_SELECT_PREV:
|
|
6211
|
+
this.moveSequenceSelection(-1);
|
|
6212
|
+
return;
|
|
6213
|
+
case UI_COMMANDS.SEQUENCE_SELECT_NEXT:
|
|
6214
|
+
this.moveSequenceSelection(1);
|
|
6215
|
+
return;
|
|
6216
|
+
case UI_COMMANDS.TOGGLE_SEQUENCE_TURN:
|
|
6217
|
+
this.toggleSequenceTurnExpanded();
|
|
6218
|
+
return;
|
|
6036
6219
|
case UI_COMMANDS.TOGGLE_LOG_TAIL:
|
|
6037
6220
|
this.toggleLogTail();
|
|
6038
6221
|
return;
|
package/ui/core/src/reducer.js
CHANGED
|
@@ -670,6 +670,24 @@ export function appReducer(state, action) {
|
|
|
670
670
|
},
|
|
671
671
|
};
|
|
672
672
|
|
|
673
|
+
case "ui/sequenceExpandedTurns":
|
|
674
|
+
return {
|
|
675
|
+
...state,
|
|
676
|
+
ui: {
|
|
677
|
+
...state.ui,
|
|
678
|
+
sequenceExpandedTurns: Array.isArray(action.turns) ? action.turns : [],
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
case "ui/sequenceSelectedTurn":
|
|
683
|
+
return {
|
|
684
|
+
...state,
|
|
685
|
+
ui: {
|
|
686
|
+
...state.ui,
|
|
687
|
+
sequenceSelectedTurn: action.turn == null ? null : Number(action.turn),
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
|
|
673
691
|
case "sessions/filterQuery":
|
|
674
692
|
{
|
|
675
693
|
const nextSessions = {
|
|
@@ -701,7 +719,19 @@ export function appReducer(state, action) {
|
|
|
701
719
|
|
|
702
720
|
case "ui/modalSelection": {
|
|
703
721
|
const modal = state.ui.modal;
|
|
704
|
-
if (!modal
|
|
722
|
+
if (!modal) return state;
|
|
723
|
+
if (modal.type === "help") {
|
|
724
|
+
// The help overlay has no `items`; the row-count upper bound is
|
|
725
|
+
// clamped by selectHelpModal, so just track the scroll anchor.
|
|
726
|
+
return {
|
|
727
|
+
...state,
|
|
728
|
+
ui: {
|
|
729
|
+
...state.ui,
|
|
730
|
+
modal: { ...modal, selectedIndex: Math.max(0, Number(action.index) || 0) },
|
|
731
|
+
},
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
if (!Array.isArray(modal.items) || modal.items.length === 0) {
|
|
705
735
|
return state;
|
|
706
736
|
}
|
|
707
737
|
const nextIndex = Math.max(0, Math.min(action.index ?? 0, modal.items.length - 1));
|
|
@@ -1458,6 +1488,7 @@ export function appReducer(state, action) {
|
|
|
1458
1488
|
saving: false,
|
|
1459
1489
|
error: null,
|
|
1460
1490
|
lastSavedAt: state.admin.ghcpKey.lastSavedAt,
|
|
1491
|
+
storeAsSystem: Boolean(state.admin.ghcpKey.storeAsSystem),
|
|
1461
1492
|
},
|
|
1462
1493
|
},
|
|
1463
1494
|
};
|
|
@@ -1494,6 +1525,7 @@ export function appReducer(state, action) {
|
|
|
1494
1525
|
saving: false,
|
|
1495
1526
|
error: null,
|
|
1496
1527
|
lastSavedAt: state.admin.ghcpKey.lastSavedAt,
|
|
1528
|
+
storeAsSystem: Boolean(state.admin.ghcpKey.storeAsSystem),
|
|
1497
1529
|
},
|
|
1498
1530
|
},
|
|
1499
1531
|
};
|
|
@@ -1537,6 +1569,91 @@ export function appReducer(state, action) {
|
|
|
1537
1569
|
saving: false,
|
|
1538
1570
|
error: null,
|
|
1539
1571
|
lastSavedAt: Date.now(),
|
|
1572
|
+
storeAsSystem: Boolean(state.admin.ghcpKey.storeAsSystem),
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
case "admin/ghcpKey/setSystemTarget": {
|
|
1578
|
+
const storeAsSystem = Boolean(action.value);
|
|
1579
|
+
if (Boolean(state.admin.ghcpKey.storeAsSystem) === storeAsSystem) return state;
|
|
1580
|
+
return {
|
|
1581
|
+
...state,
|
|
1582
|
+
admin: {
|
|
1583
|
+
...state.admin,
|
|
1584
|
+
ghcpKey: {
|
|
1585
|
+
...state.admin.ghcpKey,
|
|
1586
|
+
storeAsSystem,
|
|
1587
|
+
error: null,
|
|
1588
|
+
},
|
|
1589
|
+
},
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
case "admin/systemGhcpKey/loading": {
|
|
1593
|
+
return {
|
|
1594
|
+
...state,
|
|
1595
|
+
admin: {
|
|
1596
|
+
...state.admin,
|
|
1597
|
+
systemGhcpKey: {
|
|
1598
|
+
...state.admin.systemGhcpKey,
|
|
1599
|
+
loading: true,
|
|
1600
|
+
error: null,
|
|
1601
|
+
},
|
|
1602
|
+
},
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
case "admin/systemGhcpKey/loaded": {
|
|
1606
|
+
const status = action.status || {};
|
|
1607
|
+
return {
|
|
1608
|
+
...state,
|
|
1609
|
+
admin: {
|
|
1610
|
+
...state.admin,
|
|
1611
|
+
systemGhcpKey: {
|
|
1612
|
+
supported: true,
|
|
1613
|
+
loading: false,
|
|
1614
|
+
configured: Boolean(status.configured),
|
|
1615
|
+
changedBy: status.changedBy || null,
|
|
1616
|
+
changedAt: status.changedAt || null,
|
|
1617
|
+
error: null,
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
case "admin/systemGhcpKey/loadFailed": {
|
|
1623
|
+
return {
|
|
1624
|
+
...state,
|
|
1625
|
+
admin: {
|
|
1626
|
+
...state.admin,
|
|
1627
|
+
systemGhcpKey: {
|
|
1628
|
+
...state.admin.systemGhcpKey,
|
|
1629
|
+
loading: false,
|
|
1630
|
+
error: action.error ? String(action.error) : "Failed to load System key status",
|
|
1631
|
+
},
|
|
1632
|
+
},
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
case "admin/systemGhcpKey/saved": {
|
|
1636
|
+
const status = action.status || {};
|
|
1637
|
+
return {
|
|
1638
|
+
...state,
|
|
1639
|
+
admin: {
|
|
1640
|
+
...state.admin,
|
|
1641
|
+
ghcpKey: {
|
|
1642
|
+
editing: false,
|
|
1643
|
+
draft: "",
|
|
1644
|
+
cursorIndex: 0,
|
|
1645
|
+
saving: false,
|
|
1646
|
+
error: null,
|
|
1647
|
+
lastSavedAt: Date.now(),
|
|
1648
|
+
storeAsSystem: Boolean(state.admin.ghcpKey.storeAsSystem),
|
|
1649
|
+
},
|
|
1650
|
+
systemGhcpKey: {
|
|
1651
|
+
supported: true,
|
|
1652
|
+
loading: false,
|
|
1653
|
+
configured: Boolean(status.configured),
|
|
1654
|
+
changedBy: status.changedBy || null,
|
|
1655
|
+
changedAt: status.changedAt || null,
|
|
1656
|
+
error: null,
|
|
1540
1657
|
},
|
|
1541
1658
|
},
|
|
1542
1659
|
};
|