@adhdev/mesh-shared 1.0.82-rc.23 → 1.0.82-rc.24

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.
@@ -18,10 +18,10 @@
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_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_attach_remote", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_config", "mesh_init", "mesh_cleanup_sessions", "mesh_task_history", "mesh_ledger_query", "mesh_note", "mesh_reconcile_ledger", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_node_slots", "driver_list", "driver_describe"];
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_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_attach_remote", "mesh_clone_node", "mesh_remove_node", "mesh_cleanup_worktree_nodes", "mesh_refine_node", "mesh_refine_batch", "mesh_config", "mesh_init", "mesh_cleanup_sessions", "mesh_task_history", "mesh_ledger_query", "mesh_note", "mesh_reconcile_ledger", "mesh_mission_upsert", "mesh_mission_list", "mesh_review_inbox", "mesh_node_slots", "driver_list", "driver_describe", "artifact_list"];
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: 43;
24
+ export declare const CANONICAL_MESH_TOOL_COUNT: 44;
25
25
  /**
26
26
  * Tool names retired by the 2026-09-26 tool-surface consolidation (60 → 48).
27
27
  * Every capability behind them is still reachable: each retired name maps to the
@@ -34,7 +34,7 @@ export type ChunkFrame<T extends string> = {
34
34
  export declare function isChunkType(value: unknown): value is `${string}_chunk`;
35
35
  export declare const DASHBOARD_TO_DAEMON_P2P_TYPES: readonly ["ping", "pong", "command", "input", "pty_input", "pty_resize", "screenshot_start", "screenshot_stop", "subscribe", "unsubscribe", "seqscribe_session_interest", "read", "write", "list"];
36
36
  export type DashboardToDaemonP2PType = typeof DASHBOARD_TO_DAEMON_P2P_TYPES[number];
37
- export declare const isDashboardToDaemonP2PType: (value: unknown) => value is "list" | "command" | "ping" | "pong" | "input" | "pty_input" | "pty_resize" | "screenshot_start" | "screenshot_stop" | "subscribe" | "unsubscribe" | "seqscribe_session_interest" | "read" | "write";
37
+ export declare const isDashboardToDaemonP2PType: (value: unknown) => value is "read" | "list" | "command" | "ping" | "pong" | "input" | "pty_input" | "pty_resize" | "screenshot_start" | "screenshot_stop" | "subscribe" | "unsubscribe" | "seqscribe_session_interest" | "write";
38
38
  export type DashboardToDaemonChunkType = `${DashboardToDaemonP2PType}_chunk`;
39
39
  export declare function isDashboardToDaemonChunkType(value: unknown): value is DashboardToDaemonChunkType;
40
40
  export type P2PPingMsg = {
@@ -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", "driver_list", "driver_describe", "driver_invoke"];
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.23",
3
+ "version": "1.0.82-rc.24",
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",
@@ -128,8 +128,9 @@ export const ASSISTANT_REVIEW_TURN_VERBS: readonly AssistantVerb[] = [
128
128
  ASSISTANT_VERB.projectNote,
129
129
  ]
130
130
 
131
- /**
132
- * 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
133
134
  * assistant MCP server and forwarded on every verb as
134
135
  * `ASSISTANT_SESSION_ID_ARG`. Routing / ownership only — never an auth gate.
135
136
  */
@@ -137,3 +138,208 @@ export const ASSISTANT_SESSION_ID_ENV = 'ADHDEV_ASSISTANT_SESSION_ID'
137
138
 
138
139
  /** Request arg carrying the calling assistant session id. */
139
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
+ grantedAt: number
191
+ revokedAt?: number
192
+ }
193
+
194
+ /**
195
+ * Durable owner-binding receipt persisted in the assistant daemon's
196
+ * `assistant.json`. Survives ordinary daemon restarts and session/provider
197
+ * replacements (`bindSession`), while missing/corrupt/mismatched receipts
198
+ * fail closed until owner reconciliation.
199
+ */
200
+ export interface AssistantOwnerBindingReceipt {
201
+ accountSlot: AssistantAccountSlot
202
+ daemonId: string
203
+ bindingRevision: number
204
+ boundAt: number | string
205
+ }
206
+
207
+ export type FollowedRemoteProjectState = 'followed' | 'unfollowed' | 'revoked'
208
+
209
+ export type RemoteAccessState =
210
+ | 'not_granted'
211
+ | 'granted_unfollowed'
212
+ | 'followed'
213
+ | 'revoked'
214
+ | 'host_unreachable'
215
+ | 'binding_mismatch'
216
+
217
+ /**
218
+ * Assistant-local followed remote project reference persisted in `assistant.json`.
219
+ * Never injected into `meshes.json`, `mesh-host-records.json`, `mesh-node-state-push.json`,
220
+ * or `inlineMeshCache`.
221
+ */
222
+ export interface FollowedRemoteProjectEntry {
223
+ /** Canonical key: `${canonicalDaemonId(hostDaemonId)}::${meshId}` */
224
+ key: string
225
+ hostDaemonId: string
226
+ hostLabel?: string
227
+ meshId: string
228
+ name: string
229
+ slug: string
230
+ sanitizedRepoIdentity: string | null
231
+ repoIdentity?: string
232
+ accountSlot: AssistantAccountSlot
233
+ bindingRevision: number
234
+ grantRevision: number
235
+ state: FollowedRemoteProjectState
236
+ followedAt: number | string
237
+ updatedAt: number | string
238
+ unfollowedAt?: number | string
239
+ revokedAt?: number | string
240
+ lastCursor?: string | null
241
+ coordinatorSessionId?: string | null
242
+ lastErrorCode?: string
243
+ lastObserved?: {
244
+ observedAt: number | string
245
+ coordinatorState?: string
246
+ coordinatorMissing?: boolean
247
+ coordinatorPresent?: boolean
248
+ lastHeadline?: string
249
+ summary?: string
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Content-free canonical catalog metadata returned by `assistant_remote_project_catalog`
255
+ * for meshes actually hosted on the queried remote daemon.
256
+ */
257
+ export interface RemoteProjectCatalogItem {
258
+ hostDaemonId: string
259
+ meshId: string
260
+ name: string
261
+ sanitizedRepoIdentity: string | null
262
+ coordinatorState?: 'none' | 'idle' | 'working' | 'waiting' | string
263
+ capabilities: {
264
+ status: true
265
+ read: true
266
+ poll: true
267
+ report: true
268
+ send: true
269
+ coordinatorPresent?: boolean
270
+ }
271
+ observedAt: number | string
272
+ }
273
+
274
+ /** Build the canonical `(hostDaemonId, meshId)` key used across assistant follow state. */
275
+ export function followedRemoteProjectKey(hostDaemonId: string, meshId: string): string {
276
+ const canonHost = canonicalDaemonId(hostDaemonId) ?? hostDaemonId.trim()
277
+ return `${canonHost}::${meshId.trim()}`
278
+ }
279
+
280
+ /**
281
+ * Sanitize a repository identity or remote URL into a content-free `host/path`
282
+ * identifier suitable for remote discovery catalogs. Strips credentials, query
283
+ * strings, fragments, and `.git` suffixes, and rejects local filesystem paths.
284
+ */
285
+ export function sanitizeRemoteRepoIdentity(raw: string | null | undefined): string | null {
286
+ if (typeof raw !== 'string') return null
287
+ const trimmed = raw.trim()
288
+ if (!trimmed) return null
289
+
290
+ // Reject local filesystem paths (Unix absolute/relative/home, Windows drive/UNC, file:// URLs).
291
+ if (
292
+ trimmed.startsWith('/')
293
+ || trimmed.startsWith('./')
294
+ || trimmed.startsWith('../')
295
+ || trimmed.startsWith('~/')
296
+ || trimmed.startsWith('\\')
297
+ || /^[a-zA-Z]:[\\/]/.test(trimmed)
298
+ || /^file:/i.test(trimmed)
299
+ ) {
300
+ return null
301
+ }
302
+
303
+ const withoutQuery = trimmed.replace(/[?#].*$/, '').replace(/\/+$/, '')
304
+ if (!withoutQuery) return null
305
+
306
+ let host = ''
307
+ let repoPath = ''
308
+
309
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(withoutQuery)) {
310
+ try {
311
+ const url = new URL(withoutQuery)
312
+ if (url.protocol === 'file:') return null
313
+ host = url.hostname.toLowerCase()
314
+ repoPath = decodeURIComponent(url.pathname)
315
+ .replace(/^\/+|\/+$/g, '')
316
+ .replace(/\.git$/i, '')
317
+ } catch {
318
+ return null
319
+ }
320
+ } else {
321
+ const scpMatch = withoutQuery.match(/^(?:[^@/:]+@)?(\[[^\]]+\]|[^/:]+):(.+)$/)
322
+ if (scpMatch) {
323
+ host = scpMatch[1].replace(/^\[|\]$/g, '').toLowerCase()
324
+ repoPath = scpMatch[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
325
+ } else {
326
+ const slash = withoutQuery.indexOf('/')
327
+ if (slash > 0) {
328
+ const rawHost = withoutQuery.slice(0, slash)
329
+ const atIdx = rawHost.lastIndexOf('@')
330
+ host = (atIdx >= 0 ? rawHost.slice(atIdx + 1) : rawHost).toLowerCase()
331
+ repoPath = withoutQuery.slice(slash + 1).replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
332
+ }
333
+ }
334
+ }
335
+
336
+ if (!host || !repoPath) return null
337
+ if (!host.includes('.') && host !== 'localhost') return null
338
+ if (/[\s@:\\]/.test(host)) return null
339
+ const segments = repoPath.split('/').filter(Boolean)
340
+ if (segments.length === 0) return null
341
+ if (segments.some(seg => seg === '.' || seg === '..' || /[\s@:\\]/.test(seg))) return null
342
+
343
+ return `${host}/${segments.join('/')}`.slice(0, 200)
344
+ }
345
+
@@ -67,6 +67,7 @@ export const CANONICAL_MESH_TOOL_NAMES = [
67
67
  'mesh_node_slots',
68
68
  'driver_list',
69
69
  'driver_describe',
70
+ 'artifact_list',
70
71
  ] as const;
71
72
 
72
73
  export type CanonicalMeshToolName = typeof CANONICAL_MESH_TOOL_NAMES[number];
@@ -43,6 +43,7 @@ export const WORKER_TOOLS = [
43
43
  'driver_list',
44
44
  'driver_describe',
45
45
  'driver_invoke',
46
+ 'artifact_register',
46
47
  ] as const
47
48
 
48
49
  export type WorkerTool = typeof WORKER_TOOLS[number]