@animalabs/connectome-host 0.7.2 → 0.7.4

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 (63) hide show
  1. package/CHANGELOG.md +203 -10
  2. package/HEADLESS-FLEET-PLAN.md +22 -0
  3. package/README.md +22 -11
  4. package/docs/AGENT-ONBOARDING.md +20 -1
  5. package/docs/debug-context-api.md +2 -2
  6. package/docs/retrieval-traces.md +173 -0
  7. package/docs/webui-deployment.md +2 -1
  8. package/package.json +3 -3
  9. package/scripts/audit-module-optins.ts +288 -0
  10. package/scripts/warmup-session.ts +17 -3
  11. package/src/codex-subscription-adapter.ts +13 -1
  12. package/src/framework-agent-config.ts +59 -4
  13. package/src/framework-strategy.ts +33 -3
  14. package/src/headless.ts +14 -0
  15. package/src/index.ts +95 -35
  16. package/src/logging-adapter.ts +13 -2
  17. package/src/mcpl-config.ts +8 -0
  18. package/src/modules/fleet-module.ts +60 -1
  19. package/src/modules/fleet-types.ts +30 -1
  20. package/src/modules/identity-module.ts +274 -0
  21. package/src/modules/mcpl-admin-module.ts +78 -5
  22. package/src/modules/observers-module.ts +12 -0
  23. package/src/modules/retrieval-module.ts +254 -52
  24. package/src/modules/retrieval-trace-page.ts +254 -0
  25. package/src/modules/retrieval-trace.ts +904 -0
  26. package/src/modules/settings-module.ts +28 -2
  27. package/src/modules/subscription-gc-module.ts +54 -1
  28. package/src/modules/tts-relay-module.ts +33 -18
  29. package/src/modules/web-ui-module.ts +445 -894
  30. package/src/recipe.ts +137 -12
  31. package/src/retrieval-config.ts +39 -0
  32. package/src/strategies/frontdesk-strategy.ts +34 -125
  33. package/src/tui.ts +325 -54
  34. package/src/web/panel-data.ts +1187 -0
  35. package/src/web/protocol.ts +75 -10
  36. package/test/audit-module-optins.test.ts +167 -0
  37. package/test/bedrock-prompt-caching.test.ts +170 -0
  38. package/test/fleet-panel-request.test.ts +90 -0
  39. package/test/framework-strategy-defaults.test.ts +110 -0
  40. package/test/frontdesk-strategy.test.ts +25 -37
  41. package/test/headless-panel-request.test.ts +201 -0
  42. package/test/identity-and-surfaces.test.ts +157 -0
  43. package/test/mcpl-admin-module.test.ts +23 -0
  44. package/test/mock-headless-child.ts +14 -0
  45. package/test/retrieval-auth-loopback.test.ts +49 -0
  46. package/test/retrieval-config.test.ts +74 -0
  47. package/test/retrieval-module.test.ts +821 -0
  48. package/test/subscription-gc-module.test.ts +152 -0
  49. package/test/tui-format.test.ts +106 -0
  50. package/test/web-ui-context-coverage.test.ts +1 -1
  51. package/test/web-ui-module.test.ts +189 -3
  52. package/test/web-ui-observers.test.ts +8 -5
  53. package/test/web-ui-protocol.test.ts +0 -0
  54. package/web/bun.lock +345 -0
  55. package/web/src/App.tsx +159 -44
  56. package/web/src/Context.tsx +35 -8
  57. package/web/src/ContextDocument.tsx +20 -5
  58. package/web/src/Files.tsx +2 -8
  59. package/web/src/Lessons.tsx +2 -38
  60. package/web/src/Mcpl.tsx +80 -14
  61. package/web/src/Pins.tsx +5 -0
  62. package/web/src/Settings.tsx +5 -0
  63. package/web/vite.config.ts +8 -2
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Lessons panel — read-only view of the parent process's lesson library.
2
+ * Lessons panel — read-only view of one process's lesson library (the host
3
+ * or a fleet child; the sidebar's shared scope selector decides which).
3
4
  *
4
5
  * Shows active lessons sorted by confidence; deprecated entries collapse to a
5
6
  * dim line with the reason. Manual refresh button — lessons mutate slowly
@@ -27,12 +28,6 @@ export function LessonsPanel(props: {
27
28
  /** True if the LessonsModule is mounted in the recipe. */
28
29
  moduleLoaded: boolean;
29
30
  lessons: LessonRow[];
30
- /** Currently-selected scope ('local' for the parent process, or a fleet
31
- * child name). Drives which process's lessons are shown. */
32
- scope: string;
33
- /** Selectable scopes — always includes 'local', plus every fleet child. */
34
- scopes: Array<{ id: string; label: string }>;
35
- onScopeChange(scope: string): void;
36
31
  onRefresh(): void;
37
32
  }) {
38
33
  const active = (): LessonRow[] => props.lessons.filter(l => !l.deprecated)
@@ -60,7 +55,6 @@ export function LessonsPanel(props: {
60
55
  refresh
61
56
  </button>
62
57
  </div>
63
- <ScopePicker scope={props.scope} scopes={props.scopes} onChange={props.onScopeChange} />
64
58
 
65
59
  <Show when={!props.loaded} fallback={null}>
66
60
  <div class="text-neutral-600 italic">Loading…</div>
@@ -95,36 +89,6 @@ export function LessonsPanel(props: {
95
89
  );
96
90
  }
97
91
 
98
- /** Compact scope chooser shown at the top of scoped panels. The current
99
- * scope is rendered as a pill; alternative scopes appear as siblings.
100
- * Hidden when there's only one scope (no fleet children). */
101
- export function ScopePicker(props: {
102
- scope: string;
103
- scopes: Array<{ id: string; label: string }>;
104
- onChange(scope: string): void;
105
- }) {
106
- return (
107
- <Show when={props.scopes.length > 1}>
108
- <div class="flex flex-wrap items-center gap-1 mb-2 text-[10px] font-mono">
109
- <span class="text-neutral-600 uppercase tracking-wider mr-1">scope</span>
110
- <For each={props.scopes}>{(s) => (
111
- <button
112
- type="button"
113
- class={`px-1.5 py-0.5 rounded ${
114
- props.scope === s.id
115
- ? 'bg-cyan-900/60 text-cyan-100'
116
- : 'bg-neutral-800 hover:bg-neutral-700 text-neutral-300'
117
- }`}
118
- onClick={() => props.onChange(s.id)}
119
- >
120
- {s.label}
121
- </button>
122
- )}</For>
123
- </div>
124
- </Show>
125
- );
126
- }
127
-
128
92
  function LessonCard(props: { lesson: LessonRow }) {
129
93
  const conf = (): number => Math.round(props.lesson.confidence * 100);
130
94
  const confColor = (): string => {
package/web/src/Mcpl.tsx CHANGED
@@ -1,10 +1,15 @@
1
1
  /**
2
- * MCPL admin panel — read/write view of mcpl-servers.json. Mirrors the
3
- * `/mcp list|add|remove|env` slash commands but with structured controls.
2
+ * MCPL panel — two views of one scope's MCPL world:
4
3
  *
5
- * Mutations are file-only — the host needs to restart for them to take
6
- * effect. The panel makes that explicit so operators don't expect live
7
- * reconnects.
4
+ * live what the scoped process actually LOADED (recipe opt-in +
5
+ * agent overlay), with connection status and tool counts. This
6
+ * is the per-scope truth: a conductor with zero MCPLs and a
7
+ * clerk with five share the same registry file.
8
+ * registry the shared mcpl-servers.json (editable). Mutations are
9
+ * file-only — a restart applies them — and happen on the HOST
10
+ * scope: the file is one cwd-shared registry for the whole
11
+ * fleet, so the panel goes read-only on child scopes rather
12
+ * than pretending a "child-local" edit exists.
8
13
  */
9
14
 
10
15
  import { createSignal, For, Show } from 'solid-js';
@@ -20,10 +25,25 @@ export interface McplServerRow {
20
25
  disabledFeatureSets?: string[];
21
26
  }
22
27
 
28
+ /** A server the scoped process has actually loaded, with live status. */
29
+ export interface McplLiveRow {
30
+ id: string;
31
+ connected: boolean;
32
+ toolCount: number;
33
+ toolPrefix?: string;
34
+ target?: string;
35
+ }
36
+
23
37
  export function McplPanel(props: {
24
38
  loaded: boolean;
25
39
  configPath: string;
26
40
  servers: McplServerRow[];
41
+ /** Servers the scoped process actually loaded, with connection status.
42
+ * Empty array is meaningful ("this process runs no MCPLs"); undefined
43
+ * means an older host that doesn't report it. */
44
+ live?: McplLiveRow[];
45
+ /** True on fleet-child scopes: registry edits stay on the host scope. */
46
+ readOnly?: boolean;
27
47
  onRefresh(): void;
28
48
  onAdd(input: { id: string; command: string; args?: string[]; env?: Record<string, string>; toolPrefix?: string }): void;
29
49
  onRemove(id: string): void;
@@ -45,13 +65,15 @@ export function McplPanel(props: {
45
65
  >
46
66
  refresh
47
67
  </button>
48
- <button
49
- type="button"
50
- class="px-2 py-0.5 text-[10px] bg-cyan-900/40 hover:bg-cyan-900/60 text-cyan-200 rounded font-mono"
51
- onClick={() => setShowAdd(s => !s)}
52
- >
53
- {showAdd() ? 'cancel add' : '+ add'}
54
- </button>
68
+ <Show when={!props.readOnly}>
69
+ <button
70
+ type="button"
71
+ class="px-2 py-0.5 text-[10px] bg-cyan-900/40 hover:bg-cyan-900/60 text-cyan-200 rounded font-mono"
72
+ onClick={() => setShowAdd(s => !s)}
73
+ >
74
+ {showAdd() ? 'cancel add' : '+ add'}
75
+ </button>
76
+ </Show>
55
77
  </div>
56
78
 
57
79
  <Show when={!props.loaded}>
@@ -59,11 +81,51 @@ export function McplPanel(props: {
59
81
  </Show>
60
82
 
61
83
  <Show when={props.loaded}>
84
+ {/* ---- live: what this process actually runs ---- */}
85
+ <Show when={props.live !== undefined}>
86
+ <div class="text-neutral-500 uppercase tracking-wider text-[10px] font-semibold mb-1">
87
+ loaded by this process
88
+ </div>
89
+ <Show when={props.live!.length === 0}>
90
+ <div class="text-neutral-600 italic mb-2">
91
+ No MCPL servers loaded — the recipe opts into none.
92
+ </div>
93
+ </Show>
94
+ <div class="space-y-1 mb-3">
95
+ <For each={props.live}>{(s) => (
96
+ <div class="border border-neutral-800 rounded px-2 py-1 bg-neutral-950 flex items-baseline gap-2">
97
+ <span class={`inline-block w-2 h-2 rounded-full self-center shrink-0 ${s.connected ? 'bg-emerald-500' : 'bg-rose-500'}`}
98
+ title={s.connected ? 'connected' : 'disconnected'} />
99
+ <span class="font-mono text-cyan-300 truncate">{s.id}</span>
100
+ <span class="text-[10px] text-neutral-500 shrink-0">{s.toolCount} tools</span>
101
+ <Show when={s.toolPrefix}>
102
+ <span class="text-[10px] text-neutral-600 shrink-0">prefix={s.toolPrefix}</span>
103
+ </Show>
104
+ <Show when={s.target}>
105
+ <span class="text-[10px] font-mono text-neutral-600 truncate ml-auto" title={s.target}>
106
+ {s.target}
107
+ </span>
108
+ </Show>
109
+ </div>
110
+ )}</For>
111
+ </div>
112
+ <div class="text-neutral-500 uppercase tracking-wider text-[10px] font-semibold mb-1">
113
+ shared registry
114
+ </div>
115
+ </Show>
116
+
62
117
  <div class="text-[10px] text-neutral-600 italic mb-2 break-all" title={props.configPath}>
63
118
  {props.configPath}
64
119
  </div>
65
120
 
66
- <Show when={showAdd()}>
121
+ <Show when={props.readOnly}>
122
+ <div class="text-[10px] text-neutral-500 italic mb-2">
123
+ The registry file is shared fleet-wide — edit it from the host
124
+ scope. Which entries this child loads is its recipe's opt-in.
125
+ </div>
126
+ </Show>
127
+
128
+ <Show when={showAdd() && !props.readOnly}>
67
129
  <AddServerForm
68
130
  onSubmit={(input) => {
69
131
  props.onAdd(input);
@@ -81,13 +143,14 @@ export function McplPanel(props: {
81
143
  <For each={props.servers}>{(server) => (
82
144
  <ServerCard
83
145
  server={server}
146
+ readOnly={props.readOnly ?? false}
84
147
  onRemove={() => props.onRemove(server.id)}
85
148
  onSetEnv={(env) => props.onSetEnv(server.id, env)}
86
149
  />
87
150
  )}</For>
88
151
  </div>
89
152
 
90
- <Show when={props.servers.length > 0}>
153
+ <Show when={props.servers.length > 0 && !props.readOnly}>
91
154
  <div class="mt-3 text-[10px] text-amber-300/80 italic">
92
155
  Changes are written to disk; restart the host process to apply.
93
156
  </div>
@@ -99,6 +162,7 @@ export function McplPanel(props: {
99
162
 
100
163
  function ServerCard(props: {
101
164
  server: McplServerRow;
165
+ readOnly: boolean;
102
166
  onRemove(): void;
103
167
  onSetEnv(env: Record<string, string>): void;
104
168
  }) {
@@ -117,6 +181,7 @@ function ServerCard(props: {
117
181
  <Show when={props.server.reconnect}>
118
182
  <span class="text-[10px] text-emerald-400">↻ reconnect</span>
119
183
  </Show>
184
+ <Show when={!props.readOnly}>
120
185
  <button
121
186
  type="button"
122
187
  class="ml-auto text-[10px] px-1 py-0.5 bg-neutral-800 hover:bg-neutral-700 text-neutral-300 rounded font-mono"
@@ -150,6 +215,7 @@ function ServerCard(props: {
150
215
  remove
151
216
  </button>
152
217
  </Show>
218
+ </Show>
153
219
  </div>
154
220
  <div class="font-mono text-neutral-300 text-[11px] break-all leading-tight">
155
221
  {cmdLine()}
package/web/src/Pins.tsx CHANGED
@@ -35,6 +35,11 @@ export interface PinsState {
35
35
  pinsSupported: boolean;
36
36
  levelHonored: boolean;
37
37
  deepestLevel?: number;
38
+ /** Picker candidates shipped by a fleet-child scope's snapshot — the
39
+ * client has no window into a child's message store, so the child sends
40
+ * recent real store ids along. Absent on local scope (the client builds
41
+ * its candidate list from its own message window). */
42
+ candidates?: PinCandidate[];
38
43
  }
39
44
 
40
45
  /**
@@ -91,6 +91,10 @@ const TRANSITION_HELP: Record<string, string> = {
91
91
  export function SettingsPanel(props: {
92
92
  loaded: boolean;
93
93
  state: SettingsState | null;
94
+ /** Inspection scope ('local' or fleet-child name) — routes the preview
95
+ * fetch through the host's ?scope= proxy so the DRY RUN compiles in the
96
+ * same process whose settings this panel is editing. */
97
+ scope?: string;
94
98
  onRefresh(): void;
95
99
  onApply(patch: {
96
100
  contextBudgetTokens?: number;
@@ -157,6 +161,7 @@ export function SettingsPanel(props: {
157
161
  setElapsed(null);
158
162
  try {
159
163
  const qs = new URLSearchParams({ budget: String(b), agent: props.state!.agent });
164
+ if (props.scope && props.scope !== 'local') qs.set('scope', props.scope);
160
165
  const t = num(tail());
161
166
  if (t !== undefined) qs.set('tail', String(t));
162
167
  if (render) qs.set('render', '1');
@@ -19,14 +19,20 @@ export default defineConfig({
19
19
  emptyOutDir: true,
20
20
  },
21
21
  server: {
22
- // Dev server proxies WS to the running conhost so `bun run dev` in web/
23
- // talks to a real backend. Port matches WebUiModule default.
22
+ // Dev server proxies WS + HTTP surfaces to the running conhost so
23
+ // `bun run dev` in web/ talks to a real backend. Port matches the
24
+ // WebUiModule default. Without the HTTP entries every /debug and
25
+ // /healthz fetch 404s against Vite itself in dev.
24
26
  proxy: {
25
27
  '/ws': {
26
28
  target: 'ws://127.0.0.1:7340',
27
29
  ws: true,
28
30
  changeOrigin: true,
29
31
  },
32
+ '/debug': { target: 'http://127.0.0.1:7340', changeOrigin: true },
33
+ '/healthz': { target: 'http://127.0.0.1:7340', changeOrigin: true },
34
+ '/curve': { target: 'http://127.0.0.1:7340', changeOrigin: true },
35
+ '/files': { target: 'http://127.0.0.1:7340', changeOrigin: true },
30
36
  },
31
37
  },
32
38
  });