@animalabs/connectome-host 0.3.9 → 0.4.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 +7 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +26 -0
- package/.github/workflows/changelog.yml +32 -0
- package/.github/workflows/publish.yml +46 -0
- package/CHANGELOG.md +140 -0
- package/CONTRIBUTING.md +126 -0
- package/HEADLESS-FLEET-PLAN.md +1 -1
- package/README.md +52 -5
- package/UNIFIED-TREE-PLAN.md +2 -0
- package/docs/AGENT-ONBOARDING.md +9 -8
- package/docs/DEPLOYMENTS.md +2 -2
- package/docs/DEV-ENVIRONMENT.md +28 -26
- package/docs/LOCUS-ROUTING-DESIGN.md +8 -2
- package/package.json +5 -4
- package/scripts/release-changelog.ts +32 -0
- package/src/codex-subscription-adapter.ts +938 -0
- package/src/commands.ts +95 -4
- package/src/extensions.ts +201 -0
- package/src/framework-agent-config.ts +20 -9
- package/src/framework-strategy.ts +24 -0
- package/src/index.ts +111 -19
- package/src/logging-bedrock-adapter.ts +145 -0
- package/src/modules/channel-mode-module.ts +9 -6
- package/src/modules/subscription-gc-module.ts +163 -34
- package/src/modules/tts-relay-module.ts +481 -0
- package/src/modules/web-ui-module.ts +45 -1
- package/src/recipe.ts +196 -10
- package/src/tui.ts +527 -137
- package/src/web/protocol.ts +35 -0
- package/test/codex-subscription-adapter.test.ts +226 -0
- package/test/commands-checkpoint-restore.test.ts +118 -0
- package/test/fast-command.test.ts +39 -0
- package/test/recipe-extensions.test.ts +295 -0
- package/test/recipe-provider.test.ts +14 -1
- package/test/subscription-gc-module.test.ts +156 -1
- package/test/tui-quit-confirm.test.ts +42 -0
- package/test/tui-short-agent-name.test.ts +36 -0
- package/test/web-ui-observers.test.ts +14 -0
- package/test/web-ui-protocol.test.ts +0 -0
- package/web/src/App.tsx +235 -20
- package/web/src/Branches.tsx +150 -0
- package/web/src/Context.tsx +21 -13
- package/web/src/Health.tsx +274 -0
- package/web/src/Lessons.tsx +2 -1
package/src/index.ts
CHANGED
|
@@ -16,12 +16,17 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
+
AnthropicXmlFormatter,
|
|
20
|
+
BedrockAdapter,
|
|
19
21
|
Membrane,
|
|
20
22
|
NativeFormatter,
|
|
21
23
|
OpenAIResponsesAPIAdapter,
|
|
22
24
|
OpenAIResponsesFormatter,
|
|
25
|
+
OpenRouterAdapter,
|
|
23
26
|
} from '@animalabs/membrane';
|
|
24
27
|
import { LoggingAnthropicAdapter } from './logging-adapter.js';
|
|
28
|
+
import { LoggingBedrockAdapter } from './logging-bedrock-adapter.js';
|
|
29
|
+
import { CodexSubscriptionAdapter } from './codex-subscription-adapter.js';
|
|
25
30
|
import { CallLedger } from './call-ledger.js';
|
|
26
31
|
import { SettingsModule } from './modules/settings-module.js';
|
|
27
32
|
import { AgentFramework, WorkspaceModule, resolveTimeZone, type Module, type MountConfig } from '@animalabs/agent-framework';
|
|
@@ -41,6 +46,7 @@ import { ChannelModeModule } from './modules/channel-mode-module.js';
|
|
|
41
46
|
import { WebUiModule } from './modules/web-ui-module.js';
|
|
42
47
|
import { ObserversModule } from './modules/observers-module.js';
|
|
43
48
|
import { McplAdminModule } from './modules/mcpl-admin-module.js';
|
|
49
|
+
import { TtsRelayModule } from './modules/tts-relay-module.js';
|
|
44
50
|
import { loadMcplServers, applyAgentOverlay, DEFAULT_CONFIG_PATH, DEFAULT_AGENT_OVERLAY_PATH } from './mcpl-config.js';
|
|
45
51
|
import { SessionManager } from './session-manager.js';
|
|
46
52
|
import { resolveAgentName } from './agent-name.js';
|
|
@@ -57,6 +63,7 @@ import {
|
|
|
57
63
|
import { createBranchState, resetBranchState, handleExport, type BranchState } from './commands.js';
|
|
58
64
|
import { buildFrameworkAgentConfig } from './framework-agent-config.js';
|
|
59
65
|
import { buildFrameworkStrategy } from './framework-strategy.js';
|
|
66
|
+
import { loadExtensions } from './extensions.js';
|
|
60
67
|
|
|
61
68
|
export type { AppContext };
|
|
62
69
|
|
|
@@ -69,6 +76,8 @@ const config = {
|
|
|
69
76
|
// precedence over the API key so requests never carry both auth schemes.
|
|
70
77
|
authToken: process.env.ANTHROPIC_AUTH_TOKEN,
|
|
71
78
|
openaiApiKey: process.env.OPENAI_API_KEY,
|
|
79
|
+
openrouterApiKey: process.env.OPENROUTER_API_KEY,
|
|
80
|
+
codexBinary: process.env.CODEX_BINARY,
|
|
72
81
|
model: process.env.MODEL,
|
|
73
82
|
dataDir: process.env.DATA_DIR || './data',
|
|
74
83
|
};
|
|
@@ -91,6 +100,7 @@ interface AppContext {
|
|
|
91
100
|
agentName: string;
|
|
92
101
|
branchState: BranchState;
|
|
93
102
|
userMessageCount: number;
|
|
103
|
+
codexAdapter?: CodexSubscriptionAdapter;
|
|
94
104
|
|
|
95
105
|
/** Stop current framework, switch to a different session, start new framework. */
|
|
96
106
|
switchSession(id: string): Promise<void>;
|
|
@@ -143,10 +153,16 @@ async function createFramework(
|
|
|
143
153
|
settingsModule: SettingsModule,
|
|
144
154
|
callLedger: CallLedger | null,
|
|
145
155
|
): Promise<AgentFramework> {
|
|
146
|
-
const model = config.model || recipe.agent.model ||
|
|
156
|
+
const model = config.model || recipe.agent.model ||
|
|
157
|
+
(recipe.agent.provider === 'openai-codex' ? 'gpt-5.4' : 'claude-opus-4-6');
|
|
147
158
|
const modules = recipe.modules ?? {};
|
|
148
159
|
const timeZone = resolveTimeZone(recipe.agent.timezone);
|
|
149
160
|
|
|
161
|
+
// Load recipe extensions first — custom strategies must be registered
|
|
162
|
+
// before buildFrameworkStrategy runs, and custom modules join the module
|
|
163
|
+
// list below. The loader is a dumb local import; see src/extensions.ts.
|
|
164
|
+
const extensionRegistry = await loadExtensions(recipe);
|
|
165
|
+
|
|
150
166
|
// -- Build module list --
|
|
151
167
|
// SettingsModule is constructed in main() (before the adapter, so the
|
|
152
168
|
// adapter can read its state for cross-cutting concerns like reasoning).
|
|
@@ -342,6 +358,24 @@ async function createFramework(
|
|
|
342
358
|
moduleInstances.push(new ObserversModule({ path: observersPath }));
|
|
343
359
|
}
|
|
344
360
|
|
|
361
|
+
// TTS relay tap — opt-in per recipe. Pure trace-bus consumer: mirrors the
|
|
362
|
+
// agent's streaming generation to a melodeus-tts-relay /bot endpoint and
|
|
363
|
+
// trims posted messages on voice interruptions. See modules.ttsRelay in
|
|
364
|
+
// recipe.ts for the config contract (url/token validated at recipe load).
|
|
365
|
+
if (modules.ttsRelay) {
|
|
366
|
+
moduleInstances.push(new TtsRelayModule(modules.ttsRelay));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Extension-registered modules. Instantiated last so built-in modules keep
|
|
370
|
+
// their historical positions; each factory gets the minimal runtime context
|
|
371
|
+
// plus its declaring extension's config blob.
|
|
372
|
+
const extensionModules: Module[] = [];
|
|
373
|
+
for (const entry of extensionRegistry.modules) {
|
|
374
|
+
const instance = entry.factory({ timeZone, storePath, model, config: entry.config });
|
|
375
|
+
extensionModules.push(instance);
|
|
376
|
+
moduleInstances.push(instance);
|
|
377
|
+
}
|
|
378
|
+
|
|
345
379
|
// -- Build MCP server list --
|
|
346
380
|
//
|
|
347
381
|
// Recipes are opt-in: a file entry from mcpl-servers.json is loaded only
|
|
@@ -401,14 +435,14 @@ async function createFramework(
|
|
|
401
435
|
// No server augmentation needed — gate is wired via FrameworkConfig.gate
|
|
402
436
|
|
|
403
437
|
// -- Build strategy --
|
|
404
|
-
const strategy = buildFrameworkStrategy(recipe, model, timeZone);
|
|
438
|
+
const strategy = buildFrameworkStrategy(recipe, model, timeZone, extensionRegistry);
|
|
405
439
|
const agentConfig = buildFrameworkAgentConfig(recipe, agentName, model, strategy);
|
|
406
440
|
|
|
407
441
|
// -- Create framework --
|
|
408
442
|
const framework = await AgentFramework.create({
|
|
409
443
|
storePath,
|
|
410
444
|
membrane,
|
|
411
|
-
|
|
445
|
+
agents: [agentConfig],
|
|
412
446
|
modules: moduleInstances,
|
|
413
447
|
mcplServers: finalServers,
|
|
414
448
|
gate: gateOptions,
|
|
@@ -467,6 +501,13 @@ async function createFramework(
|
|
|
467
501
|
channelModeModule.setFramework(framework);
|
|
468
502
|
}
|
|
469
503
|
|
|
504
|
+
// Extension modules get the same duck-typed post-creation hook the
|
|
505
|
+
// built-ins use: if the instance exposes setFramework, call it.
|
|
506
|
+
for (const instance of extensionModules) {
|
|
507
|
+
const hooked = instance as unknown as { setFramework?: (f: AgentFramework) => void };
|
|
508
|
+
hooked.setFramework?.(framework);
|
|
509
|
+
}
|
|
510
|
+
|
|
470
511
|
if (mcplAdminModule) {
|
|
471
512
|
mcplAdminModule.setFramework(framework);
|
|
472
513
|
}
|
|
@@ -733,10 +774,18 @@ async function main() {
|
|
|
733
774
|
const recipe = await resolveRecipe();
|
|
734
775
|
const provider = recipe.agent.provider ?? 'anthropic';
|
|
735
776
|
|
|
777
|
+
if (provider === 'openrouter' && !config.openrouterApiKey) {
|
|
778
|
+
console.error('Missing OPENROUTER_API_KEY for recipe provider "openrouter".');
|
|
779
|
+
process.exit(1);
|
|
780
|
+
}
|
|
736
781
|
if (provider === 'openai-responses' && !config.openaiApiKey) {
|
|
737
782
|
console.error('Missing OPENAI_API_KEY for recipe provider "openai-responses".');
|
|
738
783
|
process.exit(1);
|
|
739
784
|
}
|
|
785
|
+
if (provider === 'bedrock' && !(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)) {
|
|
786
|
+
console.error('Missing AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY for recipe provider "bedrock".');
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
740
789
|
if (provider === 'anthropic' && !config.apiKey && !config.authToken) {
|
|
741
790
|
console.error('Missing ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN). Set one in .env or environment.');
|
|
742
791
|
process.exit(1);
|
|
@@ -761,22 +810,54 @@ async function main() {
|
|
|
761
810
|
defaultTtl: recipe.agent.cacheTtl ?? '5m',
|
|
762
811
|
})
|
|
763
812
|
: null;
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
|
|
813
|
+
// The Codex subscription adapter owns ChatGPT login/refresh independently
|
|
814
|
+
// of the API-key transports below.
|
|
815
|
+
const codexAdapter = provider === 'openai-codex'
|
|
816
|
+
? new CodexSubscriptionAdapter({
|
|
817
|
+
codexBinary: config.codexBinary,
|
|
818
|
+
fastMode: recipe.agent.codex?.fastMode ?? false,
|
|
819
|
+
})
|
|
820
|
+
: undefined;
|
|
821
|
+
const openrouterAdapter = provider === 'openrouter'
|
|
822
|
+
? new OpenRouterAdapter({
|
|
823
|
+
apiKey: config.openrouterApiKey!,
|
|
824
|
+
xTitle: recipe.agent.name ?? recipe.name,
|
|
825
|
+
})
|
|
826
|
+
: undefined;
|
|
827
|
+
// Bedrock: legacy Claude models (3.5 Sonnet 0620/1022, Opus 3) that have
|
|
828
|
+
// left the Anthropic API but survive on AWS. The adapter reads AWS_* env
|
|
829
|
+
// vars (AWS_REGION defaults us-west-2) and maps standard Claude model IDs
|
|
830
|
+
// to Bedrock IDs (explicit map + `anthropic.<id>-v1:0` fallback). Uses the
|
|
831
|
+
// Anthropic-native message shape, so NativeFormatter applies unchanged.
|
|
832
|
+
// No CallLedger: prompt caching is rejected outright by legacy Bedrock
|
|
833
|
+
// models (tested 2026-07-21). Wrapped for llm-calls.jsonl visibility.
|
|
834
|
+
const bedrockAdapter = provider === 'bedrock'
|
|
835
|
+
? new LoggingBedrockAdapter({}, llmLogPath)
|
|
836
|
+
: undefined;
|
|
767
837
|
const adapter = provider === 'openai-responses'
|
|
768
838
|
? new OpenAIResponsesAPIAdapter({
|
|
769
839
|
apiKey: config.openaiApiKey!,
|
|
770
840
|
baseURL: process.env.OPENAI_BASE_URL || undefined,
|
|
771
841
|
})
|
|
772
|
-
: new LoggingAnthropicAdapter(
|
|
842
|
+
: bedrockAdapter ?? openrouterAdapter ?? codexAdapter ?? new LoggingAnthropicAdapter(
|
|
773
843
|
{
|
|
844
|
+
// Anthropic OAuth wins over API-key auth when both are present.
|
|
845
|
+
// Subscription tokens additionally require this beta header.
|
|
846
|
+
// Recipe-declared betas (agent.anthropicBetas, e.g. context-1m)
|
|
847
|
+
// are merged into the same anthropic-beta header either way.
|
|
774
848
|
...(config.authToken
|
|
775
849
|
? {
|
|
776
850
|
authToken: config.authToken,
|
|
777
|
-
defaultHeaders: {
|
|
851
|
+
defaultHeaders: {
|
|
852
|
+
'anthropic-beta': ['oauth-2025-04-20', ...(recipe.agent.anthropicBetas ?? [])].join(','),
|
|
853
|
+
},
|
|
778
854
|
}
|
|
779
|
-
: {
|
|
855
|
+
: {
|
|
856
|
+
apiKey: config.apiKey!,
|
|
857
|
+
...(recipe.agent.anthropicBetas?.length
|
|
858
|
+
? { defaultHeaders: { 'anthropic-beta': recipe.agent.anthropicBetas.join(',') } }
|
|
859
|
+
: {}),
|
|
860
|
+
}),
|
|
780
861
|
baseURL: process.env.ANTHROPIC_BASE_URL || undefined,
|
|
781
862
|
},
|
|
782
863
|
llmLogPath,
|
|
@@ -815,9 +896,15 @@ async function main() {
|
|
|
815
896
|
const agentName = resolved.name;
|
|
816
897
|
|
|
817
898
|
const membrane = new Membrane(adapter, {
|
|
818
|
-
formatter: provider === 'openai-responses'
|
|
899
|
+
formatter: provider === 'openai-responses' || provider === 'openai-codex'
|
|
819
900
|
? new OpenAIResponsesFormatter()
|
|
820
|
-
:
|
|
901
|
+
: recipe.agent.formatter === 'anthropic-xml'
|
|
902
|
+
? new AnthropicXmlFormatter()
|
|
903
|
+
: new NativeFormatter(),
|
|
904
|
+
// Bedrock legacy Claude models 400 on any cache_control block
|
|
905
|
+
// ("your request did not allow prompt caching") — suppress the
|
|
906
|
+
// historical promptCaching=true default on that transport.
|
|
907
|
+
...(provider === 'bedrock' ? { defaultPromptCaching: false } : {}),
|
|
821
908
|
// Anchor the assistant role for internal callers that don't set
|
|
822
909
|
// request.assistantParticipant themselves (autobio compression,
|
|
823
910
|
// executeMerge). Mismatch here flips stored assistant turns to
|
|
@@ -837,6 +924,7 @@ async function main() {
|
|
|
837
924
|
agentName,
|
|
838
925
|
branchState: createBranchState(),
|
|
839
926
|
userMessageCount: 0,
|
|
927
|
+
codexAdapter,
|
|
840
928
|
|
|
841
929
|
async switchSession(id: string) {
|
|
842
930
|
handleExport(this);
|
|
@@ -884,14 +972,18 @@ async function main() {
|
|
|
884
972
|
setupMcplStderrLog(app, storePath);
|
|
885
973
|
getWebUiModule(framework)?.setApp(app);
|
|
886
974
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
975
|
+
try {
|
|
976
|
+
if (headless) {
|
|
977
|
+
const { runHeadless } = await import('./headless.js');
|
|
978
|
+
await runHeadless(app, process.argv.slice(2));
|
|
979
|
+
} else if (noTui) {
|
|
980
|
+
await runPiped(app);
|
|
981
|
+
} else {
|
|
982
|
+
const { runTui } = await import('./tui.js');
|
|
983
|
+
await runTui(app);
|
|
984
|
+
}
|
|
985
|
+
} finally {
|
|
986
|
+
codexAdapter?.dispose();
|
|
895
987
|
}
|
|
896
988
|
}
|
|
897
989
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// BedrockAdapter wrapper that appends each LLM call's request summary, stop
|
|
2
|
+
// reason, and any error to the same `llm-calls.<iso>.jsonl` file the
|
|
3
|
+
// Anthropic path uses. Purpose-built during the Aria bring-up (2026-07-21)
|
|
4
|
+
// when a 0-token assistant turn proved undiagnosable: the bedrock transport
|
|
5
|
+
// had no wire visibility at all.
|
|
6
|
+
//
|
|
7
|
+
// Kept deliberately lighter than LoggingAnthropicAdapter: request summaries
|
|
8
|
+
// always include tool NAMES (were tools attached at all? — the question that
|
|
9
|
+
// motivated this file), message/system sizes, and the stop sequences; the
|
|
10
|
+
// full raw request is retained only on errors. Responses log stop_reason,
|
|
11
|
+
// usage, and content-block shape (type + size per block) on every call.
|
|
12
|
+
//
|
|
13
|
+
// Each line: { type: 'call'|'error', kind: 'complete'|'stream', timestamp,
|
|
14
|
+
// durationMs, requestSummary, response?, rawRequest?, error? }
|
|
15
|
+
|
|
16
|
+
import { BedrockAdapter } from '@animalabs/membrane';
|
|
17
|
+
import type {
|
|
18
|
+
ProviderRequest,
|
|
19
|
+
ProviderResponse,
|
|
20
|
+
ProviderRequestOptions,
|
|
21
|
+
StreamCallbacks,
|
|
22
|
+
} from '@animalabs/membrane';
|
|
23
|
+
import { appendFileSync } from 'node:fs';
|
|
24
|
+
|
|
25
|
+
type BedrockConfig = ConstructorParameters<typeof BedrockAdapter>[0];
|
|
26
|
+
|
|
27
|
+
function summarizeRequest(request: ProviderRequest): Record<string, unknown> {
|
|
28
|
+
const msgs = (request.messages ?? []) as Array<{ role?: string; content?: unknown }>;
|
|
29
|
+
const last = msgs[msgs.length - 1];
|
|
30
|
+
const lastPreview = typeof last?.content === 'string'
|
|
31
|
+
? last.content.slice(0, 200)
|
|
32
|
+
: Array.isArray(last?.content)
|
|
33
|
+
? (last.content as Array<{ type?: string; text?: string }>)
|
|
34
|
+
.map((b) => (b.type === 'text' ? (b.text ?? '').slice(0, 120) : `[${b.type}]`))
|
|
35
|
+
.join(' | ').slice(0, 300)
|
|
36
|
+
: undefined;
|
|
37
|
+
return {
|
|
38
|
+
model: request.model,
|
|
39
|
+
maxTokens: request.maxTokens,
|
|
40
|
+
messageCount: msgs.length,
|
|
41
|
+
systemChars: typeof request.system === 'string'
|
|
42
|
+
? request.system.length
|
|
43
|
+
: Array.isArray(request.system)
|
|
44
|
+
? JSON.stringify(request.system).length
|
|
45
|
+
: 0,
|
|
46
|
+
toolNames: (request.tools as Array<{ name?: string }> | undefined)?.map((t) => t.name) ?? null,
|
|
47
|
+
toolCount: (request.tools as unknown[] | undefined)?.length ?? 0,
|
|
48
|
+
stopSequences: request.stopSequences ?? null,
|
|
49
|
+
lastMessageRole: last?.role,
|
|
50
|
+
lastMessagePreview: lastPreview,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function summarizeResponse(response: ProviderResponse): Record<string, unknown> {
|
|
55
|
+
const content = (response.content ?? []) as Array<{ type?: string; text?: string; name?: string }>;
|
|
56
|
+
return {
|
|
57
|
+
stopReason: response.stopReason ?? (response.raw as { stop_reason?: string } | undefined)?.stop_reason ?? null,
|
|
58
|
+
usage: response.usage ?? null,
|
|
59
|
+
blocks: content.map((b) => ({
|
|
60
|
+
type: b.type,
|
|
61
|
+
...(b.type === 'text' ? { chars: (b.text ?? '').length } : {}),
|
|
62
|
+
...(b.type === 'tool_use' ? { name: b.name } : {}),
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class LoggingBedrockAdapter extends BedrockAdapter {
|
|
68
|
+
private readonly logPath: string;
|
|
69
|
+
|
|
70
|
+
constructor(config: BedrockConfig, logPath: string) {
|
|
71
|
+
super(config);
|
|
72
|
+
this.logPath = logPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private log(record: Record<string, unknown>): void {
|
|
76
|
+
try {
|
|
77
|
+
appendFileSync(this.logPath, JSON.stringify(record) + '\n');
|
|
78
|
+
} catch {
|
|
79
|
+
// Logging must never break inference.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override async complete(
|
|
84
|
+
request: ProviderRequest,
|
|
85
|
+
options?: ProviderRequestOptions,
|
|
86
|
+
): Promise<ProviderResponse> {
|
|
87
|
+
const started = Date.now();
|
|
88
|
+
let rawRequest: unknown;
|
|
89
|
+
const opts: ProviderRequestOptions = {
|
|
90
|
+
...options,
|
|
91
|
+
onRequest: (req) => { rawRequest = req; options?.onRequest?.(req); },
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
const response = await super.complete(request, opts);
|
|
95
|
+
this.log({
|
|
96
|
+
type: 'call', kind: 'complete', timestamp: new Date(started).toISOString(),
|
|
97
|
+
durationMs: Date.now() - started,
|
|
98
|
+
requestSummary: summarizeRequest(request),
|
|
99
|
+
response: summarizeResponse(response),
|
|
100
|
+
});
|
|
101
|
+
return response;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this.log({
|
|
104
|
+
type: 'error', kind: 'complete', timestamp: new Date(started).toISOString(),
|
|
105
|
+
durationMs: Date.now() - started,
|
|
106
|
+
requestSummary: summarizeRequest(request),
|
|
107
|
+
rawRequest,
|
|
108
|
+
error: error instanceof Error ? { message: error.message, name: error.name } : String(error),
|
|
109
|
+
});
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override async stream(
|
|
115
|
+
request: ProviderRequest,
|
|
116
|
+
callbacks: StreamCallbacks,
|
|
117
|
+
options?: ProviderRequestOptions,
|
|
118
|
+
): Promise<ProviderResponse> {
|
|
119
|
+
const started = Date.now();
|
|
120
|
+
let rawRequest: unknown;
|
|
121
|
+
const opts: ProviderRequestOptions = {
|
|
122
|
+
...options,
|
|
123
|
+
onRequest: (req) => { rawRequest = req; options?.onRequest?.(req); },
|
|
124
|
+
};
|
|
125
|
+
try {
|
|
126
|
+
const response = await super.stream(request, callbacks, opts);
|
|
127
|
+
this.log({
|
|
128
|
+
type: 'call', kind: 'stream', timestamp: new Date(started).toISOString(),
|
|
129
|
+
durationMs: Date.now() - started,
|
|
130
|
+
requestSummary: summarizeRequest(request),
|
|
131
|
+
response: summarizeResponse(response),
|
|
132
|
+
});
|
|
133
|
+
return response;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.log({
|
|
136
|
+
type: 'error', kind: 'stream', timestamp: new Date(started).toISOString(),
|
|
137
|
+
durationMs: Date.now() - started,
|
|
138
|
+
requestSummary: summarizeRequest(request),
|
|
139
|
+
rawRequest,
|
|
140
|
+
error: error instanceof Error ? { message: error.message, name: error.name } : String(error),
|
|
141
|
+
});
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -200,11 +200,13 @@ export class ChannelModeModule implements Module {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// 3. Pin subscription-gc off so a chatty channel doesn't auto-close
|
|
203
|
-
// itself back out of debounced mode.
|
|
203
|
+
// itself back out of debounced mode. The pin layer sits above
|
|
204
|
+
// agent-level overrides, so agent_settings reset/update can't undo it
|
|
205
|
+
// and any pre-existing override survives the mode round-trip.
|
|
204
206
|
steps.push(
|
|
205
|
-
await this.callToolStep('gc-
|
|
207
|
+
await this.callToolStep('gc-pin', `${this.gcModuleName}--pin_channel_idle_limit`, {
|
|
206
208
|
channelId,
|
|
207
|
-
|
|
209
|
+
pinned: true,
|
|
208
210
|
}),
|
|
209
211
|
);
|
|
210
212
|
|
|
@@ -225,11 +227,12 @@ export class ChannelModeModule implements Module {
|
|
|
225
227
|
// 2. Close ambient traffic through the generic host lifecycle.
|
|
226
228
|
steps.push(await this.callToolStep('close', 'channel_close', { channelId, serverId: this.serverId }));
|
|
227
229
|
|
|
228
|
-
// 3.
|
|
230
|
+
// 3. Release the pin; the channel's agent override (if any) or the
|
|
231
|
+
// default limit applies again.
|
|
229
232
|
steps.push(
|
|
230
|
-
await this.callToolStep('gc-
|
|
233
|
+
await this.callToolStep('gc-unpin', `${this.gcModuleName}--pin_channel_idle_limit`, {
|
|
231
234
|
channelId,
|
|
232
|
-
|
|
235
|
+
pinned: false,
|
|
233
236
|
}),
|
|
234
237
|
);
|
|
235
238
|
|