@ziggs-ai/agent-sdk 0.1.12 → 0.2.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 +14 -0
- package/README.md +41 -2
- package/dist/AgentHost.d.ts +201 -0
- package/dist/AgentHost.js +652 -0
- package/dist/adapters/AnthropicAdapter.d.ts +39 -0
- package/dist/adapters/AnthropicAdapter.js +200 -0
- package/dist/adapters/McpAdapter.d.ts +199 -0
- package/dist/adapters/McpAdapter.js +468 -0
- package/dist/adapters/OpenAIAdapter.d.ts +26 -0
- package/dist/adapters/OpenAIAdapter.js +158 -0
- package/dist/adapters/index.js +2 -0
- package/dist/agent/Agent.d.ts +78 -0
- package/dist/agent/Agent.js +58 -0
- package/dist/agent/ClaudeSdkAgent.d.ts +38 -0
- package/dist/agent/ClaudeSdkAgent.js +322 -0
- package/dist/agent/claudeCognition.d.ts +67 -0
- package/dist/agent/claudeCognition.js +103 -0
- package/dist/agent/claudeSdkProtocol.d.ts +11 -0
- package/dist/agent/claudeSdkTools.d.ts +19 -0
- package/dist/agent/claudeSdkTools.js +57 -0
- package/dist/agent/hostedAgentConnection.d.ts +15 -0
- package/dist/agent/hostedAgentConnection.js +29 -0
- package/dist/agent/toolManagerToSdkMcp.d.ts +12 -0
- package/dist/agent/toolManagerToSdkMcp.js +123 -0
- package/dist/brain/index.d.ts +50 -0
- package/dist/brain/index.js +45 -0
- package/dist/cognition/validateContext.d.ts +11 -0
- package/dist/cognition/validateContext.js +117 -0
- package/dist/context/applyEffects.d.ts +2 -0
- package/dist/context/applyEffects.js +89 -0
- package/dist/context/batch.d.ts +2 -0
- package/dist/context/batch.js +18 -0
- package/dist/context/classifyEnvelope.d.ts +1 -0
- package/dist/context/classifyEnvelope.js +58 -0
- package/dist/context/routingLabels.d.ts +4 -0
- package/dist/context/routingLabels.js +48 -0
- package/dist/defineAgent.d.ts +14 -0
- package/dist/defineAgent.js +68 -0
- package/dist/formatters/AgreementFormatter.d.ts +18 -0
- package/dist/formatters/AgreementFormatter.js +130 -0
- package/dist/formatters/HistoryFormatter.d.ts +26 -0
- package/dist/formatters/HistoryFormatter.js +173 -0
- package/dist/formatters/index.js +2 -0
- package/{src/index.ts → dist/index.d.ts} +7 -53
- package/dist/index.js +66 -0
- package/dist/ingress/normalizeIncoming.d.ts +65 -0
- package/dist/ingress/normalizeIncoming.js +88 -0
- package/dist/memory/MemoryStore.d.ts +55 -0
- package/dist/memory/MemoryStore.js +78 -0
- package/dist/pricing/fleetDefaults.d.ts +46 -0
- package/dist/pricing/fleetDefaults.js +168 -0
- package/dist/pricing/fleetEvalFree.d.ts +7 -0
- package/dist/pricing/fleetEvalFree.js +24 -0
- package/dist/pricing/fleetFreeTierA.gen.d.ts +6 -0
- package/{src/pricing/fleetFreeTierA.gen.ts → dist/pricing/fleetFreeTierA.gen.js} +5 -6
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +7 -0
- package/dist/pricing/fleetTierByAgentId.gen.js +768 -0
- package/dist/runtime/AgentMachine.d.ts +143 -0
- package/dist/runtime/AgentMachine.js +400 -0
- package/dist/runtime/PromptBuilder.d.ts +96 -0
- package/dist/runtime/PromptBuilder.js +439 -0
- package/{src/runtime/bootstrapCtx.ts → dist/runtime/bootstrapCtx.d.ts} +1 -22
- package/dist/runtime/bootstrapCtx.js +37 -0
- package/dist/runtime/buildOutcome.d.ts +68 -0
- package/dist/runtime/buildOutcome.js +464 -0
- package/dist/runtime/defaults.d.ts +54 -0
- package/dist/runtime/defaults.js +47 -0
- package/dist/runtime/deriveRespondsToProposals.d.ts +9 -0
- package/dist/runtime/deriveRespondsToProposals.js +38 -0
- package/dist/runtime/kickoff.d.ts +15 -0
- package/dist/runtime/kickoff.js +112 -0
- package/dist/runtime/runMechanical.d.ts +25 -0
- package/dist/runtime/runMechanical.js +74 -0
- package/dist/runtime/runTurn.d.ts +63 -0
- package/dist/runtime/runTurn.js +800 -0
- package/dist/runtime/validateWorkflow.d.ts +17 -0
- package/dist/runtime/validateWorkflow.js +209 -0
- package/dist/server/ConnectionPool.d.ts +68 -0
- package/dist/server/ConnectionPool.js +166 -0
- package/dist/server/EventQueue.d.ts +33 -0
- package/dist/server/EventQueue.js +98 -0
- package/dist/server/InboxCatchUp.d.ts +24 -0
- package/dist/server/InboxCatchUp.js +214 -0
- package/dist/server/OutboxBuffer.d.ts +33 -0
- package/dist/server/OutboxBuffer.js +68 -0
- package/dist/server/SeenMessages.d.ts +13 -0
- package/dist/server/SeenMessages.js +31 -0
- package/dist/server/StateTimers.d.ts +34 -0
- package/dist/server/StateTimers.js +70 -0
- package/dist/server/ZiggsEffectHandler.d.ts +79 -0
- package/dist/server/ZiggsEffectHandler.js +319 -0
- package/dist/server/agreements/AgreementService.d.ts +50 -0
- package/dist/server/agreements/AgreementService.js +85 -0
- package/dist/server/createHealthServer.d.ts +7 -0
- package/dist/server/createHealthServer.js +75 -0
- package/dist/server/proactive/ProactiveTrigger.d.ts +33 -0
- package/dist/server/proactive/ProactiveTrigger.js +71 -0
- package/dist/server/runLauncher.d.ts +28 -0
- package/dist/server/runLauncher.js +93 -0
- package/dist/server/tasks/TaskService.d.ts +25 -0
- package/dist/server/tasks/TaskService.js +103 -0
- package/dist/server/tasks/index.js +1 -0
- package/dist/server/telemetryIngest.d.ts +24 -0
- package/dist/server/telemetryIngest.js +62 -0
- package/dist/server/tools/index.d.ts +24 -0
- package/{src/server/tools/index.ts → dist/server/tools/index.js} +4 -24
- package/dist/server/tools/tier1/protocolRunner.d.ts +39 -0
- package/dist/server/tools/tier1/protocolRunner.js +288 -0
- package/dist/server/tools/tier1/protocolTools.d.ts +12 -0
- package/dist/server/tools/tier1/protocolTools.js +37 -0
- package/dist/server/tools/tier2/connectionTools.d.ts +4 -0
- package/dist/server/tools/tier2/connectionTools.js +63 -0
- package/dist/server/tools/tier2/contextTools.d.ts +4 -0
- package/dist/server/tools/tier2/contextTools.js +46 -0
- package/dist/server/tools/tier2/discoveryTools.d.ts +4 -0
- package/dist/server/tools/tier2/discoveryTools.js +25 -0
- package/dist/server/tools/tier2/marketplaceTools.d.ts +3 -0
- package/dist/server/tools/tier2/marketplaceTools.js +22 -0
- package/dist/server/tools/tier2/paymentTools.d.ts +12 -0
- package/dist/server/tools/tier2/paymentTools.js +165 -0
- package/dist/server/ziggsconnect/ZiggsConnectClient.d.ts +35 -0
- package/dist/server/ziggsconnect/ZiggsConnectClient.js +95 -0
- package/dist/server/ziggscontext/ZiggsContextClient.d.ts +53 -0
- package/dist/server/ziggscontext/ZiggsContextClient.js +124 -0
- package/dist/server/ziggspay/ZiggsPayClient.d.ts +82 -0
- package/dist/server/ziggspay/ZiggsPayClient.js +203 -0
- package/dist/shared/ids.d.ts +1 -0
- package/dist/shared/ids.js +3 -0
- package/dist/shared/runtimeLog.d.ts +18 -0
- package/dist/shared/runtimeLog.js +68 -0
- package/dist/shared/types.d.ts +22 -0
- package/dist/shared/types.js +23 -0
- package/dist/tasks/protocolRegistry.d.ts +16 -0
- package/dist/tasks/protocolRegistry.js +20 -0
- package/dist/tools/ToolManager.d.ts +12 -0
- package/dist/tools/ToolManager.js +74 -0
- package/dist/tools/ToolProvider.d.ts +6 -0
- package/dist/tools/ToolProvider.js +21 -0
- package/dist/tools/defineTool.d.ts +27 -0
- package/dist/tools/defineTool.js +62 -0
- package/{src/tools/index.ts → dist/tools/index.d.ts} +0 -1
- package/dist/tools/index.js +6 -0
- package/dist/tools/recordReport.d.ts +41 -0
- package/dist/tools/recordReport.js +30 -0
- package/dist/types.d.ts +466 -0
- package/dist/types.js +7 -0
- package/dist/utils/jsonExtractor.d.ts +2 -0
- package/dist/utils/jsonExtractor.js +111 -0
- package/package.json +14 -6
- package/src/AgentHost.ts +0 -814
- package/src/adapters/AnthropicAdapter.ts +0 -238
- package/src/adapters/McpAdapter.ts +0 -678
- package/src/adapters/OpenAIAdapter.ts +0 -194
- package/src/agent/Agent.ts +0 -112
- package/src/agent/ClaudeSdkAgent.ts +0 -401
- package/src/agent/claudeCognition.ts +0 -139
- package/src/agent/claudeSdkTools.ts +0 -81
- package/src/agent/hostedAgentConnection.ts +0 -41
- package/src/agent/toolManagerToSdkMcp.ts +0 -136
- package/src/cognition/validateContext.ts +0 -95
- package/src/context/applyEffects.ts +0 -80
- package/src/context/batch.ts +0 -19
- package/src/context/classifyEnvelope.ts +0 -38
- package/src/context/routingLabels.ts +0 -46
- package/src/defineAgent.ts +0 -77
- package/src/formatters/AgreementFormatter.ts +0 -111
- package/src/formatters/HistoryFormatter.ts +0 -172
- package/src/ingress/normalizeIncoming.ts +0 -162
- package/src/memory/MemoryStore.ts +0 -104
- package/src/pricing/fleetDefaults.ts +0 -225
- package/src/pricing/fleetEvalFree.ts +0 -24
- package/src/pricing/fleetTierByAgentId.gen.ts +0 -775
- package/src/runtime/AgentMachine.ts +0 -484
- package/src/runtime/PromptBuilder.ts +0 -522
- package/src/runtime/buildOutcome.ts +0 -563
- package/src/runtime/defaults.ts +0 -86
- package/src/runtime/deriveRespondsToProposals.ts +0 -35
- package/src/runtime/kickoff.ts +0 -117
- package/src/runtime/runMechanical.ts +0 -106
- package/src/runtime/runTurn.ts +0 -870
- package/src/runtime/validateWorkflow.ts +0 -221
- package/src/server/ConnectionPool.ts +0 -206
- package/src/server/EventQueue.ts +0 -133
- package/src/server/InboxCatchUp.ts +0 -291
- package/src/server/OutboxBuffer.ts +0 -90
- package/src/server/SeenMessages.ts +0 -27
- package/src/server/StateTimers.ts +0 -85
- package/src/server/ZiggsEffectHandler.ts +0 -427
- package/src/server/agreements/AgreementService.ts +0 -118
- package/src/server/createHealthServer.ts +0 -85
- package/src/server/proactive/ProactiveTrigger.ts +0 -83
- package/src/server/runLauncher.ts +0 -146
- package/src/server/tasks/TaskService.ts +0 -111
- package/src/server/telemetryIngest.ts +0 -91
- package/src/server/tools/tier1/protocolRunner.ts +0 -353
- package/src/server/tools/tier1/protocolTools.ts +0 -114
- package/src/server/tools/tier2/connectionTools.ts +0 -75
- package/src/server/tools/tier2/contextTools.ts +0 -63
- package/src/server/tools/tier2/discoveryTools.ts +0 -34
- package/src/server/tools/tier2/marketplaceTools.ts +0 -27
- package/src/server/tools/tier2/paymentTools.ts +0 -170
- package/src/server/ziggsconnect/ZiggsConnectClient.ts +0 -126
- package/src/server/ziggscontext/ZiggsContextClient.ts +0 -129
- package/src/server/ziggspay/ZiggsPayClient.ts +0 -193
- package/src/shared/ids.ts +0 -3
- package/src/shared/runtimeLog.ts +0 -72
- package/src/shared/types.ts +0 -29
- package/src/tasks/protocolRegistry.ts +0 -25
- package/src/tools/ToolManager.ts +0 -95
- package/src/tools/ToolProvider.ts +0 -19
- package/src/tools/defineTool.ts +0 -90
- package/src/tools/recordReport.ts +0 -82
- package/src/types.ts +0 -483
- package/src/utils/jsonExtractor.ts +0 -100
- /package/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -0
- /package/{src/agent/claudeSdkProtocol.ts → dist/agent/claudeSdkProtocol.js} +0 -0
- /package/{src/formatters/index.ts → dist/formatters/index.d.ts} +0 -0
- /package/{src/server/tasks/index.ts → dist/server/tasks/index.d.ts} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Ctx, Identity, Workflow, EffectHandler } from '../types.js';
|
|
2
|
+
import { type TransitionEvent } from '../runtime/AgentMachine.js';
|
|
3
|
+
import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
4
|
+
import type { MemoryStore } from '../memory/MemoryStore.js';
|
|
5
|
+
import type { ToolManager } from '../tools/ToolManager.js';
|
|
6
|
+
export interface AgentOptions {
|
|
7
|
+
workflow: Workflow;
|
|
8
|
+
toolManager: ToolManager;
|
|
9
|
+
promptBuilder?: PromptBuilder;
|
|
10
|
+
}
|
|
11
|
+
export interface TickInput {
|
|
12
|
+
/** Wire event (already normalized: a `message` or `task_result`-style envelope). */
|
|
13
|
+
event: unknown;
|
|
14
|
+
/** Server-provided per-session state. Omit on the first tick — a fresh ctx is constructed. */
|
|
15
|
+
ctx?: Ctx;
|
|
16
|
+
/** Server-provided workflow state name. Omit on the first tick — defaults to `workflow.initial`. */
|
|
17
|
+
state?: string;
|
|
18
|
+
/** Who am I and what session am I running in. The Agent carries this through Ctx. */
|
|
19
|
+
identity: Identity;
|
|
20
|
+
/** The single side channel for I/O — LLM, context, tools, send, record. */
|
|
21
|
+
eff: EffectHandler;
|
|
22
|
+
/**
|
|
23
|
+
* Agent-private long-term memory. Operator-provided (default
|
|
24
|
+
* `InMemoryStore` when omitted by `AgentHost`). Key shape is the agent's
|
|
25
|
+
* choice — `counterparty:<id>`, `agreement:<id>:notes`, etc.
|
|
26
|
+
*/
|
|
27
|
+
memory?: MemoryStore;
|
|
28
|
+
/** Optional tap for FSM transition events (ZIG-442). */
|
|
29
|
+
transitionTap?: (event: TransitionEvent) => void;
|
|
30
|
+
}
|
|
31
|
+
export interface TickOutput {
|
|
32
|
+
/** Updated ctx — Server persists this against the session. */
|
|
33
|
+
ctx: Ctx;
|
|
34
|
+
/** Workflow state name after the tick — Server persists this against the session. */
|
|
35
|
+
state: string;
|
|
36
|
+
/** Whether the machine settled at a parked state. */
|
|
37
|
+
parked: boolean;
|
|
38
|
+
/** True when a state-timeout outcome matched no transition this tick — the host must NOT re-arm. */
|
|
39
|
+
stateTimeoutUnhandled: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Accumulated LLM call count and token usage for this tick.
|
|
42
|
+
* `cacheRead` / `cacheCreation` are populated by the Claude brain (its usage
|
|
43
|
+
* exposes cache traffic, which is the bulk of real cost — ZIG-759/813); the
|
|
44
|
+
* FSM path leaves them undefined. `total` includes cache tokens when present.
|
|
45
|
+
*/
|
|
46
|
+
metrics: {
|
|
47
|
+
llmCalls: number;
|
|
48
|
+
tokens: {
|
|
49
|
+
total: number;
|
|
50
|
+
input: number;
|
|
51
|
+
output: number;
|
|
52
|
+
cacheRead?: number;
|
|
53
|
+
cacheCreation?: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pure agent: workflow definition + cognition machinery. No I/O, no transport,
|
|
59
|
+
* no services. The Agent's only side channel is the `eff` handler it receives
|
|
60
|
+
* on each `tick`. Server-side state (sessions, lanes, queue, outbox) lives in
|
|
61
|
+
* `AgentHost` — not here.
|
|
62
|
+
*
|
|
63
|
+
* The contract:
|
|
64
|
+
*
|
|
65
|
+
* `tick({ event, ctx, state, identity, eff })` →
|
|
66
|
+
* `{ ctx, state, parked }`
|
|
67
|
+
*
|
|
68
|
+
* Reads top-to-bottom like a synchronous function. Every `await eff(...)`
|
|
69
|
+
* inside the cognition core is a yield to the Server, which performs the
|
|
70
|
+
* impure operation and resumes the Agent with the result.
|
|
71
|
+
*/
|
|
72
|
+
export declare class Agent {
|
|
73
|
+
workflow: Workflow;
|
|
74
|
+
toolManager: ToolManager;
|
|
75
|
+
promptBuilder: PromptBuilder;
|
|
76
|
+
constructor({ workflow, toolManager, promptBuilder }: AgentOptions);
|
|
77
|
+
tick({ event, ctx, state, identity, eff, memory, transitionTap }: TickInput): Promise<TickOutput>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AgentMachine } from '../runtime/AgentMachine.js';
|
|
2
|
+
import { runTurn } from '../runtime/runTurn.js';
|
|
3
|
+
import { PromptBuilder } from '../runtime/PromptBuilder.js';
|
|
4
|
+
/**
|
|
5
|
+
* Pure agent: workflow definition + cognition machinery. No I/O, no transport,
|
|
6
|
+
* no services. The Agent's only side channel is the `eff` handler it receives
|
|
7
|
+
* on each `tick`. Server-side state (sessions, lanes, queue, outbox) lives in
|
|
8
|
+
* `AgentHost` — not here.
|
|
9
|
+
*
|
|
10
|
+
* The contract:
|
|
11
|
+
*
|
|
12
|
+
* `tick({ event, ctx, state, identity, eff })` →
|
|
13
|
+
* `{ ctx, state, parked }`
|
|
14
|
+
*
|
|
15
|
+
* Reads top-to-bottom like a synchronous function. Every `await eff(...)`
|
|
16
|
+
* inside the cognition core is a yield to the Server, which performs the
|
|
17
|
+
* impure operation and resumes the Agent with the result.
|
|
18
|
+
*/
|
|
19
|
+
export class Agent {
|
|
20
|
+
workflow;
|
|
21
|
+
toolManager;
|
|
22
|
+
promptBuilder;
|
|
23
|
+
constructor({ workflow, toolManager, promptBuilder }) {
|
|
24
|
+
if (!workflow?.states)
|
|
25
|
+
throw new Error('Agent: workflow with states is required');
|
|
26
|
+
if (!toolManager)
|
|
27
|
+
throw new Error('Agent: toolManager is required');
|
|
28
|
+
this.workflow = workflow;
|
|
29
|
+
this.toolManager = toolManager;
|
|
30
|
+
this.promptBuilder = promptBuilder || new PromptBuilder();
|
|
31
|
+
}
|
|
32
|
+
async tick({ event, ctx, state, identity, eff, memory, transitionTap }) {
|
|
33
|
+
if (!identity?.sessionId)
|
|
34
|
+
throw new Error('Agent.tick: identity.sessionId is required');
|
|
35
|
+
if (!eff)
|
|
36
|
+
throw new Error('Agent.tick: eff is required');
|
|
37
|
+
const machine = new AgentMachine({
|
|
38
|
+
workflow: this.workflow,
|
|
39
|
+
executionCore: runTurn,
|
|
40
|
+
eff,
|
|
41
|
+
toolManager: this.toolManager,
|
|
42
|
+
promptBuilder: this.promptBuilder,
|
|
43
|
+
memory,
|
|
44
|
+
input: { identity, ctx, state },
|
|
45
|
+
transitionTap,
|
|
46
|
+
});
|
|
47
|
+
// `send` awaits the recursive _step until the machine settles at a
|
|
48
|
+
// parked state (or stops). When it returns, we have the final state.
|
|
49
|
+
await machine.send({ event: event, identity });
|
|
50
|
+
return {
|
|
51
|
+
ctx: machine.ctx,
|
|
52
|
+
state: machine.state,
|
|
53
|
+
parked: machine.isParked,
|
|
54
|
+
stateTimeoutUnhandled: machine.stateTimeoutUnhandled,
|
|
55
|
+
metrics: { llmCalls: machine.llmCalls, tokens: machine.tokens },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import type { Workflow } from '../types.js';
|
|
3
|
+
import type { ToolManager } from '../tools/ToolManager.js';
|
|
4
|
+
import type { TickInput, TickOutput } from './Agent.js';
|
|
5
|
+
export type CognitionKind = 'fsm' | 'claude-sdk';
|
|
6
|
+
export interface ClaudeSdkAgentOptions {
|
|
7
|
+
workflow: Workflow;
|
|
8
|
+
toolManager: ToolManager;
|
|
9
|
+
description: string;
|
|
10
|
+
specialization?: string | null;
|
|
11
|
+
model: string;
|
|
12
|
+
anthropicKey: string;
|
|
13
|
+
operatorKey: string;
|
|
14
|
+
agentId: string;
|
|
15
|
+
maxTurns?: number;
|
|
16
|
+
/** Injectable for tests. Defaults to @anthropic-ai/claude-agent-sdk query(). */
|
|
17
|
+
queryFn?: typeof query;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A Ziggs agent whose brain is the Claude Agent SDK. Same host, same `eff`
|
|
21
|
+
* side channel, same tool surface as the FSM `Agent` — the ONLY difference is
|
|
22
|
+
* the loop: instead of the FSM + `runTurn`, a wake drives a headless Claude
|
|
23
|
+
* `query()` (see ZIG-784: "host is the product; the brain is how it decides").
|
|
24
|
+
*/
|
|
25
|
+
export declare class ClaudeSdkAgent {
|
|
26
|
+
workflow: Workflow;
|
|
27
|
+
toolManager: ToolManager;
|
|
28
|
+
private description;
|
|
29
|
+
private specialization;
|
|
30
|
+
private model;
|
|
31
|
+
private anthropicKey;
|
|
32
|
+
private operatorKey;
|
|
33
|
+
private agentId;
|
|
34
|
+
private maxTurns;
|
|
35
|
+
private queryFn;
|
|
36
|
+
constructor(opts: ClaudeSdkAgentOptions);
|
|
37
|
+
tick({ event, ctx, state, identity, eff }: TickInput): Promise<TickOutput>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { outcomeFromEvent } from '../runtime/buildOutcome.js';
|
|
3
|
+
import { bootstrapCtx } from '../runtime/bootstrapCtx.js';
|
|
4
|
+
import { CLAUDE_SDK_PROTOCOL } from './claudeSdkProtocol.js';
|
|
5
|
+
import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
|
|
6
|
+
import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAssistantText, } from './claudeCognition.js';
|
|
7
|
+
import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
|
|
8
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
9
|
+
const DEFAULT_STATE = 'ready';
|
|
10
|
+
/**
|
|
11
|
+
* Per-task turn cap. A wake drives one `query()` that may loop up to this many
|
|
12
|
+
* internal turns; the tool schema + system prompt ride every turn, so this is
|
|
13
|
+
* the direct multiplier on a task's token spend. 30 let a confused MCP agent
|
|
14
|
+
* burn schema×30 per wake; brokered tasks (fetch → record → done) converge in a
|
|
15
|
+
* handful of turns, so 12 is ample. Env-overridable (raise for genuinely long
|
|
16
|
+
* agents). ZIG-759. */
|
|
17
|
+
const DEFAULT_MAX_TURNS = Number(process.env.CLAUDE_SDK_MAX_TURNS ?? 12);
|
|
18
|
+
/**
|
|
19
|
+
* Hard ceiling on billable input tokens (incl. cache read/write) a single wake
|
|
20
|
+
* may spend before we abort the query. Defense-in-depth on the shared fleet key
|
|
21
|
+
* against a runaway loop; set high enough that normal tasks never trip it. The
|
|
22
|
+
* task ends unfinished (and logs loudly) rather than burning unbounded. ZIG-759. */
|
|
23
|
+
const MAX_TASK_INPUT_TOKENS = Number(process.env.CLAUDE_SDK_MAX_INPUT_TOKENS ?? 500_000);
|
|
24
|
+
/**
|
|
25
|
+
* Per-lane wake-rate circuit breaker. The per-wake caps above bound one wake;
|
|
26
|
+
* nothing bounded the NUMBER of wakes — the agent-private artifact echo drove
|
|
27
|
+
* one wake per wake, forever. The echo is fixed at the source (backend skips
|
|
28
|
+
* agent actors; host drops self-caused events), so this is a backstop for any
|
|
29
|
+
* future echo path: a lane waking faster than real traffic plausibly drives it
|
|
30
|
+
* skips the LLM (and logs loudly) instead of draining the fleet key. */
|
|
31
|
+
const WAKE_WINDOW_MS = 10 * 60_000;
|
|
32
|
+
const MAX_WAKES_PER_WINDOW = Number(process.env.CLAUDE_SDK_MAX_WAKES_PER_WINDOW ?? 40);
|
|
33
|
+
/**
|
|
34
|
+
* Resumed-transcript ceiling (ZIG-816). Each wake resumes the lane's Claude
|
|
35
|
+
* session, so the whole transcript (event prompts, tool results, summaries)
|
|
36
|
+
* replays on every turn — mostly as cache reads, but it grows without bound on
|
|
37
|
+
* a long-lived lane and dominates per-wake cost. When a wake's billable input
|
|
38
|
+
* crosses this, we drop the session id so the NEXT wake starts fresh. The
|
|
39
|
+
* protocol is backend-authoritative (the brain re-reads via ziggs_context_snapshot),
|
|
40
|
+
* so the transcript is convenience memory, not the source of truth — safe to
|
|
41
|
+
* discard. Env-overridable. Kept below MAX_TASK_INPUT_TOKENS so a lane resets
|
|
42
|
+
* before it can approach the per-wake abort. */
|
|
43
|
+
const MAX_SESSION_RESUME_INPUT_TOKENS = Number(process.env.CLAUDE_SDK_MAX_SESSION_INPUT_TOKENS ?? 200_000);
|
|
44
|
+
/** Total billable input for one API response (base + cache read + cache write). */
|
|
45
|
+
function billableInput(usage) {
|
|
46
|
+
if (!usage)
|
|
47
|
+
return 0;
|
|
48
|
+
return ((usage.input_tokens ?? 0) +
|
|
49
|
+
(usage.cache_read_input_tokens ?? 0) +
|
|
50
|
+
(usage.cache_creation_input_tokens ?? 0));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Per-API-call usage. Only assistant turns carry it — the result message's
|
|
54
|
+
* usage is the CUMULATIVE total of the whole query, so adding it on top of
|
|
55
|
+
* the per-turn sums would double-count the wake.
|
|
56
|
+
*/
|
|
57
|
+
function usageOf(message) {
|
|
58
|
+
if (message.type === 'assistant') {
|
|
59
|
+
return message.message?.usage;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function extractMetrics(message) {
|
|
64
|
+
if (message.type !== 'result')
|
|
65
|
+
return null;
|
|
66
|
+
const usage = message.usage;
|
|
67
|
+
// Billable input includes cache reads/writes — that is what the Anthropic
|
|
68
|
+
// console counts and what the fleet key pays for. Reporting only the
|
|
69
|
+
// uncached slice made our telemetry read ~10-20x under the real usage.
|
|
70
|
+
const input = billableInput(usage);
|
|
71
|
+
const output = usage?.output_tokens ?? 0;
|
|
72
|
+
// Cache read/write are already folded into `input` above; surfaced separately
|
|
73
|
+
// here for per-tick visibility (metricsTap metadata / dashboards, ZIG-813).
|
|
74
|
+
const cacheRead = usage?.cache_read_input_tokens ?? 0;
|
|
75
|
+
const cacheCreation = usage?.cache_creation_input_tokens ?? 0;
|
|
76
|
+
return {
|
|
77
|
+
llmCalls: message.num_turns ?? 1,
|
|
78
|
+
// `input` is already the full billable input (base + cache); total = input + output.
|
|
79
|
+
tokens: { total: input + output, input, output, cacheRead, cacheCreation },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the agreement this wake acts under, for a delegated per-user MCP call
|
|
84
|
+
* (ZIG-831/832). Without it the gateway resolves the connection owner from
|
|
85
|
+
* `senderId` — which, under a delegation, is the delegating agent (who owns no
|
|
86
|
+
* connection) → a spurious NO_USER_CONNECTION even though the payer IS connected.
|
|
87
|
+
*
|
|
88
|
+
* The id must be found on ANY wake the worker might fetch on, not just
|
|
89
|
+
* `task_result` events. In practice the worker often fetches while reacting to
|
|
90
|
+
* the proposal-approved / agreement-active lifecycle events, which carry the id
|
|
91
|
+
* at the event top level rather than under `result`. Reading only
|
|
92
|
+
* `result.agreementId` (the original ZIG-831 wiring) left those wakes with no
|
|
93
|
+
* agreement → the false "authorize your connection" prompt. Resolve from every
|
|
94
|
+
* reliable source instead:
|
|
95
|
+
* 1. task_result events → `result.agreementId`
|
|
96
|
+
* 2. agreement-lifecycle events → top-level `agreementId`
|
|
97
|
+
* 3. task lanes → `sessionId` IS the agreement id
|
|
98
|
+
* 4. fallback → the single active agreement in context this agent provides
|
|
99
|
+
*/
|
|
100
|
+
function resolveOnBehalfAgreementId(rawEvent, contextSnap, agentId, sessionId) {
|
|
101
|
+
const str = (v) => typeof v === 'string' && v ? v : undefined;
|
|
102
|
+
const fromResult = str(rawEvent?.result?.agreementId);
|
|
103
|
+
if (fromResult)
|
|
104
|
+
return fromResult;
|
|
105
|
+
const fromEvent = str(rawEvent?.agreementId);
|
|
106
|
+
if (fromEvent)
|
|
107
|
+
return fromEvent;
|
|
108
|
+
if (typeof sessionId === 'string' && sessionId.startsWith('agr_'))
|
|
109
|
+
return sessionId;
|
|
110
|
+
// Fallback: exactly one active agreement in context where this agent is the
|
|
111
|
+
// provider. Guarded to a single match so we never send the wrong agreement.
|
|
112
|
+
const ags = Array.isArray(contextSnap?.agreements)
|
|
113
|
+
? contextSnap.agreements
|
|
114
|
+
: [];
|
|
115
|
+
const provides = ags.filter((a) => {
|
|
116
|
+
const p = a?.parties ?? a;
|
|
117
|
+
const providerIds = [p?.provider, p?.providerAgent, a?.providerId, a?.providerAgentId, a?.executorId];
|
|
118
|
+
const isProvider = providerIds.some((x) => x === agentId);
|
|
119
|
+
const status = a?.status != null ? String(a.status).toLowerCase() : '';
|
|
120
|
+
const active = !status || status === 'active' || status === 'approved';
|
|
121
|
+
return isProvider && active;
|
|
122
|
+
});
|
|
123
|
+
if (provides.length === 1) {
|
|
124
|
+
return str(provides[0].agreementId) ?? str(provides[0].id) ?? str(provides[0]._id);
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A Ziggs agent whose brain is the Claude Agent SDK. Same host, same `eff`
|
|
130
|
+
* side channel, same tool surface as the FSM `Agent` — the ONLY difference is
|
|
131
|
+
* the loop: instead of the FSM + `runTurn`, a wake drives a headless Claude
|
|
132
|
+
* `query()` (see ZIG-784: "host is the product; the brain is how it decides").
|
|
133
|
+
*/
|
|
134
|
+
export class ClaudeSdkAgent {
|
|
135
|
+
workflow;
|
|
136
|
+
toolManager;
|
|
137
|
+
description;
|
|
138
|
+
specialization;
|
|
139
|
+
model;
|
|
140
|
+
anthropicKey;
|
|
141
|
+
operatorKey;
|
|
142
|
+
agentId;
|
|
143
|
+
maxTurns;
|
|
144
|
+
queryFn;
|
|
145
|
+
constructor(opts) {
|
|
146
|
+
if (!opts.workflow?.states)
|
|
147
|
+
throw new Error('ClaudeSdkAgent: workflow with states is required');
|
|
148
|
+
if (!opts.toolManager)
|
|
149
|
+
throw new Error('ClaudeSdkAgent: toolManager is required');
|
|
150
|
+
if (!opts.anthropicKey)
|
|
151
|
+
throw new Error('ClaudeSdkAgent: anthropicKey is required');
|
|
152
|
+
this.workflow = opts.workflow;
|
|
153
|
+
this.toolManager = opts.toolManager;
|
|
154
|
+
this.description = opts.description;
|
|
155
|
+
this.specialization = opts.specialization ?? null;
|
|
156
|
+
this.model = opts.model;
|
|
157
|
+
this.anthropicKey = opts.anthropicKey;
|
|
158
|
+
this.operatorKey = opts.operatorKey;
|
|
159
|
+
this.agentId = opts.agentId;
|
|
160
|
+
this.maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
161
|
+
this.queryFn = opts.queryFn ?? query;
|
|
162
|
+
}
|
|
163
|
+
async tick({ event, ctx, state, identity, eff }) {
|
|
164
|
+
if (!identity?.sessionId)
|
|
165
|
+
throw new Error('ClaudeSdkAgent.tick: identity.sessionId is required');
|
|
166
|
+
if (!eff)
|
|
167
|
+
throw new Error('ClaudeSdkAgent.tick: eff is required');
|
|
168
|
+
const rawEvent = event;
|
|
169
|
+
const nextCtx = bootstrapCtx(identity, ctx);
|
|
170
|
+
nextCtx.lastOutcome = outcomeFromEvent(rawEvent ?? null, identity.agentId);
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const recentWakes = (nextCtx.recentWakes ?? []).filter((t) => now - t < WAKE_WINDOW_MS);
|
|
173
|
+
if (recentWakes.length >= MAX_WAKES_PER_WINDOW) {
|
|
174
|
+
runtimeLog.warn('ClaudeSdkAgent', `wake-rate breaker tripped: ${recentWakes.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
|
|
175
|
+
`session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`);
|
|
176
|
+
nextCtx.recentWakes = recentWakes;
|
|
177
|
+
return {
|
|
178
|
+
ctx: nextCtx,
|
|
179
|
+
state: state ?? DEFAULT_STATE,
|
|
180
|
+
parked: true,
|
|
181
|
+
stateTimeoutUnhandled: false,
|
|
182
|
+
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
nextCtx.recentWakes = [...recentWakes, now];
|
|
186
|
+
let contextSnap = null;
|
|
187
|
+
try {
|
|
188
|
+
contextSnap = await eff({
|
|
189
|
+
kind: 'read-context',
|
|
190
|
+
sessionId: identity.sessionId,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
runtimeLog.warn('ClaudeSdkAgent', `read-context failed session=${identity.sessionId}: ${err.message}`);
|
|
195
|
+
}
|
|
196
|
+
// Same guard as the FSM's runTurn: a message wake for a chat the backend
|
|
197
|
+
// no longer resolves (deleted / not visible) has no world to act in —
|
|
198
|
+
// skip the query instead of burning a wake on a fabricated empty history.
|
|
199
|
+
// Task lanes (sessionId = agreementId) also trip chatMissing and must run.
|
|
200
|
+
if (contextSnap?.chatMissing === true && nextCtx.lastOutcome?.kind === 'message-received') {
|
|
201
|
+
runtimeLog.warn('ClaudeSdkAgent', `chat ${identity.sessionId} not found on backend — skipping message wake agent=${this.agentId}`);
|
|
202
|
+
return {
|
|
203
|
+
ctx: nextCtx,
|
|
204
|
+
state: state ?? DEFAULT_STATE,
|
|
205
|
+
parked: true,
|
|
206
|
+
stateTimeoutUnhandled: false,
|
|
207
|
+
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const roleBlock = [
|
|
211
|
+
`## Your role`,
|
|
212
|
+
this.description,
|
|
213
|
+
this.specialization ? `Specialization: ${this.specialization}` : '',
|
|
214
|
+
].filter(Boolean).join('\n');
|
|
215
|
+
const systemPrompt = `${CLAUDE_SDK_PROTOCOL}\n\n${roleBlock}`;
|
|
216
|
+
const runId = crypto.randomUUID();
|
|
217
|
+
const toolContext = {
|
|
218
|
+
operatorKey: this.operatorKey,
|
|
219
|
+
agentId: this.agentId,
|
|
220
|
+
chatId: identity.sessionId,
|
|
221
|
+
sessionId: identity.sessionId,
|
|
222
|
+
eff,
|
|
223
|
+
senderId: rawEvent?.senderId != null ? String(rawEvent.senderId) : undefined,
|
|
224
|
+
senderType: rawEvent?.senderType != null ? String(rawEvent.senderType) : undefined,
|
|
225
|
+
// ZIG-831/832: agreement this wake acts under → lets a delegated per-user
|
|
226
|
+
// MCP call resolve the originating end-user (payer) rather than the
|
|
227
|
+
// delegating agent (senderId). Resolved from every reliable wire source,
|
|
228
|
+
// not just task_result events — see resolveOnBehalfAgreementId.
|
|
229
|
+
onBehalfOfAgreementId: resolveOnBehalfAgreementId(rawEvent, contextSnap, this.agentId, identity.sessionId),
|
|
230
|
+
runId,
|
|
231
|
+
};
|
|
232
|
+
const availableTools = await this.toolManager.getAvailableTools();
|
|
233
|
+
const mcpServer = buildZiggsSdkMcpServer(availableTools, () => toolContext, {
|
|
234
|
+
name: 'ziggs',
|
|
235
|
+
instructions: 'In-process Ziggs tools backed by api-client. No external MCP process.',
|
|
236
|
+
});
|
|
237
|
+
const prompt = buildClaudeEventPrompt(rawEvent, summarizeContext(contextSnap));
|
|
238
|
+
const mcpServers = { ziggs: mcpServer };
|
|
239
|
+
assertHostedAgentZiggsTools(mcpServers);
|
|
240
|
+
const queryOptions = claudeQueryOptions({
|
|
241
|
+
systemPrompt,
|
|
242
|
+
model: this.model,
|
|
243
|
+
mcpServers,
|
|
244
|
+
maxTurns: this.maxTurns,
|
|
245
|
+
apiKey: this.anthropicKey,
|
|
246
|
+
stderr: (data) => {
|
|
247
|
+
const line = data.trim();
|
|
248
|
+
if (line)
|
|
249
|
+
runtimeLog.warn('ClaudeSdkAgent', `stderr session=${identity.sessionId}: ${line}`);
|
|
250
|
+
},
|
|
251
|
+
resume: nextCtx.claudeSessionId,
|
|
252
|
+
});
|
|
253
|
+
let metrics = { llmCalls: 0, tokens: { total: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
|
|
254
|
+
let sessionId = nextCtx.claudeSessionId;
|
|
255
|
+
let spentInput = 0;
|
|
256
|
+
try {
|
|
257
|
+
for await (const message of this.queryFn({ prompt, options: queryOptions })) {
|
|
258
|
+
if ('session_id' in message && typeof message.session_id === 'string') {
|
|
259
|
+
sessionId = message.session_id;
|
|
260
|
+
}
|
|
261
|
+
// Surface the model's own prose so a hired Claude agent is as legible as
|
|
262
|
+
// an FSM agent (ZIG-787). Each completed assistant turn's text is recorded
|
|
263
|
+
// as a `thought` — the SAME agent-private channel the FSM uses for its
|
|
264
|
+
// reasoning (runTurn `emitThought` → record-event → recordThought), so it
|
|
265
|
+
// shows in the hired-agent view without cluttering the counterparty chat.
|
|
266
|
+
// Pure tool-use turns carry no text and record nothing.
|
|
267
|
+
//
|
|
268
|
+
// Fire-and-forget on purpose: this runs inside `for await (query())`, so
|
|
269
|
+
// awaiting an artifacts round-trip per turn would couple model-stream
|
|
270
|
+
// consumption to the artifacts backend — a slow/wedged backend would stall
|
|
271
|
+
// the whole wake, not just fail a breadcrumb. Recording is best-effort and
|
|
272
|
+
// thought ordering is not load-bearing, so we let it run in the background
|
|
273
|
+
// and swallow failures with a warn.
|
|
274
|
+
if (message.type === 'assistant') {
|
|
275
|
+
const text = extractAssistantText(message).trim();
|
|
276
|
+
if (text) {
|
|
277
|
+
void eff({ kind: 'record-event', sessionId: identity.sessionId, entry: { kind: 'thought', text } })
|
|
278
|
+
.catch((err) => runtimeLog.warn('ClaudeSdkAgent', `record thought failed session=${identity.sessionId}: ${err.message}`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const m = extractMetrics(message);
|
|
282
|
+
if (m)
|
|
283
|
+
metrics = m;
|
|
284
|
+
if (message.type === 'result' && message.subtype !== 'success') {
|
|
285
|
+
runtimeLog.warn('ClaudeSdkAgent', `query ended without success subtype=${message.subtype} session=${identity.sessionId}`);
|
|
286
|
+
}
|
|
287
|
+
// Runaway guard (ZIG-759): abort before an unbounded loop drains the
|
|
288
|
+
// shared fleet key. Breaking the for-await calls the generator's return(),
|
|
289
|
+
// which terminates the underlying query.
|
|
290
|
+
spentInput += billableInput(usageOf(message));
|
|
291
|
+
if (spentInput > MAX_TASK_INPUT_TOKENS) {
|
|
292
|
+
runtimeLog.warn('ClaudeSdkAgent', `input-token budget exceeded (~${spentInput} > ${MAX_TASK_INPUT_TOKENS}) session=${identity.sessionId} agent=${this.agentId} — aborting query to cap spend (ZIG-759)`);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
runtimeLog.warn('ClaudeSdkAgent', `query failed session=${identity.sessionId}: ${err.message}`);
|
|
299
|
+
throw err;
|
|
300
|
+
}
|
|
301
|
+
if (sessionId)
|
|
302
|
+
nextCtx.claudeSessionId = sessionId;
|
|
303
|
+
// ZIG-816: bound resumed-transcript growth. `metrics.tokens.input` is the
|
|
304
|
+
// wake's billable input off the result message (dominated by the resumed
|
|
305
|
+
// transcript's cache reads); `spentInput` covers an aborted wake that never
|
|
306
|
+
// produced a result. Past the ceiling, forget the session so the next wake
|
|
307
|
+
// starts fresh rather than paying an ever-larger replay.
|
|
308
|
+
const wakeInput = Math.max(metrics.tokens.input, spentInput);
|
|
309
|
+
if (nextCtx.claudeSessionId && wakeInput > MAX_SESSION_RESUME_INPUT_TOKENS) {
|
|
310
|
+
runtimeLog.info('ClaudeSdkAgent', `resetting Claude session (wake input ~${wakeInput} > ${MAX_SESSION_RESUME_INPUT_TOKENS}) ` +
|
|
311
|
+
`session=${identity.sessionId} agent=${this.agentId} — next wake starts fresh (ZIG-816)`);
|
|
312
|
+
nextCtx.claudeSessionId = undefined;
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
ctx: nextCtx,
|
|
316
|
+
state: state ?? DEFAULT_STATE,
|
|
317
|
+
parked: true,
|
|
318
|
+
stateTimeoutUnhandled: false,
|
|
319
|
+
metrics,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Claude-Agent-SDK cognition pieces.
|
|
3
|
+
*
|
|
4
|
+
* Everything a Claude-brain agent needs to turn a Ziggs wake into a `query()`
|
|
5
|
+
* call, EXCEPT the loop itself. `ClaudeSdkAgent` owns the loop (drive `query()`,
|
|
6
|
+
* collect metrics); this module owns the periphery so it lives in one place and
|
|
7
|
+
* is unit-testable without spinning up the SDK.
|
|
8
|
+
*
|
|
9
|
+
* The lockdown option set (`tools: []`, `strictMcpConfig`, `settingSources: []`,
|
|
10
|
+
* bypass permissions, no local settings) is a security boundary, not a default —
|
|
11
|
+
* it MUST be applied on every Claude-brain wake. Centralizing it here means a
|
|
12
|
+
* caller cannot silently drift from it.
|
|
13
|
+
*/
|
|
14
|
+
import type { Options, SDKAssistantMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
15
|
+
import type { RawEvent } from '../runtime/buildOutcome.js';
|
|
16
|
+
export interface ClaudeQueryOptionsParams {
|
|
17
|
+
/** Protocol + role system prompt. */
|
|
18
|
+
systemPrompt: string;
|
|
19
|
+
/** Claude model id for the brain. */
|
|
20
|
+
model: string;
|
|
21
|
+
/** In-process MCP servers exposing the agent's tools (e.g. `{ ziggs }`). */
|
|
22
|
+
mcpServers: NonNullable<Options['mcpServers']>;
|
|
23
|
+
/** Max agentic turns per wake. */
|
|
24
|
+
maxTurns: number;
|
|
25
|
+
/** Working dir for the query subprocess. Defaults to `$CLAUDE_SDK_CWD` or `/tmp`. */
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/** stderr sink for the query subprocess. */
|
|
28
|
+
stderr?: (data: string) => void;
|
|
29
|
+
/** Claude session id to resume across ticks. Omitted → a fresh session. */
|
|
30
|
+
resume?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Anthropic API key for this wake. Passed to the query subprocess via the
|
|
33
|
+
* `env` option (which inherits `process.env` and overrides the key) rather
|
|
34
|
+
* than mutating the parent process's global `process.env` — so agents with
|
|
35
|
+
* different keys can share one process safely.
|
|
36
|
+
*/
|
|
37
|
+
apiKey?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
|
|
41
|
+
* model, but the host's default model is provider-agnostic (an OpenAI id today),
|
|
42
|
+
* so this prefers an explicit `claude-*` id (direct or via `services.llm`) and
|
|
43
|
+
* otherwise falls back to a safe Claude default — never an OpenAI id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveClaudeModel(preferred?: string, servicesModel?: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Build the `query()` options for a Claude-brain wake. The lockdown fields are
|
|
48
|
+
* fixed here; callers only supply the per-wake variables (prompt, model, tools,
|
|
49
|
+
* turn budget, resume). Returns `Options` so it drops straight into `query()`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function claudeQueryOptions(params: ClaudeQueryOptionsParams): Options;
|
|
52
|
+
/**
|
|
53
|
+
* Concatenate the text blocks of a completed Claude assistant turn. Tool-use,
|
|
54
|
+
* thinking, and other block kinds are skipped — only the model's user-facing
|
|
55
|
+
* prose is returned (empty string for a pure tool-use turn). This is what a
|
|
56
|
+
* Claude-brain wake records as a `thought` per turn (ZIG-787), the same way
|
|
57
|
+
* the FSM's `emitThought` surfaces the model's reasoning.
|
|
58
|
+
*/
|
|
59
|
+
export declare function extractAssistantText(message: SDKAssistantMessage): string;
|
|
60
|
+
/** One-line digest of a context snapshot for the wake prompt. */
|
|
61
|
+
export declare function summarizeContext(snap: Record<string, unknown> | null | undefined): string;
|
|
62
|
+
/**
|
|
63
|
+
* Turn a Ziggs wake event + context digest into the user prompt that starts a
|
|
64
|
+
* Claude-brain turn. The agent's standing instructions come from its system
|
|
65
|
+
* prompt (protocol + role); this is just "here's what woke you, go handle it."
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildClaudeEventPrompt(event: RawEvent | undefined, contextSummary: string): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-6';
|
|
2
|
+
/**
|
|
3
|
+
* Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
|
|
4
|
+
* model, but the host's default model is provider-agnostic (an OpenAI id today),
|
|
5
|
+
* so this prefers an explicit `claude-*` id (direct or via `services.llm`) and
|
|
6
|
+
* otherwise falls back to a safe Claude default — never an OpenAI id.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveClaudeModel(preferred, servicesModel) {
|
|
9
|
+
if (preferred?.startsWith('claude'))
|
|
10
|
+
return preferred;
|
|
11
|
+
if (servicesModel?.startsWith('claude'))
|
|
12
|
+
return servicesModel;
|
|
13
|
+
return DEFAULT_CLAUDE_MODEL;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the `query()` options for a Claude-brain wake. The lockdown fields are
|
|
17
|
+
* fixed here; callers only supply the per-wake variables (prompt, model, tools,
|
|
18
|
+
* turn budget, resume). Returns `Options` so it drops straight into `query()`.
|
|
19
|
+
*/
|
|
20
|
+
export function claudeQueryOptions(params) {
|
|
21
|
+
return {
|
|
22
|
+
systemPrompt: params.systemPrompt,
|
|
23
|
+
model: params.model,
|
|
24
|
+
mcpServers: params.mcpServers,
|
|
25
|
+
// The agent's ONLY capabilities are its MCP tools — no built-in file/bash/web
|
|
26
|
+
// tools, no project/user/plugin MCP config, no local settings / CLAUDE.md.
|
|
27
|
+
tools: [],
|
|
28
|
+
strictMcpConfig: true,
|
|
29
|
+
settingSources: [],
|
|
30
|
+
permissionMode: 'bypassPermissions',
|
|
31
|
+
allowDangerouslySkipPermissions: true,
|
|
32
|
+
maxTurns: params.maxTurns,
|
|
33
|
+
cwd: params.cwd ?? process.env.CLAUDE_SDK_CWD ?? '/tmp',
|
|
34
|
+
// Set the key for the subprocess without touching the parent process.env.
|
|
35
|
+
// `env` replaces the subprocess environment, so spread process.env to keep
|
|
36
|
+
// PATH/HOME/etc. and override only the key.
|
|
37
|
+
...(params.apiKey ? { env: { ...process.env, ANTHROPIC_API_KEY: params.apiKey } } : {}),
|
|
38
|
+
...(params.stderr ? { stderr: params.stderr } : {}),
|
|
39
|
+
...(params.resume ? { resume: params.resume } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Concatenate the text blocks of a completed Claude assistant turn. Tool-use,
|
|
44
|
+
* thinking, and other block kinds are skipped — only the model's user-facing
|
|
45
|
+
* prose is returned (empty string for a pure tool-use turn). This is what a
|
|
46
|
+
* Claude-brain wake records as a `thought` per turn (ZIG-787), the same way
|
|
47
|
+
* the FSM's `emitThought` surfaces the model's reasoning.
|
|
48
|
+
*/
|
|
49
|
+
export function extractAssistantText(message) {
|
|
50
|
+
const content = message.message?.content;
|
|
51
|
+
if (!Array.isArray(content))
|
|
52
|
+
return '';
|
|
53
|
+
let out = '';
|
|
54
|
+
for (const block of content) {
|
|
55
|
+
if (block && block.type === 'text') {
|
|
56
|
+
out += block.text ?? '';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** One-line digest of a context snapshot for the wake prompt. */
|
|
62
|
+
export function summarizeContext(snap) {
|
|
63
|
+
if (!snap)
|
|
64
|
+
return '';
|
|
65
|
+
const agreements = Array.isArray(snap.agreements) ? snap.agreements.length : 0;
|
|
66
|
+
const history = Array.isArray(snap.history) ? snap.history.length : 0;
|
|
67
|
+
const users = Array.isArray(snap.users) ? snap.users.length : 0;
|
|
68
|
+
return `history=${history} messages, agreements=${agreements}, users=${users}`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Turn a Ziggs wake event + context digest into the user prompt that starts a
|
|
72
|
+
* Claude-brain turn. The agent's standing instructions come from its system
|
|
73
|
+
* prompt (protocol + role); this is just "here's what woke you, go handle it."
|
|
74
|
+
*/
|
|
75
|
+
export function buildClaudeEventPrompt(event, contextSummary) {
|
|
76
|
+
const lines = ['You received a new Ziggs event. Handle it per your protocol.'];
|
|
77
|
+
if (event?.type)
|
|
78
|
+
lines.push(`Event type: ${event.type}`);
|
|
79
|
+
if (event?.text)
|
|
80
|
+
lines.push(`Message: ${event.text}`);
|
|
81
|
+
if (event?.senderId)
|
|
82
|
+
lines.push(`Sender: ${event.senderId}`);
|
|
83
|
+
if (event?.operation)
|
|
84
|
+
lines.push(`Operation: ${event.operation}`);
|
|
85
|
+
// ZIG-832: surface the task/agreement this event carries. Without it the brain
|
|
86
|
+
// can't tell WHICH task to update/complete (workers otherwise stall asking for
|
|
87
|
+
// their own taskId) and can't see whether the referenced step already ran or
|
|
88
|
+
// failed — which makes an orchestrator re-spawn duplicate delegations.
|
|
89
|
+
const result = event?.result;
|
|
90
|
+
if (result && typeof result === 'object') {
|
|
91
|
+
if (typeof result.taskId === 'string')
|
|
92
|
+
lines.push(`Task: ${result.taskId}`);
|
|
93
|
+
if (typeof result.agreementId === 'string')
|
|
94
|
+
lines.push(`Agreement: ${result.agreementId}`);
|
|
95
|
+
if (typeof result.state === 'string')
|
|
96
|
+
lines.push(`Task state: ${result.state}`);
|
|
97
|
+
}
|
|
98
|
+
if (contextSummary) {
|
|
99
|
+
lines.push('', 'Session context snapshot:', contextSummary);
|
|
100
|
+
}
|
|
101
|
+
lines.push('', 'Use ziggs_context_snapshot if you need more context. If the event needs nothing from you, say so briefly and stop.');
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
}
|