@animalabs/agent-framework 0.6.6 → 0.6.7
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/README.md +78 -0
- package/dist/src/agent.d.ts +39 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +172 -56
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server.js +0 -0
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -2
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/canonical-json.d.ts +2 -0
- package/dist/src/canonical-json.d.ts.map +1 -0
- package/dist/src/canonical-json.js +23 -0
- package/dist/src/canonical-json.js.map +1 -0
- package/dist/src/framework.d.ts +147 -3
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +2719 -125
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +9 -2
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +3 -1
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +29 -15
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +36 -3
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +142 -28
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +12 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +661 -2
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +4 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts +99 -0
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -0
- package/dist/src/recovery/discord-awareness-outbox.js +334 -0
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +45 -0
- package/dist/src/recovery/offline-branch.d.ts.map +1 -0
- package/dist/src/recovery/offline-branch.js +362 -0
- package/dist/src/recovery/offline-branch.js.map +1 -0
- package/dist/src/recovery/recover-cli.d.ts +3 -0
- package/dist/src/recovery/recover-cli.d.ts.map +1 -0
- package/dist/src/recovery/recover-cli.js +129 -0
- package/dist/src/recovery/recover-cli.js.map +1 -0
- package/dist/src/types/agent.d.ts +22 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +14 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +31 -0
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +2 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/agent-runtime-settings.test.js +200 -93
- package/dist/test/agent-runtime-settings.test.js.map +1 -1
- package/dist/test/busy-agent-scheduler.test.d.ts +2 -0
- package/dist/test/busy-agent-scheduler.test.d.ts.map +1 -0
- package/dist/test/busy-agent-scheduler.test.js +62 -0
- package/dist/test/busy-agent-scheduler.test.js.map +1 -0
- package/dist/test/channel-lifecycle-provenance.test.d.ts +2 -0
- package/dist/test/channel-lifecycle-provenance.test.d.ts.map +1 -0
- package/dist/test/channel-lifecycle-provenance.test.js +47 -0
- package/dist/test/channel-lifecycle-provenance.test.js.map +1 -0
- package/dist/test/discord-awareness-outbox.test.d.ts +2 -0
- package/dist/test/discord-awareness-outbox.test.d.ts.map +1 -0
- package/dist/test/discord-awareness-outbox.test.js +248 -0
- package/dist/test/discord-awareness-outbox.test.js.map +1 -0
- package/dist/test/helpers/mock-membrane.js +1 -1
- package/dist/test/helpers/mock-membrane.js.map +1 -1
- package/dist/test/maintenance-timer.test.js +100 -0
- package/dist/test/maintenance-timer.test.js.map +1 -1
- package/dist/test/mcpl-awareness-barrier.test.d.ts +2 -0
- package/dist/test/mcpl-awareness-barrier.test.d.ts.map +1 -0
- package/dist/test/mcpl-awareness-barrier.test.js +845 -0
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -0
- package/dist/test/mcpl-reconnect-reregistration.test.js +22 -3
- package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -1
- package/dist/test/mcpl-request-timeout.test.js +15 -0
- package/dist/test/mcpl-request-timeout.test.js.map +1 -1
- package/dist/test/offline-recovery-branch.test.d.ts +2 -0
- package/dist/test/offline-recovery-branch.test.d.ts.map +1 -0
- package/dist/test/offline-recovery-branch.test.js +226 -0
- package/dist/test/offline-recovery-branch.test.js.map +1 -0
- package/dist/test/present-while-acting.test.d.ts +3 -2
- package/dist/test/present-while-acting.test.d.ts.map +1 -1
- package/dist/test/present-while-acting.test.js +31 -3
- package/dist/test/present-while-acting.test.js.map +1 -1
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts +2 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts.map +1 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.js +1955 -0
- package/dist/test/primary-summary-refusal-fallback-integration.test.js.map +1 -0
- package/dist/test/primary-summary-refusal-fallback.test.d.ts +2 -0
- package/dist/test/primary-summary-refusal-fallback.test.d.ts.map +1 -0
- package/dist/test/primary-summary-refusal-fallback.test.js +753 -0
- package/dist/test/primary-summary-refusal-fallback.test.js.map +1 -0
- package/dist/test/same-round-think-policy.test.d.ts +2 -0
- package/dist/test/same-round-think-policy.test.d.ts.map +1 -0
- package/dist/test/same-round-think-policy.test.js +853 -0
- package/dist/test/same-round-think-policy.test.js.map +1 -0
- package/dist/test/workspace-read-image.test.d.ts +2 -0
- package/dist/test/workspace-read-image.test.d.ts.map +1 -0
- package/dist/test/workspace-read-image.test.js +518 -0
- package/dist/test/workspace-read-image.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/src/agent.ts +265 -62
- package/src/api/server.ts +8 -2
- package/src/canonical-json.ts +22 -0
- package/src/framework.ts +3561 -145
- package/src/gate/event-gate.ts +15 -3
- package/src/index.ts +20 -0
- package/src/mcpl/channel-registry.ts +31 -16
- package/src/mcpl/server-connection.ts +163 -28
- package/src/modules/workspace/index.ts +759 -2
- package/src/modules/workspace/types.ts +5 -0
- package/src/recovery/discord-awareness-outbox.ts +459 -0
- package/src/recovery/offline-branch.ts +510 -0
- package/src/recovery/recover-cli.ts +130 -0
- package/src/types/agent.ts +26 -0
- package/src/types/framework.ts +17 -0
- package/src/types/module.ts +33 -0
- package/src/types/trace.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animalabs/agent-framework",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Multi-agent framework with pluggable modules, persistent state, and concurrent tool execution",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
"src"
|
|
22
22
|
],
|
|
23
23
|
"bin": {
|
|
24
|
-
"agent-framework-mcp": "./dist/src/api/mcp-server.js"
|
|
24
|
+
"agent-framework-mcp": "./dist/src/api/mcp-server.js",
|
|
25
|
+
"agent-framework-recover": "./dist/src/recovery/recover-cli.js"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
27
28
|
"build": "tsc",
|
|
29
|
+
"postbuild": "node -e \"const fs=require('node:fs'); for (const p of ['dist/src/api/mcp-server.js','dist/src/recovery/recover-cli.js']) fs.chmodSync(p, 0o755)\"",
|
|
28
30
|
"dev": "tsc --watch",
|
|
29
31
|
"clean": "rm -rf dist",
|
|
30
32
|
"typecheck": "tsc --noEmit",
|
|
@@ -44,7 +46,7 @@
|
|
|
44
46
|
"license": "MIT",
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@animalabs/chronicle": "^0.2.2",
|
|
47
|
-
"@animalabs/context-manager": "^0.5.
|
|
49
|
+
"@animalabs/context-manager": "^0.5.12",
|
|
48
50
|
"@animalabs/membrane": "^0.5.73",
|
|
49
51
|
"chokidar": "^4.0.3",
|
|
50
52
|
"discord.js": "^14.25.1",
|
package/src/agent.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { Membrane, NormalizedMessage, NormalizedRequest, ContentBlock, YieldingStream } from '@animalabs/membrane';
|
|
2
2
|
import { isAbortedResponse } from '@animalabs/membrane';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { toolResultDataToHistoryString } from './tool-result-history.js';
|
|
5
|
+
import { canonicalJsonStringify } from './canonical-json.js';
|
|
6
|
+
|
|
7
|
+
export interface ActivationCompileArtifacts {
|
|
8
|
+
compileResult: CompileResult;
|
|
9
|
+
contract: PrimarySummaryContract & {
|
|
10
|
+
systemHash: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
export interface StartStreamResult {
|
|
6
15
|
stream: YieldingStream;
|
|
7
16
|
request: NormalizedRequest;
|
|
17
|
+
artifacts: ActivationCompileArtifacts;
|
|
8
18
|
}
|
|
9
19
|
import type {
|
|
10
20
|
ContextManager,
|
|
@@ -13,10 +23,15 @@ import type {
|
|
|
13
23
|
CompileResult,
|
|
14
24
|
HotContextSettingsStatus,
|
|
15
25
|
HotContextSettingsUpdate,
|
|
26
|
+
PrimarySummaryContract,
|
|
27
|
+
PrimarySummaryIdentity,
|
|
28
|
+
PrimarySummaryProjection,
|
|
16
29
|
} from '@animalabs/context-manager';
|
|
17
30
|
import type {
|
|
18
31
|
AgentConfig,
|
|
19
32
|
AgentState,
|
|
33
|
+
SameRoundThinkTextPolicy,
|
|
34
|
+
SameRoundThinkTextPolicySource,
|
|
20
35
|
PendingToolCall,
|
|
21
36
|
CompletedToolCall,
|
|
22
37
|
ToolCallId,
|
|
@@ -31,8 +46,13 @@ import type {
|
|
|
31
46
|
AgentRuntimeSettingsOverrides,
|
|
32
47
|
} from './types/index.js';
|
|
33
48
|
|
|
49
|
+
type PrimarySummaryContractWithSystemHash = PrimarySummaryContract & {
|
|
50
|
+
systemHash: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
34
53
|
const DEFAULT_CONTEXT_BUDGET_TOKENS = 100_000;
|
|
35
54
|
const DEFAULT_TRANSITION_PACE_TOKENS = 16_000;
|
|
55
|
+
const DEFAULT_SAME_ROUND_THINK_TEXT_POLICY: SameRoundThinkTextPolicy = 'public';
|
|
36
56
|
|
|
37
57
|
/**
|
|
38
58
|
* An agent wraps a context manager and manages inference state.
|
|
@@ -63,6 +83,7 @@ export class Agent {
|
|
|
63
83
|
readonly cacheTtl: NonNullable<AgentConfig['cacheTtl']>;
|
|
64
84
|
/** Provider-specific request parameters forwarded unchanged by Membrane. */
|
|
65
85
|
readonly providerParams?: Record<string, unknown>;
|
|
86
|
+
readonly sameRoundThinkTextPolicy: AgentConfig['sameRoundThinkTextPolicy'];
|
|
66
87
|
|
|
67
88
|
private _state: AgentState = { status: 'idle' };
|
|
68
89
|
private _inferenceStartedAt = 0;
|
|
@@ -76,6 +97,7 @@ export class Agent {
|
|
|
76
97
|
private readonly configuredContextBudgetTokens?: number;
|
|
77
98
|
private readonly configuredTailTokens?: number;
|
|
78
99
|
private readonly configuredTransitionPaceTokens?: number;
|
|
100
|
+
private readonly configuredSameRoundThinkTextPolicy?: SameRoundThinkTextPolicy;
|
|
79
101
|
private contextBudgetTargetTokens?: number;
|
|
80
102
|
private runtimeSettingsOverrides: AgentRuntimeSettingsOverrides = {};
|
|
81
103
|
private contextManager: ContextManager;
|
|
@@ -97,6 +119,7 @@ export class Agent {
|
|
|
97
119
|
this.refusalHandling = config.refusalHandling;
|
|
98
120
|
this.cacheTtl = config.cacheTtl ?? '1h';
|
|
99
121
|
this.providerParams = config.providerParams;
|
|
122
|
+
this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
|
|
100
123
|
this.maxStreamTokens = config.maxStreamTokens ?? 150_000;
|
|
101
124
|
this.contextBudgetTokens = config.contextBudgetTokens;
|
|
102
125
|
this.configuredContextBudgetTokens = config.contextBudgetTokens;
|
|
@@ -105,6 +128,7 @@ export class Agent {
|
|
|
105
128
|
const hot = this.getHotContextSettings();
|
|
106
129
|
this.configuredTailTokens = hot?.tailTokens;
|
|
107
130
|
this.configuredTransitionPaceTokens = hot?.transitionPaceTokens;
|
|
131
|
+
this.configuredSameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
/**
|
|
@@ -191,6 +215,141 @@ export class Agent {
|
|
|
191
215
|
return manager.updateHotContextSettings(update);
|
|
192
216
|
}
|
|
193
217
|
|
|
218
|
+
private hashJson(value: unknown): string {
|
|
219
|
+
return createHash('sha256').update(canonicalJsonStringify(value), 'utf8').digest('hex');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private primarySummaryFallbackEnabled(): boolean {
|
|
223
|
+
return this.refusalHandling?.primarySummaryFallback?.enabled === true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private buildPrimarySummaryContractForRequest(request: NormalizedRequest): PrimarySummaryContractWithSystemHash {
|
|
227
|
+
return {
|
|
228
|
+
systemHash: this.hashJson(request.system ?? ''),
|
|
229
|
+
modelConfigHash: this.hashJson({
|
|
230
|
+
requestConfig: request.config,
|
|
231
|
+
providerParams: request.providerParams,
|
|
232
|
+
promptCaching: request.promptCaching,
|
|
233
|
+
cacheTtl: request.cacheTtl,
|
|
234
|
+
assistantParticipant: request.assistantParticipant,
|
|
235
|
+
}),
|
|
236
|
+
toolContractHash: this.hashJson(request.tools ?? []),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private setPrimaryLaneContract(contract: PrimarySummaryContract | undefined): void {
|
|
241
|
+
const manager = this.contextManager as ContextManager & {
|
|
242
|
+
setPrimaryLaneContract?: (value: PrimarySummaryContract | undefined) => void;
|
|
243
|
+
};
|
|
244
|
+
manager.setPrimaryLaneContract?.(contract);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private matchingPrimarySummaryQuarantine(
|
|
248
|
+
projection: PrimarySummaryProjection,
|
|
249
|
+
contract: PrimarySummaryContract,
|
|
250
|
+
): PrimarySummaryIdentity[] {
|
|
251
|
+
const manager = this.contextManager as ContextManager & {
|
|
252
|
+
matchingPrimarySummaryQuarantine?: (
|
|
253
|
+
projection: PrimarySummaryProjection,
|
|
254
|
+
contract?: PrimarySummaryContract,
|
|
255
|
+
) => PrimarySummaryIdentity[];
|
|
256
|
+
};
|
|
257
|
+
return manager.matchingPrimarySummaryQuarantine?.(projection, contract) ?? [];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private expandPrimarySummaryProjectionRaw(
|
|
261
|
+
compiled: CompileResult,
|
|
262
|
+
summaries: ReadonlyArray<PrimarySummaryIdentity>,
|
|
263
|
+
): CompileResult {
|
|
264
|
+
const manager = this.contextManager as ContextManager & {
|
|
265
|
+
expandPrimarySummaryProjectionRaw?: (
|
|
266
|
+
compiled: CompileResult,
|
|
267
|
+
summaries: ReadonlyArray<PrimarySummaryIdentity>,
|
|
268
|
+
) => CompileResult;
|
|
269
|
+
};
|
|
270
|
+
if (typeof manager.expandPrimarySummaryProjectionRaw !== 'function') return compiled;
|
|
271
|
+
return manager.expandPrimarySummaryProjectionRaw(compiled, summaries);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private primarySummaryIdentityKey(identity: PrimarySummaryIdentity): string {
|
|
275
|
+
return [
|
|
276
|
+
identity.id,
|
|
277
|
+
identity.contentHash,
|
|
278
|
+
identity.carrierHash,
|
|
279
|
+
identity.sourceLeafHash,
|
|
280
|
+
].join('\u0000');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private buildRequestFromCompileResult(
|
|
284
|
+
compileResult: CompileResult,
|
|
285
|
+
availableTools: ToolDefinition[],
|
|
286
|
+
): NormalizedRequest {
|
|
287
|
+
let { messages, systemInjections } = compileResult;
|
|
288
|
+
|
|
289
|
+
messages = messages
|
|
290
|
+
.map((m) => ({
|
|
291
|
+
...m,
|
|
292
|
+
content: m.content.filter(
|
|
293
|
+
(b: ContentBlock) => !(b.type === "text" && (typeof b.text !== "string" || b.text.trim() === "")),
|
|
294
|
+
),
|
|
295
|
+
}))
|
|
296
|
+
.filter((m) => m.content.length > 0);
|
|
297
|
+
|
|
298
|
+
if (messages.length > 0 && messages[messages.length - 1]!.participant === this.name) {
|
|
299
|
+
messages = [...messages, {
|
|
300
|
+
participant: 'user',
|
|
301
|
+
content: [{ type: 'text', text: '[Continue]' }],
|
|
302
|
+
}];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
messages,
|
|
307
|
+
system: this.buildSystemPrompt(systemInjections),
|
|
308
|
+
config: {
|
|
309
|
+
model: this.model,
|
|
310
|
+
maxTokens: this.maxTokens,
|
|
311
|
+
...(this.temperature !== undefined && { temperature: this.temperature }),
|
|
312
|
+
...(this.thinking !== undefined && { thinking: this.thinking }),
|
|
313
|
+
},
|
|
314
|
+
tools: availableTools.length > 0 ? availableTools : undefined,
|
|
315
|
+
promptCaching: true,
|
|
316
|
+
cacheTtl: this.cacheTtl,
|
|
317
|
+
...(this.providerParams && { providerParams: this.providerParams }),
|
|
318
|
+
assistantParticipant: this.name,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async prepareActivationRequest(
|
|
323
|
+
availableTools: ToolDefinition[],
|
|
324
|
+
injections?: ContextInjection[],
|
|
325
|
+
budget?: TokenBudget,
|
|
326
|
+
): Promise<{ request: NormalizedRequest; artifacts: ActivationCompileArtifacts }> {
|
|
327
|
+
(this.contextManager as unknown as { setToolDefinitions?: (t: ToolDefinition[]) => void })
|
|
328
|
+
.setToolDefinitions?.(availableTools);
|
|
329
|
+
|
|
330
|
+
let compileResult = await this.compileWithInjections(budget, injections);
|
|
331
|
+
let request = this.buildRequestFromCompileResult(compileResult, availableTools);
|
|
332
|
+
const contract = this.buildPrimarySummaryContractForRequest(request);
|
|
333
|
+
this.setPrimaryLaneContract(contract);
|
|
334
|
+
|
|
335
|
+
const projection = compileResult.primarySummaryProjection;
|
|
336
|
+
if (this.primarySummaryFallbackEnabled() && projection) {
|
|
337
|
+
const quarantined = this.matchingPrimarySummaryQuarantine(projection, contract);
|
|
338
|
+
if (quarantined.length > 0) {
|
|
339
|
+
compileResult = this.expandPrimarySummaryProjectionRaw(compileResult, quarantined);
|
|
340
|
+
request = this.buildRequestFromCompileResult(compileResult, availableTools);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
request,
|
|
346
|
+
artifacts: {
|
|
347
|
+
compileResult,
|
|
348
|
+
contract,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
194
353
|
getRuntimeSettings(): AgentRuntimeSettingsSnapshot {
|
|
195
354
|
const hot = this.getHotContextSettings();
|
|
196
355
|
return {
|
|
@@ -202,6 +361,8 @@ export class Agent {
|
|
|
202
361
|
...(hot?.transitionPaceTokens !== undefined
|
|
203
362
|
? { transitionPaceTokens: hot.transitionPaceTokens }
|
|
204
363
|
: {}),
|
|
364
|
+
sameRoundThinkTextPolicy: this.getEffectiveSameRoundThinkTextPolicy(),
|
|
365
|
+
sameRoundThinkTextPolicySource: this.getSameRoundThinkTextPolicySource(),
|
|
205
366
|
transition: this.contextBudgetTargetTokens === undefined
|
|
206
367
|
? 'stable'
|
|
207
368
|
: hot?.blocked
|
|
@@ -219,6 +380,68 @@ export class Agent {
|
|
|
219
380
|
return { ...this.runtimeSettingsOverrides };
|
|
220
381
|
}
|
|
221
382
|
|
|
383
|
+
getEffectiveSameRoundThinkTextPolicy(): SameRoundThinkTextPolicy {
|
|
384
|
+
return this.runtimeSettingsOverrides.sameRoundThinkTextPolicy
|
|
385
|
+
?? this.configuredSameRoundThinkTextPolicy
|
|
386
|
+
?? DEFAULT_SAME_ROUND_THINK_TEXT_POLICY;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
getSameRoundThinkTextPolicySource(): SameRoundThinkTextPolicySource {
|
|
390
|
+
if (this.runtimeSettingsOverrides.sameRoundThinkTextPolicy !== undefined) {
|
|
391
|
+
return 'runtime_override';
|
|
392
|
+
}
|
|
393
|
+
if (this.configuredSameRoundThinkTextPolicy !== undefined) {
|
|
394
|
+
return 'recipe';
|
|
395
|
+
}
|
|
396
|
+
return 'compatibility_default';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getCurrentBranchGeneration():
|
|
400
|
+
| { id: string; name: string; head: number; generation: number; parentId?: string; branchPoint?: number; created: Date }
|
|
401
|
+
| null {
|
|
402
|
+
const manager = this.contextManager as ContextManager & {
|
|
403
|
+
currentBranchGeneration?: () => {
|
|
404
|
+
id: string;
|
|
405
|
+
name: string;
|
|
406
|
+
head: number;
|
|
407
|
+
generation: number;
|
|
408
|
+
parentId?: string;
|
|
409
|
+
branchPoint?: number;
|
|
410
|
+
created: Date;
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
return manager.currentBranchGeneration?.() ?? null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
buildPrimarySummaryRawExpansionRequest(
|
|
417
|
+
artifacts: ActivationCompileArtifacts,
|
|
418
|
+
summaries: ReadonlyArray<PrimarySummaryIdentity>,
|
|
419
|
+
availableTools: ToolDefinition[],
|
|
420
|
+
): { request: NormalizedRequest; artifacts: ActivationCompileArtifacts } {
|
|
421
|
+
this.setPrimaryLaneContract(artifacts.contract);
|
|
422
|
+
const alreadyExpanded = new Set(
|
|
423
|
+
artifacts.compileResult.primarySummaryProjection?.selectedSummaries
|
|
424
|
+
.filter((selection) => selection.renderedAs === 'raw_expansion')
|
|
425
|
+
.map((selection) => this.primarySummaryIdentityKey(selection.identity)) ?? [],
|
|
426
|
+
);
|
|
427
|
+
const expandable = summaries.filter((summary) =>
|
|
428
|
+
!alreadyExpanded.has(this.primarySummaryIdentityKey(summary)),
|
|
429
|
+
);
|
|
430
|
+
const compileResult = expandable.length > 0
|
|
431
|
+
? this.expandPrimarySummaryProjectionRaw(
|
|
432
|
+
artifacts.compileResult,
|
|
433
|
+
expandable,
|
|
434
|
+
)
|
|
435
|
+
: artifacts.compileResult;
|
|
436
|
+
return {
|
|
437
|
+
request: this.buildRequestFromCompileResult(compileResult, availableTools),
|
|
438
|
+
artifacts: {
|
|
439
|
+
compileResult,
|
|
440
|
+
contract: artifacts.contract,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
222
445
|
updateRuntimeSettings(patch: AgentRuntimeSettingsPatch): AgentRuntimeSettingsSnapshot {
|
|
223
446
|
this.validateRuntimeSettingsPatch(patch);
|
|
224
447
|
let nextContextBudget = this.contextBudgetTokens;
|
|
@@ -263,13 +486,17 @@ export class Agent {
|
|
|
263
486
|
this.runtimeSettingsOverrides.transitionPaceTokens = DEFAULT_TRANSITION_PACE_TOKENS;
|
|
264
487
|
}
|
|
265
488
|
for (const [key, value] of Object.entries(patch)) {
|
|
266
|
-
if (
|
|
489
|
+
if (key === 'sameRoundThinkTextPolicy') continue;
|
|
490
|
+
if (value !== undefined) (this.runtimeSettingsOverrides as Record<string, unknown>)[key] = value;
|
|
491
|
+
}
|
|
492
|
+
if (patch.sameRoundThinkTextPolicy !== undefined) {
|
|
493
|
+
this.runtimeSettingsOverrides.sameRoundThinkTextPolicy = patch.sameRoundThinkTextPolicy;
|
|
267
494
|
}
|
|
268
495
|
return this.getRuntimeSettings();
|
|
269
496
|
}
|
|
270
497
|
|
|
271
498
|
resetRuntimeSettings(keys?: Array<keyof AgentRuntimeSettingsPatch>): AgentRuntimeSettingsSnapshot {
|
|
272
|
-
const reset = new Set(keys ?? ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens']);
|
|
499
|
+
const reset = new Set(keys ?? ['contextBudgetTokens', 'tailTokens', 'transitionPaceTokens', 'sameRoundThinkTextPolicy']);
|
|
273
500
|
let nextContextBudget = this.contextBudgetTokens;
|
|
274
501
|
let nextContextTarget = this.contextBudgetTargetTokens;
|
|
275
502
|
const hotPatch: {
|
|
@@ -312,6 +539,9 @@ export class Agent {
|
|
|
312
539
|
if (reset.has('transitionPaceTokens')) {
|
|
313
540
|
delete this.runtimeSettingsOverrides.transitionPaceTokens;
|
|
314
541
|
}
|
|
542
|
+
if (reset.has('sameRoundThinkTextPolicy')) {
|
|
543
|
+
delete this.runtimeSettingsOverrides.sameRoundThinkTextPolicy;
|
|
544
|
+
}
|
|
315
545
|
return this.getRuntimeSettings();
|
|
316
546
|
}
|
|
317
547
|
|
|
@@ -348,6 +578,11 @@ export class Agent {
|
|
|
348
578
|
(!Number.isSafeInteger(patch.transitionPaceTokens) || patch.transitionPaceTokens <= 0)) {
|
|
349
579
|
throw new Error('transitionPaceTokens must be a positive safe integer');
|
|
350
580
|
}
|
|
581
|
+
if (patch.sameRoundThinkTextPolicy !== undefined &&
|
|
582
|
+
patch.sameRoundThinkTextPolicy !== 'public' &&
|
|
583
|
+
patch.sameRoundThinkTextPolicy !== 'private') {
|
|
584
|
+
throw new Error("sameRoundThinkTextPolicy must be 'public' or 'private'");
|
|
585
|
+
}
|
|
351
586
|
if ((patch.tailTokens !== undefined || patch.transitionPaceTokens !== undefined) &&
|
|
352
587
|
!this.getHotContextSettings()) {
|
|
353
588
|
throw new Error('The active context strategy does not support live tail/transition settings');
|
|
@@ -548,64 +783,8 @@ export class Agent {
|
|
|
548
783
|
injections?: ContextInjection[],
|
|
549
784
|
budget?: TokenBudget
|
|
550
785
|
): Promise<NormalizedRequest> {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
// summarizer/compression requests, or transcripts containing tool blocks
|
|
554
|
-
// are refused by Anthropic's reasoning_extraction classifier (labclaude
|
|
555
|
-
// incident, 2026-07-09). Optional chaining: older context-manager
|
|
556
|
-
// versions don't have the hook.
|
|
557
|
-
(this.contextManager as unknown as { setToolDefinitions?: (t: ToolDefinition[]) => void })
|
|
558
|
-
.setToolDefinitions?.(availableTools);
|
|
559
|
-
|
|
560
|
-
let { messages, systemInjections } = await this.compileWithInjections(budget, injections);
|
|
561
|
-
|
|
562
|
-
// Sanitize: strip empty/whitespace text blocks and drop messages left with
|
|
563
|
-
// no content. The Anthropic API rejects empty text blocks with 400
|
|
564
|
-
// "messages: text content blocks must be non-empty"; when such a block
|
|
565
|
-
// reaches a live activation request the agent cannot complete ANY turn
|
|
566
|
-
// ([inference-failed] on every attempt) until the offending message ages
|
|
567
|
-
// out of the window. Empty text blocks occur naturally: tool-only turns,
|
|
568
|
-
// delivery-failure placeholders, silent/skip turns. Non-text blocks pass
|
|
569
|
-
// through unchanged, so tool pairing is unaffected. (Field-observed
|
|
570
|
-
// 2026-07-10 on a resident agent: one empty block muted the agent's live
|
|
571
|
-
// path entirely; twin of context-manager's stripEmptyTextBlocks on the
|
|
572
|
-
// compression path.)
|
|
573
|
-
messages = messages
|
|
574
|
-
.map((m) => ({
|
|
575
|
-
...m,
|
|
576
|
-
content: m.content.filter(
|
|
577
|
-
// typeof guard is deliberate runtime defense: history loaded from
|
|
578
|
-
// disk can carry a non-string `text` despite what the types claim.
|
|
579
|
-
(b: ContentBlock) => !(b.type === "text" && (typeof b.text !== "string" || b.text.trim() === "")),
|
|
580
|
-
),
|
|
581
|
-
}))
|
|
582
|
-
.filter((m) => m.content.length > 0);
|
|
583
|
-
|
|
584
|
-
// Safety: ensure messages don't end with an assistant message.
|
|
585
|
-
// Some models reject trailing assistant messages ("prefill not supported"),
|
|
586
|
-
// and after context compression a stale assistant turn can end up last.
|
|
587
|
-
if (messages.length > 0 && messages[messages.length - 1]!.participant === this.name) {
|
|
588
|
-
messages = [...messages, {
|
|
589
|
-
participant: 'user',
|
|
590
|
-
content: [{ type: 'text', text: '[Continue]' }],
|
|
591
|
-
}];
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return {
|
|
595
|
-
messages,
|
|
596
|
-
system: this.buildSystemPrompt(systemInjections),
|
|
597
|
-
config: {
|
|
598
|
-
model: this.model,
|
|
599
|
-
maxTokens: this.maxTokens,
|
|
600
|
-
...(this.temperature !== undefined && { temperature: this.temperature }),
|
|
601
|
-
...(this.thinking !== undefined && { thinking: this.thinking }),
|
|
602
|
-
},
|
|
603
|
-
tools: availableTools.length > 0 ? availableTools : undefined,
|
|
604
|
-
promptCaching: true,
|
|
605
|
-
cacheTtl: this.cacheTtl,
|
|
606
|
-
...(this.providerParams && { providerParams: this.providerParams }),
|
|
607
|
-
assistantParticipant: this.name,
|
|
608
|
-
};
|
|
786
|
+
const { request } = await this.prepareActivationRequest(availableTools, injections, budget);
|
|
787
|
+
return request;
|
|
609
788
|
}
|
|
610
789
|
|
|
611
790
|
/**
|
|
@@ -625,7 +804,11 @@ export class Agent {
|
|
|
625
804
|
this._inferenceStartedAt = Date.now();
|
|
626
805
|
this.lastStreamInputTokens = 0;
|
|
627
806
|
|
|
628
|
-
const request = await this.
|
|
807
|
+
const { request, artifacts } = await this.prepareActivationRequest(
|
|
808
|
+
availableTools,
|
|
809
|
+
injections,
|
|
810
|
+
budget,
|
|
811
|
+
);
|
|
629
812
|
|
|
630
813
|
const stream = this.membrane.streamYielding(request, {
|
|
631
814
|
emitTokens: true,
|
|
@@ -634,7 +817,27 @@ export class Agent {
|
|
|
634
817
|
});
|
|
635
818
|
|
|
636
819
|
this._state = { status: 'streaming', stream };
|
|
637
|
-
return { stream, request };
|
|
820
|
+
return { stream, request, artifacts };
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
startStreamFromRequest(
|
|
824
|
+
request: NormalizedRequest,
|
|
825
|
+
artifacts: ActivationCompileArtifacts,
|
|
826
|
+
): StartStreamResult {
|
|
827
|
+
if (this._state.status !== 'streaming' && this._state.status !== 'idle') {
|
|
828
|
+
throw new Error(`Agent ${this.name} cannot replace stream in state ${this._state.status}`);
|
|
829
|
+
}
|
|
830
|
+
this._streamId++;
|
|
831
|
+
this._inferenceStartedAt = Date.now();
|
|
832
|
+
this.lastStreamInputTokens = 0;
|
|
833
|
+
this.setPrimaryLaneContract(artifacts.contract);
|
|
834
|
+
const stream = this.membrane.streamYielding(request, {
|
|
835
|
+
emitTokens: true,
|
|
836
|
+
emitBlocks: false,
|
|
837
|
+
emitUsage: true,
|
|
838
|
+
});
|
|
839
|
+
this._state = { status: 'streaming', stream };
|
|
840
|
+
return { stream, request, artifacts };
|
|
638
841
|
}
|
|
639
842
|
|
|
640
843
|
/**
|
package/src/api/server.ts
CHANGED
|
@@ -468,6 +468,10 @@ export class ApiServer {
|
|
|
468
468
|
store.switchBranch(params.name);
|
|
469
469
|
this.currentBranch = params.name;
|
|
470
470
|
|
|
471
|
+
// Reversible Discord awareness markers are part of the branch projection.
|
|
472
|
+
// Do not acknowledge the switch until add/remove operations were attempted.
|
|
473
|
+
await this.framework.syncDiscordAwarenessMarkers();
|
|
474
|
+
|
|
471
475
|
// Materialize config files from the new branch
|
|
472
476
|
const ws = this.framework.getModule('workspace');
|
|
473
477
|
if (ws && 'materializeMount' in ws) {
|
|
@@ -511,7 +515,7 @@ export class ApiServer {
|
|
|
511
515
|
// Undo/Redo Commands
|
|
512
516
|
// ==========================================================================
|
|
513
517
|
|
|
514
|
-
private cmdUndo(params: { agentName: string }): unknown {
|
|
518
|
+
private async cmdUndo(params: { agentName: string }): Promise<unknown> {
|
|
515
519
|
if (!params.agentName) {
|
|
516
520
|
throw new Error('agentName is required');
|
|
517
521
|
}
|
|
@@ -520,6 +524,7 @@ export class ApiServer {
|
|
|
520
524
|
|
|
521
525
|
if (result.undone) {
|
|
522
526
|
this.currentBranch = result.toBranch!;
|
|
527
|
+
await this.framework.syncDiscordAwarenessMarkers();
|
|
523
528
|
this.broadcast('branch:switched', {
|
|
524
529
|
from: result.fromBranch,
|
|
525
530
|
to: result.toBranch,
|
|
@@ -530,7 +535,7 @@ export class ApiServer {
|
|
|
530
535
|
return result;
|
|
531
536
|
}
|
|
532
537
|
|
|
533
|
-
private cmdRedo(params: { agentName: string }): unknown {
|
|
538
|
+
private async cmdRedo(params: { agentName: string }): Promise<unknown> {
|
|
534
539
|
if (!params.agentName) {
|
|
535
540
|
throw new Error('agentName is required');
|
|
536
541
|
}
|
|
@@ -539,6 +544,7 @@ export class ApiServer {
|
|
|
539
544
|
|
|
540
545
|
if (result.redone) {
|
|
541
546
|
this.currentBranch = result.toBranch!;
|
|
547
|
+
await this.framework.syncDiscordAwarenessMarkers();
|
|
542
548
|
this.broadcast('branch:switched', {
|
|
543
549
|
from: result.fromBranch,
|
|
544
550
|
to: result.toBranch,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function normalizeCanonicalJson(value: unknown): unknown {
|
|
2
|
+
if (Array.isArray(value)) {
|
|
3
|
+
return value.map((entry) => normalizeCanonicalJson(entry));
|
|
4
|
+
}
|
|
5
|
+
if (value && typeof value === 'object') {
|
|
6
|
+
const withToJson = value as { toJSON?: () => unknown };
|
|
7
|
+
if (typeof withToJson.toJSON === 'function') {
|
|
8
|
+
return normalizeCanonicalJson(withToJson.toJSON());
|
|
9
|
+
}
|
|
10
|
+
const normalized: Record<string, unknown> = {};
|
|
11
|
+
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
12
|
+
const child = normalizeCanonicalJson((value as Record<string, unknown>)[key]);
|
|
13
|
+
if (child !== undefined) normalized[key] = child;
|
|
14
|
+
}
|
|
15
|
+
return normalized;
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function canonicalJsonStringify(value: unknown): string {
|
|
21
|
+
return JSON.stringify(normalizeCanonicalJson(value)) ?? 'null';
|
|
22
|
+
}
|