@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
package/dist/runtime/runTurn.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { isAgreementId } from '@ziggs-ai/api-client';
|
|
1
2
|
import { outcomeFromActionResult } from './buildOutcome.js';
|
|
2
3
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
4
|
import { safeParseJSON } from '../utils/jsonExtractor.js';
|
|
4
5
|
import { withDeclaredMandate } from './mandateDeclaration.js';
|
|
6
|
+
import { resolveOnBehalfAgreementId, } from '../brain/onBehalfAgreement.js';
|
|
5
7
|
const MAX_TOOL_LOOP_ITERS = 20;
|
|
6
8
|
const MAX_MSG_CHARS = 3500;
|
|
7
9
|
/**
|
|
@@ -117,9 +119,7 @@ export async function runTurn(input) {
|
|
|
117
119
|
// only. a bare agreement id as sessionId is a mis-keyed lane —
|
|
118
120
|
// loud-log the empty snapshot rather than staying silent.
|
|
119
121
|
if (ctxObj.chatMissing) {
|
|
120
|
-
const looksLikeBareAgreement =
|
|
121
|
-
sessionId.startsWith('agr_') &&
|
|
122
|
-
!sessionId.startsWith('agrn-');
|
|
122
|
+
const looksLikeBareAgreement = isAgreementId(sessionId);
|
|
123
123
|
if (looksLikeBareAgreement) {
|
|
124
124
|
runtimeLog.warn('runTurn', `chatMissing on non-chat sessionId=${sessionId} (state=${stateId}) — ` +
|
|
125
125
|
`lane was keyed by agreementId instead of the agreement's origin/notification chat; ` +
|
|
@@ -265,7 +265,9 @@ export async function runTurn(input) {
|
|
|
265
265
|
// not. The runtime knows; the model would have to be told.
|
|
266
266
|
args = withDeclaredMandate(args, toolName, toolManager, ctx.activeAgreementId);
|
|
267
267
|
const decision = { action: matchingAction, tool: toolName, args };
|
|
268
|
-
const result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory,
|
|
268
|
+
const result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory, incomingEvent, addToolUsage, ctx.identity.agentId, Array.isArray(ctxObj.agreements)
|
|
269
|
+
? ctxObj.agreements
|
|
270
|
+
: null);
|
|
269
271
|
toolCallResults.push({ tc, matchingAction, result, args, actionDef });
|
|
270
272
|
}
|
|
271
273
|
let hasTerminal = false;
|
|
@@ -418,7 +420,9 @@ export async function runTurn(input) {
|
|
|
418
420
|
try {
|
|
419
421
|
// effWithTrace, not eff: JSON-path tool calls must carry runId/stateId
|
|
420
422
|
// and the wake's write keys exactly like tool-loop calls do.
|
|
421
|
-
result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory,
|
|
423
|
+
result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory, incomingEvent, addToolUsage, ctx.identity.agentId, Array.isArray(ctxObj.agreements)
|
|
424
|
+
? ctxObj.agreements
|
|
425
|
+
: null);
|
|
422
426
|
}
|
|
423
427
|
catch (err) {
|
|
424
428
|
// Only outbound sends can throw out of executeDecision — executeTool
|
|
@@ -642,17 +646,18 @@ function findActionForTool(toolName, allowedActionNames, actions) {
|
|
|
642
646
|
}
|
|
643
647
|
return null;
|
|
644
648
|
}
|
|
645
|
-
async function executeDecision(decision, actionDef, eff, sessionId, memory, incomingEvent = null, onUsage) {
|
|
649
|
+
async function executeDecision(decision, actionDef, eff, sessionId, memory, incomingEvent = null, onUsage, agentId = null, agreements = null) {
|
|
646
650
|
const boundTool = actionDef?.tool || null;
|
|
647
651
|
if (boundTool) {
|
|
648
652
|
decision.tool = boundTool;
|
|
649
|
-
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage);
|
|
653
|
+
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage, agentId, agreements);
|
|
650
654
|
}
|
|
651
655
|
if (decision.action === '_genericDiscoveryTool' && decision.tool) {
|
|
652
|
-
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage);
|
|
656
|
+
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage, agentId, agreements);
|
|
657
|
+
}
|
|
658
|
+
if (decision.tool) {
|
|
659
|
+
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage, agentId, agreements);
|
|
653
660
|
}
|
|
654
|
-
if (decision.tool)
|
|
655
|
-
return await executeTool(decision, eff, sessionId, memory, incomingEvent, onUsage);
|
|
656
661
|
if (decision.messages && Array.isArray(decision.messages) && decision.messages.length > 0) {
|
|
657
662
|
return await executeSendMessages(decision, eff, sessionId);
|
|
658
663
|
}
|
|
@@ -775,7 +780,7 @@ async function executeSendMessages(decision, eff, sessionId) {
|
|
|
775
780
|
* dependencies (taskService, agreementService, agents list, etc.), and
|
|
776
781
|
* wrapping the call in telemetry / operation-record entries if it wants.
|
|
777
782
|
*/
|
|
778
|
-
async function executeTool(decision, eff, sessionId, memory, incomingEvent = null, onUsage) {
|
|
783
|
+
async function executeTool(decision, eff, sessionId, memory, incomingEvent = null, onUsage, agentId = null, agreements = null) {
|
|
779
784
|
const { tool, args } = decision;
|
|
780
785
|
if (!tool)
|
|
781
786
|
return null;
|
|
@@ -788,13 +793,8 @@ async function executeTool(decision, eff, sessionId, memory, incomingEvent = nul
|
|
|
788
793
|
memory,
|
|
789
794
|
senderId: incomingEvent?.senderId != null ? String(incomingEvent.senderId) : undefined,
|
|
790
795
|
senderType: incomingEvent?.senderType != null ? String(incomingEvent.senderType) : undefined,
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
// resolve the originating end-user, not the delegating agent (senderId).
|
|
794
|
-
onBehalfOfAgreementId: (() => {
|
|
795
|
-
const aid = incomingEvent?.result?.agreementId;
|
|
796
|
-
return typeof aid === 'string' && aid ? aid : undefined;
|
|
797
|
-
})(),
|
|
796
|
+
// Never bind on-behalf to a sub the agent only pays for.
|
|
797
|
+
onBehalfOfAgreementId: resolveOnBehalfAgreementId(incomingEvent ?? undefined, agreements, agentId, sessionId),
|
|
798
798
|
});
|
|
799
799
|
if (r.usage)
|
|
800
800
|
onUsage?.(r.usage);
|
|
@@ -94,7 +94,7 @@ export function validateWorkflow(workflow) {
|
|
|
94
94
|
errors.push(`thinking state "${name}" requires at least one action`);
|
|
95
95
|
}
|
|
96
96
|
if (state.timeoutMs !== undefined) {
|
|
97
|
-
errors.push(`state "${name}" is thinking but declares timeoutMs — liveness timers apply to parked states only (thinking turns are bounded by
|
|
97
|
+
errors.push(`state "${name}" is thinking but declares timeoutMs — liveness timers apply to parked states only (thinking turns are bounded by Agent tickTimeoutMs)`);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
if (state.kind === 'mechanical') {
|
|
@@ -61,9 +61,14 @@ export declare function skillHash(files: SkillFile[]): string;
|
|
|
61
61
|
export declare function parseSkill(files: SkillFile[], opts?: {
|
|
62
62
|
maxChars?: number;
|
|
63
63
|
}): ParsedSkill;
|
|
64
|
+
/** Catalog name for a reference path (`references/tone.md` → `tone`). */
|
|
65
|
+
export declare function skillReferenceName(path: string): string;
|
|
66
|
+
/** First prose line of a reference, for the on-prompt catalog. */
|
|
67
|
+
export declare function skillReferenceOneLine(content: string): string;
|
|
64
68
|
/**
|
|
65
|
-
* Assemble the skill's role prompt: body
|
|
66
|
-
*
|
|
69
|
+
* Assemble the skill's role prompt: the body, then a catalog of references.
|
|
70
|
+
* Full reference text is fetched with `skill_read_reference` — it does not
|
|
71
|
+
* ride every turn.
|
|
67
72
|
*
|
|
68
73
|
* This is only the ROLE block. The Ziggs protocol is prepended separately, by
|
|
69
74
|
* the one place that builds a Claude-brain system prompt — a skill is never in a
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* with the agent's full authority, so "instructions-only" has to be a structural
|
|
9
9
|
* property of what we accept, not a convention authors are trusted to follow.
|
|
10
10
|
*
|
|
11
|
-
* This module is deliberately free of
|
|
11
|
+
* This module is deliberately free of Agent / LLM / MCP imports so it stays
|
|
12
12
|
* unit-testable standalone (same rule as scripts/lib/mcp-provision.ts).
|
|
13
13
|
*/
|
|
14
14
|
import { createHash } from 'node:crypto';
|
|
@@ -124,9 +124,24 @@ export function parseSkill(files, opts = {}) {
|
|
|
124
124
|
}
|
|
125
125
|
return { name: name, description: description, body, references, hash: skillHash(files) };
|
|
126
126
|
}
|
|
127
|
+
/** Catalog name for a reference path (`references/tone.md` → `tone`). */
|
|
128
|
+
export function skillReferenceName(path) {
|
|
129
|
+
return path.replace(/^references\//, '').replace(/\.md$/, '');
|
|
130
|
+
}
|
|
131
|
+
/** First prose line of a reference, for the on-prompt catalog. */
|
|
132
|
+
export function skillReferenceOneLine(content) {
|
|
133
|
+
for (const line of content.split(/\r?\n/)) {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
136
|
+
continue;
|
|
137
|
+
return trimmed.length > 160 ? `${trimmed.slice(0, 157)}…` : trimmed;
|
|
138
|
+
}
|
|
139
|
+
return '(empty)';
|
|
140
|
+
}
|
|
127
141
|
/**
|
|
128
|
-
* Assemble the skill's role prompt: body
|
|
129
|
-
*
|
|
142
|
+
* Assemble the skill's role prompt: the body, then a catalog of references.
|
|
143
|
+
* Full reference text is fetched with `skill_read_reference` — it does not
|
|
144
|
+
* ride every turn.
|
|
130
145
|
*
|
|
131
146
|
* This is only the ROLE block. The Ziggs protocol is prepended separately, by
|
|
132
147
|
* the one place that builds a Claude-brain system prompt — a skill is never in a
|
|
@@ -134,8 +149,10 @@ export function parseSkill(files, opts = {}) {
|
|
|
134
149
|
*/
|
|
135
150
|
export function skillRolePrompt(skill) {
|
|
136
151
|
const parts = [skill.body];
|
|
137
|
-
|
|
138
|
-
parts.push(
|
|
152
|
+
if (skill.references.length > 0) {
|
|
153
|
+
parts.push('## Skill references', 'These files are not in this prompt. Call skill_read_reference with the name when you need one. Do not invent their contents.', skill.references
|
|
154
|
+
.map((ref) => `- ${skillReferenceName(ref.path)}: ${skillReferenceOneLine(ref.content)}`)
|
|
155
|
+
.join('\n'));
|
|
139
156
|
}
|
|
140
157
|
return parts.join('\n\n');
|
|
141
158
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ParsedSkill } from './parseSkill.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fetch one skill reference by catalog name. The role prompt lists names and
|
|
4
|
+
* one line each; this is how the rest of the file is loaded.
|
|
5
|
+
*/
|
|
6
|
+
export declare function skillReadReferenceTool(skill: ParsedSkill): import("../tools/defineTool.js").ToolDefinition;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineTool } from '../tools/defineTool.js';
|
|
2
|
+
import { skillReferenceName, } from './parseSkill.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch one skill reference by catalog name. The role prompt lists names and
|
|
5
|
+
* one line each; this is how the rest of the file is loaded.
|
|
6
|
+
*/
|
|
7
|
+
export function skillReadReferenceTool(skill) {
|
|
8
|
+
const byName = new Map(skill.references.map((ref) => [skillReferenceName(ref.path), ref]));
|
|
9
|
+
return defineTool('skill_read_reference', {
|
|
10
|
+
name: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Reference name from the skill catalog (filename without .md)',
|
|
14
|
+
},
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const name = String(args['name'] ?? '').trim();
|
|
17
|
+
const ref = byName.get(name);
|
|
18
|
+
if (!ref) {
|
|
19
|
+
const available = [...byName.keys()];
|
|
20
|
+
throw new Error(`unknown skill reference "${name}". Available: ${available.join(', ') || '(none)'}`);
|
|
21
|
+
}
|
|
22
|
+
return { name, path: ref.path, content: ref.content };
|
|
23
|
+
}, {
|
|
24
|
+
description: 'Read one skill reference by name. The catalog of names is in your role prompt.',
|
|
25
|
+
isGenericFallback: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
package/dist/tools/needs.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* Scope is agents built on this SDK.
|
|
12
12
|
*/
|
|
13
13
|
import { toolName } from './defineTool.js';
|
|
14
|
-
import { connectionListGrantsTool, connectionProxyTool } from '../
|
|
15
|
-
import { MCP_CONNECTION_TOOLS } from '../
|
|
14
|
+
import { connectionListGrantsTool, connectionProxyTool } from '../platform/tools/tier2/connectionTools.js';
|
|
15
|
+
import { MCP_CONNECTION_TOOLS } from '../platform/tools/tier2/mcpConnectionTools.js';
|
|
16
16
|
/**
|
|
17
17
|
* A remote MCP server is stored as `provider: 'mcp'` and goes through the
|
|
18
18
|
* gateway. `connection_proxy` resolves a NAMED connector and refuses it with
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A custom tool builds `{ operatorKey, agentId }` by hand and stops there, so
|
|
5
5
|
* every call it makes runs on the narrowest reach fence there is: the agent's
|
|
6
|
-
* own org. `
|
|
6
|
+
* own org. `Agent` already threads the wake's lane into the tool context
|
|
7
7
|
* (as `chatId` / `sessionId`, which is what the capability adapter reads), and
|
|
8
8
|
* the backend's fence is "own org ∪ the orgs of the parties to this lane" — so
|
|
9
9
|
* a tool that does not pass it loses reach it is entitled to, and loses it
|
package/dist/tools/toolCreds.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A custom tool builds `{ operatorKey, agentId }` by hand and stops there, so
|
|
5
5
|
* every call it makes runs on the narrowest reach fence there is: the agent's
|
|
6
|
-
* own org. `
|
|
6
|
+
* own org. `Agent` already threads the wake's lane into the tool context
|
|
7
7
|
* (as `chatId` / `sessionId`, which is what the capability adapter reads), and
|
|
8
8
|
* the backend's fence is "own org ∪ the orgs of the parties to this lane" — so
|
|
9
9
|
* a tool that does not pass it loses reach it is entitled to, and loses it
|
package/dist/types.d.ts
CHANGED
|
@@ -502,7 +502,7 @@ export type Effect = {
|
|
|
502
502
|
/**
|
|
503
503
|
* Typed result map. With this shape, `await eff({ kind: 'read-context', .. })`
|
|
504
504
|
* resolves to `ContextSnapshot`, not a union — the discriminant carries the
|
|
505
|
-
* result type. Tests and
|
|
505
|
+
* result type. Tests and effect handlers implement this map exhaustively.
|
|
506
506
|
*/
|
|
507
507
|
export interface EffectResultMap {
|
|
508
508
|
'read-context': ContextSnapshot;
|
|
@@ -520,7 +520,7 @@ export interface EffectResultMap {
|
|
|
520
520
|
};
|
|
521
521
|
}
|
|
522
522
|
export type EffectResult<K extends EffectKind> = EffectResultMap[K];
|
|
523
|
-
/** The single side-channel between
|
|
523
|
+
/** The single side-channel between a Brain and its Agent. */
|
|
524
524
|
export type EffectHandler = <K extends EffectKind>(effect: Extract<Effect, {
|
|
525
525
|
kind: K;
|
|
526
526
|
}>) => Promise<EffectResult<K>>;
|
|
@@ -563,7 +563,7 @@ export interface DefineAgentInput {
|
|
|
563
563
|
[key: string]: unknown;
|
|
564
564
|
}
|
|
565
565
|
/**
|
|
566
|
-
* Frozen output of defineAgent. Consumed directly by
|
|
566
|
+
* Frozen output of defineAgent. Consumed directly by Agent / createAgent.
|
|
567
567
|
* defineAgent injects ONE default: the agreement-kickoff state `kickingOff`
|
|
568
568
|
* plus a last-position transition into it, added to states that don't already
|
|
569
569
|
* handle a proposer-side proposal-resolved/approve outcome (runtime/kickoff.ts).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "0.
|
|
29
|
+
"@ziggs-ai/api-client": "0.17.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'http';
|
|
2
|
-
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
|
-
function readJsonBody(req) {
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
const chunks = [];
|
|
6
|
-
req.on('data', (c) => chunks.push(c));
|
|
7
|
-
req.on('end', () => {
|
|
8
|
-
try {
|
|
9
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
10
|
-
resolve(raw ? JSON.parse(raw) : {});
|
|
11
|
-
}
|
|
12
|
-
catch (err) {
|
|
13
|
-
reject(err);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
req.on('error', reject);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
export function createHealthServer({ port = Number(process.env.PORT) || 8080, label = 'agents', pool, } = {}) {
|
|
20
|
-
const server = createServer(async (req, res) => {
|
|
21
|
-
const url = req.url ?? '/';
|
|
22
|
-
const startMatch = url.match(/^\/fleet-start\/([^/?]+)/);
|
|
23
|
-
if (req.method === 'POST' && startMatch && pool) {
|
|
24
|
-
const agentId = decodeURIComponent(startMatch[1]);
|
|
25
|
-
try {
|
|
26
|
-
await pool.startAgent(agentId);
|
|
27
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
28
|
-
res.end(JSON.stringify({ ok: true, agentId, active: pool.listActive().includes(agentId) }));
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
32
|
-
res.end(JSON.stringify({ ok: false, error: err.message }));
|
|
33
|
-
}
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (req.method === 'POST' && url === '/fleet-deliver' && pool) {
|
|
37
|
-
try {
|
|
38
|
-
const body = await readJsonBody(req);
|
|
39
|
-
const agentId = String(body.agentId ?? '');
|
|
40
|
-
const chatId = String(body.chatId ?? '');
|
|
41
|
-
const text = String(body.text ?? '');
|
|
42
|
-
const senderId = String(body.senderId ?? '');
|
|
43
|
-
if (!agentId || !chatId || !text || !senderId) {
|
|
44
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
45
|
-
res.end(JSON.stringify({ ok: false, error: 'agentId, chatId, text, senderId required' }));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const host = (await pool.startAgent(agentId));
|
|
49
|
-
await host.handleMessage(text, {
|
|
50
|
-
chatId,
|
|
51
|
-
entryType: 'message',
|
|
52
|
-
contentType: 'text',
|
|
53
|
-
sender: { id: senderId, type: 'USER' },
|
|
54
|
-
receiver: { id: agentId, type: 'AGENT' },
|
|
55
|
-
});
|
|
56
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
57
|
-
res.end(JSON.stringify({ ok: true, agentId }));
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
61
|
-
res.end(JSON.stringify({ ok: false, error: err.message }));
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (url === '/health' || url === '/') {
|
|
66
|
-
res.writeHead(200);
|
|
67
|
-
res.end('ok');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
res.writeHead(404);
|
|
71
|
-
res.end();
|
|
72
|
-
});
|
|
73
|
-
server.listen(port, () => runtimeLog.info(label, `health server on port ${port}`));
|
|
74
|
-
return server;
|
|
75
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ConnectionPool } from './ConnectionPool.js';
|
|
2
|
-
import { type AgentHostOptions } from '../AgentHost.js';
|
|
3
|
-
type AgentConfig = AgentHostOptions & {
|
|
4
|
-
ownAgentId?: string;
|
|
5
|
-
options?: {
|
|
6
|
-
ownAgentId?: string;
|
|
7
|
-
name?: string;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
interface SingleOptions {
|
|
11
|
-
operatorKey?: string;
|
|
12
|
-
label?: string;
|
|
13
|
-
port?: number | string;
|
|
14
|
-
healthServer?: boolean;
|
|
15
|
-
onShutdown?: () => Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
interface FleetOptions extends SingleOptions {
|
|
18
|
-
agentMeta?: Record<string, unknown>[];
|
|
19
|
-
primary?: AgentConfig | ((pool: ConnectionPool) => AgentConfig);
|
|
20
|
-
}
|
|
21
|
-
export declare function runLauncher(configOrConfigs: AgentConfig | AgentConfig[], opts?: FleetOptions): Promise<unknown>;
|
|
22
|
-
export {};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { ConnectionPool } from './ConnectionPool.js';
|
|
2
|
-
import { AgentHost } from '../AgentHost.js';
|
|
3
|
-
import { createHealthServer } from './createHealthServer.js';
|
|
4
|
-
import { boundKeyFleetWarning } from '../shared/operatorKeyClaims.js';
|
|
5
|
-
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
6
|
-
import { PollRegistry } from './polls/PollKind.js';
|
|
7
|
-
import { PollRunner } from './polls/PollRunner.js';
|
|
8
|
-
/**
|
|
9
|
-
* How long a shutdown waits for in-flight turns after it has stopped taking new
|
|
10
|
-
* work. An agent turn is an LLM call, so the old 30s was under the length of the
|
|
11
|
-
* work it was meant to protect; the container's stop timeout is what has to be
|
|
12
|
-
* larger still, or the drain is cut short by SIGKILL regardless (ECS
|
|
13
|
-
* `stopTimeout`, 120s on the fleet task definitions).
|
|
14
|
-
*/
|
|
15
|
-
const DRAIN_TIMEOUT_MS = Number(process.env.FLEET_DRAIN_TIMEOUT_MS) || 90_000;
|
|
16
|
-
/**
|
|
17
|
-
* Collect the poll kinds the agents themselves ship and start a runner for
|
|
18
|
-
* whatever they declared. This lives in the launcher, not in a caller's
|
|
19
|
-
* bootstrap file: an agent that declares `polls` and gets no runner sees no
|
|
20
|
-
* error and simply never wakes, which is indistinguishable from a quiet source.
|
|
21
|
-
* Every launcher path therefore executes polls, or refuses loudly.
|
|
22
|
-
*/
|
|
23
|
-
function startDeclaredPolls(configs, pool, { operatorKey, label }) {
|
|
24
|
-
const registry = new PollRegistry();
|
|
25
|
-
for (const cfg of configs) {
|
|
26
|
-
for (const kind of (cfg.pollKinds ?? [])) {
|
|
27
|
-
// Two agents may legitimately share a source; first registration wins and
|
|
28
|
-
// a duplicate id is not an error worth killing a fleet launch over.
|
|
29
|
-
if (!registry.get(kind.id))
|
|
30
|
-
registry.register(kind);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const runner = new PollRunner({
|
|
34
|
-
pool,
|
|
35
|
-
registry,
|
|
36
|
-
agents: configs,
|
|
37
|
-
label: `${label}-polls`,
|
|
38
|
-
...(operatorKey ? { operatorKey } : {}),
|
|
39
|
-
});
|
|
40
|
-
if (runner.size === 0)
|
|
41
|
-
return null;
|
|
42
|
-
runner.start();
|
|
43
|
-
return runner;
|
|
44
|
-
}
|
|
45
|
-
export async function runLauncher(configOrConfigs, opts = {}) {
|
|
46
|
-
if (Array.isArray(configOrConfigs))
|
|
47
|
-
return runFleet(configOrConfigs, opts);
|
|
48
|
-
return runSingle(configOrConfigs, opts);
|
|
49
|
-
}
|
|
50
|
-
async function runSingle(config, { operatorKey, label = 'agent', port = process.env.PORT || 8080, healthServer = true, onShutdown } = {}) {
|
|
51
|
-
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
52
|
-
const decorated = effectiveKey && !config.operatorKey ? { ...config, operatorKey: effectiveKey } : config;
|
|
53
|
-
const agent = new AgentHost(decorated);
|
|
54
|
-
try {
|
|
55
|
-
await agent.connectAsync();
|
|
56
|
-
runtimeLog.info(label, `"${agent['options'] ? (agent['options']?.agentId ?? agent['options']?.name) : 'agent'}" connected`);
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
runtimeLog.warn(label, `Agent failed to connect at startup: ${err.message} — will retry`);
|
|
60
|
-
}
|
|
61
|
-
const server = healthServer ? createHealthServer({ port: Number(port), label }) : null;
|
|
62
|
-
// One agent, so the "pool" is just this host.
|
|
63
|
-
const pollRunner = startDeclaredPolls([decorated], { sendTo: (_agentId, text, metadata) => agent.handleMessage(text, metadata ?? {}) }, { label, ...(effectiveKey ? { operatorKey: effectiveKey } : {}) });
|
|
64
|
-
process.on('SIGTERM', async () => {
|
|
65
|
-
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
66
|
-
pollRunner?.stop();
|
|
67
|
-
agent.disconnect();
|
|
68
|
-
await agent.drain(DRAIN_TIMEOUT_MS);
|
|
69
|
-
try {
|
|
70
|
-
await onShutdown?.();
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
runtimeLog.warn(label, `onShutdown error: ${err.message}`);
|
|
74
|
-
}
|
|
75
|
-
server?.close();
|
|
76
|
-
process.exit(0);
|
|
77
|
-
});
|
|
78
|
-
return { agent, healthServer: server ?? undefined };
|
|
79
|
-
}
|
|
80
|
-
async function runFleet(configs, { operatorKey, port = process.env.PORT || 8080, healthServer = true, label = 'launcher', agentMeta, primary: primaryConfig, onShutdown, } = {}) {
|
|
81
|
-
const pool = new ConnectionPool();
|
|
82
|
-
const effectiveKey = operatorKey ?? process.env.ZIGGS_OPERATOR_KEY;
|
|
83
|
-
const decoratedConfigs = effectiveKey
|
|
84
|
-
? configs.map(c => (c.operatorKey ? c : { ...c, operatorKey: effectiveKey }))
|
|
85
|
-
: configs;
|
|
86
|
-
pool.register(decoratedConfigs, agentMeta);
|
|
87
|
-
const server = healthServer ? createHealthServer({ port: Number(port), label, pool }) : null;
|
|
88
|
-
let resolvedPrimaryConfig = typeof primaryConfig === 'function'
|
|
89
|
-
? primaryConfig(pool)
|
|
90
|
-
: primaryConfig;
|
|
91
|
-
if (resolvedPrimaryConfig && effectiveKey && !resolvedPrimaryConfig.operatorKey) {
|
|
92
|
-
resolvedPrimaryConfig = { ...resolvedPrimaryConfig, operatorKey: effectiveKey };
|
|
93
|
-
}
|
|
94
|
-
const controlKey = effectiveKey ?? configs.find(c => c.operatorKey)?.operatorKey ?? resolvedPrimaryConfig?.operatorKey;
|
|
95
|
-
if (controlKey) {
|
|
96
|
-
// an agent-bound key half-works in a confusing way — say the big
|
|
97
|
-
// thing once at startup instead of letting N-1 per-agent loops 403.
|
|
98
|
-
const boundWarning = boundKeyFleetWarning(controlKey, pool.size);
|
|
99
|
-
if (boundWarning)
|
|
100
|
-
runtimeLog.warn(label, boundWarning);
|
|
101
|
-
}
|
|
102
|
-
let primary = null;
|
|
103
|
-
if (resolvedPrimaryConfig) {
|
|
104
|
-
primary = new AgentHost(resolvedPrimaryConfig);
|
|
105
|
-
try {
|
|
106
|
-
await primary.connectAsync();
|
|
107
|
-
runtimeLog.info(label, `Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" connected`);
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
// Backend may not be ready yet — the inbox long-poll loop retries on
|
|
111
|
-
// the next pass. Don't crash the process.
|
|
112
|
-
runtimeLog.warn(label, `Primary agent "${resolvedPrimaryConfig.name || resolvedPrimaryConfig.ownAgentId || 'primary'}" failed to connect at startup: ${err.message} — will retry`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Every registered agent connects. There is no roster to consult
|
|
116
|
-
// and no reason to defer: an agent this launcher was handed is an agent it
|
|
117
|
-
// hosts, and each host's own inbox long-poll is the only wake signal needed.
|
|
118
|
-
await pool.connectAll();
|
|
119
|
-
const connected = pool.listActive().length;
|
|
120
|
-
if (connected < pool.size) {
|
|
121
|
-
// Say it loudly and with the number. A launcher that silently hosts fewer
|
|
122
|
-
// agents than it was given is exactly how three published, hireable agents
|
|
123
|
-
// ran with no host and nothing noticed.
|
|
124
|
-
runtimeLog.warn(label, `Only ${connected}/${pool.size} agent(s) connected — the rest are registered but unhosted`);
|
|
125
|
-
}
|
|
126
|
-
runtimeLog.info(label, `Ready — ${connected}/${pool.size} agent(s) connected`);
|
|
127
|
-
const pollRunner = startDeclaredPolls(decoratedConfigs, pool, {
|
|
128
|
-
label,
|
|
129
|
-
...(effectiveKey ? { operatorKey: effectiveKey } : {}),
|
|
130
|
-
});
|
|
131
|
-
process.on('SIGTERM', async () => {
|
|
132
|
-
runtimeLog.info(label, 'SIGTERM — shutting down');
|
|
133
|
-
// Stop taking new work everywhere first, then wait for what is running.
|
|
134
|
-
//
|
|
135
|
-
// This used to disconnect and drain only `primary` — which `run.ts` never
|
|
136
|
-
// sets, so in the deployed fleet the drain was a no-op on null and the
|
|
137
|
-
// process exited straight through `disconnectAll`, killing every in-flight
|
|
138
|
-
// turn. A buyer whose request landed mid-rollout got a host that stopped
|
|
139
|
-
// mid-sentence and never reported.
|
|
140
|
-
//
|
|
141
|
-
// Draining before stopping intake is the other half of the same bug: the
|
|
142
|
-
// wait is exactly the window in which a host on its way out keeps accepting
|
|
143
|
-
// deliveries the incoming host is also being handed.
|
|
144
|
-
pollRunner?.stop();
|
|
145
|
-
primary?.disconnect?.();
|
|
146
|
-
await Promise.all([
|
|
147
|
-
pool.shutdown(DRAIN_TIMEOUT_MS),
|
|
148
|
-
primary?.drain?.(DRAIN_TIMEOUT_MS) ?? Promise.resolve(),
|
|
149
|
-
]);
|
|
150
|
-
try {
|
|
151
|
-
await onShutdown?.();
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
runtimeLog.warn(label, `onShutdown error: ${err.message}`);
|
|
155
|
-
}
|
|
156
|
-
server?.close();
|
|
157
|
-
process.exit(0);
|
|
158
|
-
});
|
|
159
|
-
return { pool, primary, healthServer: server ?? undefined };
|
|
160
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|