@animalabs/connectome-host 0.7.3 → 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.
- package/.env.example +12 -5
- package/.github/PULL_REQUEST_TEMPLATE.md +3 -2
- package/.github/workflows/changelog.yml +9 -4
- package/.github/workflows/ci.yml +5 -3
- package/.github/workflows/publish.yml +12 -6
- package/CHANGELOG.md +401 -10
- package/CONTRIBUTING.md +47 -19
- package/HEADLESS-FLEET-PLAN.md +22 -0
- package/README.md +39 -1
- package/bun.lock +27 -31
- package/changelog.d/README.md +28 -0
- package/docs/AGENT-ONBOARDING.md +1 -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 +6 -6
- package/recipes/SETUP.md +11 -5
- package/recipes/TRIUMVIRATE-SETUP.md +68 -14
- package/recipes/knowledge-miner.json +0 -30
- package/recipes/mock-test.json +19 -0
- package/recipes/triumvirate.json +6 -1
- package/scripts/audit-module-optins.ts +288 -0
- package/scripts/release-changelog.ts +210 -21
- package/src/cache-keepalive-log.ts +41 -0
- package/src/commands.ts +96 -0
- package/src/framework-strategy.ts +50 -4
- package/src/gate-telemetry.ts +106 -0
- package/src/headless.ts +24 -0
- package/src/index.ts +179 -64
- package/src/mcpl-config.ts +99 -1
- package/src/modules/fleet-module.ts +60 -1
- package/src/modules/fleet-types.ts +30 -1
- package/src/modules/identity-module.ts +310 -2
- package/src/modules/instructions-module.ts +265 -0
- package/src/modules/mcpl-admin-module.ts +89 -13
- package/src/modules/retrieval-module.ts +249 -51
- package/src/modules/retrieval-trace-page.ts +254 -0
- package/src/modules/retrieval-trace.ts +904 -0
- package/src/modules/subagent-module.ts +18 -0
- package/src/modules/tts-relay-module.ts +33 -18
- package/src/modules/web-ui-module.ts +445 -894
- package/src/recipe.ts +787 -29
- 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 +1206 -0
- package/src/web/protocol.ts +75 -10
- package/src/workspace-mounts.ts +73 -0
- package/test/audit-module-optins.test.ts +174 -0
- package/test/cache-keepalive-log.test.ts +83 -0
- package/test/conversations-recipe.test.ts +142 -0
- package/test/fleet-panel-request.test.ts +90 -0
- package/test/framework-fkm-composition.test.ts +35 -3
- package/test/framework-strategy-defaults.test.ts +41 -0
- package/test/frontdesk-strategy.test.ts +25 -37
- package/test/gate-telemetry-adapter.test.ts +84 -0
- package/test/gate-telemetry.test.ts +91 -0
- package/test/headless-panel-request.test.ts +201 -0
- package/test/identity-and-surfaces.test.ts +212 -1
- package/test/instructions-module.test.ts +258 -0
- package/test/mcpl-admin-module.test.ts +64 -0
- package/test/mcpl-agent-overlay.test.ts +51 -3
- package/test/mcpl-child-env.test.ts +64 -0
- package/test/mock-headless-child.ts +14 -0
- package/test/nudge-command.test.ts +47 -0
- package/test/recipe-cache-keepalive.test.ts +59 -0
- package/test/recipe-compression-fallback.test.ts +19 -0
- package/test/recipe-hybrid-prose-routing.test.ts +12 -0
- package/test/recipe-instructions.test.ts +176 -0
- package/test/recipe-kv-unified.test.ts +87 -0
- package/test/recipe-mcp-source.test.ts +54 -0
- package/test/recipe-openai-compatible.test.ts +54 -0
- package/test/recipe-path-resolution.test.ts +19 -8
- package/test/recipe-provider.test.ts +14 -0
- package/test/recipe-save-unresolved.test.ts +244 -0
- package/test/recipe-source-only.test.ts +38 -0
- package/test/release-changelog.test.ts +202 -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/subagent-prose-routing.test.ts +109 -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/test/workspace-mounts.test.ts +68 -0
- package/web/src/App.tsx +160 -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/Health.tsx +61 -1
- 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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Membrane } from '@animalabs/membrane';
|
|
2
|
+
import type { RecipeAgent, RecipeModules } from './recipe.js';
|
|
3
|
+
import type { RetrievalModuleConfig } from './modules/retrieval-module.js';
|
|
4
|
+
|
|
5
|
+
type RetrievalRecipeConfig = Exclude<RecipeModules['retrieval'], boolean | undefined>;
|
|
6
|
+
|
|
7
|
+
/** Translate the recipe's retrieval block into the module's runtime config. */
|
|
8
|
+
export function buildRetrievalModuleConfig(
|
|
9
|
+
membrane: Membrane,
|
|
10
|
+
retrieval: RecipeModules['retrieval'],
|
|
11
|
+
provider: RecipeAgent['provider'] = 'anthropic',
|
|
12
|
+
): RetrievalModuleConfig {
|
|
13
|
+
const config: RetrievalRecipeConfig = typeof retrieval === 'object' ? retrieval : {};
|
|
14
|
+
if (config.reasoningEffort
|
|
15
|
+
&& provider !== 'openai-responses'
|
|
16
|
+
&& provider !== 'openai-codex') {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'modules.retrieval.reasoningEffort requires agent.provider ' +
|
|
19
|
+
'"openai-responses" or "openai-codex".',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (config.reasoningEffort
|
|
23
|
+
&& (typeof config.model !== 'string' || !config.model.trim())) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'modules.retrieval.model must be a non-empty string when ' +
|
|
26
|
+
'modules.retrieval.reasoningEffort is configured.',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const retrievalReasoning = config.reasoningEffort
|
|
30
|
+
? { effort: config.reasoningEffort }
|
|
31
|
+
: undefined;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
membrane,
|
|
35
|
+
retrievalModel: config.model,
|
|
36
|
+
retrievalReasoning,
|
|
37
|
+
maxInjectedLessons: config.maxInjected,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -1,30 +1,16 @@
|
|
|
1
1
|
import { AutobiographicalStrategy } from '@animalabs/context-manager';
|
|
2
2
|
import type {
|
|
3
3
|
AutobiographicalConfig,
|
|
4
|
+
Chunk,
|
|
4
5
|
ContextEntry,
|
|
5
6
|
MessageStoreView,
|
|
6
7
|
ContextLogView,
|
|
7
8
|
TokenBudget,
|
|
8
9
|
StoredMessage,
|
|
9
|
-
SummaryEntry,
|
|
10
10
|
} from '@animalabs/context-manager';
|
|
11
11
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
12
12
|
import { formatZonedTime, resolveTimeZone } from '@animalabs/agent-framework';
|
|
13
13
|
|
|
14
|
-
// Structural mirror of AutobiographicalStrategy's internal Chunk.
|
|
15
|
-
// Kept inline because @animalabs/context-manager does not currently export it.
|
|
16
|
-
interface Chunk {
|
|
17
|
-
index: number;
|
|
18
|
-
startIndex: number;
|
|
19
|
-
endIndex: number;
|
|
20
|
-
messages: StoredMessage[];
|
|
21
|
-
tokens: number;
|
|
22
|
-
compressed: boolean;
|
|
23
|
-
diary?: string;
|
|
24
|
-
summaryId?: string;
|
|
25
|
-
phaseType?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
14
|
export type FrontdeskStrategyOptions = Partial<AutobiographicalConfig> & { timeZone?: string };
|
|
29
15
|
|
|
30
16
|
/**
|
|
@@ -35,10 +21,22 @@ export type FrontdeskStrategyOptions = Partial<AutobiographicalConfig> & { timeZ
|
|
|
35
21
|
* 1. Provenance wrapping — prepends a `[zulip · #channel · topic · @user · HH:MM · msg-id]`
|
|
36
22
|
* header to each MCPL-originated entry so the agent knows the message came from a
|
|
37
23
|
* channel and which reply path to use.
|
|
38
|
-
* 2. Topic-aware compression — chunk boundaries
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
24
|
+
* 2. Topic-aware compression — chunk boundaries close at Zulip-topic transitions
|
|
25
|
+
* (via the base chunker's `chunkBoundaryHint` seam) and the compression prompt
|
|
26
|
+
* instructs per-topic structure.
|
|
27
|
+
* 3. Question/mention salience — unanswered user questions and @mentions are named
|
|
28
|
+
* verbatim in the compression prompt so summaries preserve them.
|
|
29
|
+
*
|
|
30
|
+
* History note: through conhost 0.7.x this class forked the whole of
|
|
31
|
+
* `rebuildChunks` for feature 2 — written against a pre-chunk-persistence
|
|
32
|
+
* base, silently bypassing chunk records and the fail-closed orphan guard —
|
|
33
|
+
* and biased the hierarchical renderer's L1 selection for feature 3.
|
|
34
|
+
* Frontdesk agents now ride the adaptive path (see framework-strategy.ts
|
|
35
|
+
* defaults): chunking goes through the base implementation with a boundary
|
|
36
|
+
* hint, and salient content survives through the compression prompt rather
|
|
37
|
+
* than selection-order bias. Stores created by the fork carry no chunk
|
|
38
|
+
* records; context-manager's `migrateChunkRecords` backfills them from L1
|
|
39
|
+
* `sourceIds` on first load.
|
|
42
40
|
*/
|
|
43
41
|
export class FrontdeskStrategy extends AutobiographicalStrategy {
|
|
44
42
|
override readonly name: string = 'frontdesk';
|
|
@@ -162,77 +160,13 @@ export class FrontdeskStrategy extends AutobiographicalStrategy {
|
|
|
162
160
|
// ==========================================================================
|
|
163
161
|
|
|
164
162
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
163
|
+
* Close chunk boundaries at Zulip-topic transitions, so summaries of
|
|
164
|
+
* unrelated topics are not fused. Rides the base chunker (context-manager
|
|
165
|
+
* ≥0.6.3): record persistence, minimum-size and tool-pairing guards all
|
|
166
|
+
* apply to hinted closes.
|
|
167
167
|
*/
|
|
168
|
-
protected override
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const existingCompressed = new Map<string, Chunk>();
|
|
172
|
-
for (const chunk of this.chunks as unknown as Chunk[]) {
|
|
173
|
-
if (chunk.compressed) {
|
|
174
|
-
existingCompressed.set(this.chunkKey(chunk as never), chunk);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
this.chunks = [];
|
|
179
|
-
this.compressionQueue = [];
|
|
180
|
-
|
|
181
|
-
let currentChunk: StoredMessage[] = [];
|
|
182
|
-
let currentTokens = 0;
|
|
183
|
-
let chunkFilteredStart = 0;
|
|
184
|
-
const MIN_CHUNK = 4;
|
|
185
|
-
|
|
186
|
-
const push = (startIdx: number, endIdx: number, msgs: StoredMessage[], tokens: number) => {
|
|
187
|
-
const chunk = this.createChunk(
|
|
188
|
-
this.chunks.length,
|
|
189
|
-
startIdx,
|
|
190
|
-
endIdx,
|
|
191
|
-
msgs,
|
|
192
|
-
tokens,
|
|
193
|
-
existingCompressed as never,
|
|
194
|
-
);
|
|
195
|
-
this.chunks.push(chunk);
|
|
196
|
-
if (!chunk.compressed) this.compressionQueue.push(chunk.index);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
for (let i = 0; i < messagesToChunk.length; i++) {
|
|
200
|
-
const msg = messagesToChunk[i];
|
|
201
|
-
let msgTokens = store.estimateTokens(msg);
|
|
202
|
-
if (this.config.attachmentsIgnoreSize) {
|
|
203
|
-
msgTokens = this.estimateTextOnlyTokens(msg);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Topic boundary: close current chunk BEFORE adding msg when topic changes
|
|
207
|
-
// and the chunk has at least MIN_CHUNK messages. This keeps summaries of
|
|
208
|
-
// unrelated topics from being merged.
|
|
209
|
-
if (
|
|
210
|
-
currentChunk.length >= MIN_CHUNK &&
|
|
211
|
-
this.isTopicBoundary(currentChunk[currentChunk.length - 1], msg)
|
|
212
|
-
) {
|
|
213
|
-
push(chunkFilteredStart, i, currentChunk, currentTokens);
|
|
214
|
-
currentChunk = [];
|
|
215
|
-
currentTokens = 0;
|
|
216
|
-
chunkFilteredStart = i;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
currentChunk.push(msg);
|
|
220
|
-
currentTokens += msgTokens;
|
|
221
|
-
|
|
222
|
-
const shouldClose =
|
|
223
|
-
currentTokens >= this.config.targetChunkTokens && currentChunk.length >= MIN_CHUNK;
|
|
224
|
-
|
|
225
|
-
if (shouldClose) {
|
|
226
|
-
push(chunkFilteredStart, i + 1, currentChunk, currentTokens);
|
|
227
|
-
currentChunk = [];
|
|
228
|
-
currentTokens = 0;
|
|
229
|
-
chunkFilteredStart = i + 1;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (currentChunk.length >= MIN_CHUNK) {
|
|
234
|
-
push(chunkFilteredStart, messagesToChunk.length, currentChunk, currentTokens);
|
|
235
|
-
}
|
|
168
|
+
protected override chunkBoundaryHint(prev: StoredMessage, next: StoredMessage): boolean {
|
|
169
|
+
return this.isTopicBoundary(prev, next);
|
|
236
170
|
}
|
|
237
171
|
|
|
238
172
|
protected isTopicBoundary(prev: StoredMessage, curr: StoredMessage): boolean {
|
|
@@ -255,6 +189,11 @@ export class FrontdeskStrategy extends AutobiographicalStrategy {
|
|
|
255
189
|
// ==========================================================================
|
|
256
190
|
|
|
257
191
|
protected override getCompressionInstruction(chunk: Chunk, targetTokens: number): string {
|
|
192
|
+
// Witnessed chunks keep the base treatment (recipe-configurable
|
|
193
|
+
// witnessed prompt); the old fork predated it and steamrolled it.
|
|
194
|
+
if (this.chunkIsWitnessed(chunk)) {
|
|
195
|
+
return super.getCompressionInstruction(chunk, targetTokens);
|
|
196
|
+
}
|
|
258
197
|
const topics = new Set<string>();
|
|
259
198
|
for (const m of chunk.messages) {
|
|
260
199
|
const t = this.extractTopicKey(m);
|
|
@@ -289,43 +228,13 @@ export class FrontdeskStrategy extends AutobiographicalStrategy {
|
|
|
289
228
|
}
|
|
290
229
|
|
|
291
230
|
// ==========================================================================
|
|
292
|
-
//
|
|
293
|
-
// ==========================================================================
|
|
294
|
-
|
|
295
|
-
protected override selectL1Summaries(
|
|
296
|
-
shownL1: SummaryEntry[],
|
|
297
|
-
budget: number,
|
|
298
|
-
maxTokens: number,
|
|
299
|
-
): { selected: SummaryEntry[]; tokensUsed: number } {
|
|
300
|
-
if (shownL1.length === 0) return { selected: [], tokensUsed: 0 };
|
|
301
|
-
|
|
302
|
-
const isSalient = (s: SummaryEntry): boolean =>
|
|
303
|
-
s.sourceIds.some((id) => this.salientSourceIds.has(id));
|
|
304
|
-
|
|
305
|
-
const salient: SummaryEntry[] = [];
|
|
306
|
-
const routine: SummaryEntry[] = [];
|
|
307
|
-
for (const s of shownL1) {
|
|
308
|
-
(isSalient(s) ? salient : routine).push(s);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const selected: SummaryEntry[] = [];
|
|
312
|
-
let used = 0;
|
|
313
|
-
|
|
314
|
-
for (const group of [salient, routine]) {
|
|
315
|
-
for (const s of group) {
|
|
316
|
-
if (used + s.tokens > budget) break;
|
|
317
|
-
if (used + s.tokens > maxTokens) break;
|
|
318
|
-
selected.push(s);
|
|
319
|
-
used += s.tokens;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return { selected, tokensUsed: used };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// ==========================================================================
|
|
327
|
-
// Salience tracking (shared by 3a and 3b)
|
|
231
|
+
// Salience tracking (feeds the compression instruction)
|
|
328
232
|
// ==========================================================================
|
|
233
|
+
// The pre-adaptive frontdesk also overrode the hierarchical renderer's
|
|
234
|
+
// selectL1Summaries to emit salient L1s first under budget pressure. Under
|
|
235
|
+
// adaptive resolution the picker selects a coverage frontier — emission
|
|
236
|
+
// order is not budget-competitive — so that bias is retired; salient
|
|
237
|
+
// content survives because getCompressionInstruction names it verbatim.
|
|
329
238
|
|
|
330
239
|
/**
|
|
331
240
|
* Recompute which user messages are "unanswered questions or mentions":
|