@adhdev/mesh-shared 1.0.82-rc.23 → 1.0.82-rc.25
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.
- package/dist/assistant-protocol.d.ts +119 -2
- package/dist/index.js +79 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -2
- package/dist/index.mjs.map +1 -1
- package/dist/mesh-tool-names.d.ts +2 -2
- package/dist/protocol/dashboard-daemon-p2p.d.ts +1 -1
- package/dist/worker-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/src/assistant-protocol.ts +208 -2
- package/src/mesh-tool-names.ts +1 -0
- package/src/worker-protocol.ts +1 -0
|
@@ -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:
|
|
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" | "
|
|
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
|
@@ -128,8 +128,9 @@ export const ASSISTANT_REVIEW_TURN_VERBS: readonly AssistantVerb[] = [
|
|
|
128
128
|
ASSISTANT_VERB.projectNote,
|
|
129
129
|
]
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
|
package/src/mesh-tool-names.ts
CHANGED