@ziggs-ai/agent-sdk 0.42.2 → 0.43.1
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 +40 -0
- package/README.md +1 -1
- package/dist/Agent.d.ts +21 -8
- package/dist/Agent.js +173 -97
- package/dist/brain/ClaudeBrain.d.ts +0 -1
- package/dist/brain/ClaudeBrain.js +18 -42
- package/dist/brain/claudeCognition.js +5 -6
- package/dist/brain/claudeSdkProtocol.js +9 -9
- package/dist/brain/claudeSdkTools.d.ts +3 -3
- package/dist/brain/claudeSdkTools.js +37 -133
- package/dist/brain/hostedToolSurface.js +1 -1
- package/dist/brain/standingOfferNotice.d.ts +0 -2
- package/dist/brain/standingOfferNotice.js +1 -21
- package/dist/brain/toolManagerToSdkMcp.js +1 -1
- package/dist/brain/wakeGate.d.ts +3 -1
- package/dist/brain/wakeGate.js +6 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/ingress/normalizeIncoming.d.ts +5 -3
- package/dist/ingress/normalizeIncoming.js +9 -8
- package/dist/mcp/createMcpAgent.d.ts +15 -0
- package/dist/mcp/createMcpAgent.js +47 -11
- package/dist/memory/MemoryStore.d.ts +1 -1
- package/dist/memory/MemoryStore.js +1 -6
- package/dist/platform/ConnectionPool.js +1 -1
- package/dist/platform/OutboxBuffer.d.ts +2 -2
- package/dist/platform/OutboxBuffer.js +17 -17
- package/dist/platform/StateTimers.d.ts +3 -1
- package/dist/platform/StateTimers.js +4 -3
- package/dist/platform/WakeLedger.d.ts +10 -6
- package/dist/platform/WakeLedger.js +16 -9
- package/dist/platform/ZiggsEffectHandler.d.ts +18 -4
- package/dist/platform/ZiggsEffectHandler.js +86 -55
- package/dist/platform/agreements/AgreementService.d.ts +1 -3
- package/dist/platform/agreements/AgreementService.js +1 -7
- package/dist/platform/inboxLoop.d.ts +3 -9
- package/dist/platform/inboxLoop.js +151 -158
- package/dist/platform/polls/PollKind.d.ts +13 -16
- package/dist/platform/polls/PollKind.js +4 -4
- package/dist/platform/polls/PollRunner.d.ts +3 -20
- package/dist/platform/polls/PollRunner.js +13 -15
- package/dist/platform/polls/declaredPollRunner.d.ts +1 -1
- package/dist/platform/polls/declaredPollRunner.js +1 -1
- package/dist/platform/tasks/TaskService.d.ts +4 -3
- package/dist/platform/tasks/TaskService.js +1 -4
- package/dist/platform/telemetryIngest.js +4 -4
- package/dist/platform/tools/index.d.ts +0 -1
- package/dist/platform/tools/index.js +0 -3
- package/dist/platform/tools/tier1/protocolRunner.d.ts +1 -1
- package/dist/platform/tools/tier1/protocolRunner.js +24 -17
- package/dist/platform/tools/tier1/protocolTools.js +2 -2
- package/dist/platform/tools/tier1/wakeTools.d.ts +1 -1
- package/dist/platform/tools/tier1/wakeTools.js +2 -1
- package/dist/platform/tools/tier2/mcpConnectionTools.js +1 -1
- package/dist/runtime/AgentMachine.d.ts +2 -2
- package/dist/runtime/AgentMachine.js +4 -5
- package/dist/runtime/PromptBuilder.d.ts +1 -1
- package/dist/runtime/PromptBuilder.js +5 -4
- package/dist/runtime/buildOutcome.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.js +1 -1
- package/dist/runtime/kickoff.d.ts +14 -43
- package/dist/runtime/kickoff.js +22 -178
- package/dist/runtime/messageCommand.d.ts +9 -0
- package/dist/runtime/messageCommand.js +23 -0
- package/dist/runtime/requiredContext.d.ts +6 -0
- package/dist/runtime/requiredContext.js +23 -0
- package/dist/runtime/runTurn.js +31 -106
- package/dist/runtime/taskIntake.js +8 -22
- package/dist/runtime/wakeEffects.d.ts +3 -12
- package/dist/runtime/wakeEffects.js +17 -48
- package/dist/runtime/wakeOutcome.js +1 -1
- package/dist/skills/createSkillAgent.js +12 -1
- package/dist/tools/capabilityAdapter.js +1 -1
- package/dist/tools/recordReport.d.ts +1 -1
- package/dist/tools/recordReport.js +3 -3
- package/dist/tools/toolCreds.d.ts +1 -1
- package/dist/tools/toolCreds.js +4 -2
- package/dist/types.d.ts +17 -27
- package/package.json +3 -3
- package/dist/platform/tools/tier2/chatTools.d.ts +0 -3
- package/dist/platform/tools/tier2/chatTools.js +0 -7
- package/dist/probeChat.d.ts +0 -10
- package/dist/probeChat.js +0 -12
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export { runTurn } from './runtime/runTurn.js';
|
|
|
42
42
|
export { runMechanical } from './runtime/runMechanical.js';
|
|
43
43
|
export { PromptBuilder } from './runtime/PromptBuilder.js';
|
|
44
44
|
export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
|
|
45
|
-
export { KICKOFF_STATE,
|
|
45
|
+
export { KICKOFF_STATE, injectKickoffDefault, buildKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
|
|
46
46
|
export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
|
|
47
47
|
export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
|
|
48
48
|
export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
|
|
@@ -82,7 +82,7 @@ export {
|
|
|
82
82
|
// Tier 1 — protocol grammar (framework-managed via taskTools).
|
|
83
83
|
PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp,
|
|
84
84
|
// Tier 2 — opt-in capability bundles (spread into `tools:`).
|
|
85
|
-
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, PARTY_TOOLS, partyNoteTool, ARTIFACT_TOOLS, recordArtifactTool,
|
|
85
|
+
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, PARTY_TOOLS, partyNoteTool, ARTIFACT_TOOLS, recordArtifactTool, LISTING_TOOLS, } from './platform/tools/index.js';
|
|
86
86
|
// The always-on toolset for the hosted Claude cognition (parity guard
|
|
87
87
|
// reads this as part of the real SDK registry).
|
|
88
88
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
@@ -94,7 +94,7 @@ export { classifyIncomingEvent } from './context/classifyEnvelope.js';
|
|
|
94
94
|
export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
|
|
95
95
|
export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
|
|
96
96
|
export { classifyWorkflowEvent, findTaskResult, unwrapBatchEvent, findIncomingTaskResult } from './context/routingLabels.js';
|
|
97
|
-
export {
|
|
97
|
+
export { ArtifactsClient, AgentSearchClient, getBackendUrl,
|
|
98
98
|
// self-hosted / non-default backend: consumers of agent-sdk alone
|
|
99
99
|
// must be able to point the shared HTTP client without a direct api-client dep.
|
|
100
100
|
configureApiClient, } from '@ziggs-ai/api-client';
|
|
@@ -102,3 +102,4 @@ export { SeenMessages } from './platform/SeenMessages.js';
|
|
|
102
102
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
103
103
|
export { runAgents } from './launcher/runAgents.js';
|
|
104
104
|
export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
|
|
105
|
+
export { responseCommand } from './runtime/messageCommand.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgreementParties } from '@ziggs-ai/api-client';
|
|
2
2
|
export interface NormalizedEvent {
|
|
3
|
-
|
|
3
|
+
addressId: string | null;
|
|
4
4
|
event: {
|
|
5
5
|
senderId?: string;
|
|
6
6
|
senderType?: string;
|
|
@@ -23,6 +23,7 @@ export interface NormalizedEvent {
|
|
|
23
23
|
entryType?: string;
|
|
24
24
|
operation?: string;
|
|
25
25
|
agreementId?: string;
|
|
26
|
+
taskId?: string;
|
|
26
27
|
/** Agreement row stamp — host skip for agent-formed activation reads this. */
|
|
27
28
|
formedBy?: 'principal' | 'agent';
|
|
28
29
|
result?: unknown;
|
|
@@ -44,7 +45,8 @@ interface WireTask {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
interface WireMetadata {
|
|
47
|
-
|
|
48
|
+
taskId?: string;
|
|
49
|
+
addressId?: string;
|
|
48
50
|
messageId?: string;
|
|
49
51
|
entryType?: string;
|
|
50
52
|
contentType?: string;
|
|
@@ -62,7 +64,7 @@ interface WireMetadata {
|
|
|
62
64
|
assigneeId?: string;
|
|
63
65
|
task?: WireTask;
|
|
64
66
|
service?: {
|
|
65
|
-
|
|
67
|
+
workAgreementId?: string;
|
|
66
68
|
task?: WireTask;
|
|
67
69
|
operation?: string;
|
|
68
70
|
agreement?: {
|
|
@@ -5,7 +5,7 @@ const PROPOSAL_RESOLVED_OPERATIONS = new Set([
|
|
|
5
5
|
'proposal_approved_execute',
|
|
6
6
|
]);
|
|
7
7
|
function normalizeChatId(metadata) {
|
|
8
|
-
return metadata.
|
|
8
|
+
return metadata.addressId || null;
|
|
9
9
|
}
|
|
10
10
|
function normalizeReceiverId(metadata, taskData) {
|
|
11
11
|
const rewritten = typeof metadata.receiverId === 'string' && metadata.receiverId
|
|
@@ -27,7 +27,7 @@ function wireOperation(metadata) {
|
|
|
27
27
|
return typeof op === 'string' ? op : undefined;
|
|
28
28
|
}
|
|
29
29
|
function wireAgreementId(metadata) {
|
|
30
|
-
const raw = metadata.service?.
|
|
30
|
+
const raw = metadata.service?.workAgreementId ??
|
|
31
31
|
metadata.service?.agreementId ??
|
|
32
32
|
metadata.service?.agreement?.agreementId ??
|
|
33
33
|
metadata.agreementId;
|
|
@@ -101,7 +101,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
101
101
|
// classifier data lives under `service` on the inbox-pull wire;
|
|
102
102
|
// fall back to top-level for hand-built (direct-injection) metadata.
|
|
103
103
|
const taskData = (metadata.service?.task ?? metadata.task) || null;
|
|
104
|
-
const
|
|
104
|
+
const addressId = normalizeChatId(metadata);
|
|
105
105
|
const receiverId = normalizeReceiverId(metadata, taskData);
|
|
106
106
|
const senderType = metadata.sender?.type ? String(metadata.sender.type).toUpperCase() : undefined;
|
|
107
107
|
const entryType = typeof metadata.entryType === 'string' && metadata.entryType
|
|
@@ -117,6 +117,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
117
117
|
messageId: typeof metadata.messageId === 'string' && metadata.messageId ? metadata.messageId : undefined,
|
|
118
118
|
timestamp: Date.now(),
|
|
119
119
|
...(entryType ? { entryType } : {}),
|
|
120
|
+
...(metadata.taskId ? { taskId: metadata.taskId } : {}),
|
|
120
121
|
...(wakeAgreementId ? { agreementId: wakeAgreementId } : {}),
|
|
121
122
|
...(formedBy ? { formedBy } : {}),
|
|
122
123
|
};
|
|
@@ -126,7 +127,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
126
127
|
: wireOperation(metadata);
|
|
127
128
|
const agreementId = wireAgreementId(metadata);
|
|
128
129
|
return {
|
|
129
|
-
|
|
130
|
+
addressId,
|
|
130
131
|
event: {
|
|
131
132
|
...base,
|
|
132
133
|
type: 'agreement_lifecycle',
|
|
@@ -142,7 +143,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
142
143
|
// task notices as `task_result` so orchestrators still see completions.
|
|
143
144
|
if (entryType === 'notification' && !isTerminalTaskState(taskData?.state)) {
|
|
144
145
|
return {
|
|
145
|
-
|
|
146
|
+
addressId,
|
|
146
147
|
event: {
|
|
147
148
|
...base,
|
|
148
149
|
type: 'notification',
|
|
@@ -153,7 +154,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
return {
|
|
156
|
-
|
|
157
|
+
addressId,
|
|
157
158
|
event: { ...base, type: 'task_result', result: taskData, text },
|
|
158
159
|
};
|
|
159
160
|
}
|
|
@@ -163,7 +164,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
163
164
|
const operation = metadata.service?.operation ?? metadata.operation;
|
|
164
165
|
const rawAgreementId = metadata.service?.agreementId ?? metadata.agreementId;
|
|
165
166
|
return {
|
|
166
|
-
|
|
167
|
+
addressId,
|
|
167
168
|
event: {
|
|
168
169
|
...base,
|
|
169
170
|
type: 'notification',
|
|
@@ -174,7 +175,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
return {
|
|
177
|
-
|
|
178
|
+
addressId,
|
|
178
179
|
event: { ...base, type: 'message', text },
|
|
179
180
|
};
|
|
180
181
|
}
|
|
@@ -152,6 +152,20 @@ export interface McpAgentHandle {
|
|
|
152
152
|
export declare function buildTransport(config: McpTransportConfig, defaults?: {
|
|
153
153
|
agentId?: string;
|
|
154
154
|
}): StdioClientTransport | StreamableHTTPClientTransport;
|
|
155
|
+
type McpTool = {
|
|
156
|
+
name: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
inputSchema?: Record<string, unknown>;
|
|
159
|
+
};
|
|
160
|
+
/**
|
|
161
|
+
* The workflow an MCP agent runs, with the same assigned-task intake
|
|
162
|
+
* `defineAgent` gives every other agent. Without it a task wake reached the
|
|
163
|
+
* `idle` state, whose only way to deliver is `respond`: a reply needs an exact
|
|
164
|
+
* message or a named recipient, a task carries neither, so every task ended in
|
|
165
|
+
* a refused send and a tick timeout and the result was never written. The
|
|
166
|
+
* intake routes the wake to `task_set_result` instead.
|
|
167
|
+
*/
|
|
168
|
+
export declare function mcpAgentWorkflow(workflow: Workflow | undefined, description: string, mcpTools: McpTool[]): Workflow;
|
|
155
169
|
/**
|
|
156
170
|
* Max chars for a single tool description bound to the LLM. Verbose MCP servers
|
|
157
171
|
* embed whole manuals in their descriptions (PostHog's `execute-sql` alone is
|
|
@@ -198,3 +212,4 @@ export declare function trimDescription(desc: string | undefined): string | unde
|
|
|
198
212
|
* });
|
|
199
213
|
*/
|
|
200
214
|
export declare function createMcpAgent(options: CreateMcpAgentOptions): Promise<McpAgentHandle>;
|
|
215
|
+
export {};
|
|
@@ -7,6 +7,7 @@ import { resolveBackendUrl } from '../shared/backendUrl.js';
|
|
|
7
7
|
import { createAgent } from '../Agent.js';
|
|
8
8
|
import { laneFromToolContext } from '../tools/toolCreds.js';
|
|
9
9
|
import { thinkingDefaults } from '../runtime/defaults.js';
|
|
10
|
+
import { injectTaskIntakeDefault, TASK_INTAKE_STATE } from '../runtime/taskIntake.js';
|
|
10
11
|
/**
|
|
11
12
|
* Typed failure from the Ziggs MCP gateway. `code` is the stable
|
|
12
13
|
* machine-readable reason — e.g. `GRANT_REVOKED`, `GRANT_EXPIRED`,
|
|
@@ -34,6 +35,7 @@ export function wrapGatewayFetch(baseFetch = fetch) {
|
|
|
34
35
|
return (async (input, init) => {
|
|
35
36
|
const headers = new Headers(init?.headers);
|
|
36
37
|
headers.delete('X-Ziggs-Execution');
|
|
38
|
+
headers.delete('X-Ziggs-Attempt');
|
|
37
39
|
for (const [key, value] of Object.entries(assistantRequestHeaders()))
|
|
38
40
|
headers.set(key, value);
|
|
39
41
|
const response = await baseFetch(input, { ...init, headers });
|
|
@@ -183,15 +185,42 @@ function mapMcpTools(callTool, tools) {
|
|
|
183
185
|
isGenericFallback: true,
|
|
184
186
|
}));
|
|
185
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The workflow an MCP agent runs, with the same assigned-task intake
|
|
190
|
+
* `defineAgent` gives every other agent. Without it a task wake reached the
|
|
191
|
+
* `idle` state, whose only way to deliver is `respond`: a reply needs an exact
|
|
192
|
+
* message or a named recipient, a task carries neither, so every task ended in
|
|
193
|
+
* a refused send and a tick timeout and the result was never written. The
|
|
194
|
+
* intake routes the wake to `task_set_result` instead.
|
|
195
|
+
*/
|
|
196
|
+
export function mcpAgentWorkflow(workflow, description, mcpTools) {
|
|
197
|
+
const wf = injectTaskIntakeDefault(workflow ?? buildDefaultWorkflow(description, mcpTools));
|
|
198
|
+
const intake = wf.states[TASK_INTAKE_STATE];
|
|
199
|
+
if (intake?.kind !== 'thinking')
|
|
200
|
+
return wf;
|
|
201
|
+
// The intake is where an assigned task is worked, so it needs the MCP tools
|
|
202
|
+
// too. With task_set_result alone the agent could only close the task
|
|
203
|
+
// without doing it.
|
|
204
|
+
return {
|
|
205
|
+
...wf,
|
|
206
|
+
states: { ...wf.states, [TASK_INTAKE_STATE]: { ...intake, actions: { ...mcpToolActions(mcpTools), ...intake.actions } } },
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** Each MCP tool as an explicit action, so runTurn offers it to the model. */
|
|
210
|
+
function mcpToolActions(mcpTools) {
|
|
211
|
+
// A tool action has no outcome of its own: the turn's tool loop continues on
|
|
212
|
+
// its result, exactly as in the default idle state.
|
|
213
|
+
return Object.fromEntries(mcpTools.map(t => [t.name, {
|
|
214
|
+
tool: t.name,
|
|
215
|
+
prompt: { when: t.description || `Use ${t.name}` },
|
|
216
|
+
}]));
|
|
217
|
+
}
|
|
186
218
|
function buildDefaultWorkflow(description, mcpTools) {
|
|
187
219
|
const dflt = thinkingDefaults({ initial: 'idle' });
|
|
188
220
|
// Bind each MCP tool as an explicit action so runTurn includes them in the
|
|
189
221
|
// LLM API call and the decision schema. Without this, allowedToolNames is
|
|
190
222
|
// empty and the tools array passed to the LLM is also empty.
|
|
191
|
-
const toolActions =
|
|
192
|
-
tool: t.name,
|
|
193
|
-
prompt: { when: t.description || `Use ${t.name}` },
|
|
194
|
-
}]));
|
|
223
|
+
const toolActions = mcpToolActions(mcpTools);
|
|
195
224
|
return {
|
|
196
225
|
id: 'mcp-agent',
|
|
197
226
|
description: description || 'MCP-backed agent',
|
|
@@ -386,19 +415,26 @@ export async function createMcpAgent(options) {
|
|
|
386
415
|
// The server holds the invoking user to the people this wake may ask, so
|
|
387
416
|
// the request carries the same lane as the call that missed: in the hire's
|
|
388
417
|
// room that user is its customer.
|
|
389
|
-
const triggerConnectionRequest = async (userId
|
|
418
|
+
const triggerConnectionRequest = async (userId) => {
|
|
390
419
|
if (!transportConfig.serverUrl)
|
|
391
420
|
return null;
|
|
392
421
|
const operatorKey = transportConfig.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
|
|
393
422
|
if (!operatorKey)
|
|
394
423
|
return null;
|
|
424
|
+
const work = currentWorkExecution();
|
|
425
|
+
const context = work?.taskId ? { kind: 'task', id: work.taskId }
|
|
426
|
+
: work?.agreementId ? { kind: 'agreement', id: work.agreementId }
|
|
427
|
+
: work?.action?.kind === 'message' ? { kind: 'message', id: work.action.id } : null;
|
|
428
|
+
if (!context)
|
|
429
|
+
return null;
|
|
395
430
|
try {
|
|
396
431
|
const body = await new ConnectionsClient(operatorKey, agentId, backendBase).requestMcpConnection({
|
|
397
432
|
serverUrl: transportConfig.serverUrl,
|
|
398
433
|
tools: tools.map(t => t.name),
|
|
399
|
-
|
|
434
|
+
recipientId: userId,
|
|
435
|
+
context,
|
|
400
436
|
});
|
|
401
|
-
return
|
|
437
|
+
return typeof body.agreementId === 'string' ? body.agreementId : null;
|
|
402
438
|
}
|
|
403
439
|
catch {
|
|
404
440
|
// Best-effort: the backend dedups pending requests and the caller falls
|
|
@@ -451,8 +487,8 @@ export async function createMcpAgent(options) {
|
|
|
451
487
|
if (lastErr instanceof ZiggsGatewayError &&
|
|
452
488
|
lastErr.code === 'NO_USER_CONNECTION' &&
|
|
453
489
|
transportConfig.serverUrl) {
|
|
454
|
-
const requestId = await triggerConnectionRequest(userId
|
|
455
|
-
const link = connectionsSettingsAppUrl(appUrl
|
|
490
|
+
const requestId = await triggerConnectionRequest(userId).catch(() => null);
|
|
491
|
+
const link = requestId ? new URL(`/app/inbox?kind=agreement&id=${encodeURIComponent(requestId)}`, appUrl).toString() : connectionsSettingsAppUrl(appUrl);
|
|
456
492
|
return {
|
|
457
493
|
content: [
|
|
458
494
|
{
|
|
@@ -466,7 +502,7 @@ export async function createMcpAgent(options) {
|
|
|
466
502
|
};
|
|
467
503
|
const agent = createAgent({
|
|
468
504
|
...agentOptions,
|
|
469
|
-
workflow: workflow
|
|
505
|
+
workflow: mcpAgentWorkflow(workflow, agentOptions.description ?? '', tools),
|
|
470
506
|
tools: [...mapMcpTools(caller, tools), ...extraTools],
|
|
471
507
|
});
|
|
472
508
|
return {
|
|
@@ -509,7 +545,7 @@ export async function createMcpAgent(options) {
|
|
|
509
545
|
};
|
|
510
546
|
const caller = async (name, args) => (await clientFor()).callTool({ name, arguments: args });
|
|
511
547
|
const mcpTools = mapMcpTools(caller, rawTools);
|
|
512
|
-
const resolvedWorkflow = workflow
|
|
548
|
+
const resolvedWorkflow = mcpAgentWorkflow(workflow, agentOptions.description ?? '', rawTools);
|
|
513
549
|
const agent = createAgent({
|
|
514
550
|
...agentOptions,
|
|
515
551
|
workflow: resolvedWorkflow,
|
|
@@ -61,7 +61,7 @@ export declare class FileMemoryStore implements MemoryStore {
|
|
|
61
61
|
*
|
|
62
62
|
* Pick from the hire this wake is acting under — the same fact the
|
|
63
63
|
* server uses for acting-for: a pinned agreement, the delivery / event
|
|
64
|
-
* hire
|
|
64
|
+
* hire. Processing lane names do not grant a memory partition. Exactly one agreement gives that agreement's
|
|
65
65
|
* section. None, or several, gives null: notes are bound to an agreement,
|
|
66
66
|
* so such a wake has nowhere to keep them, and says so on a write rather
|
|
67
67
|
* than landing in a room's or the agent's shared drawer.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { agreementIdFromLane } from '@ziggs-ai/api-client';
|
|
2
1
|
/** Non-durable reference impl. Per-process; lost on restart. */
|
|
3
2
|
export class InMemoryStore {
|
|
4
3
|
store = new Map();
|
|
@@ -82,7 +81,7 @@ export class FileMemoryStore {
|
|
|
82
81
|
*
|
|
83
82
|
* Pick from the hire this wake is acting under — the same fact the
|
|
84
83
|
* server uses for acting-for: a pinned agreement, the delivery / event
|
|
85
|
-
* hire
|
|
84
|
+
* hire. Processing lane names do not grant a memory partition. Exactly one agreement gives that agreement's
|
|
86
85
|
* section. None, or several, gives null: notes are bound to an agreement,
|
|
87
86
|
* so such a wake has nowhere to keep them, and says so on a write rather
|
|
88
87
|
* than landing in a room's or the agent's shared drawer.
|
|
@@ -91,7 +90,6 @@ export function memorySectionForWake(input) {
|
|
|
91
90
|
const ids = uniqueHireIds([
|
|
92
91
|
input.activeAgreementId,
|
|
93
92
|
...(input.wakeAgreementIds ?? []),
|
|
94
|
-
agreementIdFromAgrnSession(input.sessionId),
|
|
95
93
|
]);
|
|
96
94
|
return ids.length === 1 ? `hire:${ids[0]}` : null;
|
|
97
95
|
}
|
|
@@ -146,9 +144,6 @@ export function hireIdsFromEvent(event) {
|
|
|
146
144
|
: null,
|
|
147
145
|
]);
|
|
148
146
|
}
|
|
149
|
-
function agreementIdFromAgrnSession(sessionId) {
|
|
150
|
-
return agreementIdFromLane(sessionId)?.trim() || null;
|
|
151
|
-
}
|
|
152
147
|
/**
|
|
153
148
|
* Present a store as if it only contained one section. Agent keys stay
|
|
154
149
|
* unchanged; get/put/list/delete all go through `section:`.
|
|
@@ -58,7 +58,7 @@ export class ConnectionPool {
|
|
|
58
58
|
if (effect.kind === 'send-message') {
|
|
59
59
|
const listeners = pool.responseListeners.get(agentId);
|
|
60
60
|
if (listeners?.length)
|
|
61
|
-
listeners.shift()(effect.text
|
|
61
|
+
listeners.shift()(effect.command.content.text);
|
|
62
62
|
}
|
|
63
63
|
originalTap?.(effect, response);
|
|
64
64
|
},
|
|
@@ -22,11 +22,11 @@ export declare class OutboxBuffer {
|
|
|
22
22
|
private _pending;
|
|
23
23
|
private _ttlMs;
|
|
24
24
|
constructor(options?: OutboxBufferOptions);
|
|
25
|
-
track(
|
|
25
|
+
track(addressId: string, msg: {
|
|
26
26
|
text: string;
|
|
27
27
|
receiverId?: string;
|
|
28
28
|
}): void;
|
|
29
|
-
merge(
|
|
29
|
+
merge(addressId: string, history: HistoryEntryShape[], agentId: string | null): void;
|
|
30
30
|
gc(): void;
|
|
31
31
|
private _gcChat;
|
|
32
32
|
}
|
|
@@ -10,15 +10,15 @@ export class OutboxBuffer {
|
|
|
10
10
|
constructor(options = {}) {
|
|
11
11
|
this._ttlMs = Number.isFinite(options.ttlMs) ? Math.max(1_000, options.ttlMs) : 10 * 60 * 1000;
|
|
12
12
|
}
|
|
13
|
-
track(
|
|
14
|
-
this._gcChat(
|
|
15
|
-
if (!this._pending.has(
|
|
16
|
-
this._pending.set(
|
|
17
|
-
this._pending.get(
|
|
13
|
+
track(addressId, msg) {
|
|
14
|
+
this._gcChat(addressId);
|
|
15
|
+
if (!this._pending.has(addressId))
|
|
16
|
+
this._pending.set(addressId, []);
|
|
17
|
+
this._pending.get(addressId).push({ text: msg.text, receiverId: msg.receiverId, timestamp: Date.now() });
|
|
18
18
|
}
|
|
19
|
-
merge(
|
|
20
|
-
this._gcChat(
|
|
21
|
-
const pending = this._pending.get(
|
|
19
|
+
merge(addressId, history, agentId) {
|
|
20
|
+
this._gcChat(addressId);
|
|
21
|
+
const pending = this._pending.get(addressId);
|
|
22
22
|
if (!pending || pending.length === 0)
|
|
23
23
|
return;
|
|
24
24
|
const matchedIndices = new Set();
|
|
@@ -42,27 +42,27 @@ export class OutboxBuffer {
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
if (remaining.length === 0)
|
|
45
|
-
this._pending.delete(
|
|
45
|
+
this._pending.delete(addressId);
|
|
46
46
|
else
|
|
47
|
-
this._pending.set(
|
|
47
|
+
this._pending.set(addressId, remaining);
|
|
48
48
|
}
|
|
49
49
|
gc() {
|
|
50
|
-
for (const
|
|
51
|
-
this._gcChat(
|
|
50
|
+
for (const addressId of this._pending.keys())
|
|
51
|
+
this._gcChat(addressId);
|
|
52
52
|
}
|
|
53
|
-
_gcChat(
|
|
54
|
-
const pending = this._pending.get(
|
|
53
|
+
_gcChat(addressId) {
|
|
54
|
+
const pending = this._pending.get(addressId);
|
|
55
55
|
if (!pending?.length)
|
|
56
56
|
return;
|
|
57
57
|
const cutoff = Date.now() - this._ttlMs;
|
|
58
58
|
const fresh = pending.filter(msg => msg.timestamp >= cutoff);
|
|
59
59
|
if (fresh.length === 0) {
|
|
60
|
-
this._pending.delete(
|
|
61
|
-
runtimeLog.warn('OutboxBuffer', `expired all pending messages
|
|
60
|
+
this._pending.delete(addressId);
|
|
61
|
+
runtimeLog.warn('OutboxBuffer', `expired all pending messages addressId=${addressId}`);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
if (fresh.length !== pending.length) {
|
|
65
|
-
this._pending.set(
|
|
65
|
+
this._pending.set(addressId, fresh);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -15,6 +15,7 @@ export interface StateTimeoutEvent {
|
|
|
15
15
|
* state's declared timeout, and the Claude brain has no states at all.
|
|
16
16
|
*/
|
|
17
17
|
export interface WakeAtEvent extends WorkScope {
|
|
18
|
+
wakeId: string;
|
|
18
19
|
type: 'wake-at';
|
|
19
20
|
/** What the agent said it was waiting for. Free text, for the brain to read. */
|
|
20
21
|
reason: string | null;
|
|
@@ -74,9 +75,10 @@ export declare class StateTimers {
|
|
|
74
75
|
* the agent, since "I will wake at X" is a promise the agent will otherwise
|
|
75
76
|
* make on its own.
|
|
76
77
|
*/
|
|
77
|
-
armWakeAt(laneKey: string, at: Date | number, reason?: string | null, scope?: WorkScope): {
|
|
78
|
+
armWakeAt(laneKey: string, at: Date | number, reason?: string | null, scope?: WorkScope, wakeId?: string): {
|
|
78
79
|
firesAt: number;
|
|
79
80
|
delayMs: number;
|
|
81
|
+
wakeId: string;
|
|
80
82
|
};
|
|
81
83
|
/** What is armed on this lane, if anything. Read by the specs and by `wake_at`. */
|
|
82
84
|
peek(laneKey: string): {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
3
|
/**
|
|
3
4
|
* `setTimeout` stores its delay in a signed 32-bit int. A longer delay does
|
|
@@ -75,7 +76,7 @@ export class StateTimers {
|
|
|
75
76
|
* the agent, since "I will wake at X" is a promise the agent will otherwise
|
|
76
77
|
* make on its own.
|
|
77
78
|
*/
|
|
78
|
-
armWakeAt(laneKey, at, reason = null, scope) {
|
|
79
|
+
armWakeAt(laneKey, at, reason = null, scope, wakeId = randomUUID()) {
|
|
79
80
|
const firesAtRaw = at instanceof Date ? at.getTime() : Number(at);
|
|
80
81
|
if (!Number.isFinite(firesAtRaw)) {
|
|
81
82
|
throw new Error('StateTimers.armWakeAt: `at` must be a Date or epoch ms');
|
|
@@ -94,7 +95,7 @@ export class StateTimers {
|
|
|
94
95
|
const epoch = ++this._epoch;
|
|
95
96
|
const handle = setTimeout(() => {
|
|
96
97
|
runtimeLog.debug('StateTimers', `wake-at fired laneKey=${laneKey} reason=${reason ?? 'none'} epoch=${epoch}`);
|
|
97
|
-
this._enqueue({ type: 'wake-at', reason, scheduledFor: firesAtRaw, epoch, ...wakeScope }, laneKey);
|
|
98
|
+
this._enqueue({ type: 'wake-at', wakeId, reason, scheduledFor: firesAtRaw, epoch, ...wakeScope }, laneKey);
|
|
98
99
|
}, delayMs);
|
|
99
100
|
handle.unref?.();
|
|
100
101
|
this._timers.set(laneKey, {
|
|
@@ -107,7 +108,7 @@ export class StateTimers {
|
|
|
107
108
|
armedAt: now,
|
|
108
109
|
handle,
|
|
109
110
|
});
|
|
110
|
-
return { firesAt: now + delayMs, delayMs };
|
|
111
|
+
return { firesAt: now + delayMs, delayMs, wakeId };
|
|
111
112
|
}
|
|
112
113
|
/** What is armed on this lane, if anything. Read by the specs and by `wake_at`. */
|
|
113
114
|
peek(laneKey) {
|
|
@@ -3,11 +3,14 @@ import { type MemoryStore } from '../memory/MemoryStore.js';
|
|
|
3
3
|
import type { WorkScope } from '../runtime/wakeEffects.js';
|
|
4
4
|
/** One check-back an agent asked for, as it is kept across a restart. */
|
|
5
5
|
export interface SavedWake {
|
|
6
|
+
wakeId: string;
|
|
6
7
|
laneKey: string;
|
|
7
8
|
sessionId: string;
|
|
8
9
|
firesAt: number;
|
|
9
10
|
reason: string | null;
|
|
10
11
|
scope?: WorkScope;
|
|
12
|
+
/** The conversation the lane's own messages came from, when it has one. */
|
|
13
|
+
addressId?: string;
|
|
11
14
|
executionContext?: WorkExecutionContext;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
@@ -15,30 +18,31 @@ export interface SavedWake {
|
|
|
15
18
|
*
|
|
16
19
|
* The timer itself stays in memory (`StateTimers`). This keeps the facts
|
|
17
20
|
* needed to arm it again: which lane, when, why, and under which work. A wake
|
|
18
|
-
* is written when
|
|
21
|
+
* is written when armed and marked done after successful handover, so on start the
|
|
19
22
|
* host re-arms exactly the ones still waiting, and one that passed while the
|
|
20
|
-
* host was down
|
|
23
|
+
* host was down remains eligible. A failed handover remains pending.
|
|
21
24
|
*
|
|
22
25
|
* The notes live in the agent's own memory store under a prefix no memory
|
|
23
26
|
* section uses (sections are `hire:<id>`), so the agent's memory tools never
|
|
24
27
|
* see them. Writes are chained so a mark can never land before the arm it
|
|
25
28
|
* follows.
|
|
26
29
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* agent checks for an answer before it chases anyone.
|
|
30
|
+
* Concurrent hosts may both discover a pending wake. Exact action claims fence
|
|
31
|
+
* simultaneous execution; the domain owner still decides whether work is due.
|
|
30
32
|
*/
|
|
31
33
|
export declare class WakeLedger {
|
|
32
34
|
private readonly store;
|
|
35
|
+
private inFlight;
|
|
33
36
|
private writes;
|
|
34
37
|
constructor(store: MemoryStore);
|
|
35
38
|
/** False when the store is in memory: then a restart loses wakes anyway. */
|
|
36
39
|
get durable(): boolean;
|
|
37
40
|
arm(wake: SavedWake): void;
|
|
38
|
-
done(laneKey: string): void;
|
|
41
|
+
done(laneKey: string, wakeId: string): void;
|
|
39
42
|
/** The wakes still waiting to fire, for the host to arm again on start. */
|
|
40
43
|
pending(): Promise<SavedWake[]>;
|
|
41
44
|
/** Resolves when every write so far has landed. For tests and shutdown. */
|
|
45
|
+
track(work: Promise<unknown>): void;
|
|
42
46
|
flushed(): Promise<void>;
|
|
43
47
|
private write;
|
|
44
48
|
private warn;
|
|
@@ -6,21 +6,21 @@ const PREFIX = 'host:wake-at:';
|
|
|
6
6
|
*
|
|
7
7
|
* The timer itself stays in memory (`StateTimers`). This keeps the facts
|
|
8
8
|
* needed to arm it again: which lane, when, why, and under which work. A wake
|
|
9
|
-
* is written when
|
|
9
|
+
* is written when armed and marked done after successful handover, so on start the
|
|
10
10
|
* host re-arms exactly the ones still waiting, and one that passed while the
|
|
11
|
-
* host was down
|
|
11
|
+
* host was down remains eligible. A failed handover remains pending.
|
|
12
12
|
*
|
|
13
13
|
* The notes live in the agent's own memory store under a prefix no memory
|
|
14
14
|
* section uses (sections are `hire:<id>`), so the agent's memory tools never
|
|
15
15
|
* see them. Writes are chained so a mark can never land before the arm it
|
|
16
16
|
* follows.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* agent checks for an answer before it chases anyone.
|
|
18
|
+
* Concurrent hosts may both discover a pending wake. Exact action claims fence
|
|
19
|
+
* simultaneous execution; the domain owner still decides whether work is due.
|
|
21
20
|
*/
|
|
22
21
|
export class WakeLedger {
|
|
23
22
|
store;
|
|
23
|
+
inFlight = new Set();
|
|
24
24
|
writes = Promise.resolve();
|
|
25
25
|
constructor(store) {
|
|
26
26
|
this.store = store;
|
|
@@ -33,11 +33,11 @@ export class WakeLedger {
|
|
|
33
33
|
const { laneKey, ...rest } = wake;
|
|
34
34
|
this.write(laneKey, { ...rest, state: 'armed' });
|
|
35
35
|
}
|
|
36
|
-
done(laneKey) {
|
|
36
|
+
done(laneKey, wakeId) {
|
|
37
37
|
this.writes = this.writes
|
|
38
38
|
.then(async () => {
|
|
39
39
|
const saved = (await this.store.get(PREFIX + laneKey));
|
|
40
|
-
if (saved?.state === 'armed') {
|
|
40
|
+
if (saved?.state === 'armed' && saved.wakeId === wakeId) {
|
|
41
41
|
await this.store.put(PREFIX + laneKey, { ...saved, state: 'done' });
|
|
42
42
|
}
|
|
43
43
|
})
|
|
@@ -56,18 +56,25 @@ export class WakeLedger {
|
|
|
56
56
|
continue;
|
|
57
57
|
out.push({
|
|
58
58
|
laneKey: key.slice(PREFIX.length),
|
|
59
|
+
wakeId: saved.wakeId,
|
|
59
60
|
sessionId: saved.sessionId,
|
|
60
61
|
firesAt: saved.firesAt,
|
|
61
62
|
reason: saved.reason ?? null,
|
|
62
63
|
...(saved.scope ? { scope: saved.scope } : {}),
|
|
64
|
+
...(typeof saved.addressId === 'string' && saved.addressId ? { addressId: saved.addressId } : {}),
|
|
63
65
|
...(saved.executionContext ? { executionContext: saved.executionContext } : {}),
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
return out;
|
|
67
69
|
}
|
|
68
70
|
/** Resolves when every write so far has landed. For tests and shutdown. */
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
track(work) {
|
|
72
|
+
this.inFlight.add(work);
|
|
73
|
+
void work.finally(() => this.inFlight.delete(work)).catch(() => { });
|
|
74
|
+
}
|
|
75
|
+
async flushed() {
|
|
76
|
+
await Promise.allSettled([...this.inFlight]);
|
|
77
|
+
await this.writes;
|
|
71
78
|
}
|
|
72
79
|
write(laneKey, value) {
|
|
73
80
|
this.writes = this.writes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MessageCommand, MessageCommandResult } from '@ziggs-ai/contracts';
|
|
2
2
|
/**
|
|
3
3
|
* Ziggs-flavored EffectHandler.
|
|
4
4
|
*
|
|
@@ -37,7 +37,21 @@ export interface ZiggsEffectDeps {
|
|
|
37
37
|
contextReadClient: ContextReadClient;
|
|
38
38
|
taskService: TaskService;
|
|
39
39
|
agreementService: AgreementService;
|
|
40
|
-
messageSender: (
|
|
40
|
+
messageSender: (command: MessageCommand) => Promise<MessageCommandResult>;
|
|
41
|
+
/**
|
|
42
|
+
* The explicit conversation and work selected for this wake. A processing
|
|
43
|
+
* lane alone cannot select a destination; task-first and restored wakes
|
|
44
|
+
* use their execution context without waiting for a message.
|
|
45
|
+
*/
|
|
46
|
+
conversationFor?: (sessionId: string) => {
|
|
47
|
+
agreementId?: string;
|
|
48
|
+
addressId?: string;
|
|
49
|
+
taskId?: string;
|
|
50
|
+
message?: {
|
|
51
|
+
messageId: string;
|
|
52
|
+
senderId?: string;
|
|
53
|
+
};
|
|
54
|
+
} | undefined;
|
|
41
55
|
/** Sends a streaming chunk to a client. Called per-token during stream-text effects. */
|
|
42
56
|
chunkSender?: (sessionId: string, receiverId: string, text: string, messageId: string) => void;
|
|
43
57
|
toolManager: ToolManager;
|
|
@@ -67,10 +81,10 @@ export interface ZiggsEffectDeps {
|
|
|
67
81
|
* side effects — that is the whole point of funneling both paths here.
|
|
68
82
|
*
|
|
69
83
|
* `eff` is threaded into the tool context so eff-backed tools
|
|
70
|
-
* (`context_snapshot`, `
|
|
84
|
+
* (`context_snapshot`, `message_send`) resolve through this same path
|
|
71
85
|
* regardless of which cognition invoked them.
|
|
72
86
|
*/
|
|
73
|
-
export declare function executeToolWithSideEffects(deps: Pick<ZiggsEffectDeps, 'toolManager' | 'artifactsClient' | 'telemetryClient' | 'telemetryIngest' | 'operatorKey' | 'agentId' | 'taskService' | 'agreementService' | 'contextReadClient' | 'messageSender' | 'wakeScheduler'>, eff: EffectHandler, effect: Extract<Effect, {
|
|
87
|
+
export declare function executeToolWithSideEffects(deps: Pick<ZiggsEffectDeps, 'toolManager' | 'artifactsClient' | 'telemetryClient' | 'telemetryIngest' | 'operatorKey' | 'agentId' | 'taskService' | 'agreementService' | 'contextReadClient' | 'messageSender' | 'wakeScheduler' | 'conversationFor'>, eff: EffectHandler, effect: Extract<Effect, {
|
|
74
88
|
kind: 'tool-call';
|
|
75
89
|
}>, roster: {
|
|
76
90
|
agents: unknown[];
|