@try-works/dsh-recursive-mode 0.2.4 → 0.3.0
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/delegation.d.ts +168 -0
- package/lib/enforcement.d.ts +8 -0
- package/lib/goals-projection.d.ts +92 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +773 -9
- package/lib/recursive_audit_team.tool.d.ts +2 -0
- package/lib/runtime.d.ts +112 -3
- package/lib/teams-loop.d.ts +160 -0
- package/package.json +1 -1
- package/scripts/test-recursive-mode-smoke.ts +45 -19
- package/src/delegation.ts +337 -1
- package/src/enforcement.ts +16 -1
- package/src/goals-projection.ts +149 -0
- package/src/index.ts +41 -11
- package/src/recursive_audit_team.tool.ts +141 -0
- package/src/runtime.ts +134 -6
- package/src/teams-loop.ts +259 -0
package/lib/delegation.d.ts
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque handle to the live direct-parent Agent. The live continuable service
|
|
3
|
+
* authorizes by EXACT live object identity — `ctx.agents.get(parent.id) ===
|
|
4
|
+
* parent` (authorizeLineage), `ancestry.has(parent)` (interrupt/drain), and a
|
|
5
|
+
* `WeakSet` of live ancestry — so this must be the real live `Agent`, never a
|
|
6
|
+
* structural `{ id }` copy. The seam only ever reads `id`/`session.header.cwd`
|
|
7
|
+
* for attribution, and never serializes or inspects the live object.
|
|
8
|
+
*/
|
|
9
|
+
export interface SubagentParentHandle {
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly session?: {
|
|
12
|
+
readonly header?: {
|
|
13
|
+
readonly cwd?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Durable identity of one continuable child session (string-branded in the host). */
|
|
18
|
+
export type ContinuableChildId = string;
|
|
19
|
+
/** Durable identity of one accepted inbox message (string-branded in the host). */
|
|
20
|
+
export type ContinuableMessageId = string;
|
|
21
|
+
/**
|
|
22
|
+
* Attribution for a model coordinator's follow-up to one of its children (the
|
|
23
|
+
* live `CoordinatorMessageSource` subset — see subagent/src/continuation.ts).
|
|
24
|
+
*/
|
|
25
|
+
export interface CoordinatorSourceLike {
|
|
26
|
+
readonly kind: 'coordinator';
|
|
27
|
+
readonly form: 'relay';
|
|
28
|
+
readonly senderSessionId: string;
|
|
29
|
+
}
|
|
30
|
+
/** Uniform outcome for the interrupt/drain kill-switch helpers. */
|
|
31
|
+
export interface ContinuableOpResult {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
reason?: string;
|
|
34
|
+
}
|
|
1
35
|
/** Minimal host-realm contract for ctx.subagents (the seam we call). */
|
|
2
36
|
export interface SubagentsRuntimeLike {
|
|
3
37
|
start(name: string, request: SubagentStartRequestLike): Promise<SubagentResultLike>;
|
|
4
38
|
getProvider?(name: string): unknown;
|
|
5
39
|
list?(): unknown;
|
|
40
|
+
/** T4: continuable child lifecycle (startContinuable / followup / interrupt / drain). */
|
|
41
|
+
startContinuable?(spec: ContinuableStartSpecLike): Promise<ContinuableStartLike>;
|
|
42
|
+
/** The parent MUST be the exact live Agent (object-identity authority), never a `{ id }` copy. */
|
|
43
|
+
followup?(parent: SubagentParentHandle, childId: ContinuableChildId, content: readonly {
|
|
44
|
+
type: 'text';
|
|
45
|
+
text: string;
|
|
46
|
+
}[], options: SubagentFollowupOptionsLike): Promise<ContinuableMessageId>;
|
|
47
|
+
interrupt?(targetSessionId: ContinuableChildId, authority: SubagentInterruptAuthorityLike): void;
|
|
48
|
+
drainContinuableChildren?(parent: SubagentParentHandle, childIds: readonly ContinuableChildId[]): Promise<void>;
|
|
49
|
+
drainContinuableDescendants?(parents: readonly SubagentParentHandle[]): Promise<void>;
|
|
6
50
|
}
|
|
7
51
|
export interface SubagentStartRequestLike {
|
|
8
52
|
prompt: unknown[];
|
|
@@ -37,6 +81,130 @@ export declare function delegate(input: {
|
|
|
37
81
|
provider: string;
|
|
38
82
|
request: SubagentStartRequestLike;
|
|
39
83
|
}): Promise<SubagentResultLike>;
|
|
84
|
+
/**
|
|
85
|
+
* T4: continuable-child delegation — ONE durable child receives the initial
|
|
86
|
+
* prompt (startContinuable), each REVISE is delivered as a followup to the SAME
|
|
87
|
+
* child (FIFO, working set retained), and the parent observes each round's
|
|
88
|
+
* settlement through the injected `awaitRoundResult` seam (in live usage the
|
|
89
|
+
* child's settlement lands in the parent's inbox — `reportFrom` is the
|
|
90
|
+
* CHILD-side API, so the parent-side loop collects via settlement, not by
|
|
91
|
+
* calling it). A hung reviewer is cancelled with `interruptContinuable`
|
|
92
|
+
* (keepInbox: the child's pending inbox survives). Falls back to one-shot
|
|
93
|
+
* `delegate` when the seam has no continuable methods.
|
|
94
|
+
*/
|
|
95
|
+
/** What the caller asks for when starting a continuable background child (structural subset). */
|
|
96
|
+
export interface ContinuableStartSpecLike {
|
|
97
|
+
/** The `ctx.subagents` provider whose continuable-creation capability establishes the child. */
|
|
98
|
+
readonly provider: string;
|
|
99
|
+
/** The initial delegation's short `description`, persisted as the child's creation label. */
|
|
100
|
+
readonly label: string;
|
|
101
|
+
/** Optional caller-reserved child identity. */
|
|
102
|
+
childId?: ContinuableChildId;
|
|
103
|
+
/** The delegation request (prompt + parent + toolFilter + maxDepth; no label/signal/outputSchema). */
|
|
104
|
+
readonly request: Omit<SubagentStartRequestLike, 'label' | 'signal' | 'outputSchema'>;
|
|
105
|
+
/** Caller cancellation, owning the operation only until inbox acceptance. */
|
|
106
|
+
readonly signal?: AbortSignalLike;
|
|
107
|
+
}
|
|
108
|
+
/** Minimal cancellation shape (a live AbortSignal satisfies it). */
|
|
109
|
+
export interface AbortSignalLike {
|
|
110
|
+
readonly throwIfAborted: () => void;
|
|
111
|
+
}
|
|
112
|
+
/** Identities returned once a continuable child accepted its initial prompt. */
|
|
113
|
+
export interface ContinuableStartLike {
|
|
114
|
+
/** The durable child session id, stable across activations. */
|
|
115
|
+
readonly childId: ContinuableChildId;
|
|
116
|
+
/** The accepted initial prompt's inbox message id. */
|
|
117
|
+
readonly messageId: ContinuableMessageId;
|
|
118
|
+
}
|
|
119
|
+
/** Options for following up with one continuable child (structural subset). */
|
|
120
|
+
export interface SubagentFollowupOptionsLike {
|
|
121
|
+
/** Durable attribution retained on the delivered message. */
|
|
122
|
+
readonly source: CoordinatorSourceLike;
|
|
123
|
+
/** Caller cancellation, owning the operation only until inbox acceptance. */
|
|
124
|
+
readonly signal?: AbortSignalLike;
|
|
125
|
+
}
|
|
126
|
+
/** Authority under which one interrupt request is admitted. */
|
|
127
|
+
export type SubagentInterruptAuthorityLike = {
|
|
128
|
+
readonly kind: 'user';
|
|
129
|
+
readonly parentSessionId: string;
|
|
130
|
+
} | {
|
|
131
|
+
readonly kind: 'ancestor';
|
|
132
|
+
readonly agent: SubagentParentHandle;
|
|
133
|
+
};
|
|
134
|
+
/** One round of a continuable child: the delivered text plus the observed outcome. */
|
|
135
|
+
export interface ContinuableRoundLike {
|
|
136
|
+
/** The message text delivered as this round's user prompt. */
|
|
137
|
+
text: string;
|
|
138
|
+
/** The child's observed outcome for this round. */
|
|
139
|
+
result?: SubagentResultLike;
|
|
140
|
+
/** True when this round's verdict was REVISE (a repair followup followed). */
|
|
141
|
+
revise?: boolean;
|
|
142
|
+
/** The repair instruction delivered in the followup (only when revise). */
|
|
143
|
+
repair?: string;
|
|
144
|
+
}
|
|
145
|
+
/** The full T4 delegation outcome. */
|
|
146
|
+
export interface ContinuableDelegationLike {
|
|
147
|
+
ok: boolean;
|
|
148
|
+
reason?: string;
|
|
149
|
+
/** The durable child session id (stable across rounds). */
|
|
150
|
+
childId?: ContinuableChildId;
|
|
151
|
+
/** Inbox message ids: [initial acceptance, ...followups]. */
|
|
152
|
+
messageIds?: ContinuableMessageId[];
|
|
153
|
+
rounds: ContinuableRoundLike[];
|
|
154
|
+
/** Final outcome accepted (last verdict APPROVE + result accepted). */
|
|
155
|
+
accepted: boolean;
|
|
156
|
+
/** True when the fallback one-shot `delegate()` was used (no continuable seam). */
|
|
157
|
+
fellBackToOneShot?: boolean;
|
|
158
|
+
}
|
|
159
|
+
/** Verdict vocabulary shared by T3/T4 (matches the delegated review schema). */
|
|
160
|
+
export type DelegationVerdict = 'APPROVE' | 'REVISE' | 'REJECT';
|
|
161
|
+
/** Read the verdict from a review-schema structured result (pure). */
|
|
162
|
+
export declare function readVerdictFromStructured(result: SubagentResultLike): DelegationVerdict;
|
|
163
|
+
/** Read the repair instruction from a review-schema structured result (pure). */
|
|
164
|
+
export declare function readRepairFromStructured(result: SubagentResultLike): string;
|
|
165
|
+
/**
|
|
166
|
+
* Run a multi-round delegated task on ONE durable continuable child:
|
|
167
|
+
* 1. `startContinuable` (initial prompt) — `start()` is never called.
|
|
168
|
+
* 2. `awaitRoundResult` observes the child's settlement for that round.
|
|
169
|
+
* 3. On REVISE: `followup` delivers the repair instruction to the SAME child.
|
|
170
|
+
* 4. On APPROVE/REJECT: finish (accepted only when the verdict is APPROVE and
|
|
171
|
+
* the result evaluates as accepted).
|
|
172
|
+
*
|
|
173
|
+
* `awaitRoundResult(childId, messageId)` is the ONLY parent-side observation
|
|
174
|
+
* seam: in live usage it waits for the child's settlement notice (the child's
|
|
175
|
+
* `reportFrom` lands in the parent's inbox); in tests it is a fake queue.
|
|
176
|
+
*/
|
|
177
|
+
export declare function delegateContinuable(input: {
|
|
178
|
+
subagents: SubagentsRuntimeLike;
|
|
179
|
+
provider: string;
|
|
180
|
+
label: string;
|
|
181
|
+
prompt: string;
|
|
182
|
+
parent?: SubagentParentHandle;
|
|
183
|
+
toolFilter?: unknown;
|
|
184
|
+
maxDepth?: number;
|
|
185
|
+
childId?: ContinuableChildId;
|
|
186
|
+
maxRounds?: number;
|
|
187
|
+
readVerdict?: (result: SubagentResultLike) => DelegationVerdict;
|
|
188
|
+
readRepair?: (result: SubagentResultLike) => string | undefined;
|
|
189
|
+
awaitRoundResult?: (childId: ContinuableChildId, messageId: ContinuableMessageId) => Promise<SubagentResultLike | null>;
|
|
190
|
+
}): Promise<ContinuableDelegationLike>;
|
|
191
|
+
/**
|
|
192
|
+
* T4 kill switch: interrupt one live continuable child's current turn. Admission
|
|
193
|
+
* is synchronous, the effect asynchronous, and the child's pending inbox is
|
|
194
|
+
* preserved (keepInbox semantics) — a followup later resumes the parked queue.
|
|
195
|
+
*/
|
|
196
|
+
export declare function interruptContinuable(subagents: SubagentsRuntimeLike, childId: ContinuableChildId, parentSessionId: string): ContinuableOpResult;
|
|
197
|
+
/**
|
|
198
|
+
* T4 closeout: release one continuable child (host drains its Activation and
|
|
199
|
+
* disposes its handle). No-op when the seam lacks the method (one-shot hosts).
|
|
200
|
+
*/
|
|
201
|
+
export declare function drainContinuableChildren(subagents: SubagentsRuntimeLike, parent: SubagentParentHandle, childIds: readonly ContinuableChildId[]): Promise<ContinuableOpResult>;
|
|
202
|
+
/**
|
|
203
|
+
* T4 closeout (host teardown path): release every continuable descendant below
|
|
204
|
+
* the given live parents (mirrors the live `drainContinuableDescendants`).
|
|
205
|
+
* No-op when the seam lacks the method; the host owns this at session teardown.
|
|
206
|
+
*/
|
|
207
|
+
export declare function drainContinuableDescendants(subagents: SubagentsRuntimeLike | null, parents: readonly SubagentParentHandle[]): Promise<ContinuableOpResult>;
|
|
40
208
|
export interface Reference {
|
|
41
209
|
path: string;
|
|
42
210
|
lineRange?: string;
|
package/lib/enforcement.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const DEFAULT_ENFORCEMENT: EnforcementConfig;
|
|
|
14
14
|
*/
|
|
15
15
|
export type ToolGuardDecision = {
|
|
16
16
|
kind: 'allow';
|
|
17
|
+
warn?: string;
|
|
17
18
|
} | {
|
|
18
19
|
kind: 'deny';
|
|
19
20
|
reason: string;
|
|
@@ -27,6 +28,13 @@ export interface ToolExecLike {
|
|
|
27
28
|
agent?: unknown;
|
|
28
29
|
}
|
|
29
30
|
export declare function evaluateToolGuard(exec: ToolExecLike, worktreeRoot: string, activeRunId: string, mode?: EnforcementMode): ToolGuardDecision;
|
|
31
|
+
/**
|
|
32
|
+
* T6 (approval ask→policy bridge): an `ask` decision must never be a silent
|
|
33
|
+
* allow. Under `strict` it coerces to `deny`; under `advisory` it stays `allow`
|
|
34
|
+
* but flags a `warn` so the caller never lets it through unlogged. Non-ask
|
|
35
|
+
* decisions pass through unchanged.
|
|
36
|
+
*/
|
|
37
|
+
export declare function coerceAskToDecision(decision: ToolGuardDecision, mode?: EnforcementMode): ToolGuardDecision;
|
|
30
38
|
/**
|
|
31
39
|
* Layer 8 - fs/observed lock-tamper detection.
|
|
32
40
|
* A locked *.md whose observed version differs from the stored LockHash is
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* goals-projection.ts (T1, STRENGTHENING-PLAN): project a recursive run into the
|
|
3
|
+
* native `goals` service so the run is a first-class durable, resumable,
|
|
4
|
+
* blockable object — and a gate block is durable + UI-visible rather than a
|
|
5
|
+
* one-line advisory.
|
|
6
|
+
*
|
|
7
|
+
* Pure/structural: this module takes a `GoalServiceLike` seam (the real
|
|
8
|
+
* `ctx.goals` satisfies it structurally) and an opaque `AgentHandle`, and never
|
|
9
|
+
* imports the host `@deepseek-ai/dsh-goal` package. The real service's methods
|
|
10
|
+
* take a live `Agent` and throw if the agent is not the registry's live
|
|
11
|
+
* instance, so callers pass the live agent from a tool/pre-step `exec`.
|
|
12
|
+
*
|
|
13
|
+
* Safety rule: the projection NEVER clobbers a foreign goal. A goal whose
|
|
14
|
+
* objective is not a `recursive-run:<id>` marker is left untouched (only a
|
|
15
|
+
* completed goal may be replaced, per the service contract).
|
|
16
|
+
*/
|
|
17
|
+
import type { RunState } from './lifecycle.ts';
|
|
18
|
+
/** Native goal phase (mirrors @deepseek-ai/dsh-goal GoalPhase). */
|
|
19
|
+
export type GoalPhase = 'active' | 'paused' | 'blocked' | 'complete';
|
|
20
|
+
/** CSA identity for one exact goal revision. */
|
|
21
|
+
export interface GoalRefLike {
|
|
22
|
+
id: string;
|
|
23
|
+
revision: number;
|
|
24
|
+
}
|
|
25
|
+
/** Input resolved by the service when the round cap is omitted. */
|
|
26
|
+
export interface CreateGoalRequestLike {
|
|
27
|
+
objective: string;
|
|
28
|
+
maxGoalRounds?: number;
|
|
29
|
+
}
|
|
30
|
+
/** The subset of the goal view the projection reads. */
|
|
31
|
+
export interface GoalViewLike extends GoalRefLike {
|
|
32
|
+
objective?: string;
|
|
33
|
+
phase?: GoalPhase;
|
|
34
|
+
}
|
|
35
|
+
/** Opaque handle to the live DSH Agent; the projection never inspects it further. */
|
|
36
|
+
export interface AgentHandle {
|
|
37
|
+
session?: {
|
|
38
|
+
header?: {
|
|
39
|
+
cwd?: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Structural seam for the live `goals` service (real methods return GoalView). */
|
|
44
|
+
export interface GoalServiceLike {
|
|
45
|
+
get(agent: AgentHandle): GoalViewLike | undefined;
|
|
46
|
+
create(agent: AgentHandle, req: CreateGoalRequestLike): GoalViewLike;
|
|
47
|
+
block(agent: AgentHandle, ref: GoalRefLike, reason: {
|
|
48
|
+
code: string;
|
|
49
|
+
message: string;
|
|
50
|
+
}): GoalViewLike;
|
|
51
|
+
pause(agent: AgentHandle, ref: GoalRefLike): GoalViewLike;
|
|
52
|
+
resume(agent: AgentHandle, ref: GoalRefLike): GoalViewLike;
|
|
53
|
+
complete(agent: AgentHandle, ref: GoalRefLike): GoalViewLike;
|
|
54
|
+
clear(agent: AgentHandle, ref: GoalRefLike): GoalRefLike;
|
|
55
|
+
}
|
|
56
|
+
/** Outcome of a run→goal sync. */
|
|
57
|
+
export type SyncResult = {
|
|
58
|
+
ok: true;
|
|
59
|
+
phase: GoalPhase;
|
|
60
|
+
ref?: GoalRefLike;
|
|
61
|
+
created?: boolean;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
/** Marker embedded in the goal objective so a goal can be matched to its run. */
|
|
67
|
+
export declare function runGoalTag(runId: string): string;
|
|
68
|
+
/** The durable objective string for a run goal. */
|
|
69
|
+
export declare function goalObjective(runId: string, runState?: RunState): string;
|
|
70
|
+
/** Map a run state onto the native goal phase it should project to. */
|
|
71
|
+
export declare const RUN_TO_GOAL_PHASE: {
|
|
72
|
+
readonly new: "active";
|
|
73
|
+
readonly active: "active";
|
|
74
|
+
readonly paused: "paused";
|
|
75
|
+
readonly blocked: "blocked";
|
|
76
|
+
readonly complete: "complete";
|
|
77
|
+
};
|
|
78
|
+
/** Is this goal's objective the marker for `runId`? */
|
|
79
|
+
export declare function isRunGoal(goal: GoalViewLike | undefined, runId: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Sync a run's durable goal to the requested phase. Safe: never touches a goal
|
|
82
|
+
* whose objective is not this run's marker, and never re-creates over a
|
|
83
|
+
* non-complete foreign goal.
|
|
84
|
+
*/
|
|
85
|
+
export declare function syncRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, runState: RunState): SyncResult;
|
|
86
|
+
/** Block the current run goal (used on a gate-block). Never touches a foreign goal. */
|
|
87
|
+
export declare function blockRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string, reason: {
|
|
88
|
+
code: string;
|
|
89
|
+
message: string;
|
|
90
|
+
}): SyncResult;
|
|
91
|
+
/** Bridge a run's blocked goal back to active (used on a reopen). */
|
|
92
|
+
export declare function resumeRunGoal(service: GoalServiceLike | undefined | null, agent: AgentHandle, runId: string): SyncResult;
|
package/lib/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './enforcement.ts';
|
|
|
22
22
|
export * from './policy.ts';
|
|
23
23
|
export * from './snapshot.ts';
|
|
24
24
|
export * from './live-route.ts';
|
|
25
|
+
export * from './teams-loop.ts';
|
|
25
26
|
/**
|
|
26
27
|
* Bundle plugin entry. The Loader activates this row once `tools` is available
|
|
27
28
|
* (`inject` below); the RecursiveRuntime service is constructed directly so it
|