@try-works/dsh-recursive-mode 0.1.5 → 0.1.7
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/lib/bootstrap.d.ts +6 -3
- package/lib/client/board.d.ts +4 -1
- package/lib/client/contract.d.ts +16 -13
- package/lib/client/index.d.ts +1 -1
- package/lib/client/open-state.d.ts +20 -0
- package/lib/client/slots.d.ts +13 -1
- package/lib/client.js +160 -71
- package/lib/index.js +3391 -906
- package/lib/runtime.d.ts +12 -5
- package/lib/ts-lint.d.ts +205 -0
- package/package.json +1 -1
- package/src/bootstrap.ts +338 -331
- package/src/client/board.tsx +0 -0
- package/src/client/contract.ts +19 -22
- package/src/client/index.ts +1 -1
- package/src/client/open-state.ts +46 -0
- package/src/client/slots.ts +67 -33
- package/src/commands.ts +11 -0
- package/src/index.ts +238 -228
- package/src/runtime.ts +19 -23
- package/src/ts-lint.ts +2110 -0
- package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/references/scripts/lint-recursive-run.ps1 +0 -25
- package/references/scripts/lint-recursive-run.py +0 -2870
- package/references/scripts/recursive-closeout.ps1 +0 -38
- package/references/scripts/recursive-closeout.py +0 -541
- package/references/scripts/recursive-init.ps1 +0 -515
- package/references/scripts/recursive-init.py +0 -356
- package/references/scripts/recursive-lock.ps1 +0 -26
- package/references/scripts/recursive-lock.py +0 -302
- package/references/scripts/recursive-review-bundle.ps1 +0 -58
- package/references/scripts/recursive-review-bundle.py +0 -503
- package/references/scripts/recursive-router-cli-configure.ps1 +0 -2
- package/references/scripts/recursive-router-cli-configure.py +0 -9
- package/references/scripts/recursive-router-cli-init.ps1 +0 -2
- package/references/scripts/recursive-router-cli-init.py +0 -9
- package/references/scripts/recursive-router-cli-invoke.ps1 +0 -2
- package/references/scripts/recursive-router-cli-invoke.py +0 -9
- package/references/scripts/recursive-router-cli-probe.ps1 +0 -2
- package/references/scripts/recursive-router-cli-probe.py +0 -9
- package/references/scripts/recursive-router-cli-resolve.ps1 +0 -2
- package/references/scripts/recursive-router-cli-resolve.py +0 -9
- package/references/scripts/recursive-router-cli-validate.ps1 +0 -2
- package/references/scripts/recursive-router-cli-validate.py +0 -9
- package/references/scripts/recursive-router-configure.ps1 +0 -27
- package/references/scripts/recursive-router-configure.py +0 -74
- package/references/scripts/recursive-router-init.ps1 +0 -17
- package/references/scripts/recursive-router-init.py +0 -29
- package/references/scripts/recursive-router-invoke.ps1 +0 -47
- package/references/scripts/recursive-router-invoke.py +0 -103
- package/references/scripts/recursive-router-probe.ps1 +0 -25
- package/references/scripts/recursive-router-probe.py +0 -44
- package/references/scripts/recursive-router-resolve.ps1 +0 -26
- package/references/scripts/recursive-router-resolve.py +0 -46
- package/references/scripts/recursive-router-validate.ps1 +0 -17
- package/references/scripts/recursive-router-validate.py +0 -27
- package/references/scripts/recursive-status.ps1 +0 -23
- package/references/scripts/recursive-status.py +0 -2124
- package/references/scripts/recursive-subagent-action.ps1 +0 -98
- package/references/scripts/recursive-subagent-action.py +0 -197
- package/references/scripts/recursive-training-extract.ps1 +0 -23
- package/references/scripts/recursive-training-extract.py +0 -99
- package/references/scripts/recursive-training-grpo.ps1 +0 -52
- package/references/scripts/recursive-training-grpo.py +0 -1341
- package/references/scripts/recursive-training-loader.ps1 +0 -78
- package/references/scripts/recursive-training-loader.py +0 -558
- package/references/scripts/recursive-training-mcp.ps1 +0 -29
- package/references/scripts/recursive-training-mcp.py +0 -261
- package/references/scripts/recursive-training-phase8-trigger.ps1 +0 -55
- package/references/scripts/recursive-training-phase8-trigger.py +0 -137
- package/references/scripts/recursive-training-sync.ps1 +0 -29
- package/references/scripts/recursive-training-sync.py +0 -183
- package/references/scripts/recursive_phase_rules.py +0 -367
- package/references/scripts/recursive_router_cli_lib.py +0 -2
- package/references/scripts/recursive_router_lib.py +0 -2282
- package/references/scripts/verify-locks.ps1 +0 -25
- package/references/scripts/verify-locks.py +0 -353
- package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
- package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/scripts/install-recursive-mode.ps1 +0 -956
- package/scripts/install-recursive-mode.py +0 -750
- package/scripts/lint-recursive-run.py +0 -2870
- package/scripts/recursive-closeout.py +0 -541
- package/scripts/recursive-init.py +0 -356
- package/scripts/recursive-lock.py +0 -302
- package/scripts/recursive-status.py +0 -2124
- package/scripts/recursive_phase_rules.py +0 -367
- package/scripts/recursive_router_lib.py +0 -2282
- package/scripts/verify-locks.py +0 -353
package/lib/bootstrap.d.ts
CHANGED
|
@@ -35,8 +35,11 @@ export interface StageBInput {
|
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Stage B workflow init at agent/session-start. New: bootstrap if missing +
|
|
38
|
-
* enumerate runs. Resume:
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* enumerate runs. Resume: idempotent REPAIR (not a full re-bootstrap) — the
|
|
39
|
+
* upsert-only bootstrapScaffold adds missing control-plane files/dirs and
|
|
40
|
+
* re-upserts marked blocks WITHOUT overwriting user content or touching run/
|
|
41
|
+
* artifacts, then enumerates + notes the active run. The repair is safe on
|
|
42
|
+
* fresh, partial, AND complete scaffolds (run 09 R3/R6). Always scoped to the
|
|
43
|
+
* given control-plane root.
|
|
41
44
|
*/
|
|
42
45
|
export declare function stageBWorkflowInit(input: StageBInput): StageBResult;
|
package/lib/client/board.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { RecursiveProjection, RecursiveRunCard } from '../types.ts';
|
|
2
2
|
import type { LiveProjectionValue } from './contract.ts';
|
|
3
|
+
import type { BoardSelection } from './open-state.ts';
|
|
3
4
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
4
5
|
export declare function listRuns(projection: RecursiveProjection | undefined): RecursiveRunCard[];
|
|
5
6
|
export interface BoardProps {
|
|
6
7
|
/** The live host-route snapshot (null = not loaded / no recursive root). */
|
|
7
8
|
snapshot: LiveProjectionValue | null;
|
|
9
|
+
/** Opens the drill-down inspector for a run (R3). */
|
|
10
|
+
onOpenInspector?: (selection: BoardSelection) => void;
|
|
8
11
|
}
|
|
9
|
-
export declare function Board({ snapshot }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
12
|
+
export declare function Board({ snapshot, onOpenInspector }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
10
13
|
className: string;
|
|
11
14
|
}, HTMLElement> | null;
|
package/lib/client/contract.d.ts
CHANGED
|
@@ -29,21 +29,24 @@ export interface SessionSummaryRow {
|
|
|
29
29
|
/** Agent preset this session was composed from; absent when the deployment has none. */
|
|
30
30
|
agentPreset?: string;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The session-list snapshot (mirrors the REAL SessionListState). useSessions is
|
|
34
|
+
* a SnapshotSelectorHook over this state; the identity selector `(s) => s`
|
|
35
|
+
* yields the full value, which the gate reads directly (ids/byId/current).
|
|
36
|
+
*/
|
|
33
37
|
export interface SessionListStateLike {
|
|
34
38
|
ids: string[];
|
|
35
39
|
byId: Record<string, SessionSummaryRow>;
|
|
36
40
|
current: string | undefined;
|
|
37
41
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
42
|
+
/**
|
|
43
|
+
* The REAL selector-hook shape (ui-slots store.ts):
|
|
44
|
+
* SnapshotSelectorHook<T> = <S>(sel: (s: T) => S, eq?) => S
|
|
45
|
+
* The selector is REQUIRED — useSyncExternalStoreWithSelector needs it. Calling
|
|
46
|
+
* the hook with no selector passes sel=undefined and breaks the board mount.
|
|
47
|
+
* This is the run 10 live bug.
|
|
48
|
+
*/
|
|
49
|
+
export type SnapshotSelectorHook<T> = <S>(sel: (s: T) => S, eq?: (a: S, b: S) => boolean) => S;
|
|
47
50
|
/** The slots registry surface the client injects into. */
|
|
48
51
|
export interface ClientSlots {
|
|
49
52
|
inject(seat: string, factory: (ctx: unknown) => () => void): () => void;
|
|
@@ -77,8 +80,8 @@ export interface LiveProjectionValue {
|
|
|
77
80
|
* The client-side preset gate (acceptance #3): the board/strip render ONLY when
|
|
78
81
|
* the CURRENT session's row reports agentPreset === 'recursive'. A non-recursive
|
|
79
82
|
* session (or a session whose preset is undefined) shows NOTHING — not an empty
|
|
80
|
-
* board, not a spinner.
|
|
83
|
+
* board, not a spinner. Consumes the SessionListStateLike DIRECTLY (run 10 fix).
|
|
81
84
|
*/
|
|
82
|
-
export declare function isRecursivePreset(
|
|
85
|
+
export declare function isRecursivePreset(state: SessionListStateLike): boolean;
|
|
83
86
|
/** The current session's cwd (the client passes it as a fallback hint; the host resolves the root). */
|
|
84
|
-
export declare function currentSessionCwd(
|
|
87
|
+
export declare function currentSessionCwd(state: SessionListStateLike): string;
|
package/lib/client/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type { LiveProjectionSnapshot } from './use-live.ts';
|
|
|
21
21
|
export { fetchLiveState, subscribeLiveEvents } from './host-api.ts';
|
|
22
22
|
export type { LiveRecursiveState, LiveRecursiveFrame, LiveScope } from './host-api.ts';
|
|
23
23
|
export { isRecursivePreset, currentSessionCwd } from './contract.ts';
|
|
24
|
-
export type { SessionSummaryRow, SessionListStateLike,
|
|
24
|
+
export type { SessionSummaryRow, SessionListStateLike, SnapshotSelectorHook, ClientContext } from './contract.ts';
|
|
25
25
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
26
26
|
export declare const inject: string[];
|
|
27
27
|
/** Browser-half plugin entry (R4 + SP2 R1). */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface BoardSelection {
|
|
2
|
+
worktreeRoot: string;
|
|
3
|
+
runId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface BoardState {
|
|
6
|
+
open: boolean;
|
|
7
|
+
selection: BoardSelection | null;
|
|
8
|
+
}
|
|
9
|
+
export interface BoardOpenState {
|
|
10
|
+
get(): BoardState;
|
|
11
|
+
openBoard(): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
openInspector(selection: BoardSelection): void;
|
|
14
|
+
backToBoard(): void;
|
|
15
|
+
subscribe(fn: () => void): () => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createBoardState(initial?: BoardState): BoardOpenState;
|
|
18
|
+
/** App-wide singleton shared by the launcher, overlay, board, and inspector. */
|
|
19
|
+
export declare const boardState: BoardOpenState;
|
|
20
|
+
export declare function useBoardState(store?: BoardOpenState): BoardState;
|
package/lib/client/slots.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import type { ClientContext } from './contract.ts';
|
|
1
|
+
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook } from './contract.ts';
|
|
2
2
|
import { isRecursivePreset, currentSessionCwd } from './contract.ts';
|
|
3
|
+
export type OverlayContent = 'hidden' | 'board' | 'inspector';
|
|
4
|
+
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
5
|
+
export declare function overlayContent(state: {
|
|
6
|
+
open: boolean;
|
|
7
|
+
selection: unknown;
|
|
8
|
+
}, sessions: SessionListStateLike): OverlayContent;
|
|
9
|
+
/**
|
|
10
|
+
* THE FIX (run 10): useSessions is a SELECTOR hook — always call it WITH a
|
|
11
|
+
* selector. The identity selector `(s) => s` yields the full SessionListStateLike.
|
|
12
|
+
* Exported so the spec can assert the selector is actually passed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useRecursiveSessions(useSessions: SnapshotSelectorHook<SessionListStateLike>): SessionListStateLike;
|
|
3
15
|
export declare function registerSlots(ctx: ClientContext): () => void;
|
|
4
16
|
/** Export the gate helpers for tests. */
|
|
5
17
|
export { isRecursivePreset, currentSessionCwd };
|
package/lib/client.js
CHANGED
|
@@ -10,20 +10,15 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
* The client-side preset gate (acceptance #3): the board/strip render ONLY when
|
|
11
11
|
* the CURRENT session's row reports agentPreset === 'recursive'. A non-recursive
|
|
12
12
|
* session (or a session whose preset is undefined) shows NOTHING — not an empty
|
|
13
|
-
* board, not a spinner.
|
|
13
|
+
* board, not a spinner. Consumes the SessionListStateLike DIRECTLY (run 10 fix).
|
|
14
14
|
*/
|
|
15
|
-
function isRecursivePreset(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (current === void 0) return false;
|
|
19
|
-
return snapshot.byId[current]?.agentPreset === "recursive";
|
|
15
|
+
function isRecursivePreset(state) {
|
|
16
|
+
if (state.current === void 0) return false;
|
|
17
|
+
return state.byId[state.current]?.agentPreset === "recursive";
|
|
20
18
|
}
|
|
21
19
|
/** The current session's cwd (the client passes it as a fallback hint; the host resolves the root). */
|
|
22
|
-
function currentSessionCwd(
|
|
23
|
-
|
|
24
|
-
const current = snapshot.current;
|
|
25
|
-
if (current === void 0) return "";
|
|
26
|
-
return snapshot.byId[current]?.cwd ?? "";
|
|
20
|
+
function currentSessionCwd(state) {
|
|
21
|
+
return state.current === void 0 ? "" : state.byId[state.current]?.cwd ?? "";
|
|
27
22
|
}
|
|
28
23
|
//#endregion
|
|
29
24
|
//#region src/client/derive.ts
|
|
@@ -224,13 +219,17 @@ window.__ModuleLoader__.load({
|
|
|
224
219
|
//#endregion
|
|
225
220
|
//#region src/client/board.tsx
|
|
226
221
|
/**
|
|
227
|
-
* Board overlay (SP2 R1): the kanban view over the live host-route
|
|
228
|
-
* Pure read-only renderer — no filesystem, no run-file scraping.
|
|
229
|
-
* grouped by worktree root (the projection's outer key) and mapped to
|
|
230
|
-
* current-phase lane via derive.columnForRun. Renders NOTHING when the
|
|
231
|
-
* is null (no recursive root / not loaded) or the current session is
|
|
232
|
-
* recursive preset (the gate lives in slots.ts — here an empty
|
|
233
|
-
* a null root is a no-op, not an "empty board").
|
|
222
|
+
* Board overlay (SP2 R1 + run 08 R3): the kanban view over the live host-route
|
|
223
|
+
* projection. Pure read-only renderer — no filesystem, no run-file scraping.
|
|
224
|
+
* Cards are grouped by worktree root (the projection's outer key) and mapped to
|
|
225
|
+
* their current-phase lane via derive.columnForRun. Renders NOTHING when the
|
|
226
|
+
* snapshot is null (no recursive root / not loaded) or the current session is
|
|
227
|
+
* not on the recursive preset (the gate lives in slots.ts — here an empty
|
|
228
|
+
* projection with a null root is a no-op, not an "empty board").
|
|
229
|
+
*
|
|
230
|
+
* Run 08 R3: a card click opens the drill-down inspector via onOpenInspector
|
|
231
|
+
* (the overlay swaps board <-> inspector in slots.ts). Read-only still: the
|
|
232
|
+
* callback only selects a run for display; no mutation.
|
|
234
233
|
*/
|
|
235
234
|
/** Flatten the worktree-grouped projection into a stable run list. */
|
|
236
235
|
function listRuns(projection) {
|
|
@@ -239,13 +238,17 @@ window.__ModuleLoader__.load({
|
|
|
239
238
|
for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
|
|
240
239
|
return runs;
|
|
241
240
|
}
|
|
242
|
-
function Board({ snapshot }) {
|
|
241
|
+
function Board({ snapshot, onOpenInspector }) {
|
|
243
242
|
if (snapshot === null || snapshot.root === null) return null;
|
|
244
243
|
const runs = listRuns(snapshot.projection);
|
|
245
244
|
if (runs.length === 0) return (0, react.createElement)("div", {
|
|
246
245
|
className: "rec-board",
|
|
247
246
|
"data-empty": true
|
|
248
247
|
}, (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
|
|
248
|
+
const open = (run) => () => onOpenInspector?.({
|
|
249
|
+
worktreeRoot: run.worktreeRoot,
|
|
250
|
+
runId: run.runId
|
|
251
|
+
});
|
|
249
252
|
return (0, react.createElement)("div", { className: "rec-board" }, KANBAN_LANES.map((lane) => {
|
|
250
253
|
const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
|
|
251
254
|
return (0, react.createElement)("section", {
|
|
@@ -255,7 +258,8 @@ window.__ModuleLoader__.load({
|
|
|
255
258
|
const facts = cardFacts(run);
|
|
256
259
|
return (0, react.createElement)("article", {
|
|
257
260
|
key: run.worktreeRoot + "\0" + run.runId,
|
|
258
|
-
className: "rec-card"
|
|
261
|
+
className: "rec-card",
|
|
262
|
+
onClick: open(run)
|
|
259
263
|
}, (0, react.createElement)("div", { className: "rec-card-head" }, (0, react.createElement)("strong", {}, run.runId), facts.tampered && (0, react.createElement)("span", {
|
|
260
264
|
className: "rec-badge rec-badge-tamper",
|
|
261
265
|
title: "tampered"
|
|
@@ -267,6 +271,26 @@ window.__ModuleLoader__.load({
|
|
|
267
271
|
}));
|
|
268
272
|
}
|
|
269
273
|
//#endregion
|
|
274
|
+
//#region src/client/inspector.tsx
|
|
275
|
+
/**
|
|
276
|
+
* Drill-down inspector (SP2 R1): the details-seat swap rendering one run's
|
|
277
|
+
* phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock badges,
|
|
278
|
+
* and tamper/gate facts — all from the live host-route snapshot, never disk.
|
|
279
|
+
*/
|
|
280
|
+
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails }) {
|
|
281
|
+
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
282
|
+
if (card === void 0) return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("p", {}, "Run not found: " + runId));
|
|
283
|
+
const facts = cardFacts(card);
|
|
284
|
+
const rows = expandPhaseRows(card);
|
|
285
|
+
return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("div", { className: "rec-inspector-head" }, (0, react.createElement)("button", {
|
|
286
|
+
onClick: onBackToToolDetails,
|
|
287
|
+
className: "rec-back"
|
|
288
|
+
}, "← back to tool details"), (0, react.createElement)("h2", {}, runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.state), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered")), (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
289
|
+
key: row.phase,
|
|
290
|
+
className: "rec-phase-row"
|
|
291
|
+
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), (0, react.createElement)("span", { className: "rec-badge" }, row.status), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8))))), facts.gateBlocked && (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", {}, "Blocked (" + (facts.gateKind ?? "") + ")")));
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
270
294
|
//#region src/client/strip.tsx
|
|
271
295
|
/**
|
|
272
296
|
* Status strip (SP2 R1): a thin glance at the active run's phase, gate state,
|
|
@@ -409,22 +433,101 @@ window.__ModuleLoader__.load({
|
|
|
409
433
|
return state;
|
|
410
434
|
}
|
|
411
435
|
//#endregion
|
|
436
|
+
//#region src/client/open-state.ts
|
|
437
|
+
/**
|
|
438
|
+
* Shared board open-state (run 08 R1): module-level store behind the launcher,
|
|
439
|
+
* the shell.overlay entry, and the board<->inspector swap. Zero session events,
|
|
440
|
+
* zero host mutation — the client remains a read-only projection (R5/R9).
|
|
441
|
+
*/
|
|
442
|
+
function createBoardState(initial = {
|
|
443
|
+
open: false,
|
|
444
|
+
selection: null
|
|
445
|
+
}) {
|
|
446
|
+
let state = initial;
|
|
447
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
448
|
+
const emit = () => {
|
|
449
|
+
for (const fn of listeners) fn();
|
|
450
|
+
};
|
|
451
|
+
return {
|
|
452
|
+
get: () => state,
|
|
453
|
+
openBoard: () => {
|
|
454
|
+
state = {
|
|
455
|
+
...state,
|
|
456
|
+
open: true
|
|
457
|
+
};
|
|
458
|
+
emit();
|
|
459
|
+
},
|
|
460
|
+
close: () => {
|
|
461
|
+
state = {
|
|
462
|
+
...state,
|
|
463
|
+
open: false,
|
|
464
|
+
selection: null
|
|
465
|
+
};
|
|
466
|
+
emit();
|
|
467
|
+
},
|
|
468
|
+
openInspector: (selection) => {
|
|
469
|
+
state = {
|
|
470
|
+
...state,
|
|
471
|
+
open: true,
|
|
472
|
+
selection
|
|
473
|
+
};
|
|
474
|
+
emit();
|
|
475
|
+
},
|
|
476
|
+
backToBoard: () => {
|
|
477
|
+
state = {
|
|
478
|
+
...state,
|
|
479
|
+
selection: null
|
|
480
|
+
};
|
|
481
|
+
emit();
|
|
482
|
+
},
|
|
483
|
+
subscribe: (fn) => {
|
|
484
|
+
listeners.add(fn);
|
|
485
|
+
return () => {
|
|
486
|
+
listeners.delete(fn);
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/** App-wide singleton shared by the launcher, overlay, board, and inspector. */
|
|
492
|
+
const boardState = createBoardState();
|
|
493
|
+
function useBoardState(store = boardState) {
|
|
494
|
+
return (0, react.useSyncExternalStore)(store.subscribe, store.get, store.get);
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
412
497
|
//#region src/client/slots.ts
|
|
413
498
|
/**
|
|
414
|
-
* Slot registrations (SP2 R1): the sidebar launcher, the
|
|
415
|
-
* the conversation.input.dock status strip, and the
|
|
499
|
+
* Slot registrations (SP2 R1 + run 08 R1/R3/R4): the sidebar launcher, the
|
|
500
|
+
* shell.overlay board, the conversation.input.dock status strip, and the
|
|
501
|
+
* settings.section page.
|
|
416
502
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
503
|
+
* Run 08: the launcher now OPENS the shared board store (onClick), and the
|
|
504
|
+
* shell.overlay entry renders the board gated on that store + the recursive
|
|
505
|
+
* preset, swapping to the drill-down inspector when a run is selected.
|
|
420
506
|
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
507
|
+
* Run 10 (LIVE BUG FIX): useSessions is a SnapshotSelectorHook (NOT a zero-arg
|
|
508
|
+
* thunk) — it is called WITH a selector, e.g. useSessions((s) => s) for the
|
|
509
|
+
* full SessionListStateLike. The old code called useSessions() with NO selector
|
|
510
|
+
* and wrapped the garbage in a fake {list:{getSnapshot}} — so the preset gate
|
|
511
|
+
* never saw the recursive preset and the board never mounted (launcher click
|
|
512
|
+
* did nothing). The board/strip now consume the SessionListStateLike directly.
|
|
425
513
|
*
|
|
426
514
|
* READ-ONLY (R9): the client only GETs the live host route; no mutation.
|
|
427
515
|
*/
|
|
516
|
+
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
517
|
+
function overlayContent(state, sessions) {
|
|
518
|
+
if (!state.open) return "hidden";
|
|
519
|
+
if (!isRecursivePreset(sessions)) return "hidden";
|
|
520
|
+
if (state.selection !== null) return "inspector";
|
|
521
|
+
return "board";
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* THE FIX (run 10): useSessions is a SELECTOR hook — always call it WITH a
|
|
525
|
+
* selector. The identity selector `(s) => s` yields the full SessionListStateLike.
|
|
526
|
+
* Exported so the spec can assert the selector is actually passed.
|
|
527
|
+
*/
|
|
528
|
+
function useRecursiveSessions(useSessions) {
|
|
529
|
+
return useSessions((s) => s);
|
|
530
|
+
}
|
|
428
531
|
function registerSlots(ctx) {
|
|
429
532
|
const disposers = [];
|
|
430
533
|
disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
@@ -434,7 +537,8 @@ window.__ModuleLoader__.load({
|
|
|
434
537
|
label: "Recursive runs"
|
|
435
538
|
}, () => (0, react.createElement)("button", {
|
|
436
539
|
className: "rec-launcher",
|
|
437
|
-
title: "Recursive runs"
|
|
540
|
+
title: "Recursive runs",
|
|
541
|
+
onClick: () => boardState.openBoard()
|
|
438
542
|
}, "⧉"))));
|
|
439
543
|
disposers.push(ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
440
544
|
name: "shell.overlay",
|
|
@@ -443,7 +547,7 @@ window.__ModuleLoader__.load({
|
|
|
443
547
|
}, (props) => {
|
|
444
548
|
const useSessions = props?.useSessions;
|
|
445
549
|
if (useSessions === void 0) return null;
|
|
446
|
-
return (0, react.createElement)(
|
|
550
|
+
return (0, react.createElement)(RecursiveBoardOverlay, { useSessions });
|
|
447
551
|
})));
|
|
448
552
|
disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
449
553
|
name: "conversation.input.dock",
|
|
@@ -468,38 +572,43 @@ window.__ModuleLoader__.load({
|
|
|
468
572
|
};
|
|
469
573
|
}
|
|
470
574
|
/**
|
|
471
|
-
* Board
|
|
472
|
-
*
|
|
473
|
-
*
|
|
575
|
+
* Board overlay: subscribes to the shared board store, gates on open + recursive
|
|
576
|
+
* preset, and swaps board <-> inspector (run 08 R1/R3).
|
|
577
|
+
*
|
|
578
|
+
* Run 10: consumes the full SessionListStateLike through the identity selector
|
|
579
|
+
* (useSessions((s) => s)); no fake {list:{getSnapshot}} wrapper.
|
|
474
580
|
*/
|
|
475
|
-
function
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (!isRecursivePreset(sessions)) return null;
|
|
482
|
-
const cwd = currentSessionCwd(sessions);
|
|
581
|
+
function RecursiveBoardOverlay({ useSessions }) {
|
|
582
|
+
const board = useBoardState();
|
|
583
|
+
const list = useRecursiveSessions(useSessions);
|
|
584
|
+
const content = overlayContent(board, list);
|
|
585
|
+
if (content === "hidden") return null;
|
|
586
|
+
const cwd = currentSessionCwd(list);
|
|
483
587
|
const snapshot = useLiveProjection({
|
|
484
588
|
sessionId: list.current,
|
|
485
589
|
cwd
|
|
486
590
|
});
|
|
487
|
-
return (0, react.createElement)(
|
|
591
|
+
if (content === "inspector" && board.selection !== null) return (0, react.createElement)(Inspector, {
|
|
592
|
+
runId: board.selection.runId,
|
|
593
|
+
worktreeRoot: board.selection.worktreeRoot,
|
|
594
|
+
snapshot,
|
|
595
|
+
onBackToToolDetails: () => boardState.backToBoard()
|
|
596
|
+
});
|
|
597
|
+
return (0, react.createElement)(Board, {
|
|
598
|
+
snapshot,
|
|
599
|
+
onOpenInspector: (sel) => boardState.openInspector(sel)
|
|
600
|
+
});
|
|
488
601
|
}
|
|
489
602
|
/**
|
|
490
603
|
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
491
|
-
* nothing for a non-recursive session.
|
|
604
|
+
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
492
605
|
*/
|
|
493
606
|
function RecursiveStripGate({ useSessions, sessionId }) {
|
|
494
|
-
const list = useSessions
|
|
495
|
-
|
|
496
|
-
getSnapshot: () => list,
|
|
497
|
-
subscribe: () => () => {}
|
|
498
|
-
} };
|
|
499
|
-
if (!isRecursivePreset(sessions)) return null;
|
|
607
|
+
const list = useRecursiveSessions(useSessions);
|
|
608
|
+
if (!isRecursivePreset(list)) return null;
|
|
500
609
|
const snapshot = useLiveProjection({
|
|
501
610
|
sessionId,
|
|
502
|
-
cwd: currentSessionCwd(
|
|
611
|
+
cwd: currentSessionCwd(list)
|
|
503
612
|
});
|
|
504
613
|
return (0, react.createElement)(StatusStrip, { snapshot });
|
|
505
614
|
}
|
|
@@ -516,26 +625,6 @@ window.__ModuleLoader__.load({
|
|
|
516
625
|
globalThis.__dshRecursiveModeClientApplied = void 0;
|
|
517
626
|
}
|
|
518
627
|
//#endregion
|
|
519
|
-
//#region src/client/inspector.tsx
|
|
520
|
-
/**
|
|
521
|
-
* Drill-down inspector (SP2 R1): the details-seat swap rendering one run's
|
|
522
|
-
* phase chain (with the always-shown 3.5 row + full 6/7/8 rows), lock badges,
|
|
523
|
-
* and tamper/gate facts — all from the live host-route snapshot, never disk.
|
|
524
|
-
*/
|
|
525
|
-
function Inspector({ runId, worktreeRoot, snapshot, onBackToToolDetails }) {
|
|
526
|
-
const card = snapshot?.projection?.[worktreeRoot]?.[runId];
|
|
527
|
-
if (card === void 0) return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("p", {}, "Run not found: " + runId));
|
|
528
|
-
const facts = cardFacts(card);
|
|
529
|
-
const rows = expandPhaseRows(card);
|
|
530
|
-
return (0, react.createElement)("div", { className: "rec-inspector" }, (0, react.createElement)("div", { className: "rec-inspector-head" }, (0, react.createElement)("button", {
|
|
531
|
-
onClick: onBackToToolDetails,
|
|
532
|
-
className: "rec-back"
|
|
533
|
-
}, "← back to tool details"), (0, react.createElement)("h2", {}, runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.state), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered")), (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Phases"), rows.map((row) => (0, react.createElement)("div", {
|
|
534
|
-
key: row.phase,
|
|
535
|
-
className: "rec-phase-row"
|
|
536
|
-
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), (0, react.createElement)("span", { className: "rec-badge" }, row.status), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8))))), facts.gateBlocked && (0, react.createElement)("section", { className: "rec-inspector-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", {}, "Blocked (" + (facts.gateKind ?? "") + ")")));
|
|
537
|
-
}
|
|
538
|
-
//#endregion
|
|
539
628
|
//#region src/client/index.ts
|
|
540
629
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
541
630
|
const inject = [
|