@ziggs-ai/agent-sdk 0.1.12 → 0.2.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 +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 +213 -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 +48 -0
- package/dist/server/ziggscontext/ZiggsContextClient.js +90 -0
- package/dist/server/ziggspay/ZiggsPayClient.d.ts +82 -0
- package/dist/server/ziggspay/ZiggsPayClient.js +188 -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 +13 -5
- 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
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import { query, type SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import type { Workflow, Ctx } from '../types.js';
|
|
3
|
-
import type { ToolManager } from '../tools/ToolManager.js';
|
|
4
|
-
import { outcomeFromEvent, type RawEvent } from '../runtime/buildOutcome.js';
|
|
5
|
-
import { bootstrapCtx } from '../runtime/bootstrapCtx.js';
|
|
6
|
-
import { CLAUDE_SDK_PROTOCOL } from './claudeSdkProtocol.js';
|
|
7
|
-
import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
|
|
8
|
-
import type { ClaudeSdkToolContext } from './claudeSdkTools.js';
|
|
9
|
-
import {
|
|
10
|
-
claudeQueryOptions,
|
|
11
|
-
buildClaudeEventPrompt,
|
|
12
|
-
summarizeContext,
|
|
13
|
-
extractAssistantText,
|
|
14
|
-
} from './claudeCognition.js';
|
|
15
|
-
import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
|
|
16
|
-
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
17
|
-
import type { TickInput, TickOutput } from './Agent.js';
|
|
18
|
-
|
|
19
|
-
export type CognitionKind = 'fsm' | 'claude-sdk';
|
|
20
|
-
|
|
21
|
-
export interface ClaudeSdkAgentOptions {
|
|
22
|
-
workflow: Workflow;
|
|
23
|
-
toolManager: ToolManager;
|
|
24
|
-
description: string;
|
|
25
|
-
specialization?: string | null;
|
|
26
|
-
model: string;
|
|
27
|
-
anthropicKey: string;
|
|
28
|
-
operatorKey: string;
|
|
29
|
-
agentId: string;
|
|
30
|
-
maxTurns?: number;
|
|
31
|
-
/** Injectable for tests. Defaults to @anthropic-ai/claude-agent-sdk query(). */
|
|
32
|
-
queryFn?: typeof query;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const DEFAULT_STATE = 'ready';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Per-task turn cap. A wake drives one `query()` that may loop up to this many
|
|
39
|
-
* internal turns; the tool schema + system prompt ride every turn, so this is
|
|
40
|
-
* the direct multiplier on a task's token spend. 30 let a confused MCP agent
|
|
41
|
-
* burn schema×30 per wake; brokered tasks (fetch → record → done) converge in a
|
|
42
|
-
* handful of turns, so 12 is ample. Env-overridable (raise for genuinely long
|
|
43
|
-
* agents). ZIG-759. */
|
|
44
|
-
const DEFAULT_MAX_TURNS = Number(process.env.CLAUDE_SDK_MAX_TURNS ?? 12);
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Hard ceiling on billable input tokens (incl. cache read/write) a single wake
|
|
48
|
-
* may spend before we abort the query. Defense-in-depth on the shared fleet key
|
|
49
|
-
* against a runaway loop; set high enough that normal tasks never trip it. The
|
|
50
|
-
* task ends unfinished (and logs loudly) rather than burning unbounded. ZIG-759. */
|
|
51
|
-
const MAX_TASK_INPUT_TOKENS = Number(process.env.CLAUDE_SDK_MAX_INPUT_TOKENS ?? 500_000);
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Per-lane wake-rate circuit breaker. The per-wake caps above bound one wake;
|
|
55
|
-
* nothing bounded the NUMBER of wakes — the agent-private artifact echo drove
|
|
56
|
-
* one wake per wake, forever. The echo is fixed at the source (backend skips
|
|
57
|
-
* agent actors; host drops self-caused events), so this is a backstop for any
|
|
58
|
-
* future echo path: a lane waking faster than real traffic plausibly drives it
|
|
59
|
-
* skips the LLM (and logs loudly) instead of draining the fleet key. */
|
|
60
|
-
const WAKE_WINDOW_MS = 10 * 60_000;
|
|
61
|
-
const MAX_WAKES_PER_WINDOW = Number(process.env.CLAUDE_SDK_MAX_WAKES_PER_WINDOW ?? 40);
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Resumed-transcript ceiling (ZIG-816). Each wake resumes the lane's Claude
|
|
65
|
-
* session, so the whole transcript (event prompts, tool results, summaries)
|
|
66
|
-
* replays on every turn — mostly as cache reads, but it grows without bound on
|
|
67
|
-
* a long-lived lane and dominates per-wake cost. When a wake's billable input
|
|
68
|
-
* crosses this, we drop the session id so the NEXT wake starts fresh. The
|
|
69
|
-
* protocol is backend-authoritative (the brain re-reads via ziggs_context_snapshot),
|
|
70
|
-
* so the transcript is convenience memory, not the source of truth — safe to
|
|
71
|
-
* discard. Env-overridable. Kept below MAX_TASK_INPUT_TOKENS so a lane resets
|
|
72
|
-
* before it can approach the per-wake abort. */
|
|
73
|
-
const MAX_SESSION_RESUME_INPUT_TOKENS = Number(
|
|
74
|
-
process.env.CLAUDE_SDK_MAX_SESSION_INPUT_TOKENS ?? 200_000,
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
type SdkUsage = {
|
|
78
|
-
input_tokens?: number;
|
|
79
|
-
output_tokens?: number;
|
|
80
|
-
cache_read_input_tokens?: number;
|
|
81
|
-
cache_creation_input_tokens?: number;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/** Total billable input for one API response (base + cache read + cache write). */
|
|
85
|
-
function billableInput(usage: SdkUsage | undefined): number {
|
|
86
|
-
if (!usage) return 0;
|
|
87
|
-
return (
|
|
88
|
-
(usage.input_tokens ?? 0) +
|
|
89
|
-
(usage.cache_read_input_tokens ?? 0) +
|
|
90
|
-
(usage.cache_creation_input_tokens ?? 0)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Per-API-call usage. Only assistant turns carry it — the result message's
|
|
96
|
-
* usage is the CUMULATIVE total of the whole query, so adding it on top of
|
|
97
|
-
* the per-turn sums would double-count the wake.
|
|
98
|
-
*/
|
|
99
|
-
function usageOf(message: SDKMessage): SdkUsage | undefined {
|
|
100
|
-
if (message.type === 'assistant') {
|
|
101
|
-
return (message as { message?: { usage?: SdkUsage } }).message?.usage;
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function extractMetrics(message: SDKMessage): { llmCalls: number; tokens: { total: number; input: number; output: number; cacheRead: number; cacheCreation: number } } | null {
|
|
107
|
-
if (message.type !== 'result') return null;
|
|
108
|
-
const usage = message.usage as SdkUsage | undefined;
|
|
109
|
-
// Billable input includes cache reads/writes — that is what the Anthropic
|
|
110
|
-
// console counts and what the fleet key pays for. Reporting only the
|
|
111
|
-
// uncached slice made our telemetry read ~10-20x under the real usage.
|
|
112
|
-
const input = billableInput(usage);
|
|
113
|
-
const output = usage?.output_tokens ?? 0;
|
|
114
|
-
// Cache read/write are already folded into `input` above; surfaced separately
|
|
115
|
-
// here for per-tick visibility (metricsTap metadata / dashboards, ZIG-813).
|
|
116
|
-
const cacheRead = usage?.cache_read_input_tokens ?? 0;
|
|
117
|
-
const cacheCreation = usage?.cache_creation_input_tokens ?? 0;
|
|
118
|
-
return {
|
|
119
|
-
llmCalls: message.num_turns ?? 1,
|
|
120
|
-
// `input` is already the full billable input (base + cache); total = input + output.
|
|
121
|
-
tokens: { total: input + output, input, output, cacheRead, cacheCreation },
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Resolve the agreement this wake acts under, for a delegated per-user MCP call
|
|
127
|
-
* (ZIG-831/832). Without it the gateway resolves the connection owner from
|
|
128
|
-
* `senderId` — which, under a delegation, is the delegating agent (who owns no
|
|
129
|
-
* connection) → a spurious NO_USER_CONNECTION even though the payer IS connected.
|
|
130
|
-
*
|
|
131
|
-
* The id must be found on ANY wake the worker might fetch on, not just
|
|
132
|
-
* `task_result` events. In practice the worker often fetches while reacting to
|
|
133
|
-
* the proposal-approved / agreement-active lifecycle events, which carry the id
|
|
134
|
-
* at the event top level rather than under `result`. Reading only
|
|
135
|
-
* `result.agreementId` (the original ZIG-831 wiring) left those wakes with no
|
|
136
|
-
* agreement → the false "authorize your connection" prompt. Resolve from every
|
|
137
|
-
* reliable source instead:
|
|
138
|
-
* 1. task_result events → `result.agreementId`
|
|
139
|
-
* 2. agreement-lifecycle events → top-level `agreementId`
|
|
140
|
-
* 3. task lanes → `sessionId` IS the agreement id
|
|
141
|
-
* 4. fallback → the single active agreement in context this agent provides
|
|
142
|
-
*/
|
|
143
|
-
function resolveOnBehalfAgreementId(
|
|
144
|
-
rawEvent: RawEvent | undefined,
|
|
145
|
-
contextSnap: Record<string, unknown> | null,
|
|
146
|
-
agentId: string | null | undefined,
|
|
147
|
-
sessionId: string | null | undefined,
|
|
148
|
-
): string | undefined {
|
|
149
|
-
const str = (v: unknown): string | undefined =>
|
|
150
|
-
typeof v === 'string' && v ? v : undefined;
|
|
151
|
-
|
|
152
|
-
const fromResult = str((rawEvent?.result as { agreementId?: unknown } | undefined)?.agreementId);
|
|
153
|
-
if (fromResult) return fromResult;
|
|
154
|
-
const fromEvent = str((rawEvent as { agreementId?: unknown } | undefined)?.agreementId);
|
|
155
|
-
if (fromEvent) return fromEvent;
|
|
156
|
-
if (typeof sessionId === 'string' && sessionId.startsWith('agr_')) return sessionId;
|
|
157
|
-
|
|
158
|
-
// Fallback: exactly one active agreement in context where this agent is the
|
|
159
|
-
// provider. Guarded to a single match so we never send the wrong agreement.
|
|
160
|
-
const ags = Array.isArray(contextSnap?.agreements)
|
|
161
|
-
? (contextSnap!.agreements as Array<Record<string, unknown>>)
|
|
162
|
-
: [];
|
|
163
|
-
const provides = ags.filter((a) => {
|
|
164
|
-
const p = (a?.parties as Record<string, unknown> | undefined) ?? a;
|
|
165
|
-
const providerIds = [p?.provider, p?.providerAgent, a?.providerId, a?.providerAgentId, a?.executorId];
|
|
166
|
-
const isProvider = providerIds.some((x) => x === agentId);
|
|
167
|
-
const status = a?.status != null ? String(a.status).toLowerCase() : '';
|
|
168
|
-
const active = !status || status === 'active' || status === 'approved';
|
|
169
|
-
return isProvider && active;
|
|
170
|
-
});
|
|
171
|
-
if (provides.length === 1) {
|
|
172
|
-
return str(provides[0].agreementId) ?? str(provides[0].id) ?? str(provides[0]._id);
|
|
173
|
-
}
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* A Ziggs agent whose brain is the Claude Agent SDK. Same host, same `eff`
|
|
179
|
-
* side channel, same tool surface as the FSM `Agent` — the ONLY difference is
|
|
180
|
-
* the loop: instead of the FSM + `runTurn`, a wake drives a headless Claude
|
|
181
|
-
* `query()` (see ZIG-784: "host is the product; the brain is how it decides").
|
|
182
|
-
*/
|
|
183
|
-
export class ClaudeSdkAgent {
|
|
184
|
-
workflow: Workflow;
|
|
185
|
-
toolManager: ToolManager;
|
|
186
|
-
private description: string;
|
|
187
|
-
private specialization: string | null;
|
|
188
|
-
private model: string;
|
|
189
|
-
private anthropicKey: string;
|
|
190
|
-
private operatorKey: string;
|
|
191
|
-
private agentId: string;
|
|
192
|
-
private maxTurns: number;
|
|
193
|
-
private queryFn: typeof query;
|
|
194
|
-
|
|
195
|
-
constructor(opts: ClaudeSdkAgentOptions) {
|
|
196
|
-
if (!opts.workflow?.states) throw new Error('ClaudeSdkAgent: workflow with states is required');
|
|
197
|
-
if (!opts.toolManager) throw new Error('ClaudeSdkAgent: toolManager is required');
|
|
198
|
-
if (!opts.anthropicKey) throw new Error('ClaudeSdkAgent: anthropicKey is required');
|
|
199
|
-
this.workflow = opts.workflow;
|
|
200
|
-
this.toolManager = opts.toolManager;
|
|
201
|
-
this.description = opts.description;
|
|
202
|
-
this.specialization = opts.specialization ?? null;
|
|
203
|
-
this.model = opts.model;
|
|
204
|
-
this.anthropicKey = opts.anthropicKey;
|
|
205
|
-
this.operatorKey = opts.operatorKey;
|
|
206
|
-
this.agentId = opts.agentId;
|
|
207
|
-
this.maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
208
|
-
this.queryFn = opts.queryFn ?? query;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async tick({ event, ctx, state, identity, eff }: TickInput): Promise<TickOutput> {
|
|
212
|
-
if (!identity?.sessionId) throw new Error('ClaudeSdkAgent.tick: identity.sessionId is required');
|
|
213
|
-
if (!eff) throw new Error('ClaudeSdkAgent.tick: eff is required');
|
|
214
|
-
|
|
215
|
-
const rawEvent = event as RawEvent | undefined;
|
|
216
|
-
const nextCtx: Ctx = bootstrapCtx(identity, ctx);
|
|
217
|
-
nextCtx.lastOutcome = outcomeFromEvent(rawEvent ?? null, identity.agentId);
|
|
218
|
-
|
|
219
|
-
const now = Date.now();
|
|
220
|
-
const recentWakes = (nextCtx.recentWakes ?? []).filter((t) => now - t < WAKE_WINDOW_MS);
|
|
221
|
-
if (recentWakes.length >= MAX_WAKES_PER_WINDOW) {
|
|
222
|
-
runtimeLog.warn(
|
|
223
|
-
'ClaudeSdkAgent',
|
|
224
|
-
`wake-rate breaker tripped: ${recentWakes.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
|
|
225
|
-
`session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`,
|
|
226
|
-
);
|
|
227
|
-
nextCtx.recentWakes = recentWakes;
|
|
228
|
-
return {
|
|
229
|
-
ctx: nextCtx,
|
|
230
|
-
state: state ?? DEFAULT_STATE,
|
|
231
|
-
parked: true,
|
|
232
|
-
stateTimeoutUnhandled: false,
|
|
233
|
-
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
nextCtx.recentWakes = [...recentWakes, now];
|
|
237
|
-
|
|
238
|
-
let contextSnap: Record<string, unknown> | null = null;
|
|
239
|
-
try {
|
|
240
|
-
contextSnap = await eff({
|
|
241
|
-
kind: 'read-context',
|
|
242
|
-
sessionId: identity.sessionId,
|
|
243
|
-
}) as Record<string, unknown>;
|
|
244
|
-
} catch (err) {
|
|
245
|
-
runtimeLog.warn('ClaudeSdkAgent', `read-context failed session=${identity.sessionId}: ${(err as Error).message}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Same guard as the FSM's runTurn: a message wake for a chat the backend
|
|
249
|
-
// no longer resolves (deleted / not visible) has no world to act in —
|
|
250
|
-
// skip the query instead of burning a wake on a fabricated empty history.
|
|
251
|
-
// Task lanes (sessionId = agreementId) also trip chatMissing and must run.
|
|
252
|
-
if (contextSnap?.chatMissing === true && nextCtx.lastOutcome?.kind === 'message-received') {
|
|
253
|
-
runtimeLog.warn(
|
|
254
|
-
'ClaudeSdkAgent',
|
|
255
|
-
`chat ${identity.sessionId} not found on backend — skipping message wake agent=${this.agentId}`,
|
|
256
|
-
);
|
|
257
|
-
return {
|
|
258
|
-
ctx: nextCtx,
|
|
259
|
-
state: state ?? DEFAULT_STATE,
|
|
260
|
-
parked: true,
|
|
261
|
-
stateTimeoutUnhandled: false,
|
|
262
|
-
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const roleBlock = [
|
|
267
|
-
`## Your role`,
|
|
268
|
-
this.description,
|
|
269
|
-
this.specialization ? `Specialization: ${this.specialization}` : '',
|
|
270
|
-
].filter(Boolean).join('\n');
|
|
271
|
-
const systemPrompt = `${CLAUDE_SDK_PROTOCOL}\n\n${roleBlock}`;
|
|
272
|
-
|
|
273
|
-
const runId = crypto.randomUUID();
|
|
274
|
-
const toolContext: ClaudeSdkToolContext = {
|
|
275
|
-
operatorKey: this.operatorKey,
|
|
276
|
-
agentId: this.agentId,
|
|
277
|
-
chatId: identity.sessionId,
|
|
278
|
-
sessionId: identity.sessionId,
|
|
279
|
-
eff,
|
|
280
|
-
senderId: rawEvent?.senderId != null ? String(rawEvent.senderId) : undefined,
|
|
281
|
-
senderType: rawEvent?.senderType != null ? String(rawEvent.senderType) : undefined,
|
|
282
|
-
// ZIG-831/832: agreement this wake acts under → lets a delegated per-user
|
|
283
|
-
// MCP call resolve the originating end-user (payer) rather than the
|
|
284
|
-
// delegating agent (senderId). Resolved from every reliable wire source,
|
|
285
|
-
// not just task_result events — see resolveOnBehalfAgreementId.
|
|
286
|
-
onBehalfOfAgreementId: resolveOnBehalfAgreementId(
|
|
287
|
-
rawEvent,
|
|
288
|
-
contextSnap,
|
|
289
|
-
this.agentId,
|
|
290
|
-
identity.sessionId,
|
|
291
|
-
),
|
|
292
|
-
runId,
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const availableTools = await this.toolManager.getAvailableTools();
|
|
296
|
-
const mcpServer = buildZiggsSdkMcpServer(availableTools, () => toolContext, {
|
|
297
|
-
name: 'ziggs',
|
|
298
|
-
instructions: 'In-process Ziggs tools backed by api-client. No external MCP process.',
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const prompt = buildClaudeEventPrompt(rawEvent, summarizeContext(contextSnap));
|
|
302
|
-
const mcpServers = { ziggs: mcpServer };
|
|
303
|
-
assertHostedAgentZiggsTools(mcpServers);
|
|
304
|
-
const queryOptions = claudeQueryOptions({
|
|
305
|
-
systemPrompt,
|
|
306
|
-
model: this.model,
|
|
307
|
-
mcpServers,
|
|
308
|
-
maxTurns: this.maxTurns,
|
|
309
|
-
apiKey: this.anthropicKey,
|
|
310
|
-
stderr: (data: string) => {
|
|
311
|
-
const line = data.trim();
|
|
312
|
-
if (line) runtimeLog.warn('ClaudeSdkAgent', `stderr session=${identity.sessionId}: ${line}`);
|
|
313
|
-
},
|
|
314
|
-
resume: nextCtx.claudeSessionId,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
let metrics = { llmCalls: 0, tokens: { total: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
|
|
318
|
-
let sessionId = nextCtx.claudeSessionId;
|
|
319
|
-
let spentInput = 0;
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
for await (const message of this.queryFn({ prompt, options: queryOptions })) {
|
|
323
|
-
if ('session_id' in message && typeof message.session_id === 'string') {
|
|
324
|
-
sessionId = message.session_id;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Surface the model's own prose so a hired Claude agent is as legible as
|
|
328
|
-
// an FSM agent (ZIG-787). Each completed assistant turn's text is recorded
|
|
329
|
-
// as a `thought` — the SAME agent-private channel the FSM uses for its
|
|
330
|
-
// reasoning (runTurn `emitThought` → record-event → recordThought), so it
|
|
331
|
-
// shows in the hired-agent view without cluttering the counterparty chat.
|
|
332
|
-
// Pure tool-use turns carry no text and record nothing.
|
|
333
|
-
//
|
|
334
|
-
// Fire-and-forget on purpose: this runs inside `for await (query())`, so
|
|
335
|
-
// awaiting an artifacts round-trip per turn would couple model-stream
|
|
336
|
-
// consumption to the artifacts backend — a slow/wedged backend would stall
|
|
337
|
-
// the whole wake, not just fail a breadcrumb. Recording is best-effort and
|
|
338
|
-
// thought ordering is not load-bearing, so we let it run in the background
|
|
339
|
-
// and swallow failures with a warn.
|
|
340
|
-
if (message.type === 'assistant') {
|
|
341
|
-
const text = extractAssistantText(message).trim();
|
|
342
|
-
if (text) {
|
|
343
|
-
void eff({ kind: 'record-event', sessionId: identity.sessionId, entry: { kind: 'thought', text } })
|
|
344
|
-
.catch((err) => runtimeLog.warn(
|
|
345
|
-
'ClaudeSdkAgent',
|
|
346
|
-
`record thought failed session=${identity.sessionId}: ${(err as Error).message}`,
|
|
347
|
-
));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const m = extractMetrics(message);
|
|
352
|
-
if (m) metrics = m;
|
|
353
|
-
if (message.type === 'result' && message.subtype !== 'success') {
|
|
354
|
-
runtimeLog.warn(
|
|
355
|
-
'ClaudeSdkAgent',
|
|
356
|
-
`query ended without success subtype=${message.subtype} session=${identity.sessionId}`,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
// Runaway guard (ZIG-759): abort before an unbounded loop drains the
|
|
360
|
-
// shared fleet key. Breaking the for-await calls the generator's return(),
|
|
361
|
-
// which terminates the underlying query.
|
|
362
|
-
spentInput += billableInput(usageOf(message));
|
|
363
|
-
if (spentInput > MAX_TASK_INPUT_TOKENS) {
|
|
364
|
-
runtimeLog.warn(
|
|
365
|
-
'ClaudeSdkAgent',
|
|
366
|
-
`input-token budget exceeded (~${spentInput} > ${MAX_TASK_INPUT_TOKENS}) session=${identity.sessionId} agent=${this.agentId} — aborting query to cap spend (ZIG-759)`,
|
|
367
|
-
);
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
} catch (err) {
|
|
372
|
-
runtimeLog.warn('ClaudeSdkAgent', `query failed session=${identity.sessionId}: ${(err as Error).message}`);
|
|
373
|
-
throw err;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (sessionId) nextCtx.claudeSessionId = sessionId;
|
|
377
|
-
|
|
378
|
-
// ZIG-816: bound resumed-transcript growth. `metrics.tokens.input` is the
|
|
379
|
-
// wake's billable input off the result message (dominated by the resumed
|
|
380
|
-
// transcript's cache reads); `spentInput` covers an aborted wake that never
|
|
381
|
-
// produced a result. Past the ceiling, forget the session so the next wake
|
|
382
|
-
// starts fresh rather than paying an ever-larger replay.
|
|
383
|
-
const wakeInput = Math.max(metrics.tokens.input, spentInput);
|
|
384
|
-
if (nextCtx.claudeSessionId && wakeInput > MAX_SESSION_RESUME_INPUT_TOKENS) {
|
|
385
|
-
runtimeLog.info(
|
|
386
|
-
'ClaudeSdkAgent',
|
|
387
|
-
`resetting Claude session (wake input ~${wakeInput} > ${MAX_SESSION_RESUME_INPUT_TOKENS}) ` +
|
|
388
|
-
`session=${identity.sessionId} agent=${this.agentId} — next wake starts fresh (ZIG-816)`,
|
|
389
|
-
);
|
|
390
|
-
nextCtx.claudeSessionId = undefined;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
ctx: nextCtx,
|
|
395
|
-
state: state ?? DEFAULT_STATE,
|
|
396
|
-
parked: true,
|
|
397
|
-
stateTimeoutUnhandled: false,
|
|
398
|
-
metrics,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
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
|
-
|
|
17
|
-
export interface ClaudeQueryOptionsParams {
|
|
18
|
-
/** Protocol + role system prompt. */
|
|
19
|
-
systemPrompt: string;
|
|
20
|
-
/** Claude model id for the brain. */
|
|
21
|
-
model: string;
|
|
22
|
-
/** In-process MCP servers exposing the agent's tools (e.g. `{ ziggs }`). */
|
|
23
|
-
mcpServers: NonNullable<Options['mcpServers']>;
|
|
24
|
-
/** Max agentic turns per wake. */
|
|
25
|
-
maxTurns: number;
|
|
26
|
-
/** Working dir for the query subprocess. Defaults to `$CLAUDE_SDK_CWD` or `/tmp`. */
|
|
27
|
-
cwd?: string;
|
|
28
|
-
/** stderr sink for the query subprocess. */
|
|
29
|
-
stderr?: (data: string) => void;
|
|
30
|
-
/** Claude session id to resume across ticks. Omitted → a fresh session. */
|
|
31
|
-
resume?: string;
|
|
32
|
-
/**
|
|
33
|
-
* Anthropic API key for this wake. Passed to the query subprocess via the
|
|
34
|
-
* `env` option (which inherits `process.env` and overrides the key) rather
|
|
35
|
-
* than mutating the parent process's global `process.env` — so agents with
|
|
36
|
-
* different keys can share one process safely.
|
|
37
|
-
*/
|
|
38
|
-
apiKey?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-6';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Pick the Claude model for a Claude-brain wake. The brain must run a `claude-*`
|
|
45
|
-
* model, but the host's default model is provider-agnostic (an OpenAI id today),
|
|
46
|
-
* so this prefers an explicit `claude-*` id (direct or via `services.llm`) and
|
|
47
|
-
* otherwise falls back to a safe Claude default — never an OpenAI id.
|
|
48
|
-
*/
|
|
49
|
-
export function resolveClaudeModel(preferred?: string, servicesModel?: string): string {
|
|
50
|
-
if (preferred?.startsWith('claude')) return preferred;
|
|
51
|
-
if (servicesModel?.startsWith('claude')) return servicesModel;
|
|
52
|
-
return DEFAULT_CLAUDE_MODEL;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Build the `query()` options for a Claude-brain wake. The lockdown fields are
|
|
57
|
-
* fixed here; callers only supply the per-wake variables (prompt, model, tools,
|
|
58
|
-
* turn budget, resume). Returns `Options` so it drops straight into `query()`.
|
|
59
|
-
*/
|
|
60
|
-
export function claudeQueryOptions(params: ClaudeQueryOptionsParams): Options {
|
|
61
|
-
return {
|
|
62
|
-
systemPrompt: params.systemPrompt,
|
|
63
|
-
model: params.model,
|
|
64
|
-
mcpServers: params.mcpServers,
|
|
65
|
-
// The agent's ONLY capabilities are its MCP tools — no built-in file/bash/web
|
|
66
|
-
// tools, no project/user/plugin MCP config, no local settings / CLAUDE.md.
|
|
67
|
-
tools: [],
|
|
68
|
-
strictMcpConfig: true,
|
|
69
|
-
settingSources: [],
|
|
70
|
-
permissionMode: 'bypassPermissions',
|
|
71
|
-
allowDangerouslySkipPermissions: true,
|
|
72
|
-
maxTurns: params.maxTurns,
|
|
73
|
-
cwd: params.cwd ?? process.env.CLAUDE_SDK_CWD ?? '/tmp',
|
|
74
|
-
// Set the key for the subprocess without touching the parent process.env.
|
|
75
|
-
// `env` replaces the subprocess environment, so spread process.env to keep
|
|
76
|
-
// PATH/HOME/etc. and override only the key.
|
|
77
|
-
...(params.apiKey ? { env: { ...process.env, ANTHROPIC_API_KEY: params.apiKey } } : {}),
|
|
78
|
-
...(params.stderr ? { stderr: params.stderr } : {}),
|
|
79
|
-
...(params.resume ? { resume: params.resume } : {}),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Concatenate the text blocks of a completed Claude assistant turn. Tool-use,
|
|
85
|
-
* thinking, and other block kinds are skipped — only the model's user-facing
|
|
86
|
-
* prose is returned (empty string for a pure tool-use turn). This is what a
|
|
87
|
-
* Claude-brain wake records as a `thought` per turn (ZIG-787), the same way
|
|
88
|
-
* the FSM's `emitThought` surfaces the model's reasoning.
|
|
89
|
-
*/
|
|
90
|
-
export function extractAssistantText(message: SDKAssistantMessage): string {
|
|
91
|
-
const content = message.message?.content;
|
|
92
|
-
if (!Array.isArray(content)) return '';
|
|
93
|
-
let out = '';
|
|
94
|
-
for (const block of content) {
|
|
95
|
-
if (block && (block as { type?: string }).type === 'text') {
|
|
96
|
-
out += (block as { text?: string }).text ?? '';
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return out;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** One-line digest of a context snapshot for the wake prompt. */
|
|
103
|
-
export function summarizeContext(snap: Record<string, unknown> | null | undefined): string {
|
|
104
|
-
if (!snap) return '';
|
|
105
|
-
const agreements = Array.isArray(snap.agreements) ? snap.agreements.length : 0;
|
|
106
|
-
const history = Array.isArray(snap.history) ? snap.history.length : 0;
|
|
107
|
-
const users = Array.isArray(snap.users) ? snap.users.length : 0;
|
|
108
|
-
return `history=${history} messages, agreements=${agreements}, users=${users}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Turn a Ziggs wake event + context digest into the user prompt that starts a
|
|
113
|
-
* Claude-brain turn. The agent's standing instructions come from its system
|
|
114
|
-
* prompt (protocol + role); this is just "here's what woke you, go handle it."
|
|
115
|
-
*/
|
|
116
|
-
export function buildClaudeEventPrompt(event: RawEvent | undefined, contextSummary: string): string {
|
|
117
|
-
const lines: string[] = ['You received a new Ziggs event. Handle it per your protocol.'];
|
|
118
|
-
if (event?.type) lines.push(`Event type: ${event.type}`);
|
|
119
|
-
if (event?.text) lines.push(`Message: ${event.text}`);
|
|
120
|
-
if (event?.senderId) lines.push(`Sender: ${event.senderId}`);
|
|
121
|
-
if (event?.operation) lines.push(`Operation: ${event.operation}`);
|
|
122
|
-
// ZIG-832: surface the task/agreement this event carries. Without it the brain
|
|
123
|
-
// can't tell WHICH task to update/complete (workers otherwise stall asking for
|
|
124
|
-
// their own taskId) and can't see whether the referenced step already ran or
|
|
125
|
-
// failed — which makes an orchestrator re-spawn duplicate delegations.
|
|
126
|
-
const result = event?.result as
|
|
127
|
-
| { taskId?: unknown; agreementId?: unknown; state?: unknown }
|
|
128
|
-
| undefined;
|
|
129
|
-
if (result && typeof result === 'object') {
|
|
130
|
-
if (typeof result.taskId === 'string') lines.push(`Task: ${result.taskId}`);
|
|
131
|
-
if (typeof result.agreementId === 'string') lines.push(`Agreement: ${result.agreementId}`);
|
|
132
|
-
if (typeof result.state === 'string') lines.push(`Task state: ${result.state}`);
|
|
133
|
-
}
|
|
134
|
-
if (contextSummary) {
|
|
135
|
-
lines.push('', 'Session context snapshot:', contextSummary);
|
|
136
|
-
}
|
|
137
|
-
lines.push('', 'Use ziggs_context_snapshot if you need more context. If the event needs nothing from you, say so briefly and stop.');
|
|
138
|
-
return lines.join('\n');
|
|
139
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { defineTool, type ToolDefinition } from '../tools/defineTool.js';
|
|
3
|
-
import type { EffectHandler } from '../types.js';
|
|
4
|
-
|
|
5
|
-
export interface ClaudeSdkToolContext extends Record<string, unknown> {
|
|
6
|
-
operatorKey: string;
|
|
7
|
-
agentId: string;
|
|
8
|
-
chatId?: string | null;
|
|
9
|
-
sessionId?: string | null;
|
|
10
|
-
eff?: EffectHandler;
|
|
11
|
-
/** Human/agent whose message woke the agent — forwarded to the shared tool executor (ZIG-775). */
|
|
12
|
-
senderId?: string;
|
|
13
|
-
senderType?: string;
|
|
14
|
-
/** Agreement this wake acts under (ZIG-831) — lets a delegated per-user MCP call resolve the originating end-user. */
|
|
15
|
-
onBehalfOfAgreementId?: string;
|
|
16
|
-
/** Per-wake id grouping this tick's tool calls in telemetry (ZIG-775). */
|
|
17
|
-
runId?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Named distinctly from ziggs-mcp's per-type `ziggs_read_context`: this is the
|
|
21
|
-
// one-shot aggregate snapshot (grant-fenced server-side), not a cursored read.
|
|
22
|
-
export const ziggsContextSnapshotTool: ToolDefinition = defineTool(
|
|
23
|
-
'ziggs_context_snapshot',
|
|
24
|
-
{
|
|
25
|
-
chatId: { type: 'string', description: 'Chat/session id. Defaults to the current lane session.' },
|
|
26
|
-
maxMessages: { type: 'number', description: 'Optional message history cap.' },
|
|
27
|
-
},
|
|
28
|
-
async (args, ctx) => {
|
|
29
|
-
const c = ctx as ClaudeSdkToolContext;
|
|
30
|
-
if (!c.eff) throw new Error('ziggs_context_snapshot requires eff in tool context');
|
|
31
|
-
const sessionId = (args.chatId as string | undefined) ?? c.chatId ?? c.sessionId;
|
|
32
|
-
if (!sessionId) throw new Error('ziggs_context_snapshot: chatId is required');
|
|
33
|
-
return c.eff({
|
|
34
|
-
kind: 'read-context',
|
|
35
|
-
sessionId,
|
|
36
|
-
opts: args.maxMessages != null ? { maxMessages: Number(args.maxMessages) } : undefined,
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
{ description: 'Read the chat snapshot: history, agreements, agents, and users for a session.' },
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
export const ziggsSendMessageTool: ToolDefinition = defineTool(
|
|
43
|
-
'ziggs_send_message',
|
|
44
|
-
{
|
|
45
|
-
chatId: { type: 'string', description: 'Chat id. Defaults to current session.' },
|
|
46
|
-
receiverId: { type: 'string', description: 'Recipient user or agent id. Defaults to the sender of the event that woke you — omit when replying to them.' },
|
|
47
|
-
text: { type: 'string', required: true },
|
|
48
|
-
idempotencyKey: { type: 'string', description: 'Reuse for retry-safe resend of the same logical message.' },
|
|
49
|
-
},
|
|
50
|
-
async (args, ctx) => {
|
|
51
|
-
const c = ctx as ClaudeSdkToolContext;
|
|
52
|
-
if (!c.eff) throw new Error('ziggs_send_message requires eff in tool context');
|
|
53
|
-
const sessionId = (args.chatId as string | undefined) ?? c.chatId ?? c.sessionId;
|
|
54
|
-
if (!sessionId) throw new Error('ziggs_send_message: chatId is required');
|
|
55
|
-
// Replying to whoever woke you is the overwhelmingly common case; making
|
|
56
|
-
// the model spell it out cost a failed tool call + a full extra LLM turn
|
|
57
|
-
// per reply. Batch/timer wakes carry no sender — those still require an
|
|
58
|
-
// explicit receiverId.
|
|
59
|
-
const receiverId = (args.receiverId as string | undefined) ?? c.senderId;
|
|
60
|
-
if (!receiverId) throw new Error('ziggs_send_message: receiverId is required (this wake has no sender to default to)');
|
|
61
|
-
const messageId = args.idempotencyKey
|
|
62
|
-
? `sdk_idem_${String(args.idempotencyKey)}`
|
|
63
|
-
: `sdk_${randomUUID()}`;
|
|
64
|
-
return c.eff({
|
|
65
|
-
kind: 'send-message',
|
|
66
|
-
sessionId,
|
|
67
|
-
receiverId,
|
|
68
|
-
text: String(args.text),
|
|
69
|
-
messageId,
|
|
70
|
-
});
|
|
71
|
-
},
|
|
72
|
-
{ description: 'Send a user-visible chat message. Conversation only — never use for finished-work reporting.' },
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// Inbox read/ack are deliberately NOT here: the host owns the pull/watermark
|
|
76
|
-
// cycle (InboxCatchUp on connect). A brain-side ack raced the host's and the
|
|
77
|
-
// mandatory per-wake inbox poll fed the tool-operation echo loop (ZIG-759).
|
|
78
|
-
export const CLAUDE_SDK_TOOLS: ToolDefinition[] = [
|
|
79
|
-
ziggsContextSnapshotTool,
|
|
80
|
-
ziggsSendMessageTool,
|
|
81
|
-
];
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hosted fleet agents connect via api-client (AgentHost), never inbound ziggs-mcp.
|
|
3
|
-
* @see ZIG-781
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type McpServerSpec = {
|
|
7
|
-
type?: string;
|
|
8
|
-
command?: string;
|
|
9
|
-
args?: string[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function looksLikeInboundZiggsMcp(command: string, args: string[] = []): boolean {
|
|
13
|
-
const joined = `${command} ${args.join(' ')}`;
|
|
14
|
-
return /ziggs-mcp|@ziggs-ai\/ziggs-mcp/i.test(joined);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Reject wiring a hosted agent to the inbound Ziggs MCP stdio surface.
|
|
19
|
-
* Claude SDK agents must use in-process ToolManager tools (createSdkMcpServer).
|
|
20
|
-
*/
|
|
21
|
-
export function assertHostedAgentZiggsTools(
|
|
22
|
-
mcpServers: Record<string, McpServerSpec | unknown> | undefined,
|
|
23
|
-
): void {
|
|
24
|
-
if (!mcpServers) return;
|
|
25
|
-
for (const [name, raw] of Object.entries(mcpServers)) {
|
|
26
|
-
if (!raw || typeof raw !== 'object') continue;
|
|
27
|
-
const spec = raw as McpServerSpec;
|
|
28
|
-
if (spec.type === 'stdio') {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`Hosted Ziggs agents must not use stdio MCP (server "${name}"). ` +
|
|
31
|
-
'Connect via api-client / AgentHost in-process tools.',
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
if (looksLikeInboundZiggsMcp(String(spec.command ?? ''), spec.args ?? [])) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Hosted Ziggs agents must not spawn ziggs-mcp (inbound MCP server "${name}"). ` +
|
|
37
|
-
'Use AgentHost + in-process tools.',
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|