@adhdev/mesh-shared 1.0.60-rc.7 → 1.0.60-rc.70

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/dist/brain-routing.d.ts +2 -2
  2. package/dist/index.d.ts +12 -0
  3. package/dist/index.js +3264 -35
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2931 -35
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/keyed-doc-delta.d.ts +83 -0
  8. package/dist/keyed-status-delta.d.ts +76 -0
  9. package/dist/magi.d.ts +1 -1
  10. package/dist/mesh-tool-names.d.ts +27 -2
  11. package/dist/mesh-vocabulary.d.ts +70 -0
  12. package/dist/mission-brief.d.ts +62 -0
  13. package/dist/outbound-message.d.ts +202 -0
  14. package/dist/path-ownership.d.ts +101 -0
  15. package/dist/protocol/daemon-server.d.ts +381 -0
  16. package/dist/protocol/dashboard-daemon-p2p.d.ts +216 -0
  17. package/dist/protocol/dashboard-server.d.ts +240 -0
  18. package/dist/protocol/envelope.d.ts +55 -0
  19. package/dist/protocol/index.d.ts +15 -0
  20. package/dist/session-launch.d.ts +113 -0
  21. package/dist/session-status.d.ts +61 -0
  22. package/dist/slot-proposal.d.ts +1 -1
  23. package/dist/turn-evidence.d.ts +438 -0
  24. package/dist/turn-ipc.d.ts +1040 -0
  25. package/dist/types.d.ts +11 -0
  26. package/dist/worker-protocol.d.ts +69 -0
  27. package/dist/ws-protocol.d.ts +71 -41
  28. package/package.json +1 -1
  29. package/src/brain-routing.ts +3 -3
  30. package/src/index.ts +12 -0
  31. package/src/keyed-doc-delta.ts +348 -0
  32. package/src/keyed-status-delta.ts +239 -0
  33. package/src/magi.ts +1 -1
  34. package/src/mesh-tool-names.ts +65 -19
  35. package/src/mesh-vocabulary.ts +98 -0
  36. package/src/mission-brief.ts +153 -0
  37. package/src/outbound-message.ts +320 -0
  38. package/src/path-ownership.ts +271 -0
  39. package/src/protocol/daemon-server.ts +545 -0
  40. package/src/protocol/dashboard-daemon-p2p.ts +280 -0
  41. package/src/protocol/dashboard-server.ts +340 -0
  42. package/src/protocol/envelope.ts +75 -0
  43. package/src/protocol/index.ts +16 -0
  44. package/src/session-launch.ts +247 -0
  45. package/src/session-status.ts +140 -0
  46. package/src/slot-proposal.ts +1 -1
  47. package/src/turn-evidence.ts +602 -0
  48. package/src/turn-ipc.ts +2331 -0
  49. package/src/types.ts +8 -0
  50. package/src/worker-protocol.ts +164 -0
  51. package/src/ws-protocol.ts +102 -93
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Keyed document delta — the diff/fold pair behind the `mesh.status` lane
3
+ * (the dashboard's mesh view; data-path audit 2026-09-29 P1-5).
4
+ *
5
+ * A document is plain top-level fields plus named keyed COLLECTIONS (arrays of
6
+ * rows identified by an id field, possibly nested one object deep, e.g.
7
+ * `queue.tasks`). The daemon digests every body it would have sent, diffs it
8
+ * against the digest of the last body DELIVERED to a subscription, and sends
9
+ * only the difference: changed top-level fields, and per collection the rows
10
+ * whose content changed (changed fields only), the fields a row lost, and the
11
+ * rows that disappeared. An unchanged document diffs to `null` — zero bytes on
12
+ * the wire. The dashboard folds each delta into its held snapshot; the fold of
13
+ * every delta since a snapshot equals the latest snapshot (pinned by
14
+ * keyed-doc-delta.test.ts).
15
+ *
16
+ * `volatileTopLevel` fields (a build stamp, live process counters) are not a
17
+ * change by themselves; when something else changed they ride along.
18
+ *
19
+ * Pure leaf: plain objects in, plain objects out; no transport, no clock.
20
+ */
21
+ export interface KeyedDocSpec {
22
+ /** Collection path (`nodes`, `queue.tasks`) → the id field of its rows. */
23
+ collections: Readonly<Record<string, string>>;
24
+ /** Top-level fields that change on every build without meaning anything. */
25
+ volatileTopLevel?: readonly string[];
26
+ /**
27
+ * The part of a collection row that decides whether it CHANGED (drop the
28
+ * per-build stamps inside it). A row whose stable view is unchanged sends
29
+ * nothing; a changed field is sent with its full current value, stamps
30
+ * included. Must not mutate `row`.
31
+ */
32
+ stableRow?: (collection: string, row: Record<string, unknown>) => Record<string, unknown>;
33
+ }
34
+ type Row = Record<string, unknown>;
35
+ interface RowDigest {
36
+ readonly value: Row;
37
+ /** The row as compared (stableRow view). */
38
+ readonly stable: Row;
39
+ readonly sig: string;
40
+ fieldSigs?: Map<string, string>;
41
+ }
42
+ interface CollectionDigest {
43
+ readonly rows: ReadonlyMap<string, RowDigest>;
44
+ readonly order: readonly string[];
45
+ }
46
+ export interface KeyedDocDigest {
47
+ readonly top: ReadonlyMap<string, string>;
48
+ readonly topValues: Readonly<Record<string, unknown>>;
49
+ readonly collections: ReadonlyMap<string, CollectionDigest>;
50
+ readonly volatile: ReadonlySet<string>;
51
+ }
52
+ export interface KeyedCollectionDelta {
53
+ /** New rows (whole) and changed rows (id + changed fields). */
54
+ upsert?: Row[];
55
+ /** Fields a row lost, by row id. */
56
+ unsetFields?: Record<string, string[]>;
57
+ removed?: string[];
58
+ order?: string[];
59
+ /** The collection is no longer present in the document at all. */
60
+ absent?: true;
61
+ }
62
+ export interface KeyedDocDelta {
63
+ set?: Record<string, unknown>;
64
+ unset?: string[];
65
+ collections?: Record<string, KeyedCollectionDelta>;
66
+ }
67
+ /** Digest one document. Cheap to hold: values are referenced, not copied. */
68
+ export declare function digestKeyedDoc(doc: Record<string, unknown>, spec: KeyedDocSpec): KeyedDocDigest;
69
+ /**
70
+ * The difference `prev → next`, or `null` when nothing a consumer can observe
71
+ * changed (volatile top-level fields alone are not a change).
72
+ */
73
+ export declare function diffKeyedDoc(prev: KeyedDocDigest, next: KeyedDocDigest, spec: KeyedDocSpec): KeyedDocDelta | null;
74
+ /** Rebuild the full document from a held one plus a delta. Never mutates `held`. */
75
+ export declare function foldKeyedDoc<T extends Record<string, unknown>>(held: T, delta: KeyedDocDelta, spec: KeyedDocSpec): T;
76
+ /**
77
+ * The `mesh.status` document: nodes keyed by `nodeId`, queue tasks and missions
78
+ * by `id`. `refreshedAt` and `sourceOfTruth` (its aggregate-cache age /
79
+ * returnedAt) are per-call stamps and the counters are live process tallies —
80
+ * none of them is a change by itself.
81
+ */
82
+ export declare const MESH_STATUS_DOC_SPEC: KeyedDocSpec;
83
+ export {};
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Keyed status delta — the diff/fold pair behind the `daemon.metadata` lane
3
+ * (the ONE daemon→dashboard state lane; data-path audit 2026-09-29 P0-3).
4
+ *
5
+ * The daemon digests every body it would have sent, diffs it against the
6
+ * digest of the last body DELIVERED to a subscription, and sends only the
7
+ * difference: changed top-level fields, changed daemon-level `status` fields,
8
+ * and per-session changed fields keyed by session id, with removals explicit.
9
+ * An unchanged state diffs to `null` — zero bytes on the wire. The dashboard
10
+ * folds each delta into its held snapshot; the fold of every delta since a
11
+ * snapshot equals the latest snapshot (pinned by keyed-status-delta.test.ts).
12
+ *
13
+ * Pure leaf: plain objects in, plain objects out; no transport, no clock.
14
+ */
15
+ /** A session row: identified by `id`, everything else is a field. */
16
+ export interface KeyedStatusSession {
17
+ id: string;
18
+ [field: string]: unknown;
19
+ }
20
+ /** The snapshot body being diffed: top-level fields plus a `status` with a keyed session list. */
21
+ export interface KeyedStatusBody {
22
+ status: {
23
+ sessions: KeyedStatusSession[];
24
+ [field: string]: unknown;
25
+ };
26
+ [field: string]: unknown;
27
+ }
28
+ export interface KeyedStatusDigestOptions {
29
+ /** Top-level keys that are never diffed (envelope identity: daemonId, …). */
30
+ ignoreTopLevel?: readonly string[];
31
+ /** `status` keys that are never diffed (in addition to `sessions`). */
32
+ ignoreStatus?: readonly string[];
33
+ /**
34
+ * Session fields that change on every build without meaning anything
35
+ * (a build-time `lastUpdated` stamp). A change to ONLY these fields is not
36
+ * a change; when something else changed they ride along.
37
+ */
38
+ volatileSessionFields?: readonly string[];
39
+ }
40
+ interface SessionDigest {
41
+ readonly value: KeyedStatusSession;
42
+ /** Signature of every non-volatile field (fast unchanged check). */
43
+ readonly sig: string;
44
+ /** Per-field signatures, computed lazily — only for sessions that changed. */
45
+ fieldSigs?: Map<string, string>;
46
+ }
47
+ export interface KeyedStatusDigest {
48
+ readonly top: ReadonlyMap<string, string>;
49
+ readonly topValues: Readonly<Record<string, unknown>>;
50
+ readonly status: ReadonlyMap<string, string>;
51
+ readonly statusValues: Readonly<Record<string, unknown>>;
52
+ readonly sessions: ReadonlyMap<string, SessionDigest>;
53
+ readonly order: readonly string[];
54
+ readonly volatile: ReadonlySet<string>;
55
+ }
56
+ export interface KeyedStatusDelta {
57
+ set?: Record<string, unknown>;
58
+ unset?: string[];
59
+ statusSet?: Record<string, unknown>;
60
+ statusUnset?: string[];
61
+ sessions?: KeyedStatusSession[];
62
+ sessionUnset?: Record<string, string[]>;
63
+ removedSessionIds?: string[];
64
+ sessionOrder?: string[];
65
+ }
66
+ /** Digest one body. Cheap to hold: values are referenced, not copied. */
67
+ export declare function digestKeyedStatus(body: KeyedStatusBody, options?: KeyedStatusDigestOptions): KeyedStatusDigest;
68
+ /**
69
+ * The difference `prev → next`, or `null` when nothing a consumer can observe
70
+ * changed. `prev === null` is not accepted here — a subscription with no
71
+ * delivered baseline gets a snapshot, not a delta.
72
+ */
73
+ export declare function diffKeyedStatus(prev: KeyedStatusDigest, next: KeyedStatusDigest): KeyedStatusDelta | null;
74
+ /** Rebuild the full body from a held one plus a delta. Never mutates `held`. */
75
+ export declare function foldKeyedStatus<T extends KeyedStatusBody>(held: T, delta: KeyedStatusDelta): T;
76
+ export {};
package/dist/magi.d.ts CHANGED
@@ -50,7 +50,7 @@
50
50
  * already pinned the (node, provider) target, so any difficulty would be inert at best
51
51
  * and would fight the panel's own slot selection at worst. To change how hard a
52
52
  * replica thinks or how much parallelism a node grants, edit that NODE's capability
53
- * slots (`mesh_node_slots_set`); it is a different axis, on purpose.
53
+ * slots (`mesh_node_slots` action "set"); it is a different axis, on purpose.
54
54
  *
55
55
  * Unknown keys are dropped rather than rejected, so slots written by another version
56
56
  * stay readable. Write paths pair the normalizer with
@@ -18,7 +18,32 @@
18
18
  * Order mirrors mcp-server `ALL_MESH_TOOLS` for easy visual diffing, but the consistency
19
19
  * checks are set-based (order-insensitive).
20
20
  */
21
- export declare const CANONICAL_MESH_TOOL_NAMES: readonly ["mesh_status", "mesh_route_preview", "mesh_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_graph_view", "mesh_graph_gate_claim", "mesh_graph_gate_release", "mesh_graph_gate_abandon", "mesh_graph_node_patch", "mesh_queue_cancel", "mesh_queue_requeue", "mesh_send_task", "mesh_read_chat", "mesh_read_debug", "mesh_read_terminal", "mesh_send_keys", "mesh_launch_session", "mesh_git_status", "mesh_read_node_logs", "mesh_fast_forward_node", "mesh_restart_daemon", "mesh_checkpoint", "mesh_approve", "mesh_answer_question", "mesh_list_pending_approvals", "mesh_plan_onboarding", "mesh_create", "mesh_add_node", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_refine_config", "mesh_change_impact_config", "mesh_init", "mesh_reinit", "mesh_write_mesh_json_config", "mesh_refine_plan", "mesh_cleanup_sessions", "mesh_prune_stale_direct", "mesh_task_history", "mesh_ledger_query", "mesh_record_note", "mesh_forget_note", "mesh_reconcile_ledger", "mesh_requeue_held_events", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_magi_review", "mesh_magi_collect", "mesh_magi_kind_panel_set", "mesh_magi_kind_panel_list", "mesh_node_slots_set", "mesh_node_slots_list", "mesh_node_slots_propose", "mesh_coordinator_prompt_append_get", "mesh_coordinator_prompt_append_set"];
21
+ export declare const CANONICAL_MESH_TOOL_NAMES: readonly ["mesh_status", "mesh_route_preview", "mesh_list_nodes", "mesh_enqueue_batch", "mesh_enqueue_task", "mesh_view_queue", "mesh_graph_view", "mesh_graph_gate", "mesh_graph_node_patch", "mesh_queue_cancel", "mesh_queue_requeue", "mesh_send_task", "mesh_read_chat", "mesh_read_debug", "mesh_read_terminal", "mesh_send_keys", "mesh_launch_session", "mesh_git_status", "mesh_read_node_logs", "mesh_fast_forward_node", "mesh_restart_daemon", "mesh_checkpoint", "mesh_approve", "mesh_answer_question", "mesh_list_pending_approvals", "mesh_create", "mesh_add_node", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_config", "mesh_init", "mesh_refine_plan", "mesh_cleanup_sessions", "mesh_task_history", "mesh_ledger_query", "mesh_note", "mesh_reconcile_ledger", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_magi_review", "mesh_magi_collect", "mesh_magi_kind_panel", "mesh_node_slots", "mesh_coordinator_prompt_append"];
22
22
  export type CanonicalMeshToolName = typeof CANONICAL_MESH_TOOL_NAMES[number];
23
23
  /** The count the `NN tools` barrel doc comments and consistency test assert against. */
24
- export declare const CANONICAL_MESH_TOOL_COUNT: 61;
24
+ export declare const CANONICAL_MESH_TOOL_COUNT: 48;
25
+ /**
26
+ * Tool names retired by the 2026-09-26 tool-surface consolidation (60 → 48).
27
+ * Every capability behind them is still reachable: each retired name maps to the
28
+ * merged tool plus the discriminator argument that selects the same behaviour.
29
+ *
30
+ * mcp-server answers a call to a retired name with an error naming the
31
+ * replacement (it does NOT forward silently — a coordinator running an old
32
+ * prompt must learn the new spelling), and the daemon-core coordinator prompt
33
+ * test asserts none of these names is advertised anywhere in the prompt.
34
+ *
35
+ * `args` is the discriminator to add; every other argument keeps its name.
36
+ * `note` covers the one rename where an argument moved (the old
37
+ * `mesh_graph_gate_claim({ extend_seconds })` extend-only mode).
38
+ */
39
+ export declare const RETIRED_MESH_TOOLS: Readonly<Record<string, {
40
+ readonly tool: CanonicalMeshToolName;
41
+ readonly args: Readonly<Record<string, string>>;
42
+ readonly note?: string;
43
+ }>>;
44
+ /**
45
+ * The error text for a call to a retired tool name, or null when `name` is not
46
+ * retired. One wording shared by every MCP mode so the redirect reads the same
47
+ * wherever an old prompt calls it.
48
+ */
49
+ export declare function retiredMeshToolError(name: string): string | null;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * mesh-vocabulary — the ONE declaration of every mesh enum.
3
+ *
4
+ * Wiring-unification Phase A3 (docs/design/2026-09-23-wiring-unification.md).
5
+ *
6
+ * Before this file `MeshTaskStatus` was declared twice in daemon-core with
7
+ * different members (one carried `in_progress`, which no task row ever held),
8
+ * a third time in `repo-mesh-types.ts`, and loosened to `| string` in web-core;
9
+ * the MCP tool schemas hand-copied the task-mode list six times. Every enum a
10
+ * mesh tool, ledger row, queue entry or dashboard view branches on is declared
11
+ * here once, as a `const` tuple, and the type is derived from it. JSON-schema
12
+ * producers use `enumOf()` so the published schema cannot drift from the code.
13
+ *
14
+ * `MeshTaskDifficulty` / `MESH_TASK_DIFFICULTIES` already live in
15
+ * ./brain-routing.ts and are re-exported here for one-stop discovery.
16
+ */
17
+ export { MESH_TASK_DIFFICULTIES, type MeshTaskDifficulty } from './brain-routing';
18
+ export declare const MESH_TASK_STATUSES: readonly ["pending", "assigned", "completed", "failed", "cancelled"];
19
+ export type MeshTaskStatus = typeof MESH_TASK_STATUSES[number];
20
+ export declare const MESH_TERMINAL_TASK_STATUSES: readonly ["completed", "failed", "cancelled"];
21
+ export type MeshTerminalTaskStatus = typeof MESH_TERMINAL_TASK_STATUSES[number];
22
+ export declare const MESH_TASK_MODES: readonly ["code_change", "validation", "live_debug_readonly", "launch_app", "convergence"];
23
+ export type MeshTaskMode = typeof MESH_TASK_MODES[number];
24
+ export declare const MESH_TASK_PRIORITIES: readonly ["low", "normal", "high"];
25
+ export type MeshTaskPriority = typeof MESH_TASK_PRIORITIES[number];
26
+ export declare const MESH_THINKING_LEVELS: readonly ["low", "medium", "high"];
27
+ export type MeshThinkingLevel = typeof MESH_THINKING_LEVELS[number];
28
+ /**
29
+ * How a message is delivered to a session that may be busy.
30
+ *
31
+ * 'when_idle' — DEFAULT. Never disturbs a running turn; a busy session's task
32
+ * is queued and auto-delivered on its next idle transition.
33
+ * 'interrupt' — Abort the in-flight turn (the provider's own stop control),
34
+ * then deliver once the session settles to idle. The name is
35
+ * deliberately blunt: the unfinished work is LOST. `immediate`
36
+ * was rejected as a name because it reads like a gentle overlay.
37
+ */
38
+ export declare const MESH_DELIVERY_MODES: readonly ["when_idle", "interrupt"];
39
+ export type MeshDeliveryMode = typeof MESH_DELIVERY_MODES[number];
40
+ /**
41
+ * What to do with a node's sessions when the node is removed or a MAGI fan-out
42
+ * settles. Declared here so daemon-core (`RepoMeshSessionCleanupMode`), the
43
+ * web dashboard picker and the MCP schemas all read one list.
44
+ */
45
+ export declare const MESH_SESSION_CLEANUP_MODES: readonly ["preserve", "stop", "delete_stopped", "stop_and_delete"];
46
+ export type MeshSessionCleanupMode = typeof MESH_SESSION_CLEANUP_MODES[number];
47
+ /** Outcome a delegated worker may report through `report_completion`. */
48
+ export declare const WORKER_REPORT_OUTCOMES: readonly ["completed", "blocked", "failed"];
49
+ export type WorkerReportOutcome = typeof WORKER_REPORT_OUTCOMES[number];
50
+ /** Final branch state a worker reports (see coordinator convergence rules). */
51
+ export declare const WORKER_BRANCH_STATES: readonly ["merged_to_main", "pushed_feature_branch_needs_merge", "blocked_review", "cleanup_candidate", "not_mergeable"];
52
+ export type WorkerBranchState = typeof WORKER_BRANCH_STATES[number];
53
+ export declare const isMeshTaskStatus: (value: unknown) => value is "failed" | "pending" | "assigned" | "completed" | "cancelled";
54
+ export declare const isMeshTerminalTaskStatus: (value: unknown) => value is "failed" | "completed" | "cancelled";
55
+ export declare const isMeshTaskMode: (value: unknown) => value is "code_change" | "validation" | "live_debug_readonly" | "launch_app" | "convergence";
56
+ export declare const isMeshTaskPriority: (value: unknown) => value is "low" | "high" | "normal";
57
+ export declare const isMeshThinkingLevel: (value: unknown) => value is "medium" | "low" | "high";
58
+ export declare const isMeshDeliveryMode: (value: unknown) => value is "when_idle" | "interrupt";
59
+ export declare const isMeshSessionCleanupMode: (value: unknown) => value is "preserve" | "stop" | "delete_stopped" | "stop_and_delete";
60
+ export declare const isWorkerReportOutcome: (value: unknown) => value is "failed" | "blocked" | "completed";
61
+ export declare const isWorkerBranchState: (value: unknown) => value is "merged_to_main" | "pushed_feature_branch_needs_merge" | "blocked_review" | "cleanup_candidate" | "not_mergeable";
62
+ /**
63
+ * JSON-schema fragment for a string enum, derived from the tuple. Use this in
64
+ * every MCP tool schema instead of spelling the member list again.
65
+ */
66
+ export declare function enumOf<T extends readonly string[]>(values: T, description?: string): {
67
+ type: 'string';
68
+ enum: T[number][];
69
+ description?: string;
70
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * mission-brief — the goal/constraints/done-criteria/handoff a coordinator
3
+ * attaches to a mission at `mesh_mission_upsert` time, and the deterministic
4
+ * rendering of that brief into a dispatched task's worker-protocol footer
5
+ * (wiring-unification Phase H2, `docs/design/2026-09-23-wiring-unification.md`
6
+ * §7c).
7
+ *
8
+ * `mesh_mission_upsert` today carries only a free-text `goal` string — no
9
+ * structured constraints, done-criteria or handoff notes, and nothing from a
10
+ * mission ever reaches a dispatched worker's task body. This module is the
11
+ * pure, dependency-free core: a normalized `MissionBrief` shape with length
12
+ * caps (mirrors `report_completion.summary`'s "prefer specifics" discipline —
13
+ * a brief is meant to be read by a worker mid-task, not skimmed by a human),
14
+ * and a deterministic Markdown renderer capped at 1,200 chars so a mission
15
+ * with many/long fields cannot blow a dispatched task's token budget.
16
+ *
17
+ * This module does NOT decide where the rendered block is inserted (that is
18
+ * `worker-protocol.ts`'s `renderWorkerProtocolFooter`, which places it above
19
+ * the marker line — see that file), and does NOT touch storage (H2's proposal
20
+ * keeps `mesh_missions` as the row source; this is the shape callers upsert
21
+ * into/read out of that row's `goal`/new `brief` columns).
22
+ */
23
+ /** Total rendered-block cap (design doc H2 "bounded payload" discipline, mirrored from `listMeshMissionsForTool`). */
24
+ export declare const MISSION_BRIEF_RENDER_MAX_CHARS = 1200;
25
+ export interface MissionBrief {
26
+ /** What this mission is trying to accomplish. Required — an empty brief is not meaningful. */
27
+ goal: string;
28
+ /** Hard constraints a worker must respect (e.g. "do not touch daemon-core", "no npm install"). */
29
+ constraints?: string[];
30
+ /** How to know the mission is actually done. */
31
+ doneCriteria?: string[];
32
+ /** Standing notes for whoever picks up mission work next. */
33
+ handoffNotes?: string[];
34
+ /** Paths this mission's tasks collectively own (H1 integration point — surfaced, not enforced here). */
35
+ ownedPaths?: string[];
36
+ }
37
+ export interface NormalizeMissionBriefResult {
38
+ /** `null` when the input had no usable `goal` — callers should treat this as "no brief", not an empty one. */
39
+ readonly brief: MissionBrief | null;
40
+ /** What was trimmed/dropped, for a caller that wants to warn (never thrown). */
41
+ readonly truncated: ReadonlyArray<{
42
+ field: string;
43
+ reason: 'field_too_long' | 'list_too_long' | 'item_too_long';
44
+ }>;
45
+ }
46
+ /**
47
+ * Normalize a caller-supplied brief. Never throws. Returns `brief: null` when
48
+ * there is no non-empty `goal` — the caller (e.g. `mesh_mission_upsert`'s
49
+ * handler) decides whether that means "reject" or "no brief attached", this
50
+ * module only normalizes shape.
51
+ */
52
+ export declare function normalizeMissionBrief(input: unknown): NormalizeMissionBriefResult;
53
+ /**
54
+ * Deterministic Markdown rendering of a brief, capped at
55
+ * `MISSION_BRIEF_RENDER_MAX_CHARS`. Same field order every call (goal,
56
+ * constraints, done criteria, owned paths, handoff notes) so a snapshot test
57
+ * or a worker reading two renders of the same brief sees no drift. If the
58
+ * assembled text exceeds the cap, trailing sections are dropped whole (never
59
+ * mid-line) until it fits, and a final `(brief truncated)` marker is appended
60
+ * — a worker should never receive a block cut off mid-sentence.
61
+ */
62
+ export declare function renderMissionBriefBlock(brief: MissionBrief): string;
@@ -0,0 +1,202 @@
1
+ /**
2
+ * outbound-message — the one send-side unit crossing every transport.
3
+ *
4
+ * Wiring-unification Phase D (docs/design/2026-09-23-wiring-unification.md §6,
5
+ * D1) / workstream D-web. Full type derivation in the scratchpad
6
+ * `phase-D-plan.md` §1.1-§1.2 (`OutboundMessage`/`SendPolicy`/`SubmitOutcome`/
7
+ * `SendRefusal`). `SendRefusal` is imported from here by Phase C1's
8
+ * `turn-evidence.ts` (`RECLAIMING_SEND_REFUSALS`) — this file owns the type,
9
+ * C1 owns which of its members reclaim a mesh dispatch attempt.
10
+ *
11
+ * Before this type, a chat send was represented by twelve different loose
12
+ * request shapes (`{message, sendNow?, interrupt?}`, `{message, input}`,
13
+ * mesh `agent_command` args, MCP tool args, ...) and parked messages had no
14
+ * identity at all — the driver queue was keyed by exact text, so send-now,
15
+ * cancel, interrupt and the web echo-retirement all recovered identity by
16
+ * string matching. `OutboundMessage.messageId` is minted once, at the
17
+ * ORIGIN, and is the identity every layer keys on afterwards: the wire
18
+ * field in the `send_chat` command payload, the driver's queue key (daemon
19
+ * side, Phase D-daemon), the web pending-store key (`pendingQueuedMessages.ts`)
20
+ * and the echo-retirement key (`conversation-message-snapshot.ts`).
21
+ *
22
+ * This file is dependency-free by construction (mesh-shared leaf rule):
23
+ * `InputEnvelopeWire`/`InputPartWire` are STRUCTURAL MIRRORS of daemon-core's
24
+ * `InputEnvelope`/`InputPart` (`oss/packages/daemon-core/src/providers/
25
+ * io-contracts.ts`), not re-exports — daemon-core assigns its richer type
26
+ * into this one, never the other way round, so this package's published
27
+ * `.d.ts` never references a package that depends on it.
28
+ */
29
+ export interface ContentAnnotationsWire {
30
+ audience?: ('user' | 'assistant')[];
31
+ priority?: number;
32
+ }
33
+ export interface TextInputPartWire {
34
+ type: 'text';
35
+ text: string;
36
+ }
37
+ export interface ImageInputPartWire {
38
+ type: 'image';
39
+ mimeType: string;
40
+ uri?: string;
41
+ data?: string;
42
+ alt?: string;
43
+ }
44
+ export interface AudioInputPartWire {
45
+ type: 'audio';
46
+ mimeType: string;
47
+ uri?: string;
48
+ data?: string;
49
+ transcript?: string;
50
+ }
51
+ export interface VideoInputPartWire {
52
+ type: 'video';
53
+ mimeType: string;
54
+ uri?: string;
55
+ data?: string;
56
+ transcript?: string;
57
+ posterUri?: string;
58
+ }
59
+ export interface ResourceInputPartWire {
60
+ type: 'resource';
61
+ uri: string;
62
+ mimeType?: string;
63
+ name?: string;
64
+ text?: string;
65
+ data?: string;
66
+ }
67
+ export interface ResourceLinkInputPartWire {
68
+ type: 'resource_link';
69
+ uri: string;
70
+ name: string;
71
+ title?: string;
72
+ description?: string;
73
+ mimeType?: string;
74
+ size?: number;
75
+ annotations?: ContentAnnotationsWire;
76
+ }
77
+ export type InputPartWire = TextInputPartWire | ImageInputPartWire | AudioInputPartWire | VideoInputPartWire | ResourceLinkInputPartWire | ResourceInputPartWire;
78
+ export interface InputEnvelopeWire {
79
+ parts: InputPartWire[];
80
+ textFallback: string;
81
+ metadata?: {
82
+ source?: 'dashboard' | 'shortcut_api' | 'provider_script' | 'session_replay';
83
+ clientTimestamp?: number;
84
+ };
85
+ }
86
+ /**
87
+ * Who minted this `OutboundMessage`. Determines default policy and refusal
88
+ * surface — 'mesh' origins get the mesh-specific `SendRefusal` reasons,
89
+ * interactive origins get the interactive ones.
90
+ *
91
+ * `'api'`/`'cli'` are carried per the design doc's D1 type even though no
92
+ * current call site produces them distinct from `'dashboard'`/`'mcp'`/
93
+ * `'mesh'` (flagged as an open question in phase-D-plan.md §7.3 risk 5) —
94
+ * kept here unchanged pending an owner decision; dropping a union member is
95
+ * a decision for whoever lands D-daemon/D2, not this workstream.
96
+ */
97
+ export type OutboundMessageOrigin = 'dashboard' | 'mcp' | 'mesh' | 'api' | 'cli';
98
+ /**
99
+ * How this message should be admitted when the session is not idle.
100
+ * Replaces the loose `sendNow`/`interrupt`/`force`/`forceSend` booleans (and
101
+ * mesh `delivery_mode`) with one closed union. `'queue'` is the default for
102
+ * every origin.
103
+ */
104
+ export type SendPolicy = {
105
+ mode: 'queue';
106
+ } | {
107
+ mode: 'send_now';
108
+ } | {
109
+ mode: 'interrupt';
110
+ };
111
+ /**
112
+ * Every reason `submit()` can refuse a message, closed so callers branch on it
113
+ * instead of matching free text.
114
+ *
115
+ * ★ This is THE `SendRefusal` (docs/design/2026-09-23-wiring-unification.md
116
+ * §6 D1) — Phase C1's `turn-evidence.ts` imports `SendRefusal`/
117
+ * `SEND_REFUSAL_REASONS` from this file (`RECLAIMING_SEND_REFUSALS`, the
118
+ * subset that means a mesh dispatch attempt must be reclaimed rather than
119
+ * retried) rather than declaring its own copy. Do not fork this vocabulary —
120
+ * a member renamed or removed here must be re-checked against
121
+ * `RECLAIMING_SEND_REFUSALS`'s three members (`session_exited`, `no_target`,
122
+ * `unsupported_input`), which `satisfies readonly SendRefusal[]` there keeps
123
+ * honest at compile time.
124
+ */
125
+ export type SendRefusal = 'not_ready' | 'send_in_flight' | 'not_generating' | 'not_supported' | 'platform_unsupported' | 'modal_parked' | 'interrupt_not_implemented' | 'interrupt_refused' | 'idle_timeout' | 'session_exited' | 'unsupported_input' | 'no_target' | 'bootstrap_pending' | 'duplicate_dispatch' | 'internal_error';
126
+ /**
127
+ * Which write path carried a delivered/queued message (Phase D2, additive
128
+ * detail — callers branch on `kind`; `route` only refines the report):
129
+ *
130
+ * `pty` — the ordinary composer write (or the driver FIFO for `queued`);
131
+ * `agent_queue` — the POSIX split write the CLI's OWN input queue takes while
132
+ * a turn is generating (`send_now`). Written, not yet answered;
133
+ * `interrupt` — the turn in flight was stopped first, then this body was
134
+ * written (or re-parked) as a new turn;
135
+ * `acp` — handed to an ACP agent's `session/prompt`.
136
+ */
137
+ export type SubmitRoute = 'pty' | 'agent_queue' | 'interrupt' | 'acp';
138
+ /** The stop key pressed by an `interrupt` submit (logs / operator display). */
139
+ export interface SubmitInterruptDetail {
140
+ keyName: string;
141
+ /** 'declared' = spec-declared stop key whose busy→idle effect was never observed live. */
142
+ confidence: 'proven' | 'declared';
143
+ }
144
+ /**
145
+ * The one closed outcome shape every caller receives from `submit()`.
146
+ *
147
+ * The optional fields are additive DETAIL (Phase D2): `route`/`interrupt` say
148
+ * how a message was written, `message` is a human-readable refusal line and
149
+ * `restored` says whether a parked body that an out-of-band submit had claimed
150
+ * is back in the driver queue (`false` = nothing holds it any more). None of
151
+ * them changes what `kind` means.
152
+ */
153
+ export type SubmitOutcome = {
154
+ kind: 'delivered';
155
+ route?: SubmitRoute;
156
+ interrupt?: SubmitInterruptDetail;
157
+ } | {
158
+ kind: 'queued';
159
+ position: number;
160
+ route?: SubmitRoute;
161
+ interrupt?: SubmitInterruptDetail;
162
+ } | {
163
+ kind: 'duplicate';
164
+ of: string;
165
+ } | {
166
+ kind: 'refused';
167
+ reason: SendRefusal;
168
+ message?: string;
169
+ restored?: boolean;
170
+ };
171
+ /**
172
+ * The one send-side unit crossing every transport. `messageId` is minted at
173
+ * the ORIGIN (never by the daemon) — this is what makes cross-transport
174
+ * dedupe and identity-based claim/cancel/send-now possible.
175
+ */
176
+ export interface OutboundMessage {
177
+ messageId: string;
178
+ sessionId: string;
179
+ input: InputEnvelopeWire;
180
+ origin: OutboundMessageOrigin;
181
+ policy: SendPolicy;
182
+ createdAt: number;
183
+ /**
184
+ * Present only for a mesh-dispatched message; lets the turn ledger
185
+ * (Phase C) bind the send to the attempt without the sender needing to
186
+ * know what an "attempt" is. Optional and inert until C lands.
187
+ */
188
+ meshAttemptRef?: string;
189
+ }
190
+ export declare function mintMessageId(now?: number): string;
191
+ export declare function isMessageId(value: unknown): value is string;
192
+ export declare const OUTBOUND_MESSAGE_ORIGINS: readonly ["dashboard", "mcp", "mesh", "api", "cli"];
193
+ export declare function isOutboundMessageOrigin(value: unknown): value is OutboundMessageOrigin;
194
+ export declare const SEND_POLICY_MODES: readonly ["queue", "send_now", "interrupt"];
195
+ export declare function isSendPolicyMode(value: unknown): value is SendPolicy['mode'];
196
+ export declare function isSendPolicy(value: unknown): value is SendPolicy;
197
+ export declare const SEND_REFUSAL_REASONS: readonly ["not_ready", "send_in_flight", "not_generating", "not_supported", "platform_unsupported", "modal_parked", "interrupt_not_implemented", "interrupt_refused", "idle_timeout", "session_exited", "unsupported_input", "no_target", "bootstrap_pending", "duplicate_dispatch", "internal_error"];
198
+ export declare function isSendRefusal(value: unknown): value is SendRefusal;
199
+ export declare function isSubmitOutcome(value: unknown): value is SubmitOutcome;
200
+ export declare function isInputEnvelopeWire(value: unknown): value is InputEnvelopeWire;
201
+ /** Minimal structural check for the wire boundary — not a full decoder (mesh-shared stays a pure leaf; semantic validation stays with the handler that owns it). */
202
+ export declare function isOutboundMessage(value: unknown): value is OutboundMessage;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * path-ownership — declared file-ownership sets for `code_change` mesh tasks
3
+ * (wiring-unification Phase H1, `docs/design/2026-09-23-wiring-unification.md`
4
+ * §7c).
5
+ *
6
+ * Today two `code_change` tasks on two different nodes (two worktrees of the
7
+ * same branch, or main + a worktree) can silently touch the same file with
8
+ * zero warning — the only existing isolation is node-level ("one active write
9
+ * task per node"), never path-level. This module is the pure, dependency-free
10
+ * core of the fix: normalizing a caller-declared `owned_paths` list, and
11
+ * comparing declared sets for overlap. It has NO opinion on enforcement policy
12
+ * (refuse vs warn, which axis a caller compares against) — that is the mesh
13
+ * runtime's job (`claimNextTask` / `claimNextQueueTask` in daemon-core); this
14
+ * module only answers "do these two declarations conflict" and "did the
15
+ * touched-files report stay inside the declaration".
16
+ *
17
+ * SCOPE DECISION (glob support): minimal, deliberately. A declaration entry is
18
+ * either an exact repo-relative path (`src/foo.ts`) or a directory prefix
19
+ * spelled with a trailing `/**` (`src/mesh/**`) meaning "this path and
20
+ * everything under it". No `*`/`?` mid-segment wildcards, no `!` negation, no
21
+ * brace expansion. Two `code_change` tasks racing on overlapping files is a
22
+ * coordination bug to catch, not a general glob engine to build — a full
23
+ * minimatch-style matcher would need its own test suite and still leave the
24
+ * same ambiguity (does `src/*.ts` "contain" `src/foo.ts`?) the design doc's
25
+ * risk list calls out. `src/` (no `**`) is treated as an exact single-path
26
+ * entry, i.e. it owns a literal file/dir node named `src`, NOT everything
27
+ * under it — callers who want subtree ownership must write `src/**`.
28
+ *
29
+ * Rejected inputs (absolute paths, `..` segments, backslash-only entries that
30
+ * still resolve outside the repo after normalization) are dropped rather than
31
+ * silently coerced — `normalizeOwnedPaths` reports what it dropped so the
32
+ * caller (enqueue-time validation) can decide whether to warn or refuse.
33
+ */
34
+ /** One normalized ownership entry: a repo-relative path, optionally a `**` subtree prefix. */
35
+ export interface OwnedPathEntry {
36
+ /** Repo-relative path with forward slashes, no leading `./`, no trailing slash. */
37
+ readonly path: string;
38
+ /** True when this entry was declared as `<path>/**` (owns the whole subtree). */
39
+ readonly subtree: boolean;
40
+ }
41
+ /** A caller's normalized ownership declaration. */
42
+ export interface OwnedPathsDeclaration {
43
+ readonly paths: readonly OwnedPathEntry[];
44
+ }
45
+ export interface NormalizeOwnedPathsResult {
46
+ readonly declaration: OwnedPathsDeclaration;
47
+ /** Raw input entries that were dropped, with why — surfaced to the caller, never silently coerced. */
48
+ readonly rejected: ReadonlyArray<{
49
+ input: string;
50
+ reason: string;
51
+ }>;
52
+ }
53
+ /** Cap mirrors `depends_on`'s implicit list-size discipline (design doc H1 proposal). */
54
+ export declare const MAX_OWNED_PATHS = 64;
55
+ /**
56
+ * Normalize a caller-supplied `owned_paths` array. Never throws — invalid
57
+ * entries are dropped and reported in `rejected`; duplicates (after
58
+ * normalization) collapse to one entry (subtree wins if either declaration
59
+ * form is present for the same path). Entries beyond `MAX_OWNED_PATHS` (after
60
+ * dedup) are dropped with reason `over_cap`, preserving declaration order.
61
+ */
62
+ export declare function normalizeOwnedPaths(input: unknown): NormalizeOwnedPathsResult;
63
+ /** True when two declarations claim any overlapping path. Pure set comparison — no policy (refuse vs warn) here. */
64
+ export declare function pathsOverlap(a: OwnedPathsDeclaration, b: OwnedPathsDeclaration): boolean;
65
+ export interface InFlightOwnership {
66
+ readonly taskId: string;
67
+ readonly paths: OwnedPathsDeclaration;
68
+ }
69
+ export interface OwnershipConflict {
70
+ readonly taskId: string;
71
+ /** The specific overlapping paths, for a readable refusal message. */
72
+ readonly overlappingPaths: readonly string[];
73
+ }
74
+ /**
75
+ * Compare a candidate declaration against every other in-flight task's
76
+ * declaration and return every conflict found (not just the first), each
77
+ * naming the owning task id and the specific overlapping path(s) — so a
78
+ * refusal message can say exactly what collided, not just that it did.
79
+ *
80
+ * Backward compatibility (design doc risk #4): an EMPTY candidate declaration
81
+ * never conflicts (opt-in only — absent `owned_paths` performs no overlap
82
+ * check for that task), and an in-flight entry with an empty declaration is
83
+ * never reported as a conflict source either.
84
+ */
85
+ export declare function findOwnershipConflicts(candidate: OwnedPathsDeclaration, inFlight: readonly InFlightOwnership[]): OwnershipConflict[];
86
+ export interface TouchedFilesOutsideOwnershipResult {
87
+ /** Touched files not covered by any declared owned-path entry. */
88
+ readonly undeclaredTouched: readonly string[];
89
+ /** Declared entries that no touched file matched (informational only — under-reporting is not flagged as a mismatch by policy, just visible here). */
90
+ readonly unusedDeclarations: readonly string[];
91
+ }
92
+ /**
93
+ * Compare a worker's `report_completion.touched_files` against its task's
94
+ * declared `owned_paths`. Pure comparison only — per the design doc, a
95
+ * mismatch is "surfaced as evidence, not silently accepted"; this function
96
+ * does not decide whether that is an error. Each `touched` entry is
97
+ * normalized with the same rules as a declaration (invalid entries — e.g. an
98
+ * absolute path a worker reported by mistake — are treated as undeclared
99
+ * rather than silently dropped, since they cannot match anything).
100
+ */
101
+ export declare function touchedFilesOutsideOwnership(touched: readonly string[], owned: OwnedPathsDeclaration): TouchedFilesOutsideOwnershipResult;