@try-works/dsh-recursive-mode 0.1.2 → 0.1.4
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/cordis.patch.yml +15 -10
- package/lib/bootstrap.d.ts +9 -2
- package/lib/client/apply-guard.d.ts +14 -0
- package/lib/client/board.d.ts +5 -4
- package/lib/client/contract.d.ts +57 -24
- package/lib/client/host-api.d.ts +26 -0
- package/lib/client/index.d.ts +16 -9
- package/lib/client/inspector.d.ts +3 -3
- package/lib/client/slots.d.ts +4 -6
- package/lib/client/strip.d.ts +4 -3
- package/lib/client/use-live.d.ts +9 -0
- package/lib/client.js +252 -174
- package/lib/index.d.ts +11 -11
- package/lib/index.js +972 -4504
- package/lib/init-templates.d.ts +41 -0
- package/lib/live-route.d.ts +69 -0
- package/lib/phase-rules.d.ts +34 -0
- package/lib/runtime.d.ts +24 -17
- package/lib/snapshot.d.ts +11 -0
- package/package.json +4 -3
- package/preset/recursive/agent.cordis.yml +7 -4
- package/preset/recursive/preset.yml +1 -1
- package/references/agents-block.md +95 -0
- package/references/artifact-template.md +2574 -0
- package/references/bodies/claude.md +7 -0
- package/references/bodies/codex-agents.md +95 -0
- package/references/bodies/copilot.md +7 -0
- package/references/bodies/cursorrules.md +7 -0
- package/references/bodies/decisions.md +5 -0
- package/references/bodies/delegated-verification.md +49 -0
- package/references/bodies/memory-router.md +45 -0
- package/references/bodies/phase8-skill-memory.md +48 -0
- package/references/bodies/plans-bridge.md +43 -0
- package/references/bodies/recursive-agents-router.md +64 -0
- package/references/bodies/skill-discovery.md +51 -0
- package/references/bodies/skill-memory-router.md +42 -0
- package/references/bodies/state.md +5 -0
- package/references/bootstrap/RECURSIVE.md +2362 -0
- package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/references/scripts/lint-recursive-run.ps1 +25 -0
- package/references/scripts/lint-recursive-run.py +2870 -0
- package/references/scripts/recursive-closeout.ps1 +38 -0
- package/references/scripts/recursive-closeout.py +541 -0
- package/references/scripts/recursive-init.ps1 +515 -0
- package/references/scripts/recursive-init.py +356 -0
- package/references/scripts/recursive-lock.ps1 +26 -0
- package/references/scripts/recursive-lock.py +302 -0
- package/references/scripts/recursive-review-bundle.ps1 +58 -0
- package/references/scripts/recursive-review-bundle.py +503 -0
- package/references/scripts/recursive-router-cli-configure.ps1 +2 -0
- package/references/scripts/recursive-router-cli-configure.py +9 -0
- package/references/scripts/recursive-router-cli-init.ps1 +2 -0
- package/references/scripts/recursive-router-cli-init.py +9 -0
- package/references/scripts/recursive-router-cli-invoke.ps1 +2 -0
- package/references/scripts/recursive-router-cli-invoke.py +9 -0
- package/references/scripts/recursive-router-cli-probe.ps1 +2 -0
- package/references/scripts/recursive-router-cli-probe.py +9 -0
- package/references/scripts/recursive-router-cli-resolve.ps1 +2 -0
- package/references/scripts/recursive-router-cli-resolve.py +9 -0
- package/references/scripts/recursive-router-cli-validate.ps1 +2 -0
- package/references/scripts/recursive-router-cli-validate.py +9 -0
- package/references/scripts/recursive-router-configure.ps1 +27 -0
- package/references/scripts/recursive-router-configure.py +74 -0
- package/references/scripts/recursive-router-init.ps1 +17 -0
- package/references/scripts/recursive-router-init.py +29 -0
- package/references/scripts/recursive-router-invoke.ps1 +47 -0
- package/references/scripts/recursive-router-invoke.py +103 -0
- package/references/scripts/recursive-router-probe.ps1 +25 -0
- package/references/scripts/recursive-router-probe.py +44 -0
- package/references/scripts/recursive-router-resolve.ps1 +26 -0
- package/references/scripts/recursive-router-resolve.py +46 -0
- package/references/scripts/recursive-router-validate.ps1 +17 -0
- package/references/scripts/recursive-router-validate.py +27 -0
- package/references/scripts/recursive-status.ps1 +23 -0
- package/references/scripts/recursive-status.py +2124 -0
- package/references/scripts/recursive-subagent-action.ps1 +98 -0
- package/references/scripts/recursive-subagent-action.py +197 -0
- package/references/scripts/recursive-training-extract.ps1 +23 -0
- package/references/scripts/recursive-training-extract.py +99 -0
- package/references/scripts/recursive-training-grpo.ps1 +52 -0
- package/references/scripts/recursive-training-grpo.py +1341 -0
- package/references/scripts/recursive-training-loader.ps1 +78 -0
- package/references/scripts/recursive-training-loader.py +558 -0
- package/references/scripts/recursive-training-mcp.ps1 +29 -0
- package/references/scripts/recursive-training-mcp.py +261 -0
- package/references/scripts/recursive-training-phase8-trigger.ps1 +55 -0
- package/references/scripts/recursive-training-phase8-trigger.py +137 -0
- package/references/scripts/recursive-training-sync.ps1 +29 -0
- package/references/scripts/recursive-training-sync.py +183 -0
- package/references/scripts/recursive_phase_rules.py +367 -0
- package/references/scripts/recursive_router_cli_lib.py +2 -0
- package/references/scripts/recursive_router_lib.py +2282 -0
- package/references/scripts/verify-locks.ps1 +25 -0
- package/references/scripts/verify-locks.py +353 -0
- 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-preset.cmd +7 -0
- package/scripts/install-preset.js +101 -0
- package/scripts/lint-recursive-run.py +7 -5
- package/scripts/test-recursive-mode-smoke.ts +29 -28
- package/src/bootstrap.ts +253 -39
- package/src/client/apply-guard.ts +23 -0
- package/src/client/board.tsx +0 -0
- package/src/client/contract.ts +72 -24
- package/src/client/host-api.ts +90 -0
- package/src/client/index.ts +22 -11
- package/src/client/inspector.tsx +7 -9
- package/src/client/slots.ts +70 -26
- package/src/client/strip.tsx +9 -9
- package/src/client/use-live.ts +52 -0
- package/src/index.ts +89 -146
- package/src/init-templates.ts +276 -0
- package/src/live-route.ts +163 -0
- package/src/phase-rules.ts +262 -0
- package/src/policy.ts +28 -4
- package/src/runtime.ts +129 -77
- package/src/snapshot.ts +104 -0
- package/lib/client/node.d.ts +0 -71
- package/lib/events.d.ts +0 -173
- package/lib/projection.d.ts +0 -29
- package/src/client/node.ts +0 -156
- package/src/events.ts +0 -173
- package/src/projection.ts +0 -237
package/src/snapshot.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapshotWorkspace(root): fold the filesystem into the RecursiveProjection whole value.
|
|
3
|
+
*
|
|
4
|
+
* SP2 R1 (live-route) — the ONLY projection path. The board/strip read this through
|
|
5
|
+
* the host HTTP/SSE route; cold resume and every GET are a FRESH fs read (the route
|
|
6
|
+
* never serves a cached/stale snapshot). Per-workspace: root is the resolved
|
|
7
|
+
* control-plane root (cwd == workspace root, cwd == subdir, or cwd == repo root).
|
|
8
|
+
*
|
|
9
|
+
* Phase-lock facts (lockedAt/lockHash) are enriched from lock receipts because
|
|
10
|
+
* foldRun PhaseState carries no lock fields; state comes from getArtifactState.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
import { discoverRuns } from './run.ts'
|
|
15
|
+
import { foldRun } from './status.ts'
|
|
16
|
+
import { getLockStatus, receiptPath, readReceipt } from './lock.ts'
|
|
17
|
+
import type { RecursiveProjection, RecursiveRunCard, RecursivePhaseRow, RecursiveRunState } from './types.ts'
|
|
18
|
+
|
|
19
|
+
/** One locked-phase fact from a lock receipt (artifacts/<file>.receipt.json). */
|
|
20
|
+
interface LockFacts {
|
|
21
|
+
lockedAt?: string
|
|
22
|
+
lockHash?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve a run's lock facts for one artifact file (receipt first, status fallback). */
|
|
26
|
+
function lockFactsOf(runDir: string, artifact: string): LockFacts {
|
|
27
|
+
const receipt = readReceipt(runDir, artifact)
|
|
28
|
+
if (receipt && typeof receipt.locked_at === 'string' && typeof receipt.artifact_hash === 'string') {
|
|
29
|
+
return {
|
|
30
|
+
lockedAt: receipt.locked_at,
|
|
31
|
+
lockHash: receipt.artifact_hash,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// No usable receipt: fall back to the artifact's own LockedAt/LockHash fields.
|
|
35
|
+
const status = getLockStatus(join(runDir, artifact))
|
|
36
|
+
if (status !== 'LOCKED') return {}
|
|
37
|
+
return {
|
|
38
|
+
lockedAt: new Date(0).toISOString().replace(/\.\d{3}Z$/, 'Z'),
|
|
39
|
+
lockHash: '0'.repeat(64),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One phase row from a run's folded status (locked facts enriched). */
|
|
44
|
+
function phaseRowOf(runDir: string, key: string, file: string, status: string): RecursivePhaseRow {
|
|
45
|
+
const row: RecursivePhaseRow = { phase: key, status }
|
|
46
|
+
if (status === 'LOCKED') {
|
|
47
|
+
const facts = lockFactsOf(runDir, file)
|
|
48
|
+
if (facts.lockedAt !== undefined) row.lockedAt = facts.lockedAt
|
|
49
|
+
if (facts.lockHash !== undefined) row.lockHash = facts.lockHash
|
|
50
|
+
}
|
|
51
|
+
return row
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fold one run directory into a RecursiveRunCard (the wire shape the board renders).
|
|
56
|
+
* Runs that no longer exist on disk are omitted (the fs is the source of truth).
|
|
57
|
+
*/
|
|
58
|
+
export function foldRunCard(runDir: string, runId: string, worktreeRoot: string): RecursiveRunCard {
|
|
59
|
+
const status = foldRun(runDir, runId)
|
|
60
|
+
// Wire contract parity (deleted projection.ts): phases are keyed by FILENAME
|
|
61
|
+
// (the recursive/phase event carried phase: '03-implementation-summary.md'),
|
|
62
|
+
// which is what derive.ts phaseGroupOf parses. phase.key ('03', '00R', '00W')
|
|
63
|
+
// would never match the filename regex and would break columnForRun/cardFacts.
|
|
64
|
+
const phases: Record<string, RecursivePhaseRow> = {}
|
|
65
|
+
for (const phase of status.phases) {
|
|
66
|
+
if (phase.exists) phases[phase.file] = phaseRowOf(runDir, phase.file, phase.file, phase.status)
|
|
67
|
+
}
|
|
68
|
+
const state = runStateOf(status)
|
|
69
|
+
return {
|
|
70
|
+
runId,
|
|
71
|
+
worktreeRoot,
|
|
72
|
+
phases,
|
|
73
|
+
state,
|
|
74
|
+
tampers: {},
|
|
75
|
+
subagents: {},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Derive the run-level state from the folded status. The fs stores no run-level
|
|
81
|
+
* state file, so the honest mapping is: 'complete' when every non-optional phase
|
|
82
|
+
* exists and is lock-valid, else 'active'. Transient facts (gateBlocked, tamper,
|
|
83
|
+
* subagent activity) are NOT fs-recoverable and read as absent (recorded
|
|
84
|
+
* limitation; future enhancement).
|
|
85
|
+
*/
|
|
86
|
+
function runStateOf(status: ReturnType<typeof foldRun>): RecursiveRunState {
|
|
87
|
+
const mandatory = status.phases.filter(p => !p.optional)
|
|
88
|
+
const complete = mandatory.every(p => p.exists && p.lockValid)
|
|
89
|
+
return complete ? 'complete' : 'active'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fold every run under <root>/.recursive/run/ into the projection, grouped by
|
|
94
|
+
* worktree root then runId. A fresh fs read on every call (cold-resume live).
|
|
95
|
+
*/
|
|
96
|
+
export function snapshotWorkspace(root: string): RecursiveProjection {
|
|
97
|
+
const projection: RecursiveProjection = {}
|
|
98
|
+
for (const runId of discoverRuns(root)) {
|
|
99
|
+
const runDir = join(root, '.recursive', 'run', runId)
|
|
100
|
+
projection[root] ??= {}
|
|
101
|
+
projection[root][runId] = foldRunCard(runDir, runId, root)
|
|
102
|
+
}
|
|
103
|
+
return projection
|
|
104
|
+
}
|
package/lib/client/node.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversation run node (Phase D R7, §11.6): a structural ConversationNodeDefinition
|
|
3
|
-
* over the recursive/* event family, keyed by runId + worktreeRoot. One compact,
|
|
4
|
-
* live node per run in the Chat view. PURE fold (no React) — the renderer lives
|
|
5
|
-
* in slots.ts.
|
|
6
|
-
*
|
|
7
|
-
* The real ConversationNodeDefinition contract arrives from
|
|
8
|
-
* @deepseek-ai/dsh-client-runtime/client at compose time; the structural shape
|
|
9
|
-
* here is type-compatible with the cookbook surface (match/start/update/
|
|
10
|
-
* buildViewNode + target/kind). The host links the bundle's exports to the real
|
|
11
|
-
* runtime identities.
|
|
12
|
-
*/
|
|
13
|
-
import type { ClientSessionEvent } from './contract.ts';
|
|
14
|
-
export declare const RECURSIVE_NODE_KIND = "recursive/run";
|
|
15
|
-
/** The chat-node payload the renderer receives (pure derived state). */
|
|
16
|
-
export interface RecursiveNodeData {
|
|
17
|
-
readonly runId: string;
|
|
18
|
-
readonly worktreeRoot: string;
|
|
19
|
-
readonly state: string;
|
|
20
|
-
readonly currentPhase: string | null;
|
|
21
|
-
readonly lockedCount: number;
|
|
22
|
-
readonly totalPhases: number;
|
|
23
|
-
readonly tampered: boolean;
|
|
24
|
-
readonly gateBlocked: boolean;
|
|
25
|
-
readonly mergedToRepoRoot?: string;
|
|
26
|
-
}
|
|
27
|
-
/** Pure fold state for one run node. */
|
|
28
|
-
export interface RecursiveNodeState {
|
|
29
|
-
runId: string;
|
|
30
|
-
worktreeRoot: string;
|
|
31
|
-
state: string;
|
|
32
|
-
phases: Record<string, {
|
|
33
|
-
phase: string;
|
|
34
|
-
status: string;
|
|
35
|
-
}>;
|
|
36
|
-
tampered: boolean;
|
|
37
|
-
gateBlocked: boolean;
|
|
38
|
-
mergedToRepoRoot?: string;
|
|
39
|
-
}
|
|
40
|
-
/** Extract a stable (runId, worktreeRoot) business id + lifecycle role from one event. */
|
|
41
|
-
export declare function matchRecursiveNode(event: ClientSessionEvent): {
|
|
42
|
-
id: string;
|
|
43
|
-
role: 'start' | 'update';
|
|
44
|
-
} | null;
|
|
45
|
-
/** Fold one recursive/* event into the node state. */
|
|
46
|
-
export declare function foldRecursiveNode(s: RecursiveNodeState | undefined, event: ClientSessionEvent): RecursiveNodeState;
|
|
47
|
-
/** The structural ConversationNodeDefinition (host links to the real contract). */
|
|
48
|
-
export declare const recursiveNodeDefinition: {
|
|
49
|
-
kind: string;
|
|
50
|
-
target: string;
|
|
51
|
-
match: typeof matchRecursiveNode;
|
|
52
|
-
start: (_ctx: unknown, match: {
|
|
53
|
-
event: ClientSessionEvent;
|
|
54
|
-
}) => RecursiveNodeState;
|
|
55
|
-
update: (ctx: {
|
|
56
|
-
state: RecursiveNodeState;
|
|
57
|
-
}, match: {
|
|
58
|
-
event: ClientSessionEvent;
|
|
59
|
-
}) => RecursiveNodeState;
|
|
60
|
-
buildViewNode: (ctx: {
|
|
61
|
-
key: string;
|
|
62
|
-
id: string;
|
|
63
|
-
state?: RecursiveNodeState;
|
|
64
|
-
}) => {
|
|
65
|
-
key: string;
|
|
66
|
-
kind: string;
|
|
67
|
-
id: string;
|
|
68
|
-
target: string;
|
|
69
|
-
data: RecursiveNodeData;
|
|
70
|
-
} | null;
|
|
71
|
-
};
|
package/lib/events.d.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* recursive/* session events (Phase D R1, PROPOSAL 11.2/11.6): the complete
|
|
3
|
-
* durable event vocabulary the projection folds, the board renders, and the
|
|
4
|
-
* conversation node correlates. Every payload carries a non-empty
|
|
5
|
-
* { runId, worktreeRoot } so the whole plane is worktree-aware and the
|
|
6
|
-
* projection can group runs by their control-plane root (binding
|
|
7
|
-
* workspace-scoping invariant, run 03 R1).
|
|
8
|
-
*
|
|
9
|
-
* Events are LOG-ONLY (no live mirror): the projection unit (projection.ts)
|
|
10
|
-
* is the only derived view. Payloads are plain JSON and declared on the
|
|
11
|
-
* SessionEventMap merge via @deepseek-ai/dsh-session/types.
|
|
12
|
-
*
|
|
13
|
-
* The pure constructor helpers below enforce the runId + non-empty
|
|
14
|
-
* worktreeRoot invariant at emit time, so a malformed event fails loud at
|
|
15
|
-
* the emit site instead of silently landing cross-workspace.
|
|
16
|
-
*/
|
|
17
|
-
import type { RecursiveRunState } from './types.ts';
|
|
18
|
-
/** Declare the recursive/* event family on the session event map (merge-extensible). */
|
|
19
|
-
declare module '@deepseek-ai/dsh-session/types' {
|
|
20
|
-
interface SessionEventMap {
|
|
21
|
-
/**
|
|
22
|
-
* A transition the lifecycle driver is ABOUT to validate/commit (logged
|
|
23
|
-
* before any file write). The pre-step gate reads this as the caller of the
|
|
24
|
-
* transition set — never a projection event (log-only internal signal).
|
|
25
|
-
*/
|
|
26
|
-
'recursive/phase-intent': {
|
|
27
|
-
runId: string;
|
|
28
|
-
worktreeRoot: string;
|
|
29
|
-
targetArtifact: string;
|
|
30
|
-
kind: 'lock' | 'reopen' | 'advance';
|
|
31
|
-
evidence?: Record<string, unknown>;
|
|
32
|
-
};
|
|
33
|
-
/** A run was created/bootstrapped in this workspace. */
|
|
34
|
-
'recursive/run-created': {
|
|
35
|
-
runId: string;
|
|
36
|
-
worktreeRoot: string;
|
|
37
|
-
template?: string;
|
|
38
|
-
repo?: string;
|
|
39
|
-
};
|
|
40
|
-
/** A phase transition committed (validate -> commit -> flush -> emit). */
|
|
41
|
-
'recursive/phase': {
|
|
42
|
-
runId: string;
|
|
43
|
-
worktreeRoot: string;
|
|
44
|
-
phase: string;
|
|
45
|
-
status: string;
|
|
46
|
-
};
|
|
47
|
-
/** A phase artifact was locked (lock hash + timestamp recorded). */
|
|
48
|
-
'recursive/phase-locked': {
|
|
49
|
-
runId: string;
|
|
50
|
-
worktreeRoot: string;
|
|
51
|
-
phase: string;
|
|
52
|
-
lockedAt: string;
|
|
53
|
-
lockHash: string;
|
|
54
|
-
};
|
|
55
|
-
/** A transition's gate predicate failed (rejected in strict, warned in advisory). */
|
|
56
|
-
'recursive/gate-blocked': {
|
|
57
|
-
runId: string;
|
|
58
|
-
worktreeRoot: string;
|
|
59
|
-
phase: string;
|
|
60
|
-
failures: string[];
|
|
61
|
-
kind: string;
|
|
62
|
-
};
|
|
63
|
-
/** fs/observed detected a change to a locked artifact outside a tool call. */
|
|
64
|
-
'recursive/tamper': {
|
|
65
|
-
runId: string;
|
|
66
|
-
worktreeRoot: string;
|
|
67
|
-
path: string;
|
|
68
|
-
reason: string;
|
|
69
|
-
};
|
|
70
|
-
/** A run was merged back to the repo root (conversation history re-keys). */
|
|
71
|
-
'recursive/run-merged': {
|
|
72
|
-
runId: string;
|
|
73
|
-
worktreeRoot: string;
|
|
74
|
-
repoRoot: string;
|
|
75
|
-
};
|
|
76
|
-
/** Run-level lifecycle flag (active/paused/blocked/complete + reason). */
|
|
77
|
-
'recursive/run-state': {
|
|
78
|
-
runId: string;
|
|
79
|
-
worktreeRoot: string;
|
|
80
|
-
state: RecursiveRunState;
|
|
81
|
-
reason?: string;
|
|
82
|
-
};
|
|
83
|
-
/** A subagent started for this run. */
|
|
84
|
-
'recursive/subagent-start': {
|
|
85
|
-
runId: string;
|
|
86
|
-
worktreeRoot: string;
|
|
87
|
-
childId: string;
|
|
88
|
-
role: string;
|
|
89
|
-
provider: string;
|
|
90
|
-
};
|
|
91
|
-
/** A subagent finished for this run. */
|
|
92
|
-
'recursive/subagent-end': {
|
|
93
|
-
runId: string;
|
|
94
|
-
worktreeRoot: string;
|
|
95
|
-
childId: string;
|
|
96
|
-
role: string;
|
|
97
|
-
provider: string;
|
|
98
|
-
status: 'running' | 'done' | 'failed';
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
/** Enforce the runId + non-empty worktreeRoot invariant at emit time. */
|
|
103
|
-
export declare function requireRunKey(runId: string, worktreeRoot: string): void;
|
|
104
|
-
export interface PhaseIntentInput {
|
|
105
|
-
runId: string;
|
|
106
|
-
worktreeRoot: string;
|
|
107
|
-
targetArtifact: string;
|
|
108
|
-
kind: 'lock' | 'reopen' | 'advance';
|
|
109
|
-
evidence?: Record<string, unknown>;
|
|
110
|
-
}
|
|
111
|
-
export interface RunCreatedInput {
|
|
112
|
-
runId: string;
|
|
113
|
-
worktreeRoot: string;
|
|
114
|
-
template?: string;
|
|
115
|
-
repo?: string;
|
|
116
|
-
}
|
|
117
|
-
export interface PhaseInput {
|
|
118
|
-
runId: string;
|
|
119
|
-
worktreeRoot: string;
|
|
120
|
-
phase: string;
|
|
121
|
-
status: string;
|
|
122
|
-
}
|
|
123
|
-
export interface PhaseLockedInput {
|
|
124
|
-
runId: string;
|
|
125
|
-
worktreeRoot: string;
|
|
126
|
-
phase: string;
|
|
127
|
-
lockedAt: string;
|
|
128
|
-
lockHash: string;
|
|
129
|
-
}
|
|
130
|
-
export interface GateBlockedInput {
|
|
131
|
-
runId: string;
|
|
132
|
-
worktreeRoot: string;
|
|
133
|
-
phase: string;
|
|
134
|
-
failures: string[];
|
|
135
|
-
kind: string;
|
|
136
|
-
}
|
|
137
|
-
export interface TamperInput {
|
|
138
|
-
runId: string;
|
|
139
|
-
worktreeRoot: string;
|
|
140
|
-
path: string;
|
|
141
|
-
reason: string;
|
|
142
|
-
}
|
|
143
|
-
export interface RunMergedInput {
|
|
144
|
-
runId: string;
|
|
145
|
-
worktreeRoot: string;
|
|
146
|
-
repoRoot: string;
|
|
147
|
-
}
|
|
148
|
-
export interface RunStateInput {
|
|
149
|
-
runId: string;
|
|
150
|
-
worktreeRoot: string;
|
|
151
|
-
state: RecursiveRunState;
|
|
152
|
-
reason?: string;
|
|
153
|
-
}
|
|
154
|
-
export interface SubagentInput {
|
|
155
|
-
runId: string;
|
|
156
|
-
worktreeRoot: string;
|
|
157
|
-
childId: string;
|
|
158
|
-
role: string;
|
|
159
|
-
provider: string;
|
|
160
|
-
}
|
|
161
|
-
export interface SubagentEndInput extends SubagentInput {
|
|
162
|
-
status: 'running' | 'done' | 'failed';
|
|
163
|
-
}
|
|
164
|
-
export declare function phaseIntent(data: PhaseIntentInput): PhaseIntentInput;
|
|
165
|
-
export declare function runCreated(data: RunCreatedInput): RunCreatedInput;
|
|
166
|
-
export declare function phase(data: PhaseInput): PhaseInput;
|
|
167
|
-
export declare function phaseLocked(data: PhaseLockedInput): PhaseLockedInput;
|
|
168
|
-
export declare function gateBlocked(data: GateBlockedInput): GateBlockedInput;
|
|
169
|
-
export declare function tamper(data: TamperInput): TamperInput;
|
|
170
|
-
export declare function runMerged(data: RunMergedInput): RunMergedInput;
|
|
171
|
-
export declare function runState(data: RunStateInput): RunStateInput;
|
|
172
|
-
export declare function subagentStart(data: SubagentInput): SubagentInput;
|
|
173
|
-
export declare function subagentEnd(data: SubagentEndInput): SubagentEndInput;
|
package/lib/projection.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ZodType } from 'zod';
|
|
2
|
-
import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection';
|
|
3
|
-
import type { RecursiveProjection } from './types.ts';
|
|
4
|
-
/** Declare the 'recursive' projection key on the shared type table. */
|
|
5
|
-
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
6
|
-
interface SessionProjectionMap {
|
|
7
|
-
/** Runs grouped by worktree root, then runId. */
|
|
8
|
-
recursive: RecursiveProjection;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
/** Structural event shape the pure fold reads (unit-testable without a session). */
|
|
12
|
-
export interface RecursiveEventLike {
|
|
13
|
-
type: string;
|
|
14
|
-
data?: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
/** Mutable fold accumulator (private to the pure fold; never leaks). */
|
|
17
|
-
export interface RecursiveFoldState {
|
|
18
|
-
/** The workspace control-plane root this unit is scoped to ('' = accept all). */
|
|
19
|
-
workspaceRoot: string;
|
|
20
|
-
/** Runs grouped by worktree root then runId. */
|
|
21
|
-
byWorktree: RecursiveProjection;
|
|
22
|
-
}
|
|
23
|
-
export declare function emptyRecursiveFoldState(workspaceRoot?: string): RecursiveFoldState;
|
|
24
|
-
/** Whether a worktreeRoot is inside (or equal to) the workspace root. */
|
|
25
|
-
export declare function isInsideWorkspace(worktreeRoot: string, workspaceRoot: string): boolean;
|
|
26
|
-
export declare function foldRecursiveProjection(state: RecursiveFoldState, event: RecursiveEventLike): RecursiveFoldState;
|
|
27
|
-
export declare const recursiveProjectionSchema: ZodType<RecursiveProjection>;
|
|
28
|
-
/** The registered projection unit (key 'recursive'). */
|
|
29
|
-
export declare const recursiveProjectionUnit: ProjectionDefinition<'recursive', RecursiveFoldState>;
|
package/src/client/node.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversation run node (Phase D R7, §11.6): a structural ConversationNodeDefinition
|
|
3
|
-
* over the recursive/* event family, keyed by runId + worktreeRoot. One compact,
|
|
4
|
-
* live node per run in the Chat view. PURE fold (no React) — the renderer lives
|
|
5
|
-
* in slots.ts.
|
|
6
|
-
*
|
|
7
|
-
* The real ConversationNodeDefinition contract arrives from
|
|
8
|
-
* @deepseek-ai/dsh-client-runtime/client at compose time; the structural shape
|
|
9
|
-
* here is type-compatible with the cookbook surface (match/start/update/
|
|
10
|
-
* buildViewNode + target/kind). The host links the bundle's exports to the real
|
|
11
|
-
* runtime identities.
|
|
12
|
-
*/
|
|
13
|
-
import type { ClientSessionEvent } from './contract.ts'
|
|
14
|
-
import type { RecursiveRunCard } from '../types.ts'
|
|
15
|
-
|
|
16
|
-
export const RECURSIVE_NODE_KIND = 'recursive/run'
|
|
17
|
-
|
|
18
|
-
/** The chat-node payload the renderer receives (pure derived state). */
|
|
19
|
-
export interface RecursiveNodeData {
|
|
20
|
-
readonly runId: string
|
|
21
|
-
readonly worktreeRoot: string
|
|
22
|
-
readonly state: string
|
|
23
|
-
readonly currentPhase: string | null
|
|
24
|
-
readonly lockedCount: number
|
|
25
|
-
readonly totalPhases: number
|
|
26
|
-
readonly tampered: boolean
|
|
27
|
-
readonly gateBlocked: boolean
|
|
28
|
-
readonly mergedToRepoRoot?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Pure fold state for one run node. */
|
|
32
|
-
export interface RecursiveNodeState {
|
|
33
|
-
runId: string
|
|
34
|
-
worktreeRoot: string
|
|
35
|
-
state: string
|
|
36
|
-
phases: Record<string, { phase: string; status: string }>
|
|
37
|
-
tampered: boolean
|
|
38
|
-
gateBlocked: boolean
|
|
39
|
-
mergedToRepoRoot?: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const GROUP_ORDER: readonly string[] = ['00', '01', '01.5', '02', '03', '03.5', '04', '05', '06', '07', '08']
|
|
43
|
-
const GROUP_INDEX = new Map(GROUP_ORDER.map((g, i) => [g, i]))
|
|
44
|
-
|
|
45
|
-
function phaseGroupOf(phase: string): string | null {
|
|
46
|
-
const m = /^(\d{2}(?:\.\d)?)(?:-|$)/.exec(phase)
|
|
47
|
-
return m ? m[1] : null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function currentPhaseOf(phases: Record<string, { phase: string; status: string }>): string | null {
|
|
51
|
-
let best: string | null = null
|
|
52
|
-
let bestIdx = -1
|
|
53
|
-
for (const key of Object.keys(phases)) {
|
|
54
|
-
const g = phaseGroupOf(key)
|
|
55
|
-
if (g === null) continue
|
|
56
|
-
const idx = GROUP_INDEX.get(g) ?? -1
|
|
57
|
-
if (idx > bestIdx) { bestIdx = idx; best = key }
|
|
58
|
-
}
|
|
59
|
-
return best
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function nodeDataOf(s: RecursiveNodeState): RecursiveNodeData {
|
|
63
|
-
const lockedCount = Object.values(s.phases).filter(p => p.status === 'LOCKED').length
|
|
64
|
-
return {
|
|
65
|
-
runId: s.runId,
|
|
66
|
-
worktreeRoot: s.worktreeRoot,
|
|
67
|
-
state: s.state,
|
|
68
|
-
currentPhase: currentPhaseOf(s.phases),
|
|
69
|
-
lockedCount,
|
|
70
|
-
totalPhases: Object.keys(s.phases).length,
|
|
71
|
-
tampered: s.tampered,
|
|
72
|
-
gateBlocked: s.gateBlocked,
|
|
73
|
-
...(s.mergedToRepoRoot === undefined ? {} : { mergedToRepoRoot: s.mergedToRepoRoot }),
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Extract a stable (runId, worktreeRoot) business id + lifecycle role from one event. */
|
|
78
|
-
export function matchRecursiveNode(event: ClientSessionEvent): { id: string; role: 'start' | 'update' } | null {
|
|
79
|
-
if (typeof event.type !== 'string' || !event.type.startsWith('recursive/')) return null
|
|
80
|
-
const data = event.data ?? {}
|
|
81
|
-
const runId = typeof data.runId === 'string' ? data.runId : ''
|
|
82
|
-
const worktreeRoot = typeof data.worktreeRoot === 'string' ? data.worktreeRoot : ''
|
|
83
|
-
if (runId === '' || worktreeRoot === '') return null
|
|
84
|
-
return { id: worktreeRoot + '\u0000' + runId, role: event.type === 'recursive/run-created' ? 'start' : 'update' }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Fold one recursive/* event into the node state. */
|
|
88
|
-
export function foldRecursiveNode(s: RecursiveNodeState | undefined, event: ClientSessionEvent): RecursiveNodeState {
|
|
89
|
-
const data = event.data ?? {}
|
|
90
|
-
const runId = typeof data.runId === 'string' ? data.runId : ''
|
|
91
|
-
const worktreeRoot = typeof data.worktreeRoot === 'string' ? data.worktreeRoot : ''
|
|
92
|
-
const base: RecursiveNodeState = s ?? {
|
|
93
|
-
runId,
|
|
94
|
-
worktreeRoot,
|
|
95
|
-
state: 'active',
|
|
96
|
-
phases: {},
|
|
97
|
-
tampered: false,
|
|
98
|
-
gateBlocked: false,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
switch (event.type) {
|
|
102
|
-
case 'recursive/run-created':
|
|
103
|
-
base.runId = runId
|
|
104
|
-
base.worktreeRoot = worktreeRoot
|
|
105
|
-
return base
|
|
106
|
-
case 'recursive/phase': {
|
|
107
|
-
const phase = String(data.phase ?? '')
|
|
108
|
-
if (phase !== '') base.phases[phase] = { phase, status: String(data.status ?? '') }
|
|
109
|
-
return base
|
|
110
|
-
}
|
|
111
|
-
case 'recursive/phase-locked': {
|
|
112
|
-
const phase = String(data.phase ?? '')
|
|
113
|
-
if (phase !== '') base.phases[phase] = { phase, status: 'LOCKED' }
|
|
114
|
-
return base
|
|
115
|
-
}
|
|
116
|
-
case 'recursive/gate-blocked':
|
|
117
|
-
base.gateBlocked = true
|
|
118
|
-
return base
|
|
119
|
-
case 'recursive/tamper':
|
|
120
|
-
base.tampered = true
|
|
121
|
-
return base
|
|
122
|
-
case 'recursive/run-state':
|
|
123
|
-
base.state = typeof data.state === 'string' ? data.state : base.state
|
|
124
|
-
return base
|
|
125
|
-
case 'recursive/run-merged': {
|
|
126
|
-
const repoRoot = typeof data.repoRoot === 'string' ? data.repoRoot : ''
|
|
127
|
-
if (repoRoot !== '') {
|
|
128
|
-
base.mergedToRepoRoot = repoRoot
|
|
129
|
-
base.worktreeRoot = repoRoot
|
|
130
|
-
}
|
|
131
|
-
return base
|
|
132
|
-
}
|
|
133
|
-
default:
|
|
134
|
-
return base
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** The structural ConversationNodeDefinition (host links to the real contract). */
|
|
139
|
-
export const recursiveNodeDefinition = {
|
|
140
|
-
kind: RECURSIVE_NODE_KIND,
|
|
141
|
-
target: 'chat',
|
|
142
|
-
match: matchRecursiveNode,
|
|
143
|
-
start: (_ctx: unknown, match: { event: ClientSessionEvent }): RecursiveNodeState => foldRecursiveNode(undefined, match.event),
|
|
144
|
-
update: (ctx: { state: RecursiveNodeState }, match: { event: ClientSessionEvent }): RecursiveNodeState => foldRecursiveNode(ctx.state, match.event),
|
|
145
|
-
buildViewNode: (ctx: { key: string; id: string; state?: RecursiveNodeState }) => {
|
|
146
|
-
const state = ctx.state
|
|
147
|
-
if (state === undefined) return null
|
|
148
|
-
return {
|
|
149
|
-
key: ctx.key,
|
|
150
|
-
kind: RECURSIVE_NODE_KIND,
|
|
151
|
-
id: ctx.id,
|
|
152
|
-
target: 'chat',
|
|
153
|
-
data: nodeDataOf(state),
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
}
|