@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.
- package/CHANGELOG.md +203 -10
- package/HEADLESS-FLEET-PLAN.md +22 -0
- package/README.md +22 -11
- package/docs/AGENT-ONBOARDING.md +20 -1
- package/docs/debug-context-api.md +2 -2
- package/docs/retrieval-traces.md +173 -0
- package/docs/webui-deployment.md +2 -1
- package/package.json +3 -3
- package/scripts/audit-module-optins.ts +288 -0
- package/scripts/warmup-session.ts +17 -3
- package/src/codex-subscription-adapter.ts +13 -1
- package/src/framework-agent-config.ts +59 -4
- package/src/framework-strategy.ts +33 -3
- package/src/headless.ts +14 -0
- package/src/index.ts +95 -35
- package/src/logging-adapter.ts +13 -2
- package/src/mcpl-config.ts +8 -0
- package/src/modules/fleet-module.ts +60 -1
- package/src/modules/fleet-types.ts +30 -1
- package/src/modules/identity-module.ts +274 -0
- package/src/modules/mcpl-admin-module.ts +78 -5
- package/src/modules/observers-module.ts +12 -0
- package/src/modules/retrieval-module.ts +254 -52
- package/src/modules/retrieval-trace-page.ts +254 -0
- package/src/modules/retrieval-trace.ts +904 -0
- package/src/modules/settings-module.ts +28 -2
- package/src/modules/subscription-gc-module.ts +54 -1
- package/src/modules/tts-relay-module.ts +33 -18
- package/src/modules/web-ui-module.ts +445 -894
- package/src/recipe.ts +137 -12
- package/src/retrieval-config.ts +39 -0
- package/src/strategies/frontdesk-strategy.ts +34 -125
- package/src/tui.ts +325 -54
- package/src/web/panel-data.ts +1187 -0
- package/src/web/protocol.ts +75 -10
- package/test/audit-module-optins.test.ts +167 -0
- package/test/bedrock-prompt-caching.test.ts +170 -0
- package/test/fleet-panel-request.test.ts +90 -0
- package/test/framework-strategy-defaults.test.ts +110 -0
- package/test/frontdesk-strategy.test.ts +25 -37
- package/test/headless-panel-request.test.ts +201 -0
- package/test/identity-and-surfaces.test.ts +157 -0
- package/test/mcpl-admin-module.test.ts +23 -0
- package/test/mock-headless-child.ts +14 -0
- package/test/retrieval-auth-loopback.test.ts +49 -0
- package/test/retrieval-config.test.ts +74 -0
- package/test/retrieval-module.test.ts +821 -0
- package/test/subscription-gc-module.test.ts +152 -0
- package/test/tui-format.test.ts +106 -0
- package/test/web-ui-context-coverage.test.ts +1 -1
- package/test/web-ui-module.test.ts +189 -3
- package/test/web-ui-observers.test.ts +8 -5
- package/test/web-ui-protocol.test.ts +0 -0
- package/web/bun.lock +345 -0
- package/web/src/App.tsx +159 -44
- package/web/src/Context.tsx +35 -8
- package/web/src/ContextDocument.tsx +20 -5
- package/web/src/Files.tsx +2 -8
- package/web/src/Lessons.tsx +2 -38
- package/web/src/Mcpl.tsx +80 -14
- package/web/src/Pins.tsx +5 -0
- package/web/src/Settings.tsx +5 -0
- package/web/vite.config.ts +8 -2
package/web/src/Lessons.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lessons panel — read-only view of
|
|
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
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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={
|
|
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
|
/**
|
package/web/src/Settings.tsx
CHANGED
|
@@ -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');
|
package/web/vite.config.ts
CHANGED
|
@@ -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
|
|
23
|
-
// talks to a real backend. Port matches
|
|
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
|
});
|