@adhdev/mesh-shared 1.0.82-rc.3 → 1.0.82-rc.30

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 (55) hide show
  1. package/dist/assistant-protocol.d.ts +140 -6
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.js +529 -110
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +500 -110
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/launch-defaults.d.ts +194 -0
  8. package/dist/mesh-tool-names.d.ts +2 -2
  9. package/dist/mesh-vocabulary.d.ts +13 -4
  10. package/dist/node-facts.d.ts +41 -1
  11. package/dist/outbound-message.d.ts +7 -0
  12. package/dist/path-ownership.d.ts +1 -1
  13. package/dist/protocol/daemon-server.d.ts +9 -0
  14. package/dist/protocol/dashboard-daemon-p2p.d.ts +1 -1
  15. package/dist/protocol/dashboard-server.d.ts +10 -0
  16. package/dist/provider-approval.d.ts +36 -0
  17. package/dist/provider-question.d.ts +19 -0
  18. package/dist/quota-freshness.d.ts +65 -0
  19. package/dist/role-launch.d.ts +11 -0
  20. package/dist/session-launch.d.ts +3 -22
  21. package/dist/slot-proposal.d.ts +14 -0
  22. package/dist/task-source-phase.d.ts +75 -0
  23. package/dist/turn-evidence.d.ts +24 -12
  24. package/dist/turn-ipc-mission.d.ts +4 -0
  25. package/dist/turn-ipc-queue.d.ts +19 -0
  26. package/dist/turn-ipc.d.ts +2 -2
  27. package/dist/types.d.ts +1 -0
  28. package/dist/worker-protocol.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/src/assistant-protocol.ts +247 -6
  31. package/src/daemon-normalize.ts +7 -5
  32. package/src/index.ts +7 -0
  33. package/src/keyed-doc-delta.ts +9 -1
  34. package/src/launch-defaults.ts +168 -0
  35. package/src/mesh-tool-names.ts +5 -0
  36. package/src/mesh-vocabulary.ts +12 -2
  37. package/src/node-facts.ts +36 -1
  38. package/src/outbound-message.ts +17 -0
  39. package/src/path-ownership.ts +4 -6
  40. package/src/protocol/daemon-server.ts +7 -2
  41. package/src/protocol/dashboard-server.ts +8 -2
  42. package/src/provider-approval.ts +59 -0
  43. package/src/provider-question.ts +31 -0
  44. package/src/quota-freshness.ts +139 -0
  45. package/src/role-launch.ts +51 -0
  46. package/src/session-launch.ts +8 -1
  47. package/src/slot-proposal.ts +61 -2
  48. package/src/task-source-phase.ts +61 -0
  49. package/src/turn-evidence.ts +22 -19
  50. package/src/turn-ipc-mission.ts +8 -2
  51. package/src/turn-ipc-queue.ts +29 -1
  52. package/src/turn-ipc-turn.ts +1 -1
  53. package/src/turn-ipc.ts +1 -0
  54. package/src/types.ts +1 -0
  55. package/src/worker-protocol.ts +4 -0
@@ -0,0 +1,75 @@
1
+ import type { OwnedPathEntry } from './path-ownership';
2
+ export type MeshTaskPhase = 'source' | 'integration';
3
+ /** Host-produced evidence. Never accepted from MCP/peer-supplied paths or branch labels. */
4
+ export interface PhysicalOwnershipScope {
5
+ version: 1;
6
+ repoIdentity: string;
7
+ machineId: string;
8
+ nodeId: string;
9
+ worktree: string;
10
+ worktreeIdentity: string;
11
+ gitDir: string;
12
+ commonGitDir: string;
13
+ phase: MeshTaskPhase;
14
+ gitResources: {
15
+ index: string;
16
+ refs: string;
17
+ hooks: string;
18
+ config: string;
19
+ };
20
+ resources: Array<{
21
+ path: string;
22
+ identity?: string;
23
+ subtree: boolean;
24
+ }>;
25
+ declaration: readonly OwnedPathEntry[];
26
+ }
27
+ /** A source packet is an input to integration, never release/feature acceptance. */
28
+ export interface MeshSourceCandidate {
29
+ version: 1;
30
+ readiness: 'uncommitted' | 'verified_source_candidate';
31
+ taskId: string;
32
+ missionId: string;
33
+ revision: number;
34
+ repoIdentity: string;
35
+ machineId: string;
36
+ canonicalWorktree: string;
37
+ rootBaseSha: string;
38
+ ossBaseSha: string;
39
+ rootCandidateSha?: string;
40
+ ossCandidateSha?: string;
41
+ patchSha256: string;
42
+ manifestSha256: string;
43
+ touchedPaths: string[];
44
+ tests: Array<{
45
+ command: string;
46
+ exit: number;
47
+ logSha256: string;
48
+ }>;
49
+ settledCommands: boolean;
50
+ integrationTaskId: string;
51
+ logicalOverlapTaskIds: string[];
52
+ }
53
+ /** Product B consumes one frozen batch; normal hooks/parity/gates remain required. */
54
+ export interface MeshIntegrationBatchContract {
55
+ version: 1;
56
+ batchId: string;
57
+ candidates: MeshSourceCandidate[];
58
+ integrationOrder: string[];
59
+ approvedRootSha: string;
60
+ approvedOssSha: string;
61
+ bundleInvocationCount: 1;
62
+ requiredGates: string[];
63
+ normalHooksRequired: true;
64
+ independentReviewRequired: true;
65
+ ownerLandingApprovalRequired: true;
66
+ }
67
+ /** These only rank legal targets; targetNodeId/SessionId and requiredTags remain hard. */
68
+ export interface MeshRoutingPreferences {
69
+ nodeIds?: string[];
70
+ providers?: string[];
71
+ }
72
+ export interface MeshReviewConstraint {
73
+ sourceTaskId: string;
74
+ differentProvider: true;
75
+ }
@@ -58,20 +58,20 @@ export declare const SESSION_REBOUND_REASONS: readonly ["restart", "live_holder"
58
58
  export type SessionReboundReason = typeof SESSION_REBOUND_REASONS[number];
59
59
  export declare const SUSPENSION_MODALS: readonly ["approval", "choice"];
60
60
  export type SuspensionModal = typeof SUSPENSION_MODALS[number];
61
- export declare const SUSPENSION_RESOLUTIONS: readonly ["approved", "rejected", "answered"];
61
+ export declare const SUSPENSION_RESOLUTIONS: readonly ["approved", "rejected", "answered", "dismissed"];
62
62
  export type SuspensionResolution = typeof SUSPENSION_RESOLUTIONS[number];
63
- export declare const SUSPENSION_RESOLUTION_VIAS: readonly ["modal_button", "auto_approve", "prompt_answer"];
63
+ export declare const SUSPENSION_RESOLUTION_VIAS: readonly ["modal_button", "auto_approve", "prompt_answer", "modal_disappeared"];
64
64
  export type SuspensionResolutionVia = typeof SUSPENSION_RESOLUTION_VIAS[number];
65
65
  export declare const TURN_END_STRENGTHS: readonly ["genuine", "weak"];
66
66
  export type TurnEndStrength = typeof TURN_END_STRENGTHS[number];
67
67
  /** Why a provider-side weak end was released (was free text `blockReason`). */
68
68
  export declare const TURN_END_BLOCK_REASONS: readonly ["missing_final_assistant", "finalization_timeout", "terminal_block_hard_cap", "decoupled_completion"];
69
69
  export type TurnEndBlockReason = typeof TURN_END_BLOCK_REASONS[number];
70
- export declare const NATIVE_TURN_OUTCOMES: readonly ["completed", "aborted"];
70
+ export declare const NATIVE_TURN_OUTCOMES: readonly ["completed", "aborted", "failed"];
71
71
  export type NativeTurnOutcome = typeof NATIVE_TURN_OUTCOMES[number];
72
72
  export declare const LIVENESS_RESULTS: readonly ["alive", "unknown", "read_failed", "dead"];
73
73
  export type LivenessResult = typeof LIVENESS_RESULTS[number];
74
- export declare const PROVIDER_FAILURES: readonly ["auth_failed", "billing_failed"];
74
+ export declare const PROVIDER_FAILURES: readonly ["auth_failed", "billing_failed", "usage_limit_exceeded", "server_overloaded", "provider_error"];
75
75
  export type ProviderFailure = typeof PROVIDER_FAILURES[number];
76
76
  /**
77
77
  * `daemon_restart` (wiring-unification follow-up, design §5): boot-time
@@ -99,14 +99,14 @@ export type NoProgressObservedStatus = SessionStatus | 'unknown';
99
99
  * Reasons a turn hold exists. Holds are attempt-scoped only (C2: delivery-side
100
100
  * waits are consumer deferral on the `turn.deliver` cursor, not hold rows).
101
101
  */
102
- export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report", "await_end"];
102
+ export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report", "await_end", "report_correction"];
103
103
  export type HoldReason = typeof HOLD_REASONS[number];
104
- export declare const TURN_OUTCOMES: readonly ["completed", "failed", "cancelled"];
104
+ export declare const TURN_OUTCOMES: readonly ["completed", "blocked", "failed", "cancelled"];
105
105
  export type TurnOutcome = typeof TURN_OUTCOMES[number];
106
106
  export declare const COMMIT_STRENGTHS: readonly ["genuine", "weak", "tool_report", "operator"];
107
107
  export type CommitStrength = typeof COMMIT_STRENGTHS[number];
108
108
  /** Every terminal or reclaim reason the ledger writes. Closed: no free-text reasons. */
109
- export declare const TURN_REASONS: readonly ["turn_end", "weak_end_confirmed", "transcript_final", "worker_reported", "finalization_timeout_no_response", "session_error", "provider_auth_failed", "provider_billing_failed", "operator_cancel", "intentional_cleanup", "task_removed", "mission_abandoned", "superseded", "operator_update", "refine_terminal", "validation_terminal", "hard_ceiling", "reclaim_budget_exhausted", "hollow_max_retries", "migration_orphan", "dispatch_refused_session_exited", "dispatch_refused_no_target", "dispatch_refused_unsupported_input", "dispatch_failed", "session_exit", "session_exit_before_turn", "session_dead", "hollow_completion", "assigned_stranded_dispatch_unconfirmed", "delivered_not_consumed_redrive", "delivered_no_turn_deadline"];
109
+ export declare const TURN_REASONS: readonly ["turn_end", "weak_end_confirmed", "transcript_final", "worker_reported", "finalization_timeout_no_response", "weak_end_liveness_unconfirmed", "weak_end_probe_unobserved", "report_correction_expired", "session_error", "provider_auth_failed", "provider_billing_failed", "provider_usage_limit_exceeded", "provider_server_overloaded", "provider_error", "operator_cancel", "intentional_cleanup", "task_removed", "mission_abandoned", "superseded", "operator_update", "refine_terminal", "validation_terminal", "hard_ceiling", "reclaim_budget_exhausted", "hollow_max_retries", "migration_orphan", "dispatch_refused_session_exited", "dispatch_refused_no_target", "dispatch_refused_unsupported_input", "dispatch_failed", "session_exit", "session_exit_before_turn", "session_dead", "hollow_completion", "assigned_stranded_dispatch_unconfirmed", "delivered_not_consumed_redrive", "delivered_no_turn_deadline"];
110
110
  export type TurnReason = typeof TURN_REASONS[number];
111
111
  /**
112
112
  * What a `turn.notify` entry tells a coordinator.
@@ -118,7 +118,7 @@ export type TurnReason = typeof TURN_REASONS[number];
118
118
  * (refine terminal, worktree bootstrap, …) whose text lives only in the
119
119
  * owner's local `turn_events.payload_json` and is rendered at deliver time.
120
120
  */
121
- export declare const NOTIFY_KINDS: readonly ["completed", "failed", "cancelled", "stopped", "approval", "choice", "approval_resolved", "candidate", "no_progress", "progress", "late_completion", "mesh_event"];
121
+ export declare const NOTIFY_KINDS: readonly ["completed", "blocked", "failed", "cancelled", "stopped", "approval", "choice", "approval_resolved", "candidate", "no_progress", "progress", "late_completion", "mesh_event"];
122
122
  export type NotifyKind = typeof NOTIFY_KINDS[number];
123
123
  export interface TurnAttemptRef {
124
124
  attemptId: string;
@@ -149,6 +149,9 @@ export interface EvidenceCoordinatorIdentity {
149
149
  export interface NativeTurnMarkerRef {
150
150
  outcome: NativeTurnOutcome;
151
151
  turnId?: string;
152
+ receivedAt?: number;
153
+ startedAt?: number;
154
+ errorAt?: number;
152
155
  }
153
156
  export interface EvidenceEnvelope {
154
157
  /** Stable id of this observation; dedupe key across republish/replication. */
@@ -220,10 +223,13 @@ export type TurnEvidenceBody = {
220
223
  kind: 'suspension';
221
224
  modal: SuspensionModal;
222
225
  modalKey?: string;
226
+ providerSetup?: boolean;
227
+ clockUnknown?: boolean;
223
228
  } | {
224
229
  kind: 'suspension_resolved';
225
230
  resolution: SuspensionResolution;
226
231
  via: SuspensionResolutionVia;
232
+ modalKey?: string;
227
233
  } | {
228
234
  kind: 'turn_end';
229
235
  strength: TurnEndStrength;
@@ -233,6 +239,7 @@ export type TurnEvidenceBody = {
233
239
  blockReason?: TurnEndBlockReason;
234
240
  releasedByHardCap?: boolean;
235
241
  nativeOutcome?: NativeTurnOutcome;
242
+ nativeMarker?: NativeTurnMarkerRef;
236
243
  live?: LiveTurnPending;
237
244
  /**
238
245
  * Stamped (true only) by the WORKER's daemon when the session holds a live
@@ -268,6 +275,7 @@ export type TurnEvidenceBody = {
268
275
  } | {
269
276
  kind: 'liveness';
270
277
  result: LivenessResult;
278
+ probeStartedAtMs?: number;
271
279
  } | {
272
280
  kind: 'process_exit';
273
281
  exitCode: number | null;
@@ -288,6 +296,9 @@ export type TurnEvidenceBody = {
288
296
  hasHandoffNotes: boolean;
289
297
  branchState?: WorkerBranchState;
290
298
  touchedFileCount?: number;
299
+ } | {
300
+ kind: 'worker_report_correction_requested';
301
+ deliveryId?: string;
291
302
  } | {
292
303
  kind: 'worker_progress';
293
304
  note?: SummaryRef;
@@ -371,14 +382,15 @@ export declare const TURN_EVIDENCE_FIELD_SPECS: TurnEvidenceFieldSpecs;
371
382
  export declare const TURN_EVIDENCE_KINDS: readonly TurnEvidenceKind[];
372
383
  /** Evidence kinds that, on their own, can end a turn (the ones R18/R19/R27 reason about). */
373
384
  export declare const TERMINAL_CLASS_EVIDENCE_KINDS: readonly ["turn_end", "transcript_final", "no_progress", "worker_report", "session_error", "process_exit", "cancel", "operator_status"];
374
- export declare const isTurnEvidenceKind: (value: unknown) => value is "no_progress" | "delivered" | "liveness" | "turn_end" | "transcript_final" | "session_error" | "dispatch_failed" | "dispatch_accepted" | "delivery_refused" | "duplicate_dispatch_refusal" | "session_rebound" | "turn_started" | "suspension" | "suspension_resolved" | "transcript_activity" | "process_exit" | "worker_report" | "worker_progress" | "git_side_effect" | "cancel" | "operator_status" | "coordinator_ack" | "hold_expired";
375
- export declare const isTurnReason: (value: unknown) => value is "intentional_cleanup" | "operator_cancel" | "task_removed" | "mission_abandoned" | "superseded" | "operator_update" | "refine_terminal" | "validation_terminal" | "hard_ceiling" | "turn_end" | "weak_end_confirmed" | "transcript_final" | "worker_reported" | "finalization_timeout_no_response" | "session_error" | "provider_auth_failed" | "provider_billing_failed" | "reclaim_budget_exhausted" | "hollow_max_retries" | "migration_orphan" | "dispatch_refused_session_exited" | "dispatch_refused_no_target" | "dispatch_refused_unsupported_input" | "dispatch_failed" | "session_exit" | "session_exit_before_turn" | "session_dead" | "hollow_completion" | "assigned_stranded_dispatch_unconfirmed" | "delivered_not_consumed_redrive" | "delivered_no_turn_deadline";
376
- export declare const isNotifyKind: (value: unknown) => value is "stopped" | "failed" | "no_progress" | "completed" | "cancelled" | "approval" | "choice" | "approval_resolved" | "candidate" | "progress" | "late_completion" | "mesh_event";
377
- export declare const isTurnOutcome: (value: unknown) => value is "failed" | "completed" | "cancelled";
385
+ export declare const isTurnEvidenceKind: (value: unknown) => value is "no_progress" | "delivered" | "liveness" | "turn_end" | "transcript_final" | "session_error" | "dispatch_failed" | "dispatch_accepted" | "delivery_refused" | "duplicate_dispatch_refusal" | "session_rebound" | "turn_started" | "suspension" | "suspension_resolved" | "transcript_activity" | "process_exit" | "worker_report" | "worker_report_correction_requested" | "worker_progress" | "git_side_effect" | "cancel" | "operator_status" | "coordinator_ack" | "hold_expired";
386
+ export declare const isTurnReason: (value: unknown) => value is "provider_error" | "intentional_cleanup" | "operator_cancel" | "task_removed" | "mission_abandoned" | "superseded" | "operator_update" | "refine_terminal" | "validation_terminal" | "hard_ceiling" | "turn_end" | "weak_end_confirmed" | "transcript_final" | "worker_reported" | "finalization_timeout_no_response" | "weak_end_liveness_unconfirmed" | "weak_end_probe_unobserved" | "report_correction_expired" | "session_error" | "provider_auth_failed" | "provider_billing_failed" | "provider_usage_limit_exceeded" | "provider_server_overloaded" | "reclaim_budget_exhausted" | "hollow_max_retries" | "migration_orphan" | "dispatch_refused_session_exited" | "dispatch_refused_no_target" | "dispatch_refused_unsupported_input" | "dispatch_failed" | "session_exit" | "session_exit_before_turn" | "session_dead" | "hollow_completion" | "assigned_stranded_dispatch_unconfirmed" | "delivered_not_consumed_redrive" | "delivered_no_turn_deadline";
387
+ export declare const isNotifyKind: (value: unknown) => value is "stopped" | "failed" | "blocked" | "no_progress" | "completed" | "cancelled" | "approval" | "choice" | "approval_resolved" | "candidate" | "progress" | "late_completion" | "mesh_event";
388
+ export declare const isTurnOutcome: (value: unknown) => value is "failed" | "blocked" | "completed" | "cancelled";
378
389
  export declare const isCommitStrength: (value: unknown) => value is "operator" | "genuine" | "weak" | "tool_report";
379
390
  export declare function isEvidenceIdentifier(value: unknown): value is string;
380
391
  export declare function isSummaryRef(value: unknown): value is SummaryRef;
381
392
  export declare function isTurnAttemptRef(value: unknown): value is TurnAttemptRef;
393
+ export declare function isNativeMarkerRef(value: unknown): value is NativeTurnMarkerRef;
382
394
  /**
383
395
  * Strict structural guard: known kind, every declared field of the right class,
384
396
  * and NO undeclared field (an unknown key is how free text would sneak in).
@@ -89,6 +89,8 @@ export interface MeshMissionTaskAggregateWire {
89
89
  lastActivityAt: string | null;
90
90
  }
91
91
  export interface MeshMissionStatsWire {
92
+ /** Present on peers that project proven blocked outcomes separately. */
93
+ blocked?: number;
92
94
  missionId: string;
93
95
  taskCount: number;
94
96
  completed: number;
@@ -231,6 +233,8 @@ export interface LedgerQueryEntryWire {
231
233
  payload: Record<string, unknown>;
232
234
  }
233
235
  export interface LedgerQuerySummaryWire {
236
+ /** Absent on older peers; never folded into failed by the decoder. */
237
+ taskBlocked?: number;
234
238
  meshId: string;
235
239
  totalEntries: number;
236
240
  taskDispatched: number;
@@ -65,6 +65,8 @@ export interface QueueQueryResponse {
65
65
  counts?: Record<string, number>;
66
66
  /** `withCounts` + `historicalOlderThanMs`: terminal rows older than that age. */
67
67
  oldHistoricalCount?: number;
68
+ /** Included in counts.blocked, derived from proven worker reports; stored failed rows stay unchanged. */
69
+ legacyReportedBlockedCount?: number;
68
70
  /** `withDependencyHeads`. */
69
71
  dependencyHeads?: readonly QueueDependencyHeadWire[];
70
72
  }
@@ -194,3 +196,20 @@ export interface RecoveryContextQueryResponse {
194
196
  }
195
197
  export declare function decodeRecoveryContextQueryRequest(value: unknown): RecoveryContextQueryRequest | null;
196
198
  export declare function decodeRecoveryContextQueryResponse(value: unknown): RecoveryContextQueryResponse | null;
199
+ export interface QueueReviseRequest {
200
+ v: typeof TURN_IPC_PROTOCOL_VERSION;
201
+ meshId: string;
202
+ taskId: string;
203
+ expectedRevision: number;
204
+ patch: {
205
+ message?: string;
206
+ ownedPaths?: string[];
207
+ phase?: 'source' | 'integration';
208
+ dependsOn?: string[];
209
+ routingPreferences?: import('./task-source-phase').MeshRoutingPreferences;
210
+ review?: import('./task-source-phase').MeshReviewConstraint;
211
+ };
212
+ }
213
+ export type QueueReviseResponse = QueueRequeueResponse;
214
+ export declare function decodeQueueReviseRequest(value: unknown): QueueReviseRequest | null;
215
+ export declare const decodeQueueReviseResponse: typeof decodeQueueRequeueResponse;
@@ -94,6 +94,6 @@ export interface TurnIpcError {
94
94
  }
95
95
  export declare function isTurnIpcError(value: unknown): value is TurnIpcError;
96
96
  /** The complete, closed set of IPC command names this contract defines. */
97
- export declare const TURN_IPC_COMMANDS: readonly ["turn_observe", "mesh_record", "turn_cancel", "operator_status", "turn_query", "mesh_index_query", "mission_upsert", "mission_query", "note_upsert", "note_forget", "tool_call_record", "ledger_query", "mission_list_query", "record_local", "queue_query", "queue_enqueue", "queue_enqueue_batch", "queue_cancel", "queue_requeue", "direct_dispatch_record", "active_work_query", "recovery_context_query", "task_stats_query", "prune_stale_direct", "orphaned_pin_notify"];
97
+ export declare const TURN_IPC_COMMANDS: readonly ["turn_observe", "mesh_record", "turn_cancel", "operator_status", "turn_query", "mesh_index_query", "mission_upsert", "mission_query", "note_upsert", "note_forget", "tool_call_record", "ledger_query", "mission_list_query", "record_local", "queue_query", "queue_enqueue", "queue_enqueue_batch", "queue_cancel", "queue_requeue", "queue_revise", "direct_dispatch_record", "active_work_query", "recovery_context_query", "task_stats_query", "prune_stale_direct", "orphaned_pin_notify"];
98
98
  export type TurnIpcCommand = typeof TURN_IPC_COMMANDS[number];
99
- export declare const isTurnIpcCommand: (value: unknown) => value is "prune_stale_direct" | "operator_status" | "turn_observe" | "mesh_record" | "turn_cancel" | "turn_query" | "mesh_index_query" | "mission_upsert" | "mission_query" | "note_upsert" | "note_forget" | "tool_call_record" | "ledger_query" | "mission_list_query" | "record_local" | "queue_query" | "queue_enqueue" | "queue_enqueue_batch" | "queue_cancel" | "queue_requeue" | "direct_dispatch_record" | "active_work_query" | "recovery_context_query" | "task_stats_query" | "orphaned_pin_notify";
99
+ export declare const isTurnIpcCommand: (value: unknown) => value is "prune_stale_direct" | "operator_status" | "turn_observe" | "mesh_record" | "turn_cancel" | "turn_query" | "mesh_index_query" | "mission_upsert" | "mission_query" | "note_upsert" | "note_forget" | "tool_call_record" | "ledger_query" | "mission_list_query" | "record_local" | "queue_query" | "queue_enqueue" | "queue_enqueue_batch" | "queue_cancel" | "queue_requeue" | "queue_revise" | "direct_dispatch_record" | "active_work_query" | "recovery_context_query" | "task_stats_query" | "orphaned_pin_notify";
package/dist/types.d.ts CHANGED
@@ -120,6 +120,7 @@ export interface MeshNodeIdentified {
120
120
  [key: string]: unknown;
121
121
  }
122
122
  export interface RepoMeshSessionStatus {
123
+ providerApproval?: import('./provider-approval').ProviderApproval | null;
123
124
  sessionId: string;
124
125
  providerType?: string;
125
126
  state?: string;
@@ -30,7 +30,7 @@
30
30
  * the protocol contract the marker demarcates.
31
31
  */
32
32
  import { type MissionBrief } from './mission-brief';
33
- export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff"];
33
+ export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff", "driver_list", "driver_describe", "driver_invoke", "artifact_register"];
34
34
  export type WorkerTool = typeof WORKER_TOOLS[number];
35
35
  export declare function isWorkerTool(value: unknown): value is WorkerTool;
36
36
  /** Stable marker line so a body is never footered twice and tests can find it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/mesh-shared",
3
- "version": "1.0.82-rc.3",
3
+ "version": "1.0.82-rc.30",
4
4
  "description": "ADHDev mesh-shared — pure mesh/git status normalizers shared by daemon-core and web-core",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,22 +11,29 @@
11
11
  * - the daemon-only verbs (launch / MCP-only pull / owner actions) that have
12
12
  * no tool.
13
13
  *
14
- * Assistant tools are a separate set from the mesh tools: none of these names
15
- * belongs in `CANONICAL_MESH_TOOL_NAMES`, and the coordinator/worker MCP modes
16
- * never publish them.
14
+ * Assistant-specific tools remain separate from mesh tools. Driver discovery
15
+ * (list/describe) is shared across all three modes; invoke remains Worker-only.
17
16
  */
18
17
 
19
18
  export const ASSISTANT_TOOLS = [
20
19
  'projects',
20
+ 'environment',
21
+ 'assistant_action',
22
+ 'project_watch',
21
23
  'project_status',
22
24
  'project_send',
23
25
  'project_read',
24
26
  'project_add',
27
+ 'project_attach_remote',
25
28
  'discover_repos',
26
29
  'memory',
27
30
  'skill_view',
28
31
  'skill_manage',
29
32
  'project_note',
33
+ 'launch_defaults',
34
+ 'launch_defaults_set',
35
+ 'driver_list',
36
+ 'driver_describe',
30
37
  ] as const
31
38
 
32
39
  export type AssistantTool = typeof ASSISTANT_TOOLS[number]
@@ -40,16 +47,26 @@ export function isAssistantTool(value: unknown): value is AssistantTool {
40
47
  /** Daemon verb names (§4.4 table + the five verbs below it). */
41
48
  export const ASSISTANT_VERB = {
42
49
  // tool verbs
50
+ driverList: 'driver_list',
51
+ driverDescribe: 'driver_describe',
43
52
  projects: 'assistant_projects',
53
+ environment: 'assistant_environment',
54
+ action: 'assistant_action',
55
+ watch: 'assistant_project_watch',
56
+ executionState: 'assistant_execution_state',
57
+ executionSettings: 'assistant_execution_settings',
44
58
  projectStatus: 'assistant_project_status',
45
59
  projectSend: 'assistant_project_send',
46
60
  projectRead: 'assistant_project_read',
47
61
  projectAdd: 'assistant_project_add',
62
+ projectAttachRemote: 'assistant_project_attach_remote',
48
63
  discoverRepos: 'assistant_discover_repos',
49
64
  memory: 'assistant_memory',
50
65
  skillView: 'assistant_skill_view',
51
66
  skillManage: 'assistant_skill_manage',
52
67
  projectNote: 'assistant_project_note',
68
+ launchDefaults: 'assistant_launch_defaults',
69
+ launchDefaultsSet: 'assistant_launch_defaults_set',
53
70
  // assistant-side daemon verbs without a tool of the same name
54
71
  launch: 'launch_assistant',
55
72
  pendingRelays: 'assistant_pending_relays',
@@ -57,22 +74,33 @@ export const ASSISTANT_VERB = {
57
74
  stagedResolve: 'assistant_staged_resolve',
58
75
  storeAdmin: 'assistant_store_admin',
59
76
  importSkills: 'assistant_import_skills',
77
+ /** Projects the assistant is following (open relay threads) — list / stop following. */
78
+ threads: 'assistant_threads',
79
+ threadClose: 'assistant_thread_close',
60
80
  } as const
61
81
 
62
82
  export type AssistantVerb = typeof ASSISTANT_VERB[keyof typeof ASSISTANT_VERB]
63
83
 
64
84
  /** The daemon verb each assistant tool calls. */
65
85
  export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>> = {
86
+ driver_list: ASSISTANT_VERB.driverList,
87
+ driver_describe: ASSISTANT_VERB.driverDescribe,
66
88
  projects: ASSISTANT_VERB.projects,
89
+ environment: ASSISTANT_VERB.environment,
90
+ assistant_action: ASSISTANT_VERB.action,
91
+ project_watch: ASSISTANT_VERB.watch,
67
92
  project_status: ASSISTANT_VERB.projectStatus,
68
93
  project_send: ASSISTANT_VERB.projectSend,
69
94
  project_read: ASSISTANT_VERB.projectRead,
70
95
  project_add: ASSISTANT_VERB.projectAdd,
96
+ project_attach_remote: ASSISTANT_VERB.projectAttachRemote,
71
97
  discover_repos: ASSISTANT_VERB.discoverRepos,
72
98
  memory: ASSISTANT_VERB.memory,
73
99
  skill_view: ASSISTANT_VERB.skillView,
74
100
  skill_manage: ASSISTANT_VERB.skillManage,
75
101
  project_note: ASSISTANT_VERB.projectNote,
102
+ launch_defaults: ASSISTANT_VERB.launchDefaults,
103
+ launch_defaults_set: ASSISTANT_VERB.launchDefaultsSet,
76
104
  }
77
105
 
78
106
  /**
@@ -81,13 +109,15 @@ export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>
81
109
  * because they cause effects (a coordinator message or launch, a new
82
110
  * project), and MCP clients use readOnlyHint to skip confirmation.
83
111
  */
84
- export const ASSISTANT_WRITE_TOOLS: readonly AssistantTool[] = ['memory', 'skill_manage', 'project_note', 'project_send', 'project_add']
112
+ export const ASSISTANT_WRITE_TOOLS: readonly AssistantTool[] = ['project_watch', 'assistant_action', 'memory', 'skill_manage', 'project_note', 'project_send', 'project_add', 'project_attach_remote', 'launch_defaults_set']
85
113
 
86
114
  /** Owner-only verbs: never reachable from the assistant's MCP server (no `ipc`). */
87
115
  export const ASSISTANT_OWNER_VERBS: readonly AssistantVerb[] = [
116
+ ASSISTANT_VERB.executionSettings,
88
117
  ASSISTANT_VERB.stagedResolve,
89
118
  ASSISTANT_VERB.storeAdmin,
90
119
  ASSISTANT_VERB.importSkills,
120
+ ASSISTANT_VERB.threadClose,
91
121
  ]
92
122
 
93
123
  /** The only assistant verbs accepted while an idle review turn is open (§4.10.7). */
@@ -98,8 +128,9 @@ export const ASSISTANT_REVIEW_TURN_VERBS: readonly AssistantVerb[] = [
98
128
  ASSISTANT_VERB.projectNote,
99
129
  ]
100
130
 
101
- /**
102
- * Env var the daemon sets on the assistant CLI's process (§4.5), read by the
131
+ import { canonicalDaemonId } from './daemon-normalize'
132
+
133
+ /** Env var the daemon sets on the assistant CLI's process (§4.5), read by the
103
134
  * assistant MCP server and forwarded on every verb as
104
135
  * `ASSISTANT_SESSION_ID_ARG`. Routing / ownership only — never an auth gate.
105
136
  */
@@ -107,3 +138,213 @@ export const ASSISTANT_SESSION_ID_ENV = 'ADHDEV_ASSISTANT_SESSION_ID'
107
138
 
108
139
  /** Request arg carrying the calling assistant session id. */
109
140
  export const ASSISTANT_SESSION_ID_ARG = 'assistantSessionId'
141
+
142
+ // ─── Remote Assistant Project Follow & Host-Authoritative Grant (rc24) ──────
143
+
144
+ /**
145
+ * Conceptual single-assistant slot in the verified account namespace.
146
+ * Fixed to `'personal'` — one logical assistant per account with one
147
+ * owner-selected active execution daemon binding per project ACL.
148
+ */
149
+ export const ASSISTANT_ACCOUNT_SLOT = 'personal' as const
150
+ export type AssistantAccountSlot = typeof ASSISTANT_ACCOUNT_SLOT
151
+
152
+ /**
153
+ * Daemon commands for remote project discovery, host ACL grant/revoke, and
154
+ * assistant-side follow/unfollow. Registered outside `ASSISTANT_VERB` so the
155
+ * assistant MCP server tool/verb parity table remains untouched.
156
+ */
157
+ export const ASSISTANT_REMOTE_PROJECT_COMMAND = 'assistant_remote_project' as const
158
+ export const ASSISTANT_REMOTE_PROJECT_CATALOG_COMMAND = 'assistant_remote_project_catalog' as const
159
+ export const ASSISTANT_REMOTE_PROJECT_ACCESS_COMMAND = 'assistant_remote_project_access' as const
160
+ export const ASSISTANT_REMOTE_FOLLOW_COMMAND = 'assistant_remote_follow' as const
161
+
162
+ /**
163
+ * Narrow operations a non-roster follower deputy is permitted to invoke on
164
+ * `assistant_remote_project` when the host's `assistantRemoteAccess` ACL is active.
165
+ * `note`, `terminal`, and `inventory` remain strictly roster-member-only.
166
+ */
167
+ export const ASSISTANT_FOLLOWER_ALLOWED_OPS = ['status', 'read', 'poll', 'report', 'send'] as const
168
+ export type AssistantFollowerAllowedOp = typeof ASSISTANT_FOLLOWER_ALLOWED_OPS[number]
169
+
170
+ const ASSISTANT_FOLLOWER_ALLOWED_OP_SET: ReadonlySet<string> = new Set(ASSISTANT_FOLLOWER_ALLOWED_OPS)
171
+
172
+ export function isAssistantFollowerAllowedOp(value: unknown): value is AssistantFollowerAllowedOp {
173
+ return typeof value === 'string' && ASSISTANT_FOLLOWER_ALLOWED_OP_SET.has(value)
174
+ }
175
+
176
+ /**
177
+ * Host-authoritative narrow project ACL persisted on the hosting daemon's
178
+ * `LocalMeshEntry` in `meshes.json`. Revocation increments `grantRevision`
179
+ * and retains a `'revoked'` tombstone so stale follower caches cannot replay.
180
+ */
181
+ export interface AssistantRemoteAccessBinding {
182
+ hostDaemonId: string
183
+ meshId: string
184
+ accountSlot: AssistantAccountSlot
185
+ activeDaemonId: string
186
+ bindingRevision: number
187
+ grantRevision: number
188
+ status: 'active' | 'revoked'
189
+ allowedOps: readonly AssistantFollowerAllowedOp[]
190
+ allowCoordinatorLaunch?: boolean
191
+ grantedAt: number
192
+ revokedAt?: number
193
+ }
194
+
195
+ /**
196
+ * Durable owner-binding receipt persisted in the assistant daemon's
197
+ * `assistant.json`. Survives ordinary daemon restarts and session/provider
198
+ * replacements (`bindSession`), while missing/corrupt/mismatched receipts
199
+ * fail closed until owner reconciliation.
200
+ */
201
+ export interface AssistantOwnerBindingReceipt {
202
+ accountSlot: AssistantAccountSlot
203
+ daemonId: string
204
+ bindingRevision: number
205
+ boundAt: number | string
206
+ }
207
+
208
+ export type FollowedRemoteProjectState = 'followed' | 'unfollowed' | 'revoked'
209
+
210
+ export type RemoteAccessState =
211
+ | 'not_granted'
212
+ | 'granted_unfollowed'
213
+ | 'followed'
214
+ | 'revoked'
215
+ | 'host_unreachable'
216
+ | 'binding_mismatch'
217
+
218
+ /**
219
+ * Assistant-local followed remote project reference persisted in `assistant.json`.
220
+ * Never injected into `meshes.json`, `mesh-host-records.json`, `mesh-node-state-push.json`,
221
+ * or `inlineMeshCache`.
222
+ */
223
+ export interface FollowedRemoteProjectEntry {
224
+ /** Canonical key: `${canonicalDaemonId(hostDaemonId)}::${meshId}` */
225
+ key: string
226
+ hostDaemonId: string
227
+ hostLabel?: string
228
+ meshId: string
229
+ name: string
230
+ slug: string
231
+ sanitizedRepoIdentity: string | null
232
+ repoIdentity?: string
233
+ accountSlot: AssistantAccountSlot
234
+ bindingRevision: number
235
+ grantRevision: number
236
+ allowCoordinatorLaunch?: boolean
237
+ state: FollowedRemoteProjectState
238
+ followedAt: number | string
239
+ updatedAt: number | string
240
+ unfollowedAt?: number | string
241
+ revokedAt?: number | string
242
+ revokedReason?: string
243
+ lastCursor?: string | null
244
+ coordinatorSessionId?: string | null
245
+ lastErrorCode?: string
246
+ lastObserved?: {
247
+ observedAt: number | string
248
+ coordinatorState?: string
249
+ coordinatorMissing?: boolean
250
+ coordinatorPresent?: boolean
251
+ lastHeadline?: string
252
+ summary?: string
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Content-free canonical catalog metadata returned by `assistant_remote_project_catalog`
258
+ * for meshes actually hosted on the queried remote daemon.
259
+ */
260
+ export interface RemoteProjectCatalogItem {
261
+ hostDaemonId: string
262
+ meshId: string
263
+ name: string
264
+ sanitizedRepoIdentity: string | null
265
+ coordinatorState?: 'none' | 'idle' | 'working' | 'waiting' | string
266
+ allowCoordinatorLaunch?: boolean
267
+ capabilities: {
268
+ status: true
269
+ read: true
270
+ poll: true
271
+ report: true
272
+ send: true
273
+ coordinatorPresent?: boolean
274
+ allowCoordinatorLaunch?: boolean
275
+ }
276
+ observedAt: number | string
277
+ }
278
+
279
+ /** Build the canonical `(hostDaemonId, meshId)` key used across assistant follow state. */
280
+ export function followedRemoteProjectKey(hostDaemonId: string, meshId: string): string {
281
+ const canonHost = canonicalDaemonId(hostDaemonId) ?? hostDaemonId.trim()
282
+ return `${canonHost}::${meshId.trim()}`
283
+ }
284
+
285
+ /**
286
+ * Sanitize a repository identity or remote URL into a content-free `host/path`
287
+ * identifier suitable for remote discovery catalogs. Strips credentials, query
288
+ * strings, fragments, and `.git` suffixes, and rejects local filesystem paths.
289
+ */
290
+ export function sanitizeRemoteRepoIdentity(raw: string | null | undefined): string | null {
291
+ if (typeof raw !== 'string') return null
292
+ const trimmed = raw.trim()
293
+ if (!trimmed) return null
294
+
295
+ // Reject local filesystem paths (Unix absolute/relative/home, Windows drive/UNC, file:// URLs).
296
+ if (
297
+ trimmed.startsWith('/')
298
+ || trimmed.startsWith('./')
299
+ || trimmed.startsWith('../')
300
+ || trimmed.startsWith('~/')
301
+ || trimmed.startsWith('\\')
302
+ || /^[a-zA-Z]:[\\/]/.test(trimmed)
303
+ || /^file:/i.test(trimmed)
304
+ ) {
305
+ return null
306
+ }
307
+
308
+ const withoutQuery = trimmed.replace(/[?#].*$/, '').replace(/\/+$/, '')
309
+ if (!withoutQuery) return null
310
+
311
+ let host = ''
312
+ let repoPath = ''
313
+
314
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(withoutQuery)) {
315
+ try {
316
+ const url = new URL(withoutQuery)
317
+ if (url.protocol === 'file:') return null
318
+ host = url.hostname.toLowerCase()
319
+ repoPath = decodeURIComponent(url.pathname)
320
+ .replace(/^\/+|\/+$/g, '')
321
+ .replace(/\.git$/i, '')
322
+ } catch {
323
+ return null
324
+ }
325
+ } else {
326
+ const scpMatch = withoutQuery.match(/^(?:[^@/:]+@)?(\[[^\]]+\]|[^/:]+):(.+)$/)
327
+ if (scpMatch) {
328
+ host = scpMatch[1].replace(/^\[|\]$/g, '').toLowerCase()
329
+ repoPath = scpMatch[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
330
+ } else {
331
+ const slash = withoutQuery.indexOf('/')
332
+ if (slash > 0) {
333
+ const rawHost = withoutQuery.slice(0, slash)
334
+ const atIdx = rawHost.lastIndexOf('@')
335
+ host = (atIdx >= 0 ? rawHost.slice(atIdx + 1) : rawHost).toLowerCase()
336
+ repoPath = withoutQuery.slice(slash + 1).replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
337
+ }
338
+ }
339
+ }
340
+
341
+ if (!host || !repoPath) return null
342
+ if (!host.includes('.') && host !== 'localhost') return null
343
+ if (/[\s@:\\]/.test(host)) return null
344
+ const segments = repoPath.split('/').filter(Boolean)
345
+ if (segments.length === 0) return null
346
+ if (segments.some(seg => seg === '.' || seg === '..' || /[\s@:\\]/.test(seg))) return null
347
+
348
+ return `${host}/${segments.join('/')}`.slice(0, 200)
349
+ }
350
+
@@ -108,13 +108,15 @@ export function isPhantomDaemonEntry(
108
108
  export function machineCoreFromDaemonId(id: string | null | undefined): string | undefined {
109
109
  const trimmed = readString(id)
110
110
  if (!trimmed) return undefined
111
+ const lower = trimmed.toLowerCase()
111
112
  for (const prefix of DAEMON_ID_PREFIXES) {
112
- if (trimmed.startsWith(prefix)) {
113
+ if (lower.startsWith(prefix)) {
113
114
  const core = trimmed.slice(prefix.length).trim()
114
- return core || undefined
115
+ if (!core) return undefined
116
+ return /^mach_/i.test(core) ? `mach_${core.slice(5)}` : core
115
117
  }
116
118
  }
117
- return trimmed
119
+ return /^mach_/i.test(trimmed) ? `mach_${trimmed.slice(5)}` : trimmed
118
120
  }
119
121
 
120
122
  /**
@@ -144,8 +146,8 @@ export function machineCoreFromDaemonId(id: string | null | undefined): string |
144
146
  export function canonicalDaemonId(id: string | null | undefined): string | undefined {
145
147
  const core = machineCoreFromDaemonId(id)
146
148
  if (!core) return undefined
147
- if (!core.startsWith('mach_')) return core
148
- return `daemon_${core}`
149
+ if (!/^mach_/i.test(core)) return core
150
+ return `daemon_mach_${core.slice(5)}`
149
151
  }
150
152
 
151
153
  /** True when both ids resolve to the same machine core — i.e. they are the same