@adhdev/mesh-shared 1.0.76-rc.1 → 1.0.76-rc.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.
@@ -223,7 +223,6 @@ export declare function normalizeMeshNodeFacts(raw: unknown): MeshNodeFacts | un
223
223
  *
224
224
  * Known non-members and why, so this is not re-litigated per surface:
225
225
  * - cursor-cli — permanently impossible; no personal usage API exists
226
- * - hermes-cli — no model-axis quota to report
227
226
  *
228
227
  * ★grok-cli WAS listed here as impossible and is not: that verdict came from
229
228
  * probing `api.x.ai` / `management-api.x.ai` (the team-API billing axis, which
@@ -93,8 +93,12 @@ export interface InputEnvelopeWire {
93
93
  * `'mesh'` (flagged as an open question in phase-D-plan.md §7.3 risk 5) —
94
94
  * kept here unchanged pending an owner decision; dropping a union member is
95
95
  * a decision for whoever lands D-daemon/D2, not this workstream.
96
+ *
97
+ * `'assistant'`: the assistant layer's `project_send` into a project
98
+ * coordinator (docs/design/2026-10-07-assistant-layer.md §4.3 step 4). A label
99
+ * only — it is not an auth signal and selects no policy of its own.
96
100
  */
97
- export type OutboundMessageOrigin = 'dashboard' | 'mcp' | 'mesh' | 'api' | 'cli';
101
+ export type OutboundMessageOrigin = 'dashboard' | 'mcp' | 'mesh' | 'api' | 'cli' | 'assistant';
98
102
  /**
99
103
  * How this message should be admitted when the session is not idle.
100
104
  * Replaces the loose `sendNow`/`interrupt`/`force`/`forceSend` booleans (and
@@ -188,7 +192,7 @@ export interface OutboundMessage {
188
192
  }
189
193
  export declare function mintMessageId(now?: number): string;
190
194
  export declare function isMessageId(value: unknown): value is string;
191
- export declare const OUTBOUND_MESSAGE_ORIGINS: readonly ["dashboard", "mcp", "mesh", "api", "cli"];
195
+ export declare const OUTBOUND_MESSAGE_ORIGINS: readonly ["dashboard", "mcp", "mesh", "api", "cli", "assistant"];
192
196
  export declare function isOutboundMessageOrigin(value: unknown): value is OutboundMessageOrigin;
193
197
  export declare const SEND_POLICY_MODES: readonly ["queue", "send_now", "interrupt"];
194
198
  export declare function isSendPolicyMode(value: unknown): value is SendPolicy['mode'];
@@ -25,8 +25,10 @@ export declare function isModelAxisSource(value: unknown): value is ModelAxisSou
25
25
  /**
26
26
  * Which surface started the session. `restore` means "re-attached to a hosted
27
27
  * runtime that outlived a daemon restart" — the axis sources are preserved.
28
+ * `assistant` is the assistant layer's own session (`launch_assistant`,
29
+ * docs/design/2026-10-07-assistant-layer.md §4.5).
28
30
  */
29
- export declare const SESSION_LAUNCHED_BY: readonly ["dashboard", "mesh", "cli", "api", "restore"];
31
+ export declare const SESSION_LAUNCHED_BY: readonly ["dashboard", "mesh", "cli", "api", "restore", "assistant"];
30
32
  export type SessionLaunchedBy = typeof SESSION_LAUNCHED_BY[number];
31
33
  export declare function isSessionLaunchedBy(value: unknown): value is SessionLaunchedBy;
32
34
  /** How a value entered the axis history. */
@@ -58,8 +58,8 @@ export interface CliSlotRecipe {
58
58
  * invented heuristic. Replace wholesale once usage data (success rate, cost,
59
59
  * turn latency per provider×difficulty) can drive it.
60
60
  *
61
- * Every entry except `hermes-cli` reflects a real configured slot. `hermes-cli`
62
- * is a conservative GUESS — see its `provisional` flag.
61
+ * Every entry reflects a real configured slot. A provider absent from the table
62
+ * falls back to {@link UNKNOWN_CLI_SLOT_RECIPE}, the only `provisional` recipe.
63
63
  */
64
64
  export declare const CLI_SLOT_RECIPES: Readonly<Record<string, readonly CliSlotRecipe[]>>;
65
65
  /**
@@ -74,11 +74,13 @@ export type LivenessResult = typeof LIVENESS_RESULTS[number];
74
74
  export declare const PROVIDER_FAILURES: readonly ["auth_failed", "billing_failed"];
75
75
  export type ProviderFailure = typeof PROVIDER_FAILURES[number];
76
76
  /**
77
- * `daemon_restart` (wiring-unification follow-up, design §5): a boot-time
78
- * reconciliation observes this for a plain attempt whose session is gone
79
- * after a restart — the daemon never saw the session's own exit, so it is
80
- * not `provider_error`/`adapter_error`/etc.; it is the daemon itself
81
- * reporting "this session no longer exists".
77
+ * `daemon_restart` (wiring-unification follow-up, design §5): boot-time
78
+ * reconciliation observes this for a plain attempt that was open when the
79
+ * daemon restarted — either its session is gone, or the session was restored
80
+ * live but the turn in flight was cut by the restart. The daemon never saw
81
+ * the session's own exit or commit, so it is not `provider_error` /
82
+ * `adapter_error` / etc.; it is the daemon itself closing a turn it can no
83
+ * longer follow.
82
84
  */
83
85
  export declare const SESSION_ERROR_REASONS: readonly ["provider_error", "adapter_error", "spawn_failed", "auth_failed", "billing_failed", "unknown", "daemon_restart"];
84
86
  export type SessionErrorReason = typeof SESSION_ERROR_REASONS[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/mesh-shared",
3
- "version": "1.0.76-rc.1",
3
+ "version": "1.0.76-rc.2",
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",
@@ -0,0 +1,109 @@
1
+ /**
2
+ * assistant-protocol — the assistant layer's tool and daemon-verb names
3
+ * (docs/design/2026-10-07-assistant-layer.md §4.4).
4
+ *
5
+ * The single source for both halves of the assistant contract, like
6
+ * `WORKER_TOOLS` is for workers:
7
+ * - `ASSISTANT_TOOLS` — exactly what `adhdev mcp --assistant` will advertise
8
+ * (an mcp-server parity test pins ListTools to this tuple once that mode
9
+ * lands);
10
+ * - `ASSISTANT_TOOL_VERBS` — the daemon command each tool calls;
11
+ * - the daemon-only verbs (launch / MCP-only pull / owner actions) that have
12
+ * no tool.
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.
17
+ */
18
+
19
+ export const ASSISTANT_TOOLS = [
20
+ 'projects',
21
+ 'project_status',
22
+ 'project_send',
23
+ 'project_read',
24
+ 'project_add',
25
+ 'discover_repos',
26
+ 'memory',
27
+ 'skill_view',
28
+ 'skill_manage',
29
+ 'project_note',
30
+ ] as const
31
+
32
+ export type AssistantTool = typeof ASSISTANT_TOOLS[number]
33
+
34
+ const ASSISTANT_TOOL_SET: ReadonlySet<string> = new Set(ASSISTANT_TOOLS)
35
+
36
+ export function isAssistantTool(value: unknown): value is AssistantTool {
37
+ return typeof value === 'string' && ASSISTANT_TOOL_SET.has(value)
38
+ }
39
+
40
+ /** Daemon verb names (§4.4 table + the five verbs below it). */
41
+ export const ASSISTANT_VERB = {
42
+ // tool verbs
43
+ projects: 'assistant_projects',
44
+ projectStatus: 'assistant_project_status',
45
+ projectSend: 'assistant_project_send',
46
+ projectRead: 'assistant_project_read',
47
+ projectAdd: 'assistant_project_add',
48
+ discoverRepos: 'assistant_discover_repos',
49
+ memory: 'assistant_memory',
50
+ skillView: 'assistant_skill_view',
51
+ skillManage: 'assistant_skill_manage',
52
+ projectNote: 'assistant_project_note',
53
+ // assistant-side daemon verbs without a tool of the same name
54
+ launch: 'launch_assistant',
55
+ pendingRelays: 'assistant_pending_relays',
56
+ // owner verbs (dashboard only)
57
+ stagedResolve: 'assistant_staged_resolve',
58
+ storeAdmin: 'assistant_store_admin',
59
+ importSkills: 'assistant_import_skills',
60
+ } as const
61
+
62
+ export type AssistantVerb = typeof ASSISTANT_VERB[keyof typeof ASSISTANT_VERB]
63
+
64
+ /** The daemon verb each assistant tool calls. */
65
+ export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>> = {
66
+ projects: ASSISTANT_VERB.projects,
67
+ project_status: ASSISTANT_VERB.projectStatus,
68
+ project_send: ASSISTANT_VERB.projectSend,
69
+ project_read: ASSISTANT_VERB.projectRead,
70
+ project_add: ASSISTANT_VERB.projectAdd,
71
+ discover_repos: ASSISTANT_VERB.discoverRepos,
72
+ memory: ASSISTANT_VERB.memory,
73
+ skill_view: ASSISTANT_VERB.skillView,
74
+ skill_manage: ASSISTANT_VERB.skillManage,
75
+ project_note: ASSISTANT_VERB.projectNote,
76
+ }
77
+
78
+ /**
79
+ * Tools annotated as writes; every other assistant tool is read-only. §4.4
80
+ * lists the three store writes; project_send and project_add are here too
81
+ * because they cause effects (a coordinator message or launch, a new
82
+ * project), and MCP clients use readOnlyHint to skip confirmation.
83
+ */
84
+ export const ASSISTANT_WRITE_TOOLS: readonly AssistantTool[] = ['memory', 'skill_manage', 'project_note', 'project_send', 'project_add']
85
+
86
+ /** Owner-only verbs: never reachable from the assistant's MCP server (no `ipc`). */
87
+ export const ASSISTANT_OWNER_VERBS: readonly AssistantVerb[] = [
88
+ ASSISTANT_VERB.stagedResolve,
89
+ ASSISTANT_VERB.storeAdmin,
90
+ ASSISTANT_VERB.importSkills,
91
+ ]
92
+
93
+ /** The only assistant verbs accepted while an idle review turn is open (§4.10.7). */
94
+ export const ASSISTANT_REVIEW_TURN_VERBS: readonly AssistantVerb[] = [
95
+ ASSISTANT_VERB.memory,
96
+ ASSISTANT_VERB.skillView,
97
+ ASSISTANT_VERB.skillManage,
98
+ ASSISTANT_VERB.projectNote,
99
+ ]
100
+
101
+ /**
102
+ * Env var the daemon sets on the assistant CLI's process (§4.5), read by the
103
+ * assistant MCP server and forwarded on every verb as
104
+ * `ASSISTANT_SESSION_ID_ARG`. Routing / ownership only — never an auth gate.
105
+ */
106
+ export const ASSISTANT_SESSION_ID_ENV = 'ADHDEV_ASSISTANT_SESSION_ID'
107
+
108
+ /** Request arg carrying the calling assistant session id. */
109
+ export const ASSISTANT_SESSION_ID_ARG = 'assistantSessionId'
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export * from './session-status'
33
33
  export * from './mesh-vocabulary'
34
34
  export * from './outbound-message'
35
35
  export * from './worker-protocol'
36
+ export * from './assistant-protocol'
36
37
  export * from './protocol'
37
38
  export * from './turn-evidence'
38
39
  export * from './session-launch'
package/src/node-facts.ts CHANGED
@@ -238,7 +238,6 @@ export function normalizeMeshNodeFacts(raw: unknown): MeshNodeFacts | undefined
238
238
  *
239
239
  * Known non-members and why, so this is not re-litigated per surface:
240
240
  * - cursor-cli — permanently impossible; no personal usage API exists
241
- * - hermes-cli — no model-axis quota to report
242
241
  *
243
242
  * ★grok-cli WAS listed here as impossible and is not: that verdict came from
244
243
  * probing `api.x.ai` / `management-api.x.ai` (the team-API billing axis, which
@@ -113,8 +113,12 @@ export interface InputEnvelopeWire {
113
113
  * `'mesh'` (flagged as an open question in phase-D-plan.md §7.3 risk 5) —
114
114
  * kept here unchanged pending an owner decision; dropping a union member is
115
115
  * a decision for whoever lands D-daemon/D2, not this workstream.
116
+ *
117
+ * `'assistant'`: the assistant layer's `project_send` into a project
118
+ * coordinator (docs/design/2026-10-07-assistant-layer.md §4.3 step 4). A label
119
+ * only — it is not an auth signal and selects no policy of its own.
116
120
  */
117
- export type OutboundMessageOrigin = 'dashboard' | 'mcp' | 'mesh' | 'api' | 'cli'
121
+ export type OutboundMessageOrigin = 'dashboard' | 'mcp' | 'mesh' | 'api' | 'cli' | 'assistant'
118
122
 
119
123
  /**
120
124
  * How this message should be admitted when the session is not idle.
@@ -243,7 +247,7 @@ export function isMessageId(value: unknown): value is string {
243
247
 
244
248
  // ─── Guards ─────────────────────────────────────────────────────────────────
245
249
 
246
- export const OUTBOUND_MESSAGE_ORIGINS = ['dashboard', 'mcp', 'mesh', 'api', 'cli'] as const satisfies readonly OutboundMessageOrigin[]
250
+ export const OUTBOUND_MESSAGE_ORIGINS = ['dashboard', 'mcp', 'mesh', 'api', 'cli', 'assistant'] as const satisfies readonly OutboundMessageOrigin[]
247
251
 
248
252
  export function isOutboundMessageOrigin(value: unknown): value is OutboundMessageOrigin {
249
253
  return typeof value === 'string' && (OUTBOUND_MESSAGE_ORIGINS as readonly string[]).includes(value)
@@ -54,8 +54,10 @@ export function isModelAxisSource(value: unknown): value is ModelAxisSource {
54
54
  /**
55
55
  * Which surface started the session. `restore` means "re-attached to a hosted
56
56
  * runtime that outlived a daemon restart" — the axis sources are preserved.
57
+ * `assistant` is the assistant layer's own session (`launch_assistant`,
58
+ * docs/design/2026-10-07-assistant-layer.md §4.5).
57
59
  */
58
- export const SESSION_LAUNCHED_BY = ['dashboard', 'mesh', 'cli', 'api', 'restore'] as const
60
+ export const SESSION_LAUNCHED_BY = ['dashboard', 'mesh', 'cli', 'api', 'restore', 'assistant'] as const
59
61
 
60
62
  export type SessionLaunchedBy = typeof SESSION_LAUNCHED_BY[number]
61
63
 
@@ -64,8 +64,8 @@ export interface CliSlotRecipe {
64
64
  * invented heuristic. Replace wholesale once usage data (success rate, cost,
65
65
  * turn latency per provider×difficulty) can drive it.
66
66
  *
67
- * Every entry except `hermes-cli` reflects a real configured slot. `hermes-cli`
68
- * is a conservative GUESS — see its `provisional` flag.
67
+ * Every entry reflects a real configured slot. A provider absent from the table
68
+ * falls back to {@link UNKNOWN_CLI_SLOT_RECIPE}, the only `provisional` recipe.
69
69
  */
70
70
  export const CLI_SLOT_RECIPES: Readonly<Record<string, readonly CliSlotRecipe[]>> = Object.freeze<Record<string, readonly CliSlotRecipe[]>>({
71
71
  'claude-cli': [
@@ -115,18 +115,6 @@ export const CLI_SLOT_RECIPES: Readonly<Record<string, readonly CliSlotRecipe[]>
115
115
  rationale: 'Easy tasks only; auto model selection.',
116
116
  },
117
117
  ],
118
- 'hermes-cli': [
119
- {
120
- difficulty: ['medium'],
121
- maxParallel: 2,
122
- provisional: true,
123
- // NOTE: ESTIMATE, NOT OBSERVED. hermes-cli is absent from the live
124
- // slot configuration this table was seeded from, so `medium` is a
125
- // conservative placement rather than a transcription. Revisit once
126
- // it has real usage data.
127
- rationale: 'ESTIMATE — no live slot to transcribe; conservative mid placement. Adjust after real use.',
128
- },
129
- ],
130
118
  })
131
119
 
132
120
  /**
@@ -193,7 +181,7 @@ function slotKey(slot: NodeCapabilitySlot): string {
193
181
  [...(slot.difficulty ?? [])].sort().join('|'),
194
182
  [...(slot.capability ?? [])].sort().join('|'),
195
183
  slot.maxParallel ?? '',
196
- ].join('')
184
+ ].join('\u0000')
197
185
  }
198
186
 
199
187
  /** Dedupe detected providers by type, preserving first-seen order. */
@@ -107,11 +107,13 @@ export const PROVIDER_FAILURES = ['auth_failed', 'billing_failed'] as const
107
107
  export type ProviderFailure = typeof PROVIDER_FAILURES[number]
108
108
 
109
109
  /**
110
- * `daemon_restart` (wiring-unification follow-up, design §5): a boot-time
111
- * reconciliation observes this for a plain attempt whose session is gone
112
- * after a restart — the daemon never saw the session's own exit, so it is
113
- * not `provider_error`/`adapter_error`/etc.; it is the daemon itself
114
- * reporting "this session no longer exists".
110
+ * `daemon_restart` (wiring-unification follow-up, design §5): boot-time
111
+ * reconciliation observes this for a plain attempt that was open when the
112
+ * daemon restarted — either its session is gone, or the session was restored
113
+ * live but the turn in flight was cut by the restart. The daemon never saw
114
+ * the session's own exit or commit, so it is not `provider_error` /
115
+ * `adapter_error` / etc.; it is the daemon itself closing a turn it can no
116
+ * longer follow.
115
117
  */
116
118
  export const SESSION_ERROR_REASONS = ['provider_error', 'adapter_error', 'spawn_failed', 'auth_failed', 'billing_failed', 'unknown', 'daemon_restart'] as const
117
119
  export type SessionErrorReason = typeof SESSION_ERROR_REASONS[number]