@animalabs/connectome-host 0.7.4 → 0.8.0

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 (60) hide show
  1. package/.env.example +12 -5
  2. package/.github/PULL_REQUEST_TEMPLATE.md +3 -2
  3. package/.github/workflows/changelog.yml +9 -4
  4. package/.github/workflows/ci.yml +5 -3
  5. package/.github/workflows/publish.yml +12 -6
  6. package/CHANGELOG.md +245 -0
  7. package/CONTRIBUTING.md +47 -19
  8. package/README.md +27 -0
  9. package/bun.lock +27 -31
  10. package/changelog.d/README.md +28 -0
  11. package/package.json +5 -5
  12. package/recipes/SETUP.md +11 -5
  13. package/recipes/TRIUMVIRATE-SETUP.md +68 -14
  14. package/recipes/knowledge-miner.json +0 -30
  15. package/recipes/mock-test.json +19 -0
  16. package/recipes/triumvirate.json +6 -1
  17. package/scripts/release-changelog.ts +210 -21
  18. package/src/cache-keepalive-log.ts +41 -0
  19. package/src/commands.ts +96 -0
  20. package/src/framework-strategy.ts +37 -0
  21. package/src/gate-telemetry.ts +106 -0
  22. package/src/headless.ts +10 -0
  23. package/src/index.ts +167 -55
  24. package/src/mcpl-config.ts +99 -1
  25. package/src/modules/identity-module.ts +310 -2
  26. package/src/modules/instructions-module.ts +265 -0
  27. package/src/modules/mcpl-admin-module.ts +58 -11
  28. package/src/modules/subagent-module.ts +18 -0
  29. package/src/recipe.ts +732 -25
  30. package/src/web/panel-data.ts +19 -0
  31. package/src/workspace-mounts.ts +73 -0
  32. package/test/audit-module-optins.test.ts +10 -3
  33. package/test/cache-keepalive-log.test.ts +83 -0
  34. package/test/conversations-recipe.test.ts +142 -0
  35. package/test/framework-fkm-composition.test.ts +35 -3
  36. package/test/framework-strategy-defaults.test.ts +19 -0
  37. package/test/gate-telemetry-adapter.test.ts +84 -0
  38. package/test/gate-telemetry.test.ts +91 -0
  39. package/test/identity-and-surfaces.test.ts +212 -1
  40. package/test/instructions-module.test.ts +258 -0
  41. package/test/mcpl-admin-module.test.ts +41 -0
  42. package/test/mcpl-agent-overlay.test.ts +51 -3
  43. package/test/mcpl-child-env.test.ts +64 -0
  44. package/test/nudge-command.test.ts +47 -0
  45. package/test/recipe-cache-keepalive.test.ts +59 -0
  46. package/test/recipe-compression-fallback.test.ts +19 -0
  47. package/test/recipe-hybrid-prose-routing.test.ts +12 -0
  48. package/test/recipe-instructions.test.ts +176 -0
  49. package/test/recipe-kv-unified.test.ts +87 -0
  50. package/test/recipe-mcp-source.test.ts +54 -0
  51. package/test/recipe-openai-compatible.test.ts +54 -0
  52. package/test/recipe-path-resolution.test.ts +19 -8
  53. package/test/recipe-provider.test.ts +14 -0
  54. package/test/recipe-save-unresolved.test.ts +244 -0
  55. package/test/recipe-source-only.test.ts +38 -0
  56. package/test/release-changelog.test.ts +202 -0
  57. package/test/subagent-prose-routing.test.ts +109 -0
  58. package/test/workspace-mounts.test.ts +68 -0
  59. package/web/src/App.tsx +1 -0
  60. package/web/src/Health.tsx +61 -1
@@ -120,7 +120,8 @@ export class McplAdminModule implements Module {
120
120
  description:
121
121
  'List all MCPL servers: connection/retry state, whether policy was established, ' +
122
122
  'the effective grant, masked/denied capability paths, host-command authority, ' +
123
- 'tool count, target, and config source.',
123
+ 'validated manifest revision/fetch/negotiation freshness, tool count, target, ' +
124
+ 'and config source.',
124
125
  inputSchema: { type: 'object', properties: {} },
125
126
  },
126
127
  {
@@ -130,7 +131,14 @@ export class McplAdminModule implements Module {
130
131
  'restarts) and hot-connect it now — its tools become available immediately. ' +
131
132
  'If a server with this id is already running it is restarted with the new ' +
132
133
  'config. Provide either `command` (stdio, spawned as the host user) or `url` ' +
133
- '(WebSocket). Relative ./ args resolve against the host working directory.',
134
+ '(WebSocket). Relative ./ args resolve against the host working directory. ' +
135
+ 'Sensible defaults: omit (or pass empty) the list fields and every feature ' +
136
+ 'set and tool the server offers is available; an empty array means ' +
137
+ '"unspecified", never deny-all (deny-all is disabledTools/' +
138
+ 'disabledFeatureSets: ["*"]). Self-deployed servers get channels + tools ' +
139
+ 'only — consequential capabilities (context hooks around your inference, ' +
140
+ 'server-initiated inference, inference lifecycle) are host-masked; a server ' +
141
+ 'that genuinely needs one is an operator conversation, not a deploy flag.',
134
142
  inputSchema: {
135
143
  type: 'object',
136
144
  properties: {
@@ -142,10 +150,10 @@ export class McplAdminModule implements Module {
142
150
  token: { type: 'string', description: 'Bearer token for WebSocket auth (only when the operator hands you one — prefer `access`).' },
143
151
  access: { type: 'string', description: 'Name of a host-managed access grant (e.g. "eidoverse"): the host attaches your standing credentials to the connection automatically. Nothing for you to obtain or handle.' },
144
152
  toolPrefix: { type: 'string', description: 'Tool namespace prefix. Default: mcpl--<id>.' },
145
- reconnect: { type: 'boolean', description: 'Auto-reconnect on transport failure (default false). Note: does NOT respawn a crashed child — use mcpl_restart for that.' },
146
- enabledFeatureSets: { type: 'array', items: { type: 'string' } },
147
- disabledFeatureSets: { type: 'array', items: { type: 'string' } },
148
- enabledTools: { type: 'array', items: { type: 'string' }, description: 'Tool allow-list (bare names, * wildcard).' },
153
+ reconnect: { type: 'boolean', description: 'Auto-reconnect on transport failure. Default: true for websocket URLs (a bounced server comes back on its own), false for stdio. Note: does NOT respawn a crashed child — use mcpl_restart for that.' },
154
+ enabledFeatureSets: { type: 'array', items: { type: 'string' }, description: 'Feature-set allowlist (* wildcard). Omit or pass [] for all offered.' },
155
+ disabledFeatureSets: { type: 'array', items: { type: 'string' }, description: 'Feature-set deny-list; wins over enabled.' },
156
+ enabledTools: { type: 'array', items: { type: 'string' }, description: 'Tool allow-list (bare names, * wildcard). Omit or pass [] for all offered.' },
149
157
  disabledTools: { type: 'array', items: { type: 'string' }, description: 'Tool deny-list; wins over enabledTools.' },
150
158
  },
151
159
  required: ['id'],
@@ -239,6 +247,11 @@ export class McplAdminModule implements Module {
239
247
  maskedCapabilities?: string[];
240
248
  deniedCapabilities?: string[];
241
249
  allowHostCommands?: boolean;
250
+ manifestState?: {
251
+ lastValidatedRevision: string | null;
252
+ lastFetchedAt: number | null;
253
+ lastNegotiatedAt: number | null;
254
+ };
242
255
  }
243
256
  >;
244
257
  const overlay = readAgentOverlay(this.overlayPath);
@@ -262,7 +275,8 @@ export class McplAdminModule implements Module {
262
275
  `grant=${formatCapabilityList(s.effectiveGrant)}, ` +
263
276
  `masked=${formatCapabilityList(s.maskedCapabilities)}, ` +
264
277
  `denied=${formatCapabilityList(s.deniedCapabilities)}, ` +
265
- `hostCommands=${hostCommands}; ${s.toolCount} tools, ` +
278
+ `hostCommands=${hostCommands}, ` +
279
+ `manifest=${formatManifestState(s.manifestState)}; ${s.toolCount} tools, ` +
266
280
  `prefix=${s.toolPrefix}, source=${source}, ${target}`,
267
281
  );
268
282
  }
@@ -314,10 +328,17 @@ export class McplAdminModule implements Module {
314
328
  }
315
329
  if (typeof input.toolPrefix === 'string') entry.toolPrefix = input.toolPrefix;
316
330
  if (typeof input.reconnect === 'boolean') entry.reconnect = input.reconnect;
317
- if (Array.isArray(input.enabledFeatureSets)) entry.enabledFeatureSets = input.enabledFeatureSets.map(String);
318
- if (Array.isArray(input.disabledFeatureSets)) entry.disabledFeatureSets = input.disabledFeatureSets.map(String);
319
- if (Array.isArray(input.enabledTools)) entry.enabledTools = input.enabledTools.map(String);
320
- if (Array.isArray(input.disabledTools)) entry.disabledTools = input.disabledTools.map(String);
331
+ // Empty arrays are NOT persisted: OpenAI-style strict function calling
332
+ // forces every schema property, so callers emit `[]` meaning
333
+ // "unspecified" and a persisted empty ALLOWLIST is deny-all under the
334
+ // §5.3 pin (Mica's silently eventless eidoverse, 2026-08-04).
335
+ // resolveOverlayEntry drops them at read time too; this keeps the file
336
+ // itself from carrying the trap. Deny-all is spelled ["*"] on the
337
+ // deny-lists.
338
+ if (Array.isArray(input.enabledFeatureSets) && input.enabledFeatureSets.length) entry.enabledFeatureSets = input.enabledFeatureSets.map(String);
339
+ if (Array.isArray(input.disabledFeatureSets) && input.disabledFeatureSets.length) entry.disabledFeatureSets = input.disabledFeatureSets.map(String);
340
+ if (Array.isArray(input.enabledTools) && input.enabledTools.length) entry.enabledTools = input.enabledTools.map(String);
341
+ if (Array.isArray(input.disabledTools) && input.disabledTools.length) entry.disabledTools = input.disabledTools.map(String);
321
342
 
322
343
  // Persist to the overlay first — a connect failure still leaves the entry
323
344
  // in place so the agent can fix the server and mcpl_restart it.
@@ -407,3 +428,29 @@ function formatCapabilityList(paths: string[] | undefined): string {
407
428
  if (paths === undefined) return 'unknown';
408
429
  return `[${paths.join(',')}]`;
409
430
  }
431
+
432
+ function formatManifestState(state: {
433
+ lastValidatedRevision: string | null;
434
+ lastFetchedAt: number | null;
435
+ lastNegotiatedAt: number | null;
436
+ } | undefined): string {
437
+ if (state === undefined) return 'unknown';
438
+ return `{revision=${formatManifestRevision(state.lastValidatedRevision)},` +
439
+ `fetchedAt=${formatManifestTimestamp(state.lastFetchedAt)},` +
440
+ `negotiatedAt=${formatManifestTimestamp(state.lastNegotiatedAt)}}`;
441
+ }
442
+
443
+ /** Bound and quote the server-authored, equality-only revision before putting
444
+ * it on a model-facing text surface. Conforming revisions fit well below the
445
+ * limit; malformed peers cannot inject control lines or unbounded text. */
446
+ function formatManifestRevision(revision: string | null): string {
447
+ if (revision === null) return 'none';
448
+ const bounded = revision.length <= 64 ? revision : `${revision.slice(0, 61)}...`;
449
+ return JSON.stringify(bounded);
450
+ }
451
+
452
+ function formatManifestTimestamp(timestamp: number | null): string {
453
+ if (timestamp === null) return 'none';
454
+ const date = new Date(timestamp);
455
+ return Number.isNaN(date.getTime()) ? 'invalid' : date.toISOString();
456
+ }
@@ -1376,6 +1376,18 @@ export class SubagentModule implements Module {
1376
1376
  * 3. parent agent's `maxTokens` (by default, subagents inherit their caller's budget)
1377
1377
  * 4. last-resort framework fallback (4096) — only reached if there's no parent at all
1378
1378
  */
1379
+ /** Ephemeral agents inherit the caller's prose-routing mode. AF's Agent
1380
+ * defaults to 'locus' (ambient locus capture publishes plain prose to the
1381
+ * open channel), so a resident running proseRouting 'disabled' would still
1382
+ * spawn divers whose between-tool-calls prose leaks into its live channel
1383
+ * as parent speech — field-confirmed 2026-08-26. Falls back to the
1384
+ * configured parent agent, then to AF's own default. */
1385
+ private resolveProseRouting(callerAgentName?: string): 'locus' | 'explicit' | 'hybrid' | 'disabled' | undefined {
1386
+ const parentName = callerAgentName ?? this.config.parentAgentName;
1387
+ if (!parentName) return undefined;
1388
+ return this.framework?.getAgent(parentName)?.proseRouting;
1389
+ }
1390
+
1379
1391
  private resolveMaxTokens(callMaxTokens: number | undefined, parentAgentName?: string): number {
1380
1392
  if (callMaxTokens !== undefined) return callMaxTokens;
1381
1393
  if (this.config.defaultMaxTokens !== undefined) return this.config.defaultMaxTokens;
@@ -1703,6 +1715,9 @@ export class SubagentModule implements Module {
1703
1715
  model,
1704
1716
  systemPrompt: input.systemPrompt,
1705
1717
  maxTokens: this.resolveMaxTokens(input.maxTokens, _callerAgentName),
1718
+ ...(this.resolveProseRouting(_callerAgentName) !== undefined
1719
+ ? { proseRouting: this.resolveProseRouting(_callerAgentName) }
1720
+ : {}),
1706
1721
  maxStreamTokens: 500_000,
1707
1722
  strategy: new KnowledgeStrategy({
1708
1723
  headWindowTokens: 2_000,
@@ -1855,6 +1870,9 @@ export class SubagentModule implements Module {
1855
1870
  model,
1856
1871
  systemPrompt,
1857
1872
  maxTokens: this.resolveMaxTokens(input.maxTokens, callerAgentName),
1873
+ ...(this.resolveProseRouting(callerAgentName) !== undefined
1874
+ ? { proseRouting: this.resolveProseRouting(callerAgentName) }
1875
+ : {}),
1858
1876
  maxStreamTokens: 500_000,
1859
1877
  strategy: new KnowledgeStrategy({
1860
1878
  headWindowTokens: 2_000,