@try-works/dsh-recursive-mode 0.1.16 → 0.1.17
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 -42
- 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/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
|
|
@@ -1293,18 +1283,6 @@ window.__ModuleLoader__.load({
|
|
|
1293
1283
|
useWorkspaces: props?.useWorkspaces
|
|
1294
1284
|
});
|
|
1295
1285
|
})));
|
|
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
1286
|
disposers.push(ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1309
1287
|
name: "settings.section",
|
|
1310
1288
|
id: "recursive",
|
|
@@ -1347,19 +1325,6 @@ window.__ModuleLoader__.load({
|
|
|
1347
1325
|
onClose: () => boardState.close()
|
|
1348
1326
|
});
|
|
1349
1327
|
}
|
|
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
1328
|
//#endregion
|
|
1364
1329
|
//#region src/client/apply-guard.ts
|
|
1365
1330
|
/** Claims the client apply slot. Returns true when this call won the slot. */
|
|
@@ -1391,7 +1356,6 @@ window.__ModuleLoader__.load({
|
|
|
1391
1356
|
exports.Board = Board;
|
|
1392
1357
|
exports.Inspector = Inspector;
|
|
1393
1358
|
exports.RecursiveSettings = RecursiveSettings;
|
|
1394
|
-
exports.StatusStrip = StatusStrip;
|
|
1395
1359
|
exports.apply = apply;
|
|
1396
1360
|
exports.currentSessionCwd = currentSessionCwd;
|
|
1397
1361
|
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.17",
|
|
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/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
|
-
}
|