@mstar-harness/dsh 3.8.0 → 3.8.2

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.
Files changed (51) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +142 -194
  3. package/README.zh.md +29 -17
  4. package/bundle/README.md +83 -171
  5. package/dist/client/index.d.ts +17 -8
  6. package/dist/client/panel/MstarPanelTitle.d.ts +13 -0
  7. package/dist/client/panel/PanelView.d.ts +56 -52
  8. package/dist/client/panel/TabNav.d.ts +17 -14
  9. package/dist/client/panel/definition.d.ts +23 -0
  10. package/dist/client/panel/engine-status-client.d.ts +84 -6
  11. package/dist/client/panel/graph/project-graph.d.ts +35 -64
  12. package/dist/client/panel/graph/schema.d.ts +1 -2
  13. package/dist/client/panel/guards.d.ts +41 -1
  14. package/dist/client/panel/locale.d.ts +1 -1
  15. package/dist/client/panel/mstar-glyph.d.ts +22 -0
  16. package/dist/client/panel/pages/AgentListPage.d.ts +71 -0
  17. package/dist/client/panel/pages/EventLogPage.d.ts +7 -4
  18. package/dist/client/panel/pages/IterationInfoSection.d.ts +16 -13
  19. package/dist/client/panel/pages/IterationTaskPage.d.ts +13 -14
  20. package/dist/client/panel/panel-store.d.ts +28 -0
  21. package/dist/client/panel/sidebar.d.ts +13 -7
  22. package/dist/client/panel/state-section.d.ts +25 -3
  23. package/dist/client/panel/use-mstar-engine-status.d.ts +39 -14
  24. package/dist/client/panel/zones/Legend.d.ts +5 -3
  25. package/dist/client/panel/zones/TaskBoard.d.ts +13 -9
  26. package/dist/client.js +1038 -1242
  27. package/dist/engine-status-endpoint.d.ts +85 -8
  28. package/dist/engine-status-store.d.ts +91 -1
  29. package/dist/engine-status-wire.d.ts +9 -0
  30. package/dist/gates/_shared.d.ts +61 -9
  31. package/dist/gates/adapter.d.ts +32 -2
  32. package/dist/gates/agent-flow.d.ts +312 -60
  33. package/dist/gates/catalog.d.ts +59 -38
  34. package/dist/gates/dispatch.d.ts +11 -2
  35. package/dist/gates/goal-bridge.d.ts +10 -130
  36. package/dist/gates/plan-mode-bridge.d.ts +20 -11
  37. package/dist/gates/role-persona.d.ts +16 -0
  38. package/dist/gates/steering.d.ts +41 -0
  39. package/dist/gates/workflow-ledger.d.ts +31 -4
  40. package/dist/gates/workflow-selection.d.ts +41 -20
  41. package/dist/index.js +1208 -394
  42. package/dist/types.d.ts +50 -18
  43. package/harness-commands/amazing-pr-review.md +2 -0
  44. package/harness-commands/codebase-audit.md +2 -0
  45. package/harness-skills/mstar-host/SKILL.md +3 -1
  46. package/harness-skills/mstar-host/references/dsh-workflow-scripts.md +424 -0
  47. package/harness-skills/mstar-host/references/dsh.md +259 -266
  48. package/harness-skills/mstar-roles/references/project-manager.md +2 -0
  49. package/harness-skills/mstar-sdd/SKILL.md +2 -0
  50. package/package.json +66 -64
  51. package/dist/client/panel/pages/AgentCanvasPage.d.ts +0 -345
@@ -1,6 +1,7 @@
1
1
  import { type Context } from '@deepseek-ai/cordis';
2
2
  import type { AgentFlowView } from '../types.ts';
3
3
  import type { Config } from './_shared.ts';
4
+ import type { ActiveWorkflowSelection, SessionHint } from './workflow-selection.ts';
4
5
  /** The agent-flow ledger file name under `{HARNESS_DIR}`. */
5
6
  export declare const AGENT_FLOW_FILE = "agent-flow.jsonl";
6
7
  /** Truncation bound: the ledger keeps only the most recent events. */
@@ -26,8 +27,11 @@ export declare const AGENT_FLOW_SIZE_GATE_BYTES: number;
26
27
  * unbounded. ID-sized fields (`runId`, `childId`) SKIP the row when
27
28
  * oversized — truncating them could forge collisions; display fields
28
29
  * (`name`, `label`, `phase`) are truncated deterministically with a suffix
29
- * marker. `2^31` bounds every sequence number (envelope + member) — the
30
- * cursor-math safe range (see the consumer's durable watermark).
30
+ * marker. The settle identity fields are OPTIONAL: an invalid or oversized
31
+ * `childId` / `taskRef` omits only the FIELD — the real completion still
32
+ * records (see {@link optionalLedgerId}). `2^31` bounds every sequence number
33
+ * (envelope + member) — the cursor-math safe range (see the consumer's
34
+ * durable watermark).
31
35
  */
32
36
  export declare const WORKFLOW_LEDGER_MAX_ID_LENGTH = 512;
33
37
  /** Cap for label-sized display fields (`label`, `phase`). */
@@ -54,6 +58,20 @@ export declare const WORKFLOW_LEDGER_MAX_SEQ: number;
54
58
  * most one code point of slack. Pure — NEVER throws.
55
59
  */
56
60
  export declare function truncateLedgerField(value: string, cap: number): string;
61
+ /**
62
+ * Canonicalize one Assignment `Execute as` value for the ledger's `role`
63
+ * field: `trim()`, then strip ONE leading `@`. The `role` column is what
64
+ * every consumer groups by, so `@explore` and `explore` must be the SAME
65
+ * role — normalization happens ONCE, at the write boundary (the dispatch row
66
+ * and its pairing ref, plus the direct `recordSettle` /
67
+ * `recordSubagentLink` entry points, whose callers may hand in a raw
68
+ * Assignment value). NO case folding: role ids are host-defined strings, and
69
+ * folding could merge ids a host intends to keep distinct. No interior
70
+ * rewrite either — only the edges are trimmed. A missing role stays `''`,
71
+ * and rows already on disk are never rewritten or renormalized. Pure —
72
+ * NEVER throws.
73
+ */
74
+ export declare function normalizeRoleId(value: string): string;
57
75
  /** Logger label for the agent-flow ledger (dsh logger naming: `<scope>/<subject>`). */
58
76
  export declare const AGENT_FLOW_LOGGER = "mstar/agent-flow";
59
77
  /**
@@ -71,12 +89,12 @@ export declare const SETTLE_SEAM = "tools/post-execute";
71
89
  * surface, so the constant was renamed to the accurate `PAIRING` name. The
72
90
  * message states the VERIFIED pairing facts: the seam is emitted by the
73
91
  * registry; foreground dispatch calls settle via it, background subagents
74
- * settle via `ctx.jobs.onJobDone` pairing; only unpaired payloads stay
75
- * dispatch-only (never fabricated settlement). Logged ONCE per logger binding
76
- * (≈ once per apply — the same module-level flag) when the pairing
77
- * listener is registered.
92
+ * settle via the `ctx.inject(['jobs'])` → `jobs.onJobDone` pairing; only
93
+ * unpaired payloads stay dispatch-only (never fabricated settlement). Logged
94
+ * ONCE per logger binding (≈ once per apply — the same module-level flag)
95
+ * when the pairing listener is registered.
78
96
  */
79
- export declare const SETTLE_SEAM_PAIRING_NOTE = "settle seam \"tools/post-execute\" IS part of the verified dsh-tools registry surface (runPostExecute dispatches it for every tool call) \u2014 foreground dispatch calls settle here, background subagents settle via ctx.jobs.onJobDone pairing; only UNPAIRED payloads (non-dispatch tools, calls outside the apply-scoped pairing window) stay dispatch-only \u2014 never a fabricated settle";
97
+ export declare const SETTLE_SEAM_PAIRING_NOTE = "settle seam \"tools/post-execute\" IS part of the verified dsh-tools registry surface (runPostExecute dispatches it for every tool call) \u2014 foreground dispatch calls settle here, background subagents settle via ctx.inject(['jobs']) \u2192 jobs.onJobDone pairing; only UNPAIRED payloads (non-dispatch tools, calls outside the apply-scoped pairing window) stay dispatch-only \u2014 never a fabricated settle";
80
98
  /** Dispatch verdict vocabulary (spec §2.1.3). */
81
99
  export type DispatchVerdict = 'ok' | 'advisory' | 'denied';
82
100
  /** Settle outcome vocabulary (spec §2.1.3). */
@@ -177,13 +195,55 @@ export type AgentFlowEvent = {
177
195
  * `role` is the Assignment `Execute as` ('' when missing), `planId` /
178
196
  * `taskId` the plan + `Task N` tags. Written for every paired settle;
179
197
  * ABSENT on unpaired (legacy) settles — the client pairs on identity
180
- * presence. The registry background-task id is deliberately NOT
181
- * written here (`taskRef` is reserved as the distinct field name if a
182
- * future audit needs it — it never collides with `taskId`).
198
+ * presence (`paired` in the view never depends on `childId`).
183
199
  */
184
200
  role?: string;
185
201
  planId?: string;
186
202
  taskId?: string;
203
+ /**
204
+ * The settled child session's stable id, when a seam actually supplied
205
+ * one: the returned foreground `runId`, or a background job's child id
206
+ * once the catalog join copied it onto the dispatch ref. OMITTED
207
+ * otherwise — never fabricated, never a registry job id.
208
+ */
209
+ childId?: string;
210
+ /**
211
+ * The registry background-job id a background settle pairs on
212
+ * (`jobs.onJobDone` → `recordJobSettle`). A jobs-registry key
213
+ * (`<kind>-N`), NEVER a child session id and never the Assignment
214
+ * `Task N` tag (`taskId`).
215
+ */
216
+ taskRef?: string;
217
+ } | {
218
+ v: 1;
219
+ ts: number;
220
+ kind: 'subagent-link';
221
+ /** The dispatching session's stable id (the paired dispatch ref's agent; absent when it carried none). */
222
+ agent?: string;
223
+ /**
224
+ * The catalog `childId` — the child session the dispatch actually
225
+ * started. REQUIRED: a row whose identity is missing, empty, or
226
+ * oversized is skipped (never truncated, never fabricated).
227
+ */
228
+ childId: string;
229
+ /**
230
+ * The delegation `description` the join correlated on, DISPLAY-
231
+ * normalized and capped at this write boundary (the matcher always
232
+ * compares the RAW label, never this value).
233
+ */
234
+ label: string;
235
+ /** The paired dispatch's Assignment `Execute as` — written even when `''`. */
236
+ role: string;
237
+ /** The paired dispatch's `planIdOf(header)`. */
238
+ planId?: string;
239
+ /** The paired dispatch's Assignment `Task N` tag. */
240
+ taskId?: string;
241
+ /**
242
+ * The registry background-job id, on a link whose dispatch started a
243
+ * background job (`<kind>-N`). A continuable link omits it — that path
244
+ * starts no registry job. Never a child session id.
245
+ */
246
+ taskRef?: string;
187
247
  } | {
188
248
  v: 1;
189
249
  ts: number;
@@ -220,12 +280,71 @@ export interface AgentFlowDispatchRef {
220
280
  workflowDir: string;
221
281
  /** The dispatching session's stable id ('' when the exec carried none). */
222
282
  agent?: string;
223
- /** Assignment `Execute as` ('' when missing — the dispatch event's grammar). */
283
+ /** Assignment `Execute as`, canonicalized by `normalizeRoleId` ('' when missing — the dispatch event's grammar). */
224
284
  role: string;
225
285
  /** `planIdOf(header)` — the dispatch event's grammar. */
226
286
  planId?: string;
227
287
  /** `taskIdOf(prompt)` — the Assignment `Task N` tag, NOT a registry task id. */
228
288
  taskId?: string;
289
+ /**
290
+ * The child session's stable id, once an upstream seam actually supplied
291
+ * one: the catalog join copies the observed catalog `childId` onto this
292
+ * ref (the same object survives into `dispatchByJobId`, so a background
293
+ * settle then carries it). Omitted until known — never a synthetic id and
294
+ * never a registry job id.
295
+ */
296
+ childId?: string;
297
+ /**
298
+ * The registry job id of the background call this dispatch started
299
+ * (observed at `tools/post-execute`, paired by `recordJobSettle`). A
300
+ * jobs-registry key (`<kind>-N`) — NEVER a child session id and never the
301
+ * `Task N` tag.
302
+ */
303
+ taskRef?: string;
304
+ /**
305
+ * The admitted call-window catalog CANDIDATE of this dispatch: the live
306
+ * parent Session it runs on plus the RAW delegation label that names its
307
+ * slot. Present only while a reservation is live — `recordDispatch`
308
+ * installs it, and the post-execute branch either advances it to eligible
309
+ * or retires it (both release this field). Its presence is what lets the
310
+ * post-execute path find the join without re-deriving the session.
311
+ */
312
+ catalog?: {
313
+ session: AgentFlowSessionView;
314
+ label: string;
315
+ };
316
+ }
317
+ /**
318
+ * The structural view of one live dsh `Session` the call-window join needs:
319
+ * the VERIFIED upstream surface (`id` / `seq` / `eventAt`). A real Session
320
+ * exposes no `events` member — that read is why the join uses `eventAt(seq)`
321
+ * over a bounded window instead of copying a log. Structural by design (no
322
+ * session package dependency); `seq` is read LIVE at every use, so the
323
+ * captured view is also the catch-up bound source while `fromSeq` (stored on
324
+ * the join) keeps the capture moment.
325
+ */
326
+ export interface AgentFlowSessionView {
327
+ readonly id: string;
328
+ readonly seq: number;
329
+ eventAt(seq: number): unknown;
330
+ }
331
+ /**
332
+ * One pending call-window catalog candidate: the recorded dispatch ref and
333
+ * the `fromSeq` window start. `result` marks ELIGIBILITY — an inert
334
+ * candidate (no `result` yet, awaiting its post-execute branch) can never be
335
+ * matched by a catalog, and the slot's `null` tombstone is a
336
+ * consumed/rejected candidate, never a second one.
337
+ */
338
+ export interface AgentFlowCatalogJoin {
339
+ ref: AgentFlowDispatchRef;
340
+ fromSeq: number;
341
+ result?: {
342
+ kind: 'background';
343
+ jobId: string;
344
+ } | {
345
+ kind: 'continuable';
346
+ subagentId: string;
347
+ };
229
348
  }
230
349
  /**
231
350
  * The apply-scoped pairing store (
@@ -233,8 +352,8 @@ export interface AgentFlowDispatchRef {
233
352
  * cache; an HMR restart resets it, and completions outside the window stay
234
353
  * unpaired → no settle, the documented honest degrade). Maps are keyed by
235
354
  * the TWO verified pairing keys: the tool-call `callId` (pre → post-execute)
236
- * and the registry background-task id (post-execute background shape →
237
- * `onJobDone` terminal).
355
+ * and the registry background-job id (post-execute background shape →
356
+ * `jobs.onJobDone` terminal).
238
357
  */
239
358
  export interface AgentFlowPairing {
240
359
  /**
@@ -248,8 +367,19 @@ export interface AgentFlowPairing {
248
367
  * by the post-execute branch — the map holds only in-flight calls.
249
368
  */
250
369
  dispatchByCallId: Map<string, AgentFlowDispatchRef>;
251
- /** Registry background-job id (`JobSnapshot.id`) → the dispatch that started it (populated by the post-execute background branch; consumed by `recordTaskSettle`). */
252
- dispatchByTaskId: Map<string, AgentFlowDispatchRef>;
370
+ /** Registry background-job id (`JobSnapshot.id`) → the dispatch that started it (populated by the post-execute background branch; consumed by `recordJobSettle`). */
371
+ dispatchByJobId: Map<string, AgentFlowDispatchRef>;
372
+ /**
373
+ * The call-window catalog join's slots: the EXACT parent Session object →
374
+ * (RAW delegation label → candidate). A `null` slot is a consumed or
375
+ * rejected tombstone, never a second candidate — reuse is what would let a
376
+ * delayed duplicate label or a pre-existing catalog acquire a new owner.
377
+ * Keying on the session OBJECT (not its id) is what makes the namespace
378
+ * session-incarnation safe and delimiter-free; per Session the map holds at
379
+ * most `AGENT_FLOW_MAX_EVENTS` slots (new keys are refused at capacity —
380
+ * existing pending entries and tombstones are retained).
381
+ */
382
+ catalogBySession: WeakMap<object, Map<string, AgentFlowCatalogJoin | null>>;
253
383
  }
254
384
  /** Module-scoped log sink (bound to `mstar/agent-flow` by the entry at apply). */
255
385
  type AgentFlowLogSink = (level: 'info' | 'warn' | 'error', message: string) => void;
@@ -274,8 +404,8 @@ type AgentFlowInvalidator = (harnessDir: string) => void;
274
404
  export declare function setAgentFlowInvalidator(invalidate: AgentFlowInvalidator | undefined): AgentFlowInvalidator | undefined;
275
405
  /**
276
406
  * Bind the module's log sink (called once at apply; tests may rebind to
277
- * capture ledger logs). Rebinding RESETS the once-per-apply settle trace
278
- * flag — each binding is a fresh "apply" (production binds once; tests bind
407
+ * capture ledger logs). Rebinding RESETS the once-per-apply trace latches
408
+ * — each binding is a fresh "apply" (production binds once; tests bind
279
409
  * per case for deterministic capture).
280
410
  * @param sink - the sink (entry binds `ctx.logger('mstar/agent-flow')`);
281
411
  * `undefined` clears the binding (restores the pre-bind no-op state).
@@ -295,24 +425,50 @@ export declare function setAgentFlowLogger(sink: AgentFlowLogSink | undefined):
295
425
  */
296
426
  export declare function taskIdOf(prompt: string): string | undefined;
297
427
  /**
298
- * Resolve the agent-flow WRITE target dir for one harness dir: the ACTIVE
299
- * workflow's dir (root v2 `workflows[]` first entry — the shared active-set
300
- * resolver; the terminal-mtime fallback is catalog-read-only and MUST NOT
301
- * enter the writer's module graph). The active set is defined by root
302
- * `workflows[]` MEMBERSHIP — non-terminal lifecycles (`running` AND
303
- * `paused`; terminal lifecycles are removed at terminal) — so a paused
304
- * lifecycle stays a valid append target (explicit decision, plan
428
+ * The write-path selection for one session: the resolver verdict plus the
429
+ * absolute dir that verdict resolves to. The ledger needs BOTH (it
430
+ * classifies the unbound skip to keep the no-backfill floor honest), and
431
+ * both must come from ONE registry read — {@link resolveAgentFlowWriteDir}
432
+ * is the dir-only facade over this.
433
+ */
434
+ export interface AgentFlowWriteTarget {
435
+ /** The absolute workflow dir (`<harnessDir>/workflows/<id>`), or `null` when this session may not write. */
436
+ dir: string | null;
437
+ /** The verdict `dir` was derived from. */
438
+ selection: ActiveWorkflowSelection;
439
+ }
440
+ /**
441
+ * Resolve the agent-flow WRITE target for one harness dir: the ACTIVE
442
+ * workflow the CARRYING SESSION is bound to (root v2 `workflows[]` — the
443
+ * shared active-set resolver; the terminal-mtime fallback is
444
+ * catalog-read-only and MUST NOT enter the writer's module graph). The active
445
+ * set is defined by root `workflows[]` MEMBERSHIP — non-terminal lifecycles
446
+ * (`running` AND `paused`; terminal lifecycles are removed at terminal) — so
447
+ * a paused lifecycle stays a valid append target (explicit decision, plan
305
448
  * the workflow viz plan).
306
449
  *
307
- * No active entry → `null`: the record is SKIPPED with a one-time warn
450
+ * D4 binding: `hint` carries the session's lease/cwd/pick evidence, so two
451
+ * sessions writing in ONE harness reach two different workflow dirs. An
452
+ * omitted hint is the exec-less case — the automatic rungs miss and only a
453
+ * unique active lifecycle resolves.
454
+ *
455
+ * No bound entry → `dir: null`: the record is SKIPPED with a one-time warn
308
456
  * (per sink binding) — never a silent write into the root v1 file, never a
309
- * write into a terminal snapshot dir (compass v3.0.0 § Catalog selection
310
- * rule — the writer appends only to an active lifecycle).
457
+ * write into a terminal snapshot dir, never another session's lifecycle
458
+ * (compass v3.0.0 § Catalog selection rule — the writer appends only to an
459
+ * active lifecycle).
311
460
  * @param harnessDir - the resolved `{HARNESS_DIR}`.
312
- * @returns the absolute workflow dir (`<harnessDir>/workflows/<id>`), or
313
- * `null` when no active lifecycle resolves.
461
+ * @param hint - the carrying session's structural identity + durable pick.
314
462
  */
315
- export declare function resolveAgentFlowWriteDir(harnessDir: string): string | null;
463
+ export declare function resolveAgentFlowWriteTarget(harnessDir: string, hint?: SessionHint): AgentFlowWriteTarget;
464
+ /**
465
+ * The dir-only facade over {@link resolveAgentFlowWriteTarget} — the pinned
466
+ * writer contract every record path uses.
467
+ * @param harnessDir - the resolved `{HARNESS_DIR}`.
468
+ * @param hint - the carrying session's structural identity + durable pick.
469
+ * @returns the absolute workflow dir, or `null` when nothing may be written.
470
+ */
471
+ export declare function resolveAgentFlowWriteDir(harnessDir: string, hint?: SessionHint): string | null;
316
472
  /**
317
473
  * Lock-directory name for the per-workflow write lock :
318
474
  * guards the ledger append + size-gated truncating read-modify-write AND
@@ -378,11 +534,16 @@ export declare function withWorkflowDirLock<T>(workflowDir: string, fn: () => T,
378
534
  * only after the ledger append SUCCEEDED — a failed record never pairs to a
379
535
  * phantom dispatch. An exec-less record (host-hook path) has no callId → no
380
536
  * pairing. The pairing sub-path has its OWN catch scope: a `Map.set` throw must not log "record
381
- * failed" after the dispatch was already appended.
537
+ * failed" after the dispatch was already appended. On that same successful
538
+ * pairing the dispatch's call-window catalog candidate is RESERVED (the
539
+ * first RAW-label slot under the live parent Session, when the exec carries
540
+ * one) — the child-identity join's step 1; admission is all-or-nothing and
541
+ * never affects the dispatch, the pairing, or a later settle.
382
542
  * @param input - the harness dir; the dispatching exec's agent id; the
383
543
  * Assignment text; the gate's violations; the hard-enforcement resolution;
384
544
  * the apply-scoped pairing store (the adapter passes its own; direct callers
385
- * may omit it).
545
+ * may omit it); the carrying session's `hint` (the adapter derives it — this
546
+ * module never reads the picker store).
386
547
  */
387
548
  export declare function recordDispatch(input: {
388
549
  harnessDir: string;
@@ -391,6 +552,8 @@ export declare function recordDispatch(input: {
391
552
  violations: readonly unknown[];
392
553
  hard: boolean;
393
554
  pairing?: AgentFlowPairing;
555
+ /** The carrying session's selection hint — decides WHICH active lifecycle the row lands in. */
556
+ hint?: SessionHint;
394
557
  }): void;
395
558
  /**
396
559
  * Record one settle event (spec §2.1.3). Fully try/catch-contained; a failing
@@ -405,7 +568,15 @@ export declare function recordDispatch(input: {
405
568
  * outcome + optional duration + the PAIRED dispatch's identity
406
569
  * (`role`/`planId`/`taskId` — same field names + semantics as the dispatch
407
570
  * event; written for every paired settle, so the client can exactly pair
408
- * the settle back to its dispatch).
571
+ * the settle back to its dispatch — `role` is canonicalized by
572
+ * `normalizeRoleId`, so a direct caller's raw `@role` lands in the same
573
+ * grammar as the dispatch row) + the OPTIONAL child identity
574
+ * (`childId` — the settled child session; `taskRef` — a background settle's
575
+ * registry job id). A missing, empty, or oversized optional id is OMITTED
576
+ * from the row (never truncated, never re-keyed) and the completion still
577
+ * records. The carrying session's `hint` is the no-dir fallback (a PAIRED
578
+ * settle keeps its pinned `workflowDir` — a later pick must never split a
579
+ * dispatch from its settle).
409
580
  */
410
581
  export declare function recordSettle(input: {
411
582
  harnessDir: string;
@@ -416,6 +587,10 @@ export declare function recordSettle(input: {
416
587
  role?: string;
417
588
  planId?: string;
418
589
  taskId?: string;
590
+ /** The carrying session's selection hint — consulted ONLY when no `workflowDir` is pinned. */
591
+ hint?: SessionHint;
592
+ childId?: string;
593
+ taskRef?: string;
419
594
  }): void;
420
595
  /**
421
596
  * Record one workflow ledger event (the JSONL schema below
@@ -436,10 +611,11 @@ export declare function recordSettle(input: {
436
611
  * `harnessDir`. No active lifecycle → `false` (skipped with a one-time warn
437
612
  * — never a root v1 write, never a terminal snapshot write).
438
613
  * @param input - harness dir + optional pre-resolved active workflow dir +
439
- * the fully-shaped v1 workflow event.
614
+ * the fully-shaped v1 workflow event + the carrying session's `hint` for the
615
+ * no-dir fallback (a pinned dir always wins).
440
616
  * @returns `true` when the row was appended (the caller may durably advance
441
617
  * its watermark); `false` on a contained append failure OR when no active
442
- * lifecycle resolves — the caller must leave the cursor behind so the row
618
+ * lifecycle is bound — the caller must leave the cursor behind so the row
443
619
  * is re-attempted at the next scan (advance-then-record made a
444
620
  * failed append permanent loss).
445
621
  */
@@ -447,6 +623,8 @@ export declare function recordWorkflowEvent(input: {
447
623
  harnessDir: string;
448
624
  workflowDir?: string;
449
625
  event: AgentFlowWorkflowEvent;
626
+ /** The carrying session's selection hint — consulted ONLY when no `workflowDir` is pinned. */
627
+ hint?: SessionHint;
450
628
  }): boolean;
451
629
  /**
452
630
  * Input for {@link recordWorkflowVerdict} — one gated workflow/ralph call's
@@ -470,6 +648,13 @@ export interface WorkflowVerdictInput {
470
648
  verdict: WorkflowVerdict;
471
649
  /** The policy violation code (advisory/ask/denied rows only). */
472
650
  code?: string;
651
+ /**
652
+ * The carrying session's selection hint (the adapter derives it from the
653
+ * same `exec` it passes here) — decides WHICH active lifecycle the verdict
654
+ * row lands in. Absent (exec-less / unreadable binding store) → the row is
655
+ * skipped rather than attributed to an arbitrary lifecycle.
656
+ */
657
+ hint?: SessionHint;
473
658
  }
474
659
  /**
475
660
  * Record one workflow/ralph gate verdict row . Fully
@@ -532,18 +717,37 @@ export declare function readAgentFlow(workflowDir: string, limit?: number): Agen
532
717
  * - `result.isError === true` OR an `error` payload present → settle `error`
533
718
  * immediately (fabrication guard — the dispatch
534
719
  * call failed; a result carrying `error` without `isError` never settles ok);
535
- * - successful `result.value` shape `{ kind: 'background', taskId }` with a
536
- * valid taskId → store `taskId → dispatchRef` (the real settle arrives via
537
- * `ctx.jobs.onJobDone`); `{ kind: 'background' }` WITHOUT a valid taskId
538
- * → nothing mappable (no settle);
720
+ * - successful `result.value` shape `{ kind: 'background', jobId }` with a
721
+ * valid jobId (the registry id, `<kind>-N` — never a child session id) →
722
+ * store `jobId → dispatchRef` and the bounded job id as the ref's `taskRef`
723
+ * (so the eventual `jobs.onJobDone` settle carries it); `{ kind: 'background' }`
724
+ * WITHOUT a valid jobId → nothing mappable (no settle);
539
725
  * - `{ kind: 'continuable', subagentId }` → no terminal signal this round →
540
- * no settle (documented limit — the child owns its turns);
726
+ * no settle (documented limit — the child owns its turns), and NOTHING is
727
+ * copied from its value onto any row;
541
728
  * - any other successful value (foreground `{ kind: 'foreground', … }`
542
729
  * included) → settle `ok` (the call completed synchronously).
730
+ * Child identity: a returned foreground `runId` becomes the settle's
731
+ * `childId`, extracted from the value INDEPENDENTLY of the outcome branch —
732
+ * a failed call whose value still carries a foreground `runId` keeps that
733
+ * identity on its `error` settle (identity never turns an error into `ok`).
543
734
  * The consumed `dispatchByCallId` entry is DELETED after the branch resolves
544
735
  * the call (map pruning — each callId
545
736
  * pairs exactly once; the map holds only in-flight calls).
546
737
  *
738
+ * Catalog candidate (steps 2–3 of the child-identity join): the SAME branch
739
+ * decides the dispatch's reserved call-window catalog candidate — a valid
740
+ * background result makes it eligible expecting the job's `one-shot` catalog
741
+ * (after the job pairing + `taskRef` are in place), a valid continuable
742
+ * result makes it eligible for exactly the returned `subagentId`, and every
743
+ * other outcome (other success / foreground, tool error, missing result
744
+ * payload, malformed result, invalid background job id) retires it to its
745
+ * tombstone. Eligibility then runs the catch-up walk over
746
+ * `[fromSeq, session.seq)` through the same matcher the live observer uses,
747
+ * so a catalog appended before this seam ran is still recovered. Both
748
+ * candidate helpers are self-contained: a throwing join degrades to a log
749
+ * line and NEVER costs the settle/awarded identity its row.
750
+ *
547
751
  * The waterfall MUST be delegated via `next()` on every path — returning
548
752
  * without calling `next` bails the chain and breaks every tool call. A
549
753
  * throwing record never propagates.
@@ -554,21 +758,24 @@ export declare function readAgentFlow(workflowDir: string, limit?: number): Agen
554
758
  * @param ctx - registrant context (fiber disposal unwinds the listener).
555
759
  * @param config - the plugin Config (dispatch-tool matching).
556
760
  * @param pairing - the apply-scoped pairing store (dispatchByCallId read,
557
- * dispatchByTaskId written by the background branch).
761
+ * dispatchByJobId written by the background branch, catalogBySession
762
+ * advanced/retired here).
558
763
  */
559
764
  export declare function registerSettleListener(ctx: Context, config: Config, pairing: AgentFlowPairing): void;
560
765
  /**
561
766
  * The structural read of the dsh-jobs terminal snapshot the pairing consumes
562
- * (the settle-pairing upgrade). The `ctx.jobs.onJobDone`
563
- * contract was verified against the upstream `@deepseek-ai/dsh-jobs`
564
- * `types.ts`: `JobDoneListener = (snapshot, owner) => …`, terminal
565
- * `snapshot.status` ∈ `completed | killed | failed`, `startedAt`/`finishedAt`
566
- * are epoch ms (`finishedAt` absent while running). Structural (no runtime or
767
+ * (the settle-pairing upgrade). The `ctx.inject(['jobs'])` →
768
+ * `jobs.onJobDone` contract was verified against the upstream
769
+ * `@deepseek-ai/dsh-jobs` `types.ts`: `JobDoneListener = (snapshot, owner) =>
770
+ * …`, terminal `snapshot.status` ∈ `completed | killed | failed`,
771
+ * `startedAt`/`finishedAt` are epoch ms (`finishedAt` absent while running).
772
+ * `id` stays the registry-issued JOB id (`<kind>-N`, e.g. `subagent-1`) — it
773
+ * is a jobs-registry key, NOT a child session id. Structural (no runtime or
567
774
  * type import of the optional dsh-jobs seam — the plugin treats it as an
568
775
  * optional service, wired via `ctx.inject(['jobs'])`).
569
776
  */
570
- export interface TaskDoneSnapshot {
571
- /** The registry-issued task id (`<kind>-N`, e.g. `subagent-1`). */
777
+ export interface JobDoneSnapshot {
778
+ /** The registry-issued job id (`<kind>-N`, e.g. `subagent-1`) — never a child session id. */
572
779
  id: string;
573
780
  /** Terminal lifecycle status: `completed | killed | failed`. */
574
781
  status: string;
@@ -578,20 +785,65 @@ export interface TaskDoneSnapshot {
578
785
  finishedAt?: number;
579
786
  }
580
787
  /**
581
- * Record the settle for one background-task terminal (plan
582
- * the `ctx.jobs.onJobDone` path):
583
- * the snapshot's task id must hit the pairing store's `dispatchByTaskId`
584
- * (populated by the post-execute background branch) — a miss records NOTHING
585
- * (honest degrade, never fabricated). Outcome mapping: `completed → ok` /
586
- * `killed → denied` / `failed → error`; `durationMs = finishedAt − startedAt`
587
- * when both are present. After a SUCCESSFUL settle the consumed
588
- * `dispatchByTaskId` entry is deleted (map pruning — the map holds only
589
- * in-flight tasks; a contract-violating
788
+ * Record the settle for one background-job terminal (plan
789
+ * the `ctx.inject(['jobs'])` → `jobs.onJobDone` path):
790
+ * the snapshot's registry job id must hit the pairing store's
791
+ * `dispatchByJobId` (populated by the post-execute background branch) — a
792
+ * miss records NOTHING (honest degrade, never fabricated). Outcome mapping:
793
+ * `completed → ok` / `killed → denied` / `failed → error`;
794
+ * `durationMs = finishedAt − startedAt` when both are present. After a
795
+ * SUCCESSFUL settle the consumed `dispatchByJobId` entry is deleted (map
796
+ * pruning — the map holds only in-flight jobs; a contract-violating
590
797
  * non-terminal snapshot records nothing and KEEPS the entry so a later real
591
- * terminal can still settle). Fully contained — never throws into the task
798
+ * terminal can still settle). Fully contained — never throws into the job
592
799
  * registry's listener notification.
593
- * @param snapshot - the terminal task snapshot (structural read).
800
+ * @param snapshot - the terminal job snapshot (structural read).
594
801
  * @param pairing - the apply-scoped pairing store.
595
802
  */
596
- export declare function recordTaskSettle(snapshot: TaskDoneSnapshot, pairing: AgentFlowPairing): void;
803
+ export declare function recordJobSettle(snapshot: JobDoneSnapshot, pairing: AgentFlowPairing): void;
804
+ /**
805
+ * Record one NONTERMINAL `subagent-link` row — the call-window join's output:
806
+ * the child session a dispatch actually started, correlated back to the
807
+ * dispatch identity mstar recorded. This is an IDENTITY record, not a
808
+ * completion: the row carries NO `outcome`, NO `verdict` and NO `paired`
809
+ * marker (the child is still running this round), and `ts` is the link
810
+ * OBSERVATION time — never a fabricated child-creation time
811
+ * (`childCreatedAt` is validated upstream, it is not another ledger clock).
812
+ * The row is written ONLY from a candidate that actually matched, and only
813
+ * from the ref of a dispatch that was really recorded: a link is never a
814
+ * guess or a synthesis.
815
+ *
816
+ * Attribution follows the dispatch, not the clock: the row lands in
817
+ * `ref.workflowDir` (the dir the dispatch was appended to), never in today's
818
+ * active workflow dir, and the ref's `harnessDir` is the invalidation key.
819
+ * Fully try/catch-contained — a failing link write logs only and never
820
+ * escapes into the session append (or the job notification) that triggered
821
+ * it.
822
+ * @param input - the retained dispatch ref, the observed catalog `childId`,
823
+ * and the RAW delegation label the join matched on.
824
+ */
825
+ export declare function recordSubagentLink(input: {
826
+ ref: AgentFlowDispatchRef;
827
+ childId: string;
828
+ label: string;
829
+ }): void;
830
+ /**
831
+ * The LIVE half of the call-window join (step 4): ONE root-context
832
+ * `session/event` observer, registered by the entry at apply — before
833
+ * execution can start — that reads only an ELIGIBLE slot of the EXACT
834
+ * carrying Session. The root (untagged) registration is admitted globally by
835
+ * the session store's scope carrier, so a catalog appended by any session
836
+ * reaches it; slots for every other session are simply absent from the
837
+ * WeakMap, and an inert (pending) or tombstoned slot is skipped. Its window
838
+ * bound is the live `session.seq`, not the catch-up endpoint, so a catalog
839
+ * appended after eligibility is still joined.
840
+ *
841
+ * No cold scan and no `session/created` backfill: a catalog written before
842
+ * apply has no surviving dispatch ref, so it can never label a future
843
+ * same-label dispatch. Contained — a throwing observation never escapes the
844
+ * session append.
845
+ * @param ctx - registrant context (fiber disposal unwinds the observer).
846
+ * @param pairing - the apply-scoped pairing store (the slots).
847
+ */
848
+ export declare function registerSubagentCatalogListener(ctx: Context, pairing: AgentFlowPairing): void;
597
849
  export {};