@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
@@ -20,8 +20,9 @@
20
20
  * HARD constraint — headless boot safety: this module is reachable from
21
21
  * `apply`, and it must never make the host row statically inject `connection`
22
22
  * or `webServer`. Everything web-only is inside the optional inject child; the
23
- * endpoint's own reads (`ctx.get('sessions')`, `ctx.get('sessionController')`)
24
- * are structural and degrade when the service is absent.
23
+ * endpoint's own reads (`ctx.get('sessions')`, `ctx.get('agents')`,
24
+ * `ctx.get('sessionController')`) are structural and degrade when the service
25
+ * is absent.
25
26
  *
26
27
  * VALIDATION CHAIN — the endpoint serves ONE session's snapshot and answers
27
28
  * otherwise. In order:
@@ -37,14 +38,33 @@
37
38
  * another session's data, never a silently-close match, never a path built from
38
39
  * unvalidated input.
39
40
  *
41
+ * The response additionally carries the session's CURRENT control-state
42
+ * selection (`binding.selection`, D4) — server-resolved from the durable
43
+ * picker record + the verified live Agent — so the panel can show the chosen
44
+ * active id while `payload` / `at` / `turn` keep naming the LAST MODEL
45
+ * EMISSION. The `selectWorkflow` UI control (D4) is the write half: a LIVE
46
+ * session, an exact cwd, an ACTIVE workflow id, automatic-binding compatibility
47
+ * and a bounded session sequence are mandatory before the durable pick is
48
+ * committed.
49
+ *
40
50
  * @module @mstar-harness/dsh/engine-status-endpoint
41
51
  */
42
52
  import type { Context } from '@deepseek-ai/cordis';
43
53
  import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry';
44
54
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
45
- import type { HarnessResolver } from './gates/_shared.ts';
46
- import { MSTAR_ENGINE_STATUS_METHOD, MSTAR_ENGINE_STATUS_NAMESPACE } from './engine-status-wire.ts';
47
- export { MSTAR_ENGINE_STATUS_METHOD, MSTAR_ENGINE_STATUS_NAMESPACE };
55
+ import type { WorkflowSelectionView } from './types.ts';
56
+ import { type HarnessResolver } from './gates/_shared.ts';
57
+ import { MSTAR_ENGINE_STATUS_METHOD, MSTAR_ENGINE_STATUS_NAMESPACE, MSTAR_SELECT_WORKFLOW_METHOD } from './engine-status-wire.ts';
58
+ export { MSTAR_ENGINE_STATUS_METHOD, MSTAR_ENGINE_STATUS_NAMESPACE, MSTAR_SELECT_WORKFLOW_METHOD };
59
+ /**
60
+ * The session's CURRENT control state (D4): the selection the server resolves
61
+ * for the session it just validated — the durable picker record folded with
62
+ * the verified live Agent's lease holder. Deliberately SEPARATE from
63
+ * `payload` / `at` / `turn`, which keep naming the last model emission.
64
+ */
65
+ export interface MstarEngineStatusBinding {
66
+ readonly selection: WorkflowSelectionView;
67
+ }
48
68
  /** The served snapshot (the stored emission, echoed back with its identity). */
49
69
  export interface MstarEngineStatusOk {
50
70
  readonly status: 'ok';
@@ -56,6 +76,8 @@ export interface MstarEngineStatusOk {
56
76
  readonly turn: number;
57
77
  /** The exact catalog payload emitted to that session's model. */
58
78
  readonly payload: Record<string, unknown>;
79
+ /** Current control-state selection for this session (never the last emission's). */
80
+ readonly binding?: MstarEngineStatusBinding;
59
81
  }
60
82
  /**
61
83
  * The explicit "no answer" result. `reason` is machine-readable and always
@@ -67,12 +89,27 @@ export interface MstarEngineStatusUnavailableResult {
67
89
  }
68
90
  /** The endpoint's wire result. */
69
91
  export type MstarEngineStatusResult = MstarEngineStatusOk | MstarEngineStatusUnavailableResult;
92
+ /** The picker commit result: the durable preference was acknowledged. */
93
+ export interface MstarSelectWorkflowOk {
94
+ readonly status: 'selected';
95
+ readonly sessionId: string;
96
+ readonly workflowId: string;
97
+ }
98
+ /** The `selectWorkflow` UI control's wire result. */
99
+ export type MstarSelectWorkflowResult = MstarSelectWorkflowOk | MstarEngineStatusUnavailableResult;
70
100
  /** Options the endpoint needs from the plugin's apply scope. */
71
101
  export interface MstarEngineStatusEndpointOptions {
72
102
  /** The per-workspace `{HARNESS_DIR}` resolver (the same one the gates use). */
73
103
  readonly resolver: HarnessResolver;
74
104
  /** The boot-resolved config root when an explicit `harnessDir` is configured. */
75
105
  readonly bootHarnessDir: string | null;
106
+ /**
107
+ * A picker commit landed for `(harnessDir, sessionId)`: drop that session's
108
+ * cached catalog payload + re-emission digest so the next pre-step rebuilds
109
+ * from the acknowledged binding. Optional (a host without the catalog wiring
110
+ * still serves the control method); a throwing hook is contained.
111
+ */
112
+ readonly invalidateSelection?: (harnessDir: string, sessionId: string) => void;
76
113
  }
77
114
  /**
78
115
  * Host-side `mstar/engineStatus` service: `/api/mstar/engineStatus` serves the
@@ -81,9 +118,10 @@ export interface MstarEngineStatusEndpointOptions {
81
118
  export declare class MstarEngineStatusGateway extends TypertRemoteService {
82
119
  private readonly resolver;
83
120
  private readonly bootHarnessDir;
121
+ private readonly invalidateSelection;
84
122
  /**
85
123
  * @param ctx - owning Cordis context.
86
- * @param options - the apply-scoped resolver + boot root.
124
+ * @param options - the apply-scoped resolver + boot root (+ pick invalidation hook).
87
125
  */
88
126
  constructor(ctx: Context, options: MstarEngineStatusEndpointOptions);
89
127
  /**
@@ -91,11 +129,48 @@ export declare class MstarEngineStatusGateway extends TypertRemoteService {
91
129
  * chain documented in the module header.
92
130
  * @param sessionId - the session whose snapshot is requested (client-asserted).
93
131
  * @param cwd - the session workspace the client believes it is reading (client-asserted).
94
- * @returns the stored emission, or the explicit unavailable state with a reason.
132
+ * @returns the stored emission plus the session's current control-state
133
+ * selection, or the explicit unavailable state with a reason.
95
134
  */
96
135
  engineStatus(sessionId: string, cwd: string): Promise<MstarEngineStatusResult>;
136
+ /**
137
+ * Commit one session's durable workflow selection (the panel's picker).
138
+ *
139
+ * Mandatory before the commit: the LIVE Session (a cold/persisted-only
140
+ * target answers `session-not-live`), an exact cwd match, a bounded integer
141
+ * session sequence, an ACTIVE workflow id from the freshly resolved
142
+ * registry, and no conflicting higher-priority automatic (lease/cwd)
143
+ * binding. The exclusion floor commits as `max(stored, seq)`. An identical
144
+ * already-stored active pick is acknowledged WITHOUT advancing the floor.
145
+ * No client-supplied directory, holder or sequence is accepted.
146
+ * @param sessionId - the session whose selection is committed (client-asserted).
147
+ * @param cwd - the session workspace the client asserts (must match the live session).
148
+ * @param workflowId - the chosen ACTIVE lifecycle id.
149
+ * @returns the acknowledgement, or the explicit unavailable state with a reason.
150
+ */
151
+ selectWorkflow(sessionId: string, cwd: string, workflowId: string): Promise<MstarSelectWorkflowResult>;
97
152
  /** The validation chain itself (contained by {@link engineStatus}). */
98
153
  private serve;
154
+ /** The picker commit chain itself (contained by {@link selectWorkflow}). */
155
+ private pick;
156
+ /**
157
+ * The session's CURRENT selection: the durable picker record folded with the
158
+ * session's structural identity and — for a LIVE session — the verified live
159
+ * Agent's opaque id as the lease holder. An unreadable binding record keeps
160
+ * the structural hint only (the pick is unknown, never invented), and the
161
+ * request's `sessionId` is NEVER substituted for the holder.
162
+ */
163
+ private currentSelection;
164
+ /**
165
+ * The opaque lease-holder `Agent.id` of the session's live Agent, when the
166
+ * public agents service resolves one whose own session header identifies the
167
+ * resolved Session (id AND cwd). Absent/mismatching Agent ⇒ undefined — the
168
+ * request's `sessionId` is never used as a holder shortcut. The verification
169
+ * itself is SHARED ({@link verifiedLeaseHolderOf}) with the other
170
+ * session-level hint builders (the workflow ledger), so a lease-bound
171
+ * session resolves the same lifecycle on every path.
172
+ */
173
+ private leaseHolderOf;
99
174
  /**
100
175
  * Resolve the authoritative cwd of one session: the LIVE session first
101
176
  * (`ctx.sessions.get(id)`), then the session controller's persisted
@@ -106,7 +181,9 @@ export declare class MstarEngineStatusGateway extends TypertRemoteService {
106
181
  private resolveSessionCwd;
107
182
  }
108
183
  /**
109
- * The generated-style invocation descriptor for `/api/mstar/engineStatus`.
184
+ * The generated-style invocation descriptors for the shared `/api` gateway:
185
+ * `/api/mstar/engineStatus` (the stored snapshot + current binding) and
186
+ * `/api/mstar/selectWorkflow` (the panel's durable pick).
110
187
  *
111
188
  * Registered EXPLICITLY (not through `@Remote` SRC markers): the host gateway
112
189
  * checks its own `ctx.typert.local` table FIRST, while SRC discovery reads a
@@ -16,10 +16,18 @@
16
16
  * ```json
17
17
  * {
18
18
  * "sv": 1,
19
- * "entries": { "<session id>": [ { "rv": 1, "cwd": "/proj", "at": "…", "turn": 3, "payload": { … } } ] }
19
+ * "entries": { "<session id>": [ { "rv": 1, "cwd": "/proj", "at": "…", "turn": 3, "payload": { … } } ] },
20
+ * "bindings": { "<session id>": { "cwd": "/proj", "selectedWorkflowId": "wf-2", "excludedBeforeSeq": 12 } }
20
21
  * }
21
22
  * ```
22
23
  *
24
+ * `entries` is the emission history (what the model saw); `bindings` is the
25
+ * session-scoped workflow-selection CONTROL state (D4: the picked active
26
+ * workflow + the durable no-backfill floor). The control map is additive and
27
+ * optional — a missing map means "no pick / floor 0" — and is never part of an
28
+ * emitted payload: each writer preserves the other's field verbatim, and the
29
+ * binding writer never touches `payload` / `at` / `turn`.
30
+ *
23
31
  * `sv` is the envelope schema version and `rv` the per-entry record version:
24
32
  * a reader that does not recognize EITHER must answer "unavailable" rather
25
33
  * than parse a shape it does not understand — and (write rule below) a writer
@@ -156,6 +164,52 @@ export interface EngineStatusSnapshotWriteInput {
156
164
  /** Global byte ceiling override (test seam; production uses the constant). */
157
165
  readonly maxBytes?: number;
158
166
  }
167
+ /**
168
+ * One session's durable workflow-selection CONTROL record (D4): the active
169
+ * workflow this session picked plus the durable no-backfill floor. It lives in
170
+ * the envelope's optional top-level `bindings` map — never inside an emitted
171
+ * payload — and is keyed by the durable `session.header.id`.
172
+ */
173
+ export interface WorkflowSessionBinding {
174
+ /** The authoritative session cwd the record was written for (exact-match anchor). */
175
+ readonly cwd: string;
176
+ /** The session's chosen ACTIVE workflow id; absent = no explicit pick yet. */
177
+ readonly selectedWorkflowId?: string;
178
+ /**
179
+ * Durable exclusion floor: ledger rows at or below this sequence were
180
+ * intentionally skipped while the session was unbound and must never be
181
+ * backfilled. An absent record means floor 0 (no exclusion history).
182
+ */
183
+ readonly excludedBeforeSeq: number;
184
+ }
185
+ /**
186
+ * Binding read outcome: the stored record (or its absence as `ok` WITHOUT a
187
+ * `binding`), or why attribution is unavailable — absent and corrupt are
188
+ * distinct answers, never a silent empty record.
189
+ */
190
+ export type WorkflowSessionBindingRead = {
191
+ readonly kind: 'ok';
192
+ readonly binding?: WorkflowSessionBinding;
193
+ } | {
194
+ readonly kind: 'unavailable';
195
+ readonly reason: string;
196
+ };
197
+ /** One binding read-modify-write request (a picker commit or a floor advance). */
198
+ export interface WorkflowSessionBindingUpdate {
199
+ /** The chosen active workflow id; omitted preserves the stored preference. */
200
+ readonly selectedWorkflowId?: string;
201
+ /** The exclusion floor to merge (by max) into the stored record. */
202
+ readonly excludedBeforeSeq: number;
203
+ /** Global byte ceiling override (test seam; production uses the constant). */
204
+ readonly maxBytes?: number;
205
+ }
206
+ /** Binding write outcome: written, or the degraded reason (never throws for I/O faults). */
207
+ export type WorkflowSessionBindingWrite = {
208
+ readonly kind: 'written';
209
+ } | {
210
+ readonly kind: 'degraded';
211
+ readonly reason: string;
212
+ };
159
213
  /** Absolute snapshot file path for one `{HARNESS_DIR}`. */
160
214
  export declare function engineStatusSnapshotPath(harnessDir: string): string;
161
215
  /** The explicit unavailable result (single constructor — one shape everywhere). */
@@ -188,3 +242,39 @@ export declare function readEngineStatusSnapshot(harnessDir: string | null, sess
188
242
  * per store — the oversize warning) or the degraded reason.
189
243
  */
190
244
  export declare function writeEngineStatusSnapshot(harnessDir: string | null, input: EngineStatusSnapshotWriteInput): EngineStatusSnapshotWrite;
245
+ /**
246
+ * Read one session's durable workflow-selection binding.
247
+ *
248
+ * The answers are deliberately distinct: `{kind:'ok'}` WITHOUT a `binding`
249
+ * means there is nothing recorded for this session (no store yet, an sv=1
250
+ * envelope without the control map, no key for the session) — the caller
251
+ * proceeds unbound. `{kind:'unavailable', reason}` means the record cannot be
252
+ * trusted (`store-unreadable`, `store-invalid-json`, `store-envelope-schema`,
253
+ * `store-bindings-schema`, `cwd-mismatch`) — the caller must NOT treat that as
254
+ * "no pick" and attribute the session anywhere.
255
+ *
256
+ * The file read + parse is memoized by the file's identity (see
257
+ * {@link readBindingStoreSnapshot}); only this session's key is re-checked per
258
+ * call, so the per-tool-call / per-step cost does not scale with the store.
259
+ * @param harnessDir - the resolved `{HARNESS_DIR}`.
260
+ * @param sessionId - the durable `session.header.id` (the picker key).
261
+ * @param cwd - the authoritative session cwd (exact match against the record).
262
+ */
263
+ export declare function readWorkflowSessionBinding(harnessDir: string, sessionId: string, cwd: string): WorkflowSessionBindingRead;
264
+ /**
265
+ * Durably record one session's workflow-selection binding (the D4 picker
266
+ * commit / exclusion-floor advance) — the same read-modify-write discipline as
267
+ * {@link writeEngineStatusSnapshot}: the snapshot-dir lock, a writer-unique
268
+ * temp file, an atomic rename, and the refusal rule for a store this build
269
+ * cannot understand. The emission records (`payload` / `at` / `turn` /
270
+ * `rv` / `cwd`) are preserved EXACTLY: this write only sets
271
+ * `bindings[sessionId]`, merges the floor by max, and keeps the stored
272
+ * preference when the update omits one. A binding that cannot fit the ceiling
273
+ * is refused rather than paid for by dropping another session's record.
274
+ * @param harnessDir - the resolved `{HARNESS_DIR}`.
275
+ * @param sessionId - the durable `session.header.id`.
276
+ * @param cwd - the authoritative session cwd (must match any stored record).
277
+ * @param update - the preference (optional) and the exclusion floor.
278
+ * @returns written, or the degraded reason (never throws for I/O faults).
279
+ */
280
+ export declare function updateWorkflowSessionBinding(harnessDir: string, sessionId: string, cwd: string, update: WorkflowSessionBindingUpdate): WorkflowSessionBindingWrite;
@@ -27,3 +27,12 @@ export declare const ENGINE_STATUS_ENDPOINT = "mstar/engineStatus";
27
27
  export declare const MSTAR_ENGINE_STATUS_NAMESPACE = "mstar";
28
28
  /** Wire method of the invocation → `/api/mstar/engineStatus`. */
29
29
  export declare const MSTAR_ENGINE_STATUS_METHOD = "engineStatus";
30
+ /**
31
+ * Wire method of the panel's workflow-selection control → `/api/mstar/selectWorkflow`.
32
+ * A session-scoped UI control (never a model workflow-execution tool): the
33
+ * host acknowledges the pick durably and the next `ensure()` reads the
34
+ * current binding back.
35
+ */
36
+ export declare const MSTAR_SELECT_WORKFLOW_METHOD = "selectWorkflow";
37
+ /** The endpoint path the client calls for a workflow pick. */
38
+ export declare const SELECT_WORKFLOW_ENDPOINT = "mstar/selectWorkflow";
@@ -2,6 +2,7 @@ import z from 'schemastery';
2
2
  import type { GateResult, ValidationResult } from '@mstar-harness/engine';
3
3
  import type { Config as SkillLocalConfig } from '@deepseek-ai/dsh-skill-filesystem';
4
4
  import type { IterationGateListView, IterationGateViolationView } from '../types.ts';
5
+ import type { SessionHint } from './workflow-selection.ts';
5
6
  /** Canonical harness status file name (mstar-artifacts status.json). */
6
7
  export declare const STATUS_FILE = "status.json";
7
8
  /** Plugin configuration. */
@@ -128,15 +129,6 @@ export interface Config {
128
129
  * so P-a never applies to them.
129
130
  */
130
131
  workflowNames?: string[];
131
- /**
132
- * Goal-bridge round cap : the
133
- * flat `maxGoalRounds` the goal bridge passes to the goals service when it
134
- * mirrors the active iteration objective (bounds autonomous Phase 2
135
- * loops — the service itself throws on resume past the cap). Absent →
136
- * 256 (aligned with the GoalService default and ralph `maxRounds` —
137
- * architect decision; see the plan).
138
- */
139
- maxGoalRounds?: number;
140
132
  }
141
133
  /**
142
134
  * dsh system-prompt strict `{{variable}}` interpolation hazard: the renderer
@@ -314,6 +306,66 @@ export declare function sessionCwdOf(agent: unknown): string | undefined;
314
306
  * skips the persist instead of inventing a key.
315
307
  */
316
308
  export declare function sessionHeaderIdOf(agent: unknown): string | undefined;
309
+ /**
310
+ * The opaque id of one agent — the LEASE-holder identity (`Agent.id`,
311
+ * structural read). Deliberately distinct from {@link sessionHeaderIdOf}:
312
+ * the durable picker key is `session.header.id` while a lease's `holder` is
313
+ * the dispatching agent's own id, and the two are never substituted for one
314
+ * another (no host-prefix coercion — the resolver compares them opaquely).
315
+ */
316
+ export declare function agentIdOf(agent: unknown): string | undefined;
317
+ /**
318
+ * The VERIFIED live lease-holder identity of one session's Agent — the id the
319
+ * dispatch gate forwards to the shared resolvers for the SAME dispatch
320
+ * (`sessionHintOf(exec.agent)` → `agentIdOf`). Every consumer of a
321
+ * session-level hint (the host endpoint's control-state read, the workflow
322
+ * ledger's attribution) must forward the SAME identity, or a session whose
323
+ * lifecycle is only decidable by its lease (two active lifecycles sharing one
324
+ * `control_worktree_path`) resolves differently per call site.
325
+ *
326
+ * The handle must BE that session's agent — its own `session.header.id` names
327
+ * the session, and (when the caller has an authoritative cwd) its workspace
328
+ * must equal it. Anything else answers `undefined`: the session id is never
329
+ * substituted for a holder (they are opaque and distinct).
330
+ * @param agent - the live agent the host resolves for the session (structural read).
331
+ * @param sessionId - the session the hint is built for.
332
+ * @param cwd - the session's workspace; `undefined` skips the workspace check
333
+ * (a caller with no authoritative cwd cannot verify one).
334
+ */
335
+ export declare function verifiedLeaseHolderOf(agent: unknown, sessionId: string, cwd: string | undefined): string | undefined;
336
+ /**
337
+ * The structural session hint one event's agent supplies to the workflow
338
+ * resolvers (no dsh-session import — cold/raw Session consumers read the same
339
+ * `header.cwd` / `header.id` directly). `selectedWorkflowId` is deliberately
340
+ * ABSENT: it is the session's durable binding record, loaded by the
341
+ * composition edge that owns the store (adapter / catalog / workflow-ledger /
342
+ * plan-mode bridge) — the pure structural read never fabricates it.
343
+ * @param agent - the agent an event carries (structural read).
344
+ * @returns the hint, or `undefined` when the agent carries no session
345
+ * identity and no id at all (an exec-less hook) — an OMITTED hint, never a
346
+ * fabricated empty one.
347
+ */
348
+ export declare function sessionHintOf(agent: unknown): SessionHint | undefined;
349
+ /**
350
+ * One carrying session's selection-hint read: the hint the resolvers consume,
351
+ * plus whether the session's DURABLE binding could be trusted.
352
+ *
353
+ * `ok` without a `hint` = the event carries no session (an exec-less hook) —
354
+ * the automatic rungs miss and the registry's unique-active case still
355
+ * resolves. `unavailable` = the binding store could not be read
356
+ * (corrupt/unreadable/cwd-mismatch), so the caller must NOT attribute a
357
+ * durable write to this session — an unverifiable record is NOT "no pick",
358
+ * and must never be replaced by an empty hint that would silently attribute
359
+ * the write to whichever lifecycle happens to resolve.
360
+ */
361
+ export type SessionHintRead = {
362
+ readonly kind: 'ok';
363
+ readonly hint?: SessionHint;
364
+ } | {
365
+ readonly kind: 'unavailable';
366
+ readonly reason: string;
367
+ readonly hint?: SessionHint;
368
+ };
317
369
  /** The tool-execution actor of one fs-intent event, when it carries an agent. */
318
370
  export declare function actorAgentOf(actor: object | undefined): unknown;
319
371
  /**
@@ -1,7 +1,7 @@
1
1
  import { Service, type Context } from '@deepseek-ai/cordis';
2
2
  import type { AssignmentFields, GateResult, HostAdapter, IntegrationMergeLease, ValidationResult } from '@mstar-harness/engine';
3
3
  import type { ToolExecution } from '@deepseek-ai/dsh-tools';
4
- import type { HarnessResolver, Config } from './_shared.ts';
4
+ import type { HarnessResolver, Config, SessionHintRead } from './_shared.ts';
5
5
  import { type HarnessDocKind } from './status.ts';
6
6
  import type { AgentFlowPairing, WorkflowVerdictInput } from './agent-flow.ts';
7
7
  import { WorkflowAskCache } from './workflow-policy.ts';
@@ -109,6 +109,33 @@ export declare class DshHostAdapter extends Service implements HostAdapter {
109
109
  * cleanup extension needs it to locate the project registers).
110
110
  */
111
111
  statusGate(path: string, kind: HarnessDocKind, harnessDir: string | null): GateResult;
112
+ /**
113
+ * Derive the carrying session's workflow-selection hint for one event
114
+ * (D4): the structural identity read off the agent (`session.header.cwd` /
115
+ * `session.header.id` plus the agent's own opaque id as the lease holder)
116
+ * folded with this session's DURABLE binding preference from the
117
+ * engine-status store. Both gate reads and ledger writes route through
118
+ * this ONE derivation, so a session is attributed consistently within a
119
+ * single decision.
120
+ *
121
+ * The store is the plugin's only durable picker state, and it is read at
122
+ * THIS composition edge: `dispatch.ts` (and the writers in `agent-flow.ts`)
123
+ * receive plain hints and never touch the store — no circular
124
+ * `agent-flow → store → agent-flow` import.
125
+ *
126
+ * Degrades honestly, never with a fabricated hint:
127
+ * - no session at all (exec-less hook / agent stub) → `ok` without a hint,
128
+ * so the automatic rungs miss and only a unique active lifecycle
129
+ * resolves;
130
+ * - a session without both id AND cwd → `ok` with what IS known (the
131
+ * lease/cwd rungs and the unique-active case still work — only the
132
+ * durable pick needs the session key);
133
+ * - an unreadable/refused/cwd-mismatched binding record → `unavailable`
134
+ * (reported once through the adapter's log), so the callers keep the
135
+ * structural evidence for GATE validation but write no ledger row.
136
+ * @param agent - the event's agent (`exec.agent`), structural read.
137
+ */
138
+ sessionHintFor(agent: unknown): SessionHintRead;
112
139
  /**
113
140
  * Shared dispatch-gate core (plugin-internal): the `tools/pre-execute`
114
141
  * listener and `beforeDispatch` route through this method — ONE
@@ -122,8 +149,11 @@ export declare class DshHostAdapter extends Service implements HostAdapter {
122
149
  * @param hard - the caller's ONE `resolveDispatchHard` resolution): passed in so the record block and
123
150
  * the caller's enforcement decision share a single compass resolution;
124
151
  * when omitted (external callers) the adapter resolves it itself.
152
+ * @param hintRead - the caller's already-derived hint read (the
153
+ * `tools/pre-execute` listener derives it once and shares it with the
154
+ * workflow branch); omitted → derived here from `exec.agent`.
125
155
  */
126
- dispatchGate(prompt: string, exec?: ToolExecution, hard?: boolean): GateResult;
156
+ dispatchGate(prompt: string, exec?: ToolExecution, hard?: boolean, hintRead?: SessionHintRead): GateResult;
127
157
  /**
128
158
  * Record one workflow/ralph gate verdict row (plan
129
159
  * Task 4 — the durable ledger row for every