@ziggs-ai/agent-sdk 0.16.1 → 0.18.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/CHANGELOG.md +10 -0
- package/README.md +110 -88
- package/dist/{AgentHost.d.ts → Agent.d.ts} +50 -27
- package/dist/{AgentHost.js → Agent.js} +135 -129
- package/dist/adapters/AnthropicAdapter.d.ts +1 -1
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/{agent/ClaudeSdkAgent.d.ts → brain/ClaudeBrain.d.ts} +23 -11
- package/dist/{agent/ClaudeSdkAgent.js → brain/ClaudeBrain.js} +99 -99
- package/dist/{agent/Agent.d.ts → brain/ZiggsBrain.d.ts} +23 -20
- package/dist/{agent/Agent.js → brain/ZiggsBrain.js} +12 -11
- package/dist/{agent → brain}/claudeCognition.d.ts +1 -1
- package/dist/{agent → brain}/claudeCognition.js +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkProtocol.js +1 -1
- package/dist/{agent → brain}/claudeSdkTools.d.ts +1 -1
- package/dist/{agent → brain}/claudeSdkTools.js +5 -14
- package/dist/{agent → brain}/hostedAgentConnection.d.ts +1 -1
- package/dist/{agent → brain}/hostedAgentConnection.js +3 -3
- package/dist/brain/index.d.ts +6 -6
- package/dist/brain/index.js +2 -2
- package/dist/brain/onBehalfAgreement.d.ts +51 -0
- package/dist/brain/onBehalfAgreement.js +99 -0
- package/dist/{agent → brain}/toolManagerToSdkMcp.js +10 -1
- package/dist/defineAgent.js +4 -4
- package/dist/formatters/HistoryFormatter.js +2 -1
- package/dist/index.d.ts +30 -31
- package/dist/index.js +24 -25
- package/dist/{adapters/McpAdapter.d.ts → mcp/createMcpAgent.d.ts} +16 -15
- package/dist/{adapters/McpAdapter.js → mcp/createMcpAgent.js} +82 -46
- package/dist/memory/MemoryStore.d.ts +18 -1
- package/dist/memory/MemoryStore.js +43 -0
- package/dist/{server → platform}/ConnectionPool.d.ts +9 -3
- package/dist/{server → platform}/ConnectionPool.js +16 -4
- package/dist/{server → platform}/EventQueue.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.d.ts +1 -1
- package/dist/{server → platform}/StateTimers.js +1 -1
- package/dist/{server → platform}/ZiggsEffectHandler.d.ts +4 -4
- package/dist/{server → platform}/ZiggsEffectHandler.js +2 -2
- package/dist/{server → platform}/inboxLoop.d.ts +34 -1
- package/dist/{server → platform}/inboxLoop.js +122 -14
- package/dist/{server → platform}/polls/PollKind.d.ts +4 -7
- package/dist/{server → platform}/polls/PollKind.js +3 -6
- package/dist/{server → platform}/polls/PollRunner.d.ts +5 -6
- package/dist/{server → platform}/polls/PollRunner.js +24 -12
- package/dist/platform/polls/declaredPollRunner.d.ts +4 -0
- package/dist/platform/polls/declaredPollRunner.js +29 -0
- package/dist/{server → platform}/tools/tier2/mcpConnectionTools.js +32 -18
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +1 -1
- package/dist/runtime/buildOutcome.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +18 -18
- package/dist/runtime/validateWorkflow.js +1 -1
- package/dist/skills/parseSkill.d.ts +7 -2
- package/dist/skills/parseSkill.js +22 -5
- package/dist/skills/skillReadReference.d.ts +6 -0
- package/dist/skills/skillReadReference.js +27 -0
- package/dist/tools/needs.js +2 -2
- package/dist/tools/toolCreds.d.ts +1 -1
- package/dist/tools/toolCreds.js +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +2 -2
- package/dist/server/createHealthServer.d.ts +0 -7
- package/dist/server/createHealthServer.js +0 -75
- package/dist/server/runLauncher.d.ts +0 -22
- package/dist/server/runLauncher.js +0 -160
- /package/dist/{agent → brain}/toolManagerToSdkMcp.d.ts +0 -0
- /package/dist/{server → platform}/EventQueue.js +0 -0
- /package/dist/{server → platform}/OutboxBuffer.d.ts +0 -0
- /package/dist/{server → platform}/OutboxBuffer.js +0 -0
- /package/dist/{server → platform}/SeenMessages.d.ts +0 -0
- /package/dist/{server → platform}/SeenMessages.js +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.d.ts +0 -0
- /package/dist/{server → platform}/agreements/AgreementService.js +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.d.ts +0 -0
- /package/dist/{server → platform}/proactive/ProactiveTrigger.js +0 -0
- /package/dist/{server → platform}/requestMatch.d.ts +0 -0
- /package/dist/{server → platform}/requestMatch.js +0 -0
- /package/dist/{server → platform}/tasks/TaskService.d.ts +0 -0
- /package/dist/{server → platform}/tasks/TaskService.js +0 -0
- /package/dist/{server → platform}/tasks/index.d.ts +0 -0
- /package/dist/{server → platform}/tasks/index.js +0 -0
- /package/dist/{server → platform}/telemetryIngest.d.ts +0 -0
- /package/dist/{server → platform}/telemetryIngest.js +0 -0
- /package/dist/{server → platform}/tools/index.d.ts +0 -0
- /package/dist/{server → platform}/tools/index.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolRunner.js +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier1/protocolTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/artifactTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/chatTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/connectionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/contextTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/discoveryTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/grantsTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/introductionTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/linkTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/marketplaceTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/mcpConnectionTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/paymentTools.js +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.d.ts +0 -0
- /package/dist/{server → platform}/tools/tier2/taskTools.js +0 -0
|
@@ -1,31 +1,33 @@
|
|
|
1
|
+
import { agreementLaneId, agreementIdFromLane } from '@ziggs-ai/api-client';
|
|
2
|
+
import { createDeclaredPollRunner } from './platform/polls/declaredPollRunner.js';
|
|
1
3
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CLAUDE_SDK_TOOLS } from './
|
|
6
|
-
import { resolveClaudeModel } from './
|
|
7
|
-
import { CHAT_TOOLS } from './
|
|
8
|
-
import { EventQueue } from './
|
|
9
|
-
import { StateTimers } from './
|
|
10
|
-
import { createZiggsEffectHandler } from './
|
|
4
|
+
import { ZiggsBrain } from './brain/ZiggsBrain.js';
|
|
5
|
+
import { ClaudeBrain } from './brain/ClaudeBrain.js';
|
|
6
|
+
import { applyBrainToAgentOptions, isClaudeCognition, } from './brain/index.js';
|
|
7
|
+
import { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
8
|
+
import { resolveClaudeModel } from './brain/claudeCognition.js';
|
|
9
|
+
import { CHAT_TOOLS } from './platform/tools/tier2/chatTools.js';
|
|
10
|
+
import { EventQueue } from './platform/EventQueue.js';
|
|
11
|
+
import { StateTimers } from './platform/StateTimers.js';
|
|
12
|
+
import { createZiggsEffectHandler } from './platform/ZiggsEffectHandler.js';
|
|
11
13
|
import { normalizeIncomingEvent } from './ingress/normalizeIncoming.js';
|
|
12
14
|
import { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
13
|
-
import { InMemoryStore } from './memory/MemoryStore.js';
|
|
15
|
+
import { InMemoryStore, memorySectionForWake, partitionMemoryStore, } from './memory/MemoryStore.js';
|
|
14
16
|
import { ToolManager } from './tools/ToolManager.js';
|
|
15
17
|
import { toolName } from './tools/defineTool.js';
|
|
16
|
-
import { PROTOCOL_TOOLS } from './
|
|
17
|
-
import { marketplaceViewTool } from './
|
|
18
|
+
import { PROTOCOL_TOOLS } from './platform/tools/tier1/protocolTools.js';
|
|
19
|
+
import { marketplaceViewTool } from './platform/tools/tier2/marketplaceTools.js';
|
|
18
20
|
import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
19
21
|
import { AnthropicAdapter } from './adapters/AnthropicAdapter.js';
|
|
20
22
|
import { ArtifactsClient, ConnectionsClient, ContextReadClient, TelemetryClient, sendChatMessage, } from '@ziggs-ai/api-client';
|
|
21
23
|
import { normalizeNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
|
|
22
|
-
import { TaskService } from './
|
|
23
|
-
import { AgreementService } from './
|
|
24
|
-
import { runInboxLoop } from './
|
|
25
|
-
import { requestExactMatch } from './
|
|
26
|
-
import { SeenMessages } from './
|
|
24
|
+
import { TaskService } from './platform/tasks/TaskService.js';
|
|
25
|
+
import { AgreementService } from './platform/agreements/AgreementService.js';
|
|
26
|
+
import { runInboxLoop } from './platform/inboxLoop.js';
|
|
27
|
+
import { requestExactMatch } from './platform/requestMatch.js';
|
|
28
|
+
import { SeenMessages } from './platform/SeenMessages.js';
|
|
27
29
|
import { runtimeLog } from './shared/runtimeLog.js';
|
|
28
|
-
import { isTelemetryIngestEnabled, sendTelemetry, } from './
|
|
30
|
+
import { isTelemetryIngestEnabled, sendTelemetry, } from './platform/telemetryIngest.js';
|
|
29
31
|
import { buildWakeOutcomePayload, classifyWakeOutcome, } from './runtime/wakeOutcome.js';
|
|
30
32
|
/**
|
|
31
33
|
* The single fleet-wide default model — the one place the fallback lives.
|
|
@@ -36,9 +38,9 @@ import { buildWakeOutcomePayload, classifyWakeOutcome, } from './runtime/wakeOut
|
|
|
36
38
|
*/
|
|
37
39
|
export const DEFAULT_MODEL = 'claude-haiku-4-5';
|
|
38
40
|
// ConnectionPool lives in a separate module; resolved via a computed-path
|
|
39
|
-
// dynamic import to avoid a circular import with
|
|
41
|
+
// dynamic import to avoid a circular import with Agent.
|
|
40
42
|
async function loadConnectionPool() {
|
|
41
|
-
const modPath = './
|
|
43
|
+
const modPath = './platform/ConnectionPool.js';
|
|
42
44
|
const mod = await import(/* @vite-ignore */ modPath);
|
|
43
45
|
return mod.ConnectionPool;
|
|
44
46
|
}
|
|
@@ -57,7 +59,7 @@ export function laneSessionIdForTask(task) {
|
|
|
57
59
|
return task.originChatId;
|
|
58
60
|
}
|
|
59
61
|
if (typeof task.agreementId === 'string' && task.agreementId) {
|
|
60
|
-
return
|
|
62
|
+
return agreementLaneId(task.agreementId);
|
|
61
63
|
}
|
|
62
64
|
return 'default';
|
|
63
65
|
}
|
|
@@ -67,16 +69,11 @@ export function workScopeFromWake(event, sessionId) {
|
|
|
67
69
|
const resource = rec.resource && typeof rec.resource === 'object'
|
|
68
70
|
? rec.resource
|
|
69
71
|
: {};
|
|
70
|
-
const fromAgrn = (id) => {
|
|
71
|
-
if (typeof id !== 'string' || !id.startsWith('agrn-'))
|
|
72
|
-
return undefined;
|
|
73
|
-
return id.slice('agrn-'.length) || undefined;
|
|
74
|
-
};
|
|
75
72
|
const agreementId = (typeof rec.agreementId === 'string' && rec.agreementId) ||
|
|
76
73
|
(typeof resource.agreementId === 'string' && resource.agreementId) ||
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
agreementIdFromLane(rec.chatId) ||
|
|
75
|
+
agreementIdFromLane(resource.chatId) ||
|
|
76
|
+
agreementIdFromLane(sessionId) ||
|
|
80
77
|
undefined;
|
|
81
78
|
const taskId = (typeof rec.taskId === 'string' && rec.taskId) ||
|
|
82
79
|
(typeof resource.taskId === 'string' && resource.taskId) ||
|
|
@@ -132,7 +129,7 @@ export function warnOnShadowedPlatformTools(platformTools, ownTools, agentId) {
|
|
|
132
129
|
.filter((name) => platform.has(name));
|
|
133
130
|
if (shadowed.length === 0)
|
|
134
131
|
return;
|
|
135
|
-
console.warn(`[
|
|
132
|
+
console.warn(`[Agent] ${agentId}: tool name(s) ${shadowed.join(', ')} shadow the platform tools of the same name — ` +
|
|
136
133
|
'the agent will call yours, not Ziggs\'s. Rename them unless that is deliberate.');
|
|
137
134
|
}
|
|
138
135
|
function isLlmAdapter(v) {
|
|
@@ -178,19 +175,18 @@ function createBackendServices(options) {
|
|
|
178
175
|
};
|
|
179
176
|
}
|
|
180
177
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* transport (WS) → handleMessage → agent.tick(.. , eff) → ..
|
|
178
|
+
* One agent identity and its body: inbox, tools, contracts, memory and lanes.
|
|
179
|
+
* The brain decides on each wake; platform services perform its effects.
|
|
180
|
+
* connectAsync starts outbound inbox reads and declared external polls.
|
|
181
|
+
* An Agent never opens a listening socket.
|
|
186
182
|
*/
|
|
187
|
-
export class
|
|
183
|
+
export class Agent {
|
|
188
184
|
options;
|
|
189
185
|
toolManager;
|
|
190
186
|
// Retained for external inspection. Undefined for the Claude brain, which
|
|
191
187
|
// drives query() directly and needs no LLM adapter.
|
|
192
188
|
llm;
|
|
193
|
-
|
|
189
|
+
brain;
|
|
194
190
|
eff;
|
|
195
191
|
eventQueue;
|
|
196
192
|
sessions = new Map();
|
|
@@ -207,6 +203,8 @@ export class AgentHost {
|
|
|
207
203
|
/** The per-agent inbox long-poll loop — this agent's sole wake source. */
|
|
208
204
|
inboxLoop = null;
|
|
209
205
|
inboxLoopEnabled;
|
|
206
|
+
started = false;
|
|
207
|
+
pollRunner;
|
|
210
208
|
/** Short-lived grant-provider cache for the declared-needs gate. */
|
|
211
209
|
/**
|
|
212
210
|
* Lanes where an unmet-need refusal has already been allowed its one turn to
|
|
@@ -218,7 +216,7 @@ export class AgentHost {
|
|
|
218
216
|
unmetTasksPendingAsk = new Map();
|
|
219
217
|
grantProviderCache = null;
|
|
220
218
|
constructor(options = {}) {
|
|
221
|
-
const resolved =
|
|
219
|
+
const resolved = applyBrainToAgentOptions(options);
|
|
222
220
|
this.options = {
|
|
223
221
|
openaiKey: resolved.openaiKey || process.env.OPENAI_API_KEY,
|
|
224
222
|
anthropicKey: resolved.anthropicKey || process.env.ANTHROPIC_API_KEY,
|
|
@@ -251,6 +249,8 @@ export class AgentHost {
|
|
|
251
249
|
inboxCatchUp: resolved.inboxCatchUp,
|
|
252
250
|
telemetryIngest: resolved.telemetryIngest,
|
|
253
251
|
brain: resolved.brain,
|
|
252
|
+
polls: resolved.polls,
|
|
253
|
+
pollKinds: resolved.pollKinds,
|
|
254
254
|
cognition: resolved.cognition ?? 'fsm',
|
|
255
255
|
claudeMaxTurns: resolved.claudeMaxTurns,
|
|
256
256
|
claudeStatelessPerWake: resolved.claudeStatelessPerWake,
|
|
@@ -263,12 +263,15 @@ export class AgentHost {
|
|
|
263
263
|
// default.
|
|
264
264
|
this.tickTimeoutMs =
|
|
265
265
|
options.tickTimeoutMs ??
|
|
266
|
-
(isClaudeCognition(
|
|
266
|
+
(isClaudeCognition(resolved.cognition)
|
|
267
267
|
? Number(process.env.CLAUDE_SDK_TICK_TIMEOUT_MS ?? 300_000)
|
|
268
268
|
: 120_000);
|
|
269
269
|
this.memory = options.memory ?? new InMemoryStore();
|
|
270
270
|
this.sessionStore = options.sessionStore ?? null;
|
|
271
271
|
this._validate();
|
|
272
|
+
this.pollRunner = createDeclaredPollRunner(this.options, {
|
|
273
|
+
sendTo: (_agentId, text, metadata) => this.handleMessage(text, metadata ?? {}),
|
|
274
|
+
});
|
|
272
275
|
this._setup();
|
|
273
276
|
}
|
|
274
277
|
_validate() {
|
|
@@ -331,7 +334,7 @@ export class AgentHost {
|
|
|
331
334
|
}
|
|
332
335
|
if (!workflow?.states) {
|
|
333
336
|
// The Claude brain has no FSM — it drives query() per wake and parks. It
|
|
334
|
-
// still plugs into the
|
|
337
|
+
// still plugs into the agent's lane/timer machinery via a trivial parked
|
|
335
338
|
// workflow, synthesized here so callers don't hand-write a dummy one.
|
|
336
339
|
if (isClaudeCognition(this.options.cognition)) {
|
|
337
340
|
workflow = {
|
|
@@ -342,7 +345,7 @@ export class AgentHost {
|
|
|
342
345
|
};
|
|
343
346
|
}
|
|
344
347
|
else {
|
|
345
|
-
throw new Error('
|
|
348
|
+
throw new Error('Agent: workflow (or states + initial) is required');
|
|
346
349
|
}
|
|
347
350
|
}
|
|
348
351
|
this.eff = createZiggsEffectHandler({
|
|
@@ -359,8 +362,8 @@ export class AgentHost {
|
|
|
359
362
|
telemetryIngest: this.options.telemetryIngest,
|
|
360
363
|
effectTap: this.options.effectTap,
|
|
361
364
|
});
|
|
362
|
-
this.
|
|
363
|
-
? new
|
|
365
|
+
this.brain = isClaudeCognition(this.options.cognition)
|
|
366
|
+
? new ClaudeBrain({
|
|
364
367
|
workflow,
|
|
365
368
|
toolManager: services.toolManager,
|
|
366
369
|
description: this.options.description,
|
|
@@ -376,7 +379,7 @@ export class AgentHost {
|
|
|
376
379
|
maxTurns: this.options.claudeMaxTurns,
|
|
377
380
|
statelessPerWake: this.options.claudeStatelessPerWake,
|
|
378
381
|
})
|
|
379
|
-
: new
|
|
382
|
+
: new ZiggsBrain({
|
|
380
383
|
workflow,
|
|
381
384
|
toolManager: services.toolManager,
|
|
382
385
|
promptBuilder: services.promptBuilder,
|
|
@@ -387,7 +390,7 @@ export class AgentHost {
|
|
|
387
390
|
// than left as an unhandled rejection (made enqueue reject).
|
|
388
391
|
this.stateTimers = new StateTimers((ev, laneKey) => {
|
|
389
392
|
void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
|
|
390
|
-
runtimeLog.warn('
|
|
393
|
+
runtimeLog.warn('Agent', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
391
394
|
});
|
|
392
395
|
});
|
|
393
396
|
}
|
|
@@ -429,7 +432,7 @@ export class AgentHost {
|
|
|
429
432
|
event.resourceId) ||
|
|
430
433
|
null;
|
|
431
434
|
if (agreementId)
|
|
432
|
-
sessionId =
|
|
435
|
+
sessionId = agreementLaneId(agreementId);
|
|
433
436
|
}
|
|
434
437
|
if (!sessionId)
|
|
435
438
|
return;
|
|
@@ -456,14 +459,14 @@ export class AgentHost {
|
|
|
456
459
|
async handleMessage(text, metadata = {}) {
|
|
457
460
|
const messageId = metadata.messageId;
|
|
458
461
|
if (messageId && this.seenMessages.has(messageId)) {
|
|
459
|
-
runtimeLog.debug('
|
|
462
|
+
runtimeLog.debug('Agent', `skip duplicate messageId=${messageId}`);
|
|
460
463
|
return;
|
|
461
464
|
}
|
|
462
465
|
if (messageId)
|
|
463
466
|
this.seenMessages.mark(messageId);
|
|
464
467
|
const ownAgentId = this._resolveOwnAgentId();
|
|
465
468
|
if (!ownAgentId) {
|
|
466
|
-
runtimeLog.warn('
|
|
469
|
+
runtimeLog.warn('Agent', 'handleMessage: ownAgentId missing — dropping event');
|
|
467
470
|
return;
|
|
468
471
|
}
|
|
469
472
|
let enrichedText = typeof text === 'string' ? text : '';
|
|
@@ -483,14 +486,14 @@ export class AgentHost {
|
|
|
483
486
|
const normalized = normalizeIncomingEvent({ text: enrichedText, metadata, ownAgentId });
|
|
484
487
|
const sessionId = this.sessionResolver(normalized, ownAgentId);
|
|
485
488
|
if (!sessionId) {
|
|
486
|
-
runtimeLog.warn('
|
|
489
|
+
runtimeLog.warn('Agent', `session resolver returned null ownAgentId=${ownAgentId} chatId=${normalized.chatId ?? 'null'}`);
|
|
487
490
|
return;
|
|
488
491
|
}
|
|
489
492
|
const laneKey = `${ownAgentId}:${sessionId}`;
|
|
490
493
|
if (!this.sessions.has(laneKey)) {
|
|
491
494
|
this.sessions.set(laneKey, { sessionId, ownAgentId });
|
|
492
495
|
}
|
|
493
|
-
// The inbox loop owns ack (act-before-ack, delivery law); the
|
|
496
|
+
// The inbox loop owns ack (act-before-ack, delivery law); the agent only
|
|
494
497
|
// routes the event onto its lane. No live-path watermark ack — there is no
|
|
495
498
|
// separate live/push channel to reconcile against anymore.
|
|
496
499
|
//
|
|
@@ -536,6 +539,13 @@ export class AgentHost {
|
|
|
536
539
|
llmCalls,
|
|
537
540
|
}), isTelemetryIngestEnabled(this.options.telemetryIngest));
|
|
538
541
|
}
|
|
542
|
+
memoryForWake(session) {
|
|
543
|
+
return partitionMemoryStore(this.memory, memorySectionForWake({
|
|
544
|
+
sessionId: session.sessionId,
|
|
545
|
+
activeAgreementId: session.ctx
|
|
546
|
+
?.activeAgreementId,
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
539
549
|
/**
|
|
540
550
|
* A chat-lane session that still carries a cancelled hire from a previous
|
|
541
551
|
* engagement will agreement_get that dead row and stop. Drop it so this
|
|
@@ -546,7 +556,7 @@ export class AgentHost {
|
|
|
546
556
|
const pinned = ctx?.activeAgreementId;
|
|
547
557
|
if (!pinned || !this.agreementService)
|
|
548
558
|
return;
|
|
549
|
-
if (session.sessionId ===
|
|
559
|
+
if (session.sessionId === agreementLaneId(pinned))
|
|
550
560
|
return;
|
|
551
561
|
try {
|
|
552
562
|
const agreement = await this.agreementService.get(pinned);
|
|
@@ -564,9 +574,7 @@ export class AgentHost {
|
|
|
564
574
|
}
|
|
565
575
|
}
|
|
566
576
|
async laneAgreementIsTerminal(sessionId) {
|
|
567
|
-
|
|
568
|
-
return false;
|
|
569
|
-
const agreementId = sessionId.slice('agrn-'.length);
|
|
577
|
+
const agreementId = agreementIdFromLane(sessionId);
|
|
570
578
|
if (!agreementId || !this.agreementService)
|
|
571
579
|
return false;
|
|
572
580
|
try {
|
|
@@ -793,7 +801,7 @@ export class AgentHost {
|
|
|
793
801
|
}
|
|
794
802
|
}
|
|
795
803
|
catch (err) {
|
|
796
|
-
runtimeLog.warn('
|
|
804
|
+
runtimeLog.warn('Agent', `sessionStore.load failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
797
805
|
}
|
|
798
806
|
}
|
|
799
807
|
await this.dropStaleCancelledAgreementSession(session);
|
|
@@ -802,6 +810,7 @@ export class AgentHost {
|
|
|
802
810
|
sessionId: session.sessionId,
|
|
803
811
|
laneKey,
|
|
804
812
|
};
|
|
813
|
+
const memory = this.memoryForWake(session);
|
|
805
814
|
// Recorded here rather than after the tick: this is the last point where the
|
|
806
815
|
// input is still exactly what the brain will see, and a wake that goes on to
|
|
807
816
|
// throw is the one most worth replaying.
|
|
@@ -817,7 +826,7 @@ export class AgentHost {
|
|
|
817
826
|
});
|
|
818
827
|
}
|
|
819
828
|
catch (err) {
|
|
820
|
-
runtimeLog.warn('
|
|
829
|
+
runtimeLog.warn('Agent', `wakeTap failed: ${err?.message || err}`);
|
|
821
830
|
}
|
|
822
831
|
}
|
|
823
832
|
// The watchdog timer must be cleared once the tick settles — leaving it
|
|
@@ -842,7 +851,7 @@ export class AgentHost {
|
|
|
842
851
|
};
|
|
843
852
|
try {
|
|
844
853
|
const result = await Promise.race([
|
|
845
|
-
this.
|
|
854
|
+
this.brain.tick({ event: frame, ctx: session.ctx, state: session.state, identity, eff, memory, transitionTap: this.options.transitionTap, signal: tickAbort.signal }),
|
|
846
855
|
new Promise((_, reject) => {
|
|
847
856
|
tickWatchdog = setTimeout(() => {
|
|
848
857
|
tickAbort.abort(new Error('tick timeout'));
|
|
@@ -867,7 +876,7 @@ export class AgentHost {
|
|
|
867
876
|
// these lines for one lane. Tokens are billable input (incl. cache).
|
|
868
877
|
const m = result.metrics;
|
|
869
878
|
if (m && (m.tokens?.total ?? 0) > 0) {
|
|
870
|
-
runtimeLog.info('
|
|
879
|
+
runtimeLog.info('Agent', `wake agent=${identity.agentId} lane=${laneKey} llmCalls=${m.llmCalls} ` +
|
|
871
880
|
`tokens_in=${m.tokens.input} tokens_out=${m.tokens.output} total=${m.tokens.total}`);
|
|
872
881
|
}
|
|
873
882
|
// Single source of truth for usage/cost: emit the tick's
|
|
@@ -885,12 +894,12 @@ export class AgentHost {
|
|
|
885
894
|
});
|
|
886
895
|
}
|
|
887
896
|
catch (err) {
|
|
888
|
-
runtimeLog.warn('
|
|
897
|
+
runtimeLog.warn('Agent', `metricsTap failed: ${err?.message || err}`);
|
|
889
898
|
}
|
|
890
899
|
}
|
|
891
900
|
if (this.sessionStore) {
|
|
892
901
|
this.sessionStore.save(laneKey, { ctx: session.ctx, state: session.state }).catch((err) => {
|
|
893
|
-
runtimeLog.warn('
|
|
902
|
+
runtimeLog.warn('Agent', `sessionStore.save failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
894
903
|
});
|
|
895
904
|
}
|
|
896
905
|
// Arm only when the timeout was handled: an unhandled state-timeout must
|
|
@@ -919,12 +928,12 @@ export class AgentHost {
|
|
|
919
928
|
kind: 'no-op',
|
|
920
929
|
reason: 'tick_failed',
|
|
921
930
|
});
|
|
922
|
-
runtimeLog.warn('
|
|
931
|
+
runtimeLog.warn('Agent', `tick failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
923
932
|
// Deliberate re-arm on failure. Exact scenario: the tick timed out at
|
|
924
933
|
// tickTimeoutMs (or sessionStore.load threw) leaving session.state at a
|
|
925
934
|
// timed parked state — without re-arming, that lane would never wake
|
|
926
935
|
// again unless a real event happens to arrive.
|
|
927
|
-
this._armIfTimedParked(laneKey, session.state ?? this.
|
|
936
|
+
this._armIfTimedParked(laneKey, session.state ?? this.brain.workflow.initial);
|
|
928
937
|
// Best-effort: also notify the user so the conversation isn't silently
|
|
929
938
|
// stuck. Complements the timer re-arm above — that recovers a
|
|
930
939
|
// parked lane; this tells a waiting human.
|
|
@@ -950,12 +959,12 @@ export class AgentHost {
|
|
|
950
959
|
}
|
|
951
960
|
}
|
|
952
961
|
/**
|
|
953
|
-
* Re-run one recorded wake against THIS
|
|
962
|
+
* Re-run one recorded wake against THIS agent, and nothing else.
|
|
954
963
|
*
|
|
955
964
|
* The dev loop for a prompt: record a wake off a live run, edit the agent, and
|
|
956
965
|
* see what it does differently in seconds instead of standing a world back up
|
|
957
966
|
* for one turn. The brain, tools and effect handler are the real ones, so the
|
|
958
|
-
* turn is real — its tool calls hit the backend this
|
|
967
|
+
* turn is real — its tool calls hit the backend this agent is pointed at, which
|
|
959
968
|
* is why it belongs against a disposable world and not a shared one.
|
|
960
969
|
*
|
|
961
970
|
* Deliberately NOT routed through `_processEvent`: everything that method does
|
|
@@ -971,19 +980,23 @@ export class AgentHost {
|
|
|
971
980
|
sessionId: record.sessionId,
|
|
972
981
|
laneKey: record.laneKey || record.sessionId,
|
|
973
982
|
};
|
|
983
|
+
const memory = this.memoryForWake({
|
|
984
|
+
sessionId: record.sessionId,
|
|
985
|
+
ctx: record.ctx,
|
|
986
|
+
});
|
|
974
987
|
// Same watchdog as a live wake, for the same reason: an unaborted Claude
|
|
975
988
|
// query outlives the promise that abandoned it and keeps spending.
|
|
976
989
|
const abort = new AbortController();
|
|
977
990
|
let watchdog;
|
|
978
991
|
try {
|
|
979
992
|
const result = await Promise.race([
|
|
980
|
-
this.
|
|
993
|
+
this.brain.tick({
|
|
981
994
|
event: record.event,
|
|
982
995
|
ctx: record.ctx,
|
|
983
996
|
state: record.state,
|
|
984
997
|
identity,
|
|
985
998
|
eff: this.eff,
|
|
986
|
-
memory
|
|
999
|
+
memory,
|
|
987
1000
|
transitionTap: this.options.transitionTap,
|
|
988
1001
|
signal: abort.signal,
|
|
989
1002
|
}),
|
|
@@ -1006,7 +1019,7 @@ export class AgentHost {
|
|
|
1006
1019
|
});
|
|
1007
1020
|
}
|
|
1008
1021
|
catch (err) {
|
|
1009
|
-
runtimeLog.warn('
|
|
1022
|
+
runtimeLog.warn('Agent', `metricsTap failed: ${err?.message || err}`);
|
|
1010
1023
|
}
|
|
1011
1024
|
}
|
|
1012
1025
|
return result;
|
|
@@ -1019,25 +1032,33 @@ export class AgentHost {
|
|
|
1019
1032
|
_armIfTimedParked(laneKey, stateName) {
|
|
1020
1033
|
if (!stateName)
|
|
1021
1034
|
return;
|
|
1022
|
-
const sd = this.
|
|
1035
|
+
const sd = this.brain.workflow.states[stateName];
|
|
1023
1036
|
if (sd?.kind !== 'parked' || sd.timeoutMs === undefined)
|
|
1024
1037
|
return;
|
|
1025
1038
|
this.stateTimers.arm(laneKey, stateName, sd.timeoutMs);
|
|
1026
1039
|
}
|
|
1027
1040
|
_resolveOwnAgentId() {
|
|
1028
|
-
// The
|
|
1041
|
+
// The body is constructed for exactly one identity; its own id is the only
|
|
1029
1042
|
// source. (The socket-frame-era metadata fallbacks — self/agent/ziggsAgentId
|
|
1030
1043
|
// — are gone: nothing on the inbox wire ever carried them.)
|
|
1031
1044
|
return this.options.agentId || null;
|
|
1032
1045
|
}
|
|
1033
1046
|
async connectAsync(_timeout) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
//
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1047
|
+
if (this.started)
|
|
1048
|
+
return this;
|
|
1049
|
+
// Start the per-agent inbox long-poll loop — the agent's sole wake source.
|
|
1050
|
+
// There is no socket to connect and no reconnect: a crash is just the
|
|
1051
|
+
// loop's next iteration (the cursor is server-side). `_timeout` is accepted
|
|
1052
|
+
// for backward-compatible call sites but no longer gates a handshake.
|
|
1053
|
+
//
|
|
1054
|
+
// There is no separate catch-up sweep here any more. `tasksAwaitingMe` is
|
|
1055
|
+
// re-derived from the collection on every pass and redelivers an open
|
|
1056
|
+
// assigned task until it closes, so the loop's first pass IS the catch-up —
|
|
1057
|
+
// a restarted agent sees its standing work with nothing extra to run. The
|
|
1058
|
+
// sweep that used to sit here pulled the same list over
|
|
1059
|
+
// `GET /agents/:id/tasks` and reached one gate the inbox does not, which is
|
|
1060
|
+
// how a withheld join node got handed to its assignee before its turn.
|
|
1039
1061
|
if (this.inboxLoopEnabled) {
|
|
1040
|
-
await this._bootstrapAssignedActiveTasks();
|
|
1041
1062
|
const label = this.options.name || 'agent';
|
|
1042
1063
|
this.inboxLoop = runInboxLoop({
|
|
1043
1064
|
creds: {
|
|
@@ -1070,10 +1091,12 @@ export class AgentHost {
|
|
|
1070
1091
|
},
|
|
1071
1092
|
});
|
|
1072
1093
|
}
|
|
1094
|
+
this.pollRunner?.start();
|
|
1095
|
+
this.started = true;
|
|
1073
1096
|
return this;
|
|
1074
1097
|
}
|
|
1075
1098
|
/**
|
|
1076
|
-
* plain string compare against {@link
|
|
1099
|
+
* plain string compare against {@link AgentOptions.requestMatchTags}
|
|
1077
1100
|
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
1078
1101
|
*/
|
|
1079
1102
|
async triageRequestDelivery(request) {
|
|
@@ -1093,52 +1116,6 @@ export class AgentHost {
|
|
|
1093
1116
|
agreementId: request.agreementId,
|
|
1094
1117
|
});
|
|
1095
1118
|
}
|
|
1096
|
-
async _bootstrapAssignedActiveTasks() {
|
|
1097
|
-
const agentId = this.options.agentId;
|
|
1098
|
-
if (!agentId || !this.taskService)
|
|
1099
|
-
return;
|
|
1100
|
-
let tasks = [];
|
|
1101
|
-
try {
|
|
1102
|
-
tasks = await this.taskService.getActiveTasksForAgent(agentId);
|
|
1103
|
-
}
|
|
1104
|
-
catch (err) {
|
|
1105
|
-
runtimeLog.warn(this.options.name || 'agent', `active-task bootstrap skipped: ${err?.message ?? err}`);
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
for (const row of tasks) {
|
|
1109
|
-
const taskId = row.taskId;
|
|
1110
|
-
if (!taskId)
|
|
1111
|
-
continue;
|
|
1112
|
-
if (row.assigneeId && row.assigneeId !== agentId)
|
|
1113
|
-
continue;
|
|
1114
|
-
let full;
|
|
1115
|
-
try {
|
|
1116
|
-
full = (await this.taskService.getTask(taskId));
|
|
1117
|
-
}
|
|
1118
|
-
catch {
|
|
1119
|
-
continue;
|
|
1120
|
-
}
|
|
1121
|
-
const state = String(full.state ?? '');
|
|
1122
|
-
if (state !== 'active' && state !== 'in-progress')
|
|
1123
|
-
continue;
|
|
1124
|
-
const sessionId = laneSessionIdForTask(full);
|
|
1125
|
-
const laneKey = `${agentId}:${sessionId}`;
|
|
1126
|
-
if (!this.sessions.has(laneKey)) {
|
|
1127
|
-
this.sessions.set(laneKey, { sessionId, ownAgentId: agentId });
|
|
1128
|
-
}
|
|
1129
|
-
runtimeLog.info(this.options.name || 'agent', `bootstrap task-assigned ${taskId} (state=${state})`);
|
|
1130
|
-
await this.eventQueue.enqueue({
|
|
1131
|
-
type: 'task_result',
|
|
1132
|
-
result: {
|
|
1133
|
-
...full,
|
|
1134
|
-
taskId: full.taskId ?? taskId,
|
|
1135
|
-
state,
|
|
1136
|
-
providerIsYou: true,
|
|
1137
|
-
},
|
|
1138
|
-
chatId: sessionId,
|
|
1139
|
-
}, laneKey);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
1119
|
/** replay task-assigned for an existing active task (demo recovery). */
|
|
1143
1120
|
async wakeTaskAssignment(taskId) {
|
|
1144
1121
|
const agentId = this.options.agentId;
|
|
@@ -1166,7 +1143,13 @@ export class AgentHost {
|
|
|
1166
1143
|
chatId: sessionId,
|
|
1167
1144
|
}, laneKey);
|
|
1168
1145
|
}
|
|
1169
|
-
disconnect() {
|
|
1146
|
+
disconnect() {
|
|
1147
|
+
this.started = false;
|
|
1148
|
+
this.pollRunner?.stop();
|
|
1149
|
+
this.stateTimers.clearAll();
|
|
1150
|
+
void this.inboxLoop?.stop();
|
|
1151
|
+
this.inboxLoop = null;
|
|
1152
|
+
}
|
|
1170
1153
|
/**
|
|
1171
1154
|
* Waits for in-flight ticks to finish. Call before process.exit() on SIGTERM.
|
|
1172
1155
|
* A timeout is returned, not thrown — the caller names it (lab verdict, fleet
|
|
@@ -1174,20 +1157,43 @@ export class AgentHost {
|
|
|
1174
1157
|
*/
|
|
1175
1158
|
drain(timeoutMs = 30_000) { return this.eventQueue.waitForIdle(timeoutMs); }
|
|
1176
1159
|
registerTool(tool) { return this.toolManager.register(tool); }
|
|
1177
|
-
isConnected() { return this.inboxLoop?.running ?? false; }
|
|
1160
|
+
isConnected() { return this.started && (!this.inboxLoopEnabled || (this.inboxLoop?.running ?? false)); }
|
|
1161
|
+
/**
|
|
1162
|
+
* Whether this agent is still reading its inbox, and when it last did. One
|
|
1163
|
+
* signal, three readers: the store's online badge, an operator's health
|
|
1164
|
+
* check, and monitoring. `lastPassAt` is the read itself; `lastAttemptAt`
|
|
1165
|
+
* says the loop still turns when the server is the thing that is down.
|
|
1166
|
+
* A `pollingInbox: false` agent (inboxCatchUp disabled, lab and eval paths)
|
|
1167
|
+
* has nothing to be stale about and reports no timestamps.
|
|
1168
|
+
*/
|
|
1169
|
+
inboxLiveness() {
|
|
1170
|
+
return {
|
|
1171
|
+
agentId: this.options.agentId,
|
|
1172
|
+
connected: this.isConnected(),
|
|
1173
|
+
pollingInbox: this.inboxLoopEnabled,
|
|
1174
|
+
lastPassAt: this.inboxLoop?.lastPassAt ?? null,
|
|
1175
|
+
lastAttemptAt: this.inboxLoop?.lastAttemptAt ?? null,
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
1178
|
}
|
|
1179
1179
|
/**
|
|
1180
|
-
* createAgent(config) →
|
|
1180
|
+
* createAgent(config) → Agent
|
|
1181
1181
|
* createAgentPool([cfg, ...]) → ConnectionPool (lazy fleet, async-loaded)
|
|
1182
1182
|
*/
|
|
1183
1183
|
export function createAgent(options = {}) {
|
|
1184
|
-
return new
|
|
1184
|
+
return new Agent(options);
|
|
1185
1185
|
}
|
|
1186
1186
|
export async function createAgentPool(configs) {
|
|
1187
1187
|
const ConnectionPool = await loadConnectionPool();
|
|
1188
1188
|
const pool = new ConnectionPool();
|
|
1189
1189
|
pool.register(configs);
|
|
1190
|
-
|
|
1191
|
-
|
|
1190
|
+
try {
|
|
1191
|
+
await pool.connectAll();
|
|
1192
|
+
return pool;
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
await pool.shutdown();
|
|
1196
|
+
throw err;
|
|
1197
|
+
}
|
|
1192
1198
|
}
|
|
1193
|
-
export default
|
|
1199
|
+
export default Agent;
|
|
@@ -7,7 +7,7 @@ interface AdapterOptions {
|
|
|
7
7
|
/**
|
|
8
8
|
* Anthropic adapter satisfying the same LlmAdapter contract as OpenAIAdapter:
|
|
9
9
|
* OpenAI-wire-shaped LlmMessage[] in, { content, tool_calls, usage } out.
|
|
10
|
-
* Selected by
|
|
10
|
+
* Selected by Agent when the configured model id starts with "claude".
|
|
11
11
|
*/
|
|
12
12
|
export declare class AnthropicAdapter {
|
|
13
13
|
private client;
|
|
@@ -8,7 +8,7 @@ const MAX_COMPLETION_TOKENS = 16000;
|
|
|
8
8
|
/**
|
|
9
9
|
* Anthropic adapter satisfying the same LlmAdapter contract as OpenAIAdapter:
|
|
10
10
|
* OpenAI-wire-shaped LlmMessage[] in, { content, tool_calls, usage } out.
|
|
11
|
-
* Selected by
|
|
11
|
+
* Selected by Agent when the configured model id starts with "claude".
|
|
12
12
|
*/
|
|
13
13
|
export class AnthropicAdapter {
|
|
14
14
|
client;
|