@try-works/dsh-recursive-mode 0.1.16 → 0.1.18
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/client/index.d.ts +0 -1
- package/lib/client/slots.d.ts +2 -10
- package/lib/client.js +6 -43
- package/package.json +1 -1
- package/src/client/board.tsx +11 -1
- package/src/client/index.ts +0 -1
- package/src/client/slots.ts +2 -35
- package/src/client/use-live.ts +5 -4
- package/lib/client/strip.d.ts +0 -8
- package/src/client/strip.tsx +0 -28
package/lib/client/index.d.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
import type { ClientContext } from './contract.ts';
|
|
15
15
|
export { Board, listRuns } from './board.tsx';
|
|
16
16
|
export { Inspector } from './inspector.tsx';
|
|
17
|
-
export { StatusStrip } from './strip.tsx';
|
|
18
17
|
export { RecursiveSettings } from './settings.tsx';
|
|
19
18
|
export { useLiveProjection } from './use-live.ts';
|
|
20
19
|
export type { LiveProjectionSnapshot } from './use-live.ts';
|
package/lib/client/slots.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { type ReactNode } from 'react';
|
|
20
20
|
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts';
|
|
21
|
-
import { isRecursivePreset
|
|
21
|
+
import { isRecursivePreset } from './contract.ts';
|
|
22
22
|
export type OverlayContent = 'hidden' | 'board' | 'inspector';
|
|
23
23
|
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
24
24
|
export declare function overlayContent(state: {
|
|
@@ -53,13 +53,5 @@ export declare function RecursiveBoardOverlay({ useSessions, useWorkspaces }: {
|
|
|
53
53
|
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
54
54
|
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>;
|
|
55
55
|
}): ReactNode;
|
|
56
|
-
/**
|
|
57
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
58
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
59
|
-
*/
|
|
60
|
-
export declare function RecursiveStripGate({ useSessions, sessionId }: {
|
|
61
|
-
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
62
|
-
sessionId?: string;
|
|
63
|
-
}): ReactNode;
|
|
64
56
|
/** Export the gate helpers for tests. */
|
|
65
|
-
export { isRecursivePreset
|
|
57
|
+
export { isRecursivePreset };
|
package/lib/client.js
CHANGED
|
@@ -345,6 +345,11 @@ window.__ModuleLoader__.load({
|
|
|
345
345
|
"aria-label": "Close"
|
|
346
346
|
}, "×");
|
|
347
347
|
}
|
|
348
|
+
/** Case/trailing-separator tolerant workspace-path equality (Windows host). */
|
|
349
|
+
function sameWorkspacePath(a, b) {
|
|
350
|
+
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
351
|
+
return norm(a) === norm(b);
|
|
352
|
+
}
|
|
348
353
|
function solidPill$1(kind) {
|
|
349
354
|
return (0, react.createElement)("span", {
|
|
350
355
|
className: "rec-pill",
|
|
@@ -354,7 +359,7 @@ window.__ModuleLoader__.load({
|
|
|
354
359
|
function Board({ snapshot, workspacePath, onOpenInspector, onClose }) {
|
|
355
360
|
const { theme, toggle } = useBoardTheme();
|
|
356
361
|
if (snapshot === null || snapshot.root === null) return null;
|
|
357
|
-
const runs = listRuns(snapshot.projection);
|
|
362
|
+
const runs = workspacePath !== void 0 && workspacePath !== "" && !sameWorkspacePath(workspacePath, snapshot.root) ? [] : listRuns(snapshot.projection);
|
|
358
363
|
const headerPath = workspacePath ?? snapshot.root;
|
|
359
364
|
const open = (run) => () => onOpenInspector?.({
|
|
360
365
|
worktreeRoot: run.worktreeRoot,
|
|
@@ -447,21 +452,6 @@ window.__ModuleLoader__.load({
|
|
|
447
452
|
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), solidPill(phaseStatusPill(row.status)), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8)))), facts.gateBlocked && (0, react.createElement)("div", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", { className: "rec-detail-text" }, "Blocked (" + (facts.gateKind ?? "") + ")"))), theme, toggle);
|
|
448
453
|
}
|
|
449
454
|
//#endregion
|
|
450
|
-
//#region src/client/strip.tsx
|
|
451
|
-
/**
|
|
452
|
-
* Status strip (SP2 R1): a thin glance at the active run's phase, gate state,
|
|
453
|
-
* and tamper badge, docked at conversation.input.dock. Renders NOTHING when no
|
|
454
|
-
* run is current or the snapshot is null (the preset gate lives in slots.ts).
|
|
455
|
-
*/
|
|
456
|
-
function StatusStrip({ snapshot }) {
|
|
457
|
-
if (snapshot === null || snapshot.root === null) return null;
|
|
458
|
-
const runs = listRuns(snapshot.projection);
|
|
459
|
-
if (runs.length === 0) return null;
|
|
460
|
-
const active = runs[runs.length - 1];
|
|
461
|
-
const facts = cardFacts(active);
|
|
462
|
-
return (0, react.createElement)("div", { className: "rec-strip" }, (0, react.createElement)("span", { className: "rec-strip-run" }, active.runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.currentPhase ?? "no phases"), facts.gateBlocked && (0, react.createElement)("span", { className: "rec-badge rec-badge-gate" }, "gate blocked"), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered"));
|
|
463
|
-
}
|
|
464
|
-
//#endregion
|
|
465
455
|
//#region src/client/settings.tsx
|
|
466
456
|
/**
|
|
467
457
|
* Recursive settings page (Phase D R8, §11.8): a settings.section list entry
|
|
@@ -576,7 +566,6 @@ window.__ModuleLoader__.load({
|
|
|
576
566
|
setState(null);
|
|
577
567
|
return;
|
|
578
568
|
}
|
|
579
|
-
setState(null);
|
|
580
569
|
let disposed = false;
|
|
581
570
|
const apply = (frame) => {
|
|
582
571
|
if (!disposed) setState(frame);
|
|
@@ -1293,18 +1282,6 @@ window.__ModuleLoader__.load({
|
|
|
1293
1282
|
useWorkspaces: props?.useWorkspaces
|
|
1294
1283
|
});
|
|
1295
1284
|
})));
|
|
1296
|
-
disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
1297
|
-
name: "conversation.input.dock",
|
|
1298
|
-
id: "recursive-strip",
|
|
1299
|
-
order: 20
|
|
1300
|
-
}, (props) => {
|
|
1301
|
-
const useSessions = props?.useSessions;
|
|
1302
|
-
if (useSessions === void 0) return null;
|
|
1303
|
-
return (0, react.createElement)(RecursiveStripGate, {
|
|
1304
|
-
useSessions,
|
|
1305
|
-
sessionId: props?.sessionId
|
|
1306
|
-
});
|
|
1307
|
-
})));
|
|
1308
1285
|
disposers.push(ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1309
1286
|
name: "settings.section",
|
|
1310
1287
|
id: "recursive",
|
|
@@ -1347,19 +1324,6 @@ window.__ModuleLoader__.load({
|
|
|
1347
1324
|
onClose: () => boardState.close()
|
|
1348
1325
|
});
|
|
1349
1326
|
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
1352
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
1353
|
-
*/
|
|
1354
|
-
function RecursiveStripGate({ useSessions, sessionId }) {
|
|
1355
|
-
const list = useRecursiveSessions(useSessions);
|
|
1356
|
-
const snapshot = useLiveProjection({
|
|
1357
|
-
sessionId,
|
|
1358
|
-
cwd: currentSessionCwd(list)
|
|
1359
|
-
});
|
|
1360
|
-
if (!isRecursivePreset(list)) return null;
|
|
1361
|
-
return (0, react.createElement)(StatusStrip, { snapshot });
|
|
1362
|
-
}
|
|
1363
1327
|
//#endregion
|
|
1364
1328
|
//#region src/client/apply-guard.ts
|
|
1365
1329
|
/** Claims the client apply slot. Returns true when this call won the slot. */
|
|
@@ -1391,7 +1355,6 @@ window.__ModuleLoader__.load({
|
|
|
1391
1355
|
exports.Board = Board;
|
|
1392
1356
|
exports.Inspector = Inspector;
|
|
1393
1357
|
exports.RecursiveSettings = RecursiveSettings;
|
|
1394
|
-
exports.StatusStrip = StatusStrip;
|
|
1395
1358
|
exports.apply = apply;
|
|
1396
1359
|
exports.currentSessionCwd = currentSessionCwd;
|
|
1397
1360
|
exports.currentWorkspacePath = currentWorkspacePath;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-recursive-mode",
|
|
3
3
|
"description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + recursive_status tool",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
package/src/client/board.tsx
CHANGED
|
@@ -39,6 +39,12 @@ function closeButton(onClose: (() => void) | undefined) {
|
|
|
39
39
|
return createElement('button', { type: 'button', className: 'rec-close', onClick: onClose, title: 'Close', 'aria-label': 'Close' }, '×')
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** Case/trailing-separator tolerant workspace-path equality (Windows host). */
|
|
43
|
+
function sameWorkspacePath(a: string, b: string): boolean {
|
|
44
|
+
const norm = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase()
|
|
45
|
+
return norm(a) === norm(b)
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
function solidPill(kind: ReturnType<typeof cardPill>) {
|
|
43
49
|
return createElement('span', { className: 'rec-pill', 'data-pill': kind }, PILL_LABELS[kind])
|
|
44
50
|
}
|
|
@@ -46,7 +52,11 @@ function solidPill(kind: ReturnType<typeof cardPill>) {
|
|
|
46
52
|
export function Board({ snapshot, workspacePath, onOpenInspector, onClose }: BoardProps) {
|
|
47
53
|
const { theme, toggle } = useBoardTheme()
|
|
48
54
|
if (snapshot === null || snapshot.root === null) return null
|
|
49
|
-
|
|
55
|
+
// Run 16: when the synchronous workspace path differs from the async snapshot
|
|
56
|
+
// root, the projection is stale (from the previously-viewed workspace) — suppress
|
|
57
|
+
// its run cards so a workspace switch can never flash another workspace's runs.
|
|
58
|
+
const stale = workspacePath !== undefined && workspacePath !== '' && !sameWorkspacePath(workspacePath, snapshot.root)
|
|
59
|
+
const runs = stale ? [] : listRuns(snapshot.projection)
|
|
50
60
|
const headerPath = workspacePath ?? snapshot.root
|
|
51
61
|
const open = (run: RecursiveRunCard) => () => onOpenInspector?.({ worktreeRoot: run.worktreeRoot, runId: run.runId })
|
|
52
62
|
if (runs.length === 0) {
|
package/src/client/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { claimClientApply, releaseClientApply } from './apply-guard.ts'
|
|
|
17
17
|
|
|
18
18
|
export { Board, listRuns } from './board.tsx'
|
|
19
19
|
export { Inspector } from './inspector.tsx'
|
|
20
|
-
export { StatusStrip } from './strip.tsx'
|
|
21
20
|
export { RecursiveSettings } from './settings.tsx'
|
|
22
21
|
export { useLiveProjection } from './use-live.ts'
|
|
23
22
|
export type { LiveProjectionSnapshot } from './use-live.ts'
|
package/src/client/slots.ts
CHANGED
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { createElement, type ReactNode } from 'react'
|
|
20
20
|
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts'
|
|
21
|
-
import { isRecursivePreset,
|
|
21
|
+
import { isRecursivePreset, currentWorkspacePath } from './contract.ts'
|
|
22
22
|
import { Board } from './board.tsx'
|
|
23
23
|
import { Inspector } from './inspector.tsx'
|
|
24
|
-
import { StatusStrip } from './strip.tsx'
|
|
25
24
|
import { RecursiveSettings } from './settings.tsx'
|
|
26
25
|
import { useLiveProjection } from './use-live.ts'
|
|
27
26
|
import { boardState, useBoardState } from './open-state.ts'
|
|
@@ -33,13 +32,6 @@ interface RootSlotProps {
|
|
|
33
32
|
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
/** Structural standard-prop face for the session scope (sessionId + useSessions). */
|
|
37
|
-
interface SessionSlotProps {
|
|
38
|
-
sessionId?: string
|
|
39
|
-
useSessions?: SnapshotSelectorHook<SessionListStateLike>
|
|
40
|
-
useProjection?: unknown
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
export type OverlayContent = 'hidden' | 'board' | 'inspector'
|
|
44
36
|
|
|
45
37
|
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
@@ -103,17 +95,6 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
103
95
|
return createElement(RecursiveBoardOverlay, { useSessions, useWorkspaces: props?.useWorkspaces })
|
|
104
96
|
})))
|
|
105
97
|
|
|
106
|
-
// Status strip in the composer input dock (session scope) — gated the same way.
|
|
107
|
-
disposers.push(ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
108
|
-
name: 'conversation.input.dock',
|
|
109
|
-
id: 'recursive-strip',
|
|
110
|
-
order: 20,
|
|
111
|
-
}, (props: SessionSlotProps) => {
|
|
112
|
-
const useSessions = props?.useSessions
|
|
113
|
-
if (useSessions === undefined) return null
|
|
114
|
-
return createElement(RecursiveStripGate, { useSessions, sessionId: props?.sessionId })
|
|
115
|
-
})))
|
|
116
|
-
|
|
117
98
|
// Settings section (root scope, always present; no gate — configuration is always available).
|
|
118
99
|
disposers.push(ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
119
100
|
name: 'settings.section',
|
|
@@ -160,19 +141,5 @@ export function RecursiveBoardOverlay({ useSessions, useWorkspaces }: { useSessi
|
|
|
160
141
|
return createElement(Board, { snapshot, workspacePath: wsPath, onOpenInspector: (sel) => boardState.openInspector(sel), onClose: () => boardState.close() })
|
|
161
142
|
}
|
|
162
143
|
|
|
163
|
-
/**
|
|
164
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
165
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
166
|
-
*/
|
|
167
|
-
export function RecursiveStripGate({ useSessions, sessionId }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; sessionId?: string }): ReactNode {
|
|
168
|
-
const list = useRecursiveSessions(useSessions)
|
|
169
|
-
const cwd = currentSessionCwd(list)
|
|
170
|
-
const scope = { sessionId, cwd }
|
|
171
|
-
// Run 13: ALWAYS call useLiveProjection BEFORE the preset early return.
|
|
172
|
-
const snapshot = useLiveProjection(scope)
|
|
173
|
-
if (!isRecursivePreset(list)) return null
|
|
174
|
-
return createElement(StatusStrip, { snapshot })
|
|
175
|
-
}
|
|
176
|
-
|
|
177
144
|
/** Export the gate helpers for tests. */
|
|
178
|
-
export { isRecursivePreset
|
|
145
|
+
export { isRecursivePreset }
|
package/src/client/use-live.ts
CHANGED
|
@@ -34,10 +34,11 @@ export function useLiveProjection(scope: LiveScope): LiveProjectionSnapshot {
|
|
|
34
34
|
useEffect(() => {
|
|
35
35
|
// Empty scope = no resolvable session row yet — render nothing, never fetch.
|
|
36
36
|
if (isEmpty(scope)) { setState(null); return }
|
|
37
|
-
// 0.1.
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
37
|
+
// 0.1.18 (LIVE BUG 5): do NOT null the snapshot on scope change. 0.1.16
|
|
38
|
+
// added board.tsx's synchronous stale-workspace guard (workspacePath vs
|
|
39
|
+
// async snapshot.root), which already suppresses stale cards; nulling here
|
|
40
|
+
// blanked the board on every session/workspace switch (the "delay then
|
|
41
|
+
// stop" symptom). Keep the previous snapshot until the fresh fetch resolves.
|
|
41
42
|
let disposed = false
|
|
42
43
|
const apply = (frame: LiveRecursiveFrame) => { if (!disposed) setState(frame) }
|
|
43
44
|
|
package/lib/client/strip.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { LiveProjectionValue } from './contract.ts';
|
|
2
|
-
export interface StripProps {
|
|
3
|
-
/** The live host-route snapshot (null = not loaded / no recursive root). */
|
|
4
|
-
snapshot: LiveProjectionValue | null;
|
|
5
|
-
}
|
|
6
|
-
export declare function StatusStrip({ snapshot }: StripProps): import("react").DetailedReactHTMLElement<{
|
|
7
|
-
className: string;
|
|
8
|
-
}, HTMLElement> | null;
|
package/src/client/strip.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Status strip (SP2 R1): a thin glance at the active run's phase, gate state,
|
|
3
|
-
* and tamper badge, docked at conversation.input.dock. Renders NOTHING when no
|
|
4
|
-
* run is current or the snapshot is null (the preset gate lives in slots.ts).
|
|
5
|
-
*/
|
|
6
|
-
import { createElement } from 'react'
|
|
7
|
-
import { cardFacts } from './derive.ts'
|
|
8
|
-
import { listRuns } from './board.tsx'
|
|
9
|
-
import type { LiveProjectionValue } from './contract.ts'
|
|
10
|
-
|
|
11
|
-
export interface StripProps {
|
|
12
|
-
/** The live host-route snapshot (null = not loaded / no recursive root). */
|
|
13
|
-
snapshot: LiveProjectionValue | null
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function StatusStrip({ snapshot }: StripProps) {
|
|
17
|
-
if (snapshot === null || snapshot.root === null) return null
|
|
18
|
-
const runs = listRuns(snapshot.projection)
|
|
19
|
-
if (runs.length === 0) return null
|
|
20
|
-
const active = runs[runs.length - 1]
|
|
21
|
-
const facts = cardFacts(active)
|
|
22
|
-
return createElement('div', { className: 'rec-strip' },
|
|
23
|
-
createElement('span', { className: 'rec-strip-run' }, active.runId),
|
|
24
|
-
createElement('span', { className: 'rec-badge' }, facts.currentPhase ?? 'no phases'),
|
|
25
|
-
facts.gateBlocked && createElement('span', { className: 'rec-badge rec-badge-gate' }, 'gate blocked'),
|
|
26
|
-
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper' }, 'tampered'),
|
|
27
|
-
)
|
|
28
|
-
}
|