@ziggs-ai/agent-sdk 0.10.8 → 0.11.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 +11 -11
- package/dist/AgentHost.d.ts +15 -15
- package/dist/AgentHost.js +22 -22
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/adapters/McpAdapter.d.ts +10 -10
- package/dist/adapters/McpAdapter.js +33 -21
- package/dist/adapters/OpenAIAdapter.js +1 -1
- package/dist/agent/Agent.d.ts +2 -2
- package/dist/agent/ClaudeSdkAgent.d.ts +6 -6
- package/dist/agent/ClaudeSdkAgent.js +25 -25
- package/dist/agent/claudeCognition.d.ts +3 -3
- package/dist/agent/claudeCognition.js +7 -7
- package/dist/agent/claudeSdkTools.d.ts +3 -3
- package/dist/agent/claudeSdkTools.js +9 -9
- package/dist/agent/hostedAgentConnection.d.ts +1 -1
- package/dist/agent/hostedAgentConnection.js +1 -1
- package/dist/agent/toolManagerToSdkMcp.d.ts +1 -1
- package/dist/agent/toolManagerToSdkMcp.js +2 -2
- package/dist/context/applyEffects.js +1 -1
- package/dist/defineAgent.js +2 -2
- package/dist/formatters/HistoryFormatter.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/ingress/normalizeIncoming.d.ts +1 -1
- package/dist/ingress/normalizeIncoming.js +7 -7
- package/dist/pricing/fleetDefaults.d.ts +3 -3
- package/dist/pricing/fleetDefaults.js +3 -3
- package/dist/pricing/fleetEvalFree.d.ts +1 -1
- package/dist/pricing/fleetEvalFree.js +1 -1
- package/dist/pricing/fleetFreeTierA.gen.d.ts +1 -1
- package/dist/pricing/fleetFreeTierA.gen.js +1 -1
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
- package/dist/runtime/AgentMachine.d.ts +1 -1
- package/dist/runtime/AgentMachine.js +8 -8
- package/dist/runtime/PromptBuilder.d.ts +2 -2
- package/dist/runtime/PromptBuilder.js +3 -3
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +2 -2
- package/dist/runtime/buildOutcome.d.ts +1 -1
- package/dist/runtime/buildOutcome.js +4 -4
- package/dist/runtime/defaults.d.ts +5 -5
- package/dist/runtime/defaults.js +4 -4
- package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.js +3 -3
- package/dist/runtime/kickoff.d.ts +3 -3
- package/dist/runtime/kickoff.js +3 -3
- package/dist/runtime/runMechanical.d.ts +1 -1
- package/dist/runtime/runMechanical.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +10 -10
- package/dist/runtime/taskBookkeeping.d.ts +3 -3
- package/dist/runtime/taskBookkeeping.js +3 -3
- package/dist/runtime/taskIntake.js +2 -2
- package/dist/server/ConnectionPool.js +1 -1
- package/dist/server/EventQueue.d.ts +1 -1
- package/dist/server/EventQueue.js +1 -1
- package/dist/server/SeenMessages.d.ts +2 -2
- package/dist/server/SeenMessages.js +2 -2
- package/dist/server/ZiggsEffectHandler.d.ts +2 -2
- package/dist/server/ZiggsEffectHandler.js +8 -8
- package/dist/server/agreements/AgreementService.d.ts +3 -8
- package/dist/server/agreements/AgreementService.js +4 -7
- package/dist/server/inboxLoop.d.ts +5 -5
- package/dist/server/inboxLoop.js +21 -10
- package/dist/server/questMatch.d.ts +1 -1
- package/dist/server/questMatch.js +1 -1
- package/dist/server/runLauncher.js +3 -3
- package/dist/server/tools/index.d.ts +1 -0
- package/dist/server/tools/index.js +7 -6
- package/dist/server/tools/tier1/protocolRunner.d.ts +1 -1
- package/dist/server/tools/tier1/protocolRunner.js +19 -19
- package/dist/server/tools/tier1/protocolTools.js +4 -4
- package/dist/server/tools/tier2/artifactTools.js +2 -2
- package/dist/server/tools/tier2/chatTools.js +2 -2
- package/dist/server/tools/tier2/connectionTools.js +3 -3
- package/dist/server/tools/tier2/contextTools.js +1 -1
- package/dist/server/tools/tier2/discoveryTools.js +2 -2
- package/dist/server/tools/tier2/grantsTools.js +1 -1
- package/dist/server/tools/tier2/linkTools.js +3 -3
- package/dist/server/tools/tier2/marketplaceTools.js +1 -1
- package/dist/server/tools/tier2/mcpConnectionTools.d.ts +4 -0
- package/dist/server/tools/tier2/mcpConnectionTools.js +136 -0
- package/dist/server/tools/tier2/paymentTools.js +1 -1
- package/dist/shared/operatorKeyClaims.d.ts +2 -2
- package/dist/shared/operatorKeyClaims.js +1 -1
- package/dist/tasks/protocolRegistry.d.ts +1 -1
- package/dist/tasks/protocolRegistry.js +1 -1
- package/dist/tools/ToolManager.js +1 -1
- package/dist/tools/capabilityAdapter.d.ts +1 -1
- package/dist/tools/capabilityAdapter.js +3 -3
- package/dist/tools/defineTool.js +1 -1
- package/dist/tools/recordReport.d.ts +3 -3
- package/dist/tools/recordReport.js +3 -3
- package/dist/types.d.ts +17 -17
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
### Removed (breaking)
|
|
6
6
|
|
|
7
|
-
- Removed exports `createStaleBacklogGate`, `resolveReplayPolicy`, `newestActionableAt`, `parseReplayMaxAge`. The launcher rewrite deleted the wake machinery these gated: every agent stays connected and the write signals it, so there is no backlog to age out and nothing to replay-gate
|
|
7
|
+
- Removed exports `createStaleBacklogGate`, `resolveReplayPolicy`, `newestActionableAt`, `parseReplayMaxAge`. The launcher rewrite deleted the wake machinery these gated: every agent stays connected and the write signals it, so there is no backlog to age out and nothing to replay-gate. Pinned to 0.4.x and importing any of the four? Delete the call — the behaviour it bought is now unconditional. There is no replacement API.
|
|
8
8
|
- `FLEET_REPLAY_MAX_AGE` is no longer read; the replay window it set no longer exists. Nothing in this repo sets it, but it can come out of any deployment env that does.
|
|
9
|
-
- `Ctx` no longer carries `pendingSubtasks`, `collectedResults`, or `delegatedAgreementIds`.
|
|
10
|
-
- `WireResult.status` removed.
|
|
9
|
+
- `Ctx` no longer carries `pendingSubtasks`, `collectedResults`, or `delegatedAgreementIds`. The real bookkeeping moved into `Ctx.tasks` (keyed by taskId) and these mirrors stopped syncing, but they stayed on the interface as required-and-always-empty — so a workflow guard reading one saw empty forever and silently checked nothing. Read via `allPendingSubtasks(ctx)` / `allCollectedResults(ctx)` / `allDelegatedAgreementIds(ctx)`. Sessions persisted before `tasks` existed are still migrated on restore.
|
|
10
|
+
- `WireResult.status` removed. The wire carries `state` only; the `|| r.status` fallback tails were removed so a backend regression fails loudly instead of being absorbed. Task state resolves as `task?.state || r.state`.
|
|
11
11
|
|
|
12
12
|
### Added
|
|
13
13
|
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
- Attention gate: only the addressed agent spends a turn. A message addressed elsewhere in a shared room no longer wakes every participant.
|
|
19
19
|
- No turn on a lane whose agreement has ended, and none on the echo of a task that already reported — both previously cost a turn and produced nothing.
|
|
20
20
|
- Persona wire shapes are understood on inbound frames.
|
|
21
|
-
- A wake declares its lane so the backend can fence reach
|
|
22
|
-
- A proposal awaiting the agent's own consent counts as actionable rather than a notice to park
|
|
23
|
-
- Artifact idempotency keys are derived from content
|
|
24
|
-
- Blank tool arguments fall back to the wake's lane
|
|
21
|
+
- A wake declares its lane so the backend can fence reach.
|
|
22
|
+
- A proposal awaiting the agent's own consent counts as actionable rather than a notice to park.
|
|
23
|
+
- Artifact idempotency keys are derived from content.
|
|
24
|
+
- Blank tool arguments fall back to the wake's lane.
|
|
25
25
|
|
|
26
26
|
## 0.9.0 — 2026-07-30
|
|
27
27
|
|
|
28
28
|
### Changed (breaking)
|
|
29
29
|
|
|
30
|
-
- `task-update` / `task_set_result` accept `state` only — legacy `status` alias removed
|
|
30
|
+
- `task-update` / `task_set_result` accept `state` only — legacy `status` alias removed.
|
|
31
31
|
- Removed export `syncAggregateTaskViews`; read pending/collected/delegated via `allPendingSubtasks` / `allCollectedResults` / `allDelegatedAgreementIds`. Ctx aggregate mirrors stay empty.
|
|
32
32
|
|
|
33
33
|
## 0.4.0 — 2026-07-23
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
|
|
39
39
|
### Changed (breaking)
|
|
40
40
|
|
|
41
|
-
- Removed legacy socket-frame aliases; wire frames use canonical names only
|
|
42
|
-
- Delivery-law: classify inbound messages from the `service` wire shape
|
|
41
|
+
- Removed legacy socket-frame aliases; wire frames use canonical names only.
|
|
42
|
+
- Delivery-law: classify inbound messages from the `service` wire shape.
|
|
43
43
|
- Bumped `@ziggs-ai/api-client` to `^0.3.0`.
|
|
44
44
|
|
|
45
45
|
## 0.3.0 — 2026-07-22
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
## 0.2.0 — 2026-07-16
|
|
52
52
|
|
|
53
|
-
### Added
|
|
53
|
+
### Added
|
|
54
54
|
|
|
55
55
|
- Public brain selection API: `ziggsBrain()`, `claudeBrain()`, and `Brain` interface (`tick()`).
|
|
56
56
|
- `AgentHost` / `createAgent` accept `brain` — preferred over legacy `cognition: 'fsm' | 'claude-sdk'`.
|
package/dist/AgentHost.d.ts
CHANGED
|
@@ -41,13 +41,13 @@ export declare const DEFAULT_MODEL = "claude-haiku-4-5";
|
|
|
41
41
|
*/
|
|
42
42
|
export type SessionResolver = (normalized: NormalizedEvent, ownAgentId: string) => string | null;
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* one lane key per unit of work, derived the same way as inbox wakes.
|
|
45
45
|
*
|
|
46
46
|
* Inbox deliveries key on `chatId` (DEFAULT_SESSION_RESOLVER). Bootstrap used to
|
|
47
47
|
* prefer bare `agreementId`, which opened a second lane for the same task and
|
|
48
48
|
* made the snapshot read treat the agreement id as a chat (`chatMissing: true`,
|
|
49
49
|
* empty context). Prefer the origin chat when present; otherwise the agreement's
|
|
50
|
-
* notification chat (`agrn-<agreementId>`), which is always linked after
|
|
50
|
+
* notification chat (`agrn-<agreementId>`), which is always linked after.
|
|
51
51
|
*/
|
|
52
52
|
export declare function laneSessionIdForTask(task: {
|
|
53
53
|
originChatId?: string | null;
|
|
@@ -72,11 +72,11 @@ export interface AgentHostOptions {
|
|
|
72
72
|
name?: string | null;
|
|
73
73
|
/** Store-facing one-liner. See DefineAgentInput.description. */
|
|
74
74
|
description?: string;
|
|
75
|
-
/** Claude-brain system prompt
|
|
75
|
+
/** Claude-brain system prompt; falls back to `description`. */
|
|
76
76
|
systemPrompt?: string;
|
|
77
77
|
specialization?: string | null;
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* strings compared exactly against a quest's `match` field.
|
|
80
80
|
* Miss → ack with zero LLM. Match → normal wake. `specialization` is also
|
|
81
81
|
* checked when set.
|
|
82
82
|
*/
|
|
@@ -96,7 +96,7 @@ export interface AgentHostOptions {
|
|
|
96
96
|
/** Max agentic turns per Claude SDK wake. Default 30. */
|
|
97
97
|
claudeMaxTurns?: number;
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
99
|
+
* claude-sdk agents resume their transcript across wakes by default,
|
|
100
100
|
* so per-wake input grows monotonically on a long lane. When true, each wake
|
|
101
101
|
* starts a fresh Claude session — the protocol is backend-authoritative (the
|
|
102
102
|
* brain re-reads via context_snapshot / the wake event), so the
|
|
@@ -125,14 +125,14 @@ export interface AgentHostOptions {
|
|
|
125
125
|
sessionStore?: SessionStore;
|
|
126
126
|
/** Observability tap: called after every effect with the effect and its response. */
|
|
127
127
|
effectTap?: (effect: import('./types.js').Effect, response: unknown) => void;
|
|
128
|
-
/** Observability tap: called on every FSM state transition, no-match, depth-guard, or error-escape
|
|
128
|
+
/** Observability tap: called on every FSM state transition, no-match, depth-guard, or error-escape. */
|
|
129
129
|
transitionTap?: (event: import('./runtime/AgentMachine.js').TransitionEvent) => void;
|
|
130
130
|
/**
|
|
131
131
|
* Usage/cost tap: called once per settled tick with the tick's authoritative
|
|
132
132
|
* token usage (result.metrics). This is the SINGLE source of truth for
|
|
133
133
|
* usage/cost across every cognition — the FSM (llm-call + streamed reply +
|
|
134
134
|
* tool-internal LLM) and the Claude brain (incl. cache tokens) both funnel
|
|
135
|
-
* their spend here
|
|
135
|
+
* their spend here. effectTap / tool generations are structural
|
|
136
136
|
* tracing only and must NOT also carry billable usage, or cost double-counts.
|
|
137
137
|
*/
|
|
138
138
|
metricsTap?: (info: {
|
|
@@ -142,10 +142,10 @@ export interface AgentHostOptions {
|
|
|
142
142
|
cognition: string;
|
|
143
143
|
metrics: import('./agent/Agent.js').TickOutput['metrics'];
|
|
144
144
|
}) => void;
|
|
145
|
-
/** Production monitoring ingest
|
|
145
|
+
/** Production monitoring ingest. Default on; set false to disable. */
|
|
146
146
|
telemetryIngest?: boolean;
|
|
147
147
|
/**
|
|
148
|
-
* Pull inbox on every WS connect/reconnect
|
|
148
|
+
* Pull inbox on every WS connect/reconnect. Default true.
|
|
149
149
|
* Recovers messages lost when backend in-memory pending queues are dropped.
|
|
150
150
|
*/
|
|
151
151
|
inboxCatchUp?: boolean;
|
|
@@ -167,7 +167,7 @@ export declare function warnOnShadowedPlatformTools(platformTools: ToolDefinitio
|
|
|
167
167
|
* a transport (Ziggs WebSocket), and the lane router (per-session FIFO queue
|
|
168
168
|
* + in-memory session state).
|
|
169
169
|
*
|
|
170
|
-
* transport (WS) → handleMessage → agent.tick(
|
|
170
|
+
* transport (WS) → handleMessage → agent.tick(.. , eff) → ..
|
|
171
171
|
*/
|
|
172
172
|
export declare class AgentHost {
|
|
173
173
|
options: Required<Pick<AgentHostOptions, 'agentId' | 'operatorKey' | 'openaiKey'>> & AgentHostOptions;
|
|
@@ -208,7 +208,7 @@ export declare class AgentHost {
|
|
|
208
208
|
/** Wire entry. Normalize → resolve session → enqueue on the right lane. */
|
|
209
209
|
handleMessage(text: string, metadata?: Record<string, unknown>): Promise<void>;
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
211
|
+
* part 3 — an agreement lane outlives the agreement.
|
|
212
212
|
*
|
|
213
213
|
* A lane named `agrn-<agreementId>` keeps its Claude session for the whole
|
|
214
214
|
* engagement, so a trailing event after the deal ends pays for all of it: on
|
|
@@ -226,11 +226,11 @@ export declare class AgentHost {
|
|
|
226
226
|
*/
|
|
227
227
|
private laneAgreementIsTerminal;
|
|
228
228
|
/**
|
|
229
|
-
*
|
|
229
|
+
* the trailing turn on work that is already reported.
|
|
230
230
|
*
|
|
231
231
|
* A lane session carries the whole engagement, so the LAST wake is the most
|
|
232
232
|
* expensive one: on prod, the frame that followed a completed elicitation cost
|
|
233
|
-
* **158,733 tokens** with the session's input at 366,417 before
|
|
233
|
+
* **158,733 tokens** with the session's input at 366,417 before reset
|
|
234
234
|
* it, and produced nothing — the spec had been written and the task closed one
|
|
235
235
|
* frame earlier.
|
|
236
236
|
*
|
|
@@ -251,12 +251,12 @@ export declare class AgentHost {
|
|
|
251
251
|
private _resolveOwnAgentId;
|
|
252
252
|
connectAsync(_timeout?: number): Promise<this>;
|
|
253
253
|
/**
|
|
254
|
-
*
|
|
254
|
+
* plain string compare against {@link AgentHostOptions.questMatchTags}
|
|
255
255
|
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
256
256
|
*/
|
|
257
257
|
private triageQuestDelivery;
|
|
258
258
|
private _bootstrapAssignedActiveTasks;
|
|
259
|
-
/**
|
|
259
|
+
/** replay task-assigned for an existing active task (demo recovery). */
|
|
260
260
|
wakeTaskAssignment(taskId: string): Promise<void>;
|
|
261
261
|
disconnect(): void;
|
|
262
262
|
/** Waits for all in-flight ticks to complete. Call before process.exit() on SIGTERM. */
|
package/dist/AgentHost.js
CHANGED
|
@@ -40,13 +40,13 @@ async function loadConnectionPool() {
|
|
|
40
40
|
}
|
|
41
41
|
const DEFAULT_SESSION_RESOLVER = (normalized) => normalized.chatId;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* one lane key per unit of work, derived the same way as inbox wakes.
|
|
44
44
|
*
|
|
45
45
|
* Inbox deliveries key on `chatId` (DEFAULT_SESSION_RESOLVER). Bootstrap used to
|
|
46
46
|
* prefer bare `agreementId`, which opened a second lane for the same task and
|
|
47
47
|
* made the snapshot read treat the agreement id as a chat (`chatMissing: true`,
|
|
48
48
|
* empty context). Prefer the origin chat when present; otherwise the agreement's
|
|
49
|
-
* notification chat (`agrn-<agreementId>`), which is always linked after
|
|
49
|
+
* notification chat (`agrn-<agreementId>`), which is always linked after.
|
|
50
50
|
*/
|
|
51
51
|
export function laneSessionIdForTask(task) {
|
|
52
52
|
if (typeof task.originChatId === 'string' && task.originChatId) {
|
|
@@ -69,13 +69,13 @@ function createToolManager(options) {
|
|
|
69
69
|
taskTools = PROTOCOL_TOOLS; // 'all' or undefined
|
|
70
70
|
const claudeSdkTools = isClaudeCognition(options.cognition) ? CLAUDE_SDK_TOOLS : [];
|
|
71
71
|
const ownTools = Array.isArray(userTools) ? userTools : [];
|
|
72
|
-
//
|
|
72
|
+
// browse rides wherever claim rides. Posted-first engagements
|
|
73
73
|
// START at marketplace_view (find the listing), so an agent that can
|
|
74
74
|
// agreement_claim but cannot browse holds a default pathway it cannot begin.
|
|
75
75
|
// Both directions: shedding claim (taskTools array without it) sheds browse.
|
|
76
76
|
const canClaim = taskTools.some((t) => t.schema.function.name === 'agreement_claim');
|
|
77
77
|
const browseTools = canClaim ? [marketplaceViewTool] : [];
|
|
78
|
-
//
|
|
78
|
+
// chat_open is always-on so claim/provider kickoff (FSM) and the
|
|
79
79
|
// Claude always-on set can open the buyer chat after a store claim. CHAT_TOOLS
|
|
80
80
|
// is also folded into CLAUDE_SDK_TOOLS; Map registration makes duplicates a no-op.
|
|
81
81
|
const platformTools = [...taskTools, ...CHAT_TOOLS, ...browseTools, ...claudeSdkTools];
|
|
@@ -128,7 +128,7 @@ function createBackendServices(options) {
|
|
|
128
128
|
const agentId = options.agentId;
|
|
129
129
|
const artifactsClient = new ArtifactsClient(operatorKey, agentId);
|
|
130
130
|
const contextReadClient = new ContextReadClient(operatorKey, agentId);
|
|
131
|
-
//
|
|
131
|
+
// the role text is the prompt when the author split the two; the
|
|
132
132
|
// store-facing `description` is only the fallback for agents that never did.
|
|
133
133
|
const promptBuilder = new PromptBuilder({
|
|
134
134
|
description: options.systemPrompt || options.description,
|
|
@@ -153,7 +153,7 @@ function createBackendServices(options) {
|
|
|
153
153
|
* a transport (Ziggs WebSocket), and the lane router (per-session FIFO queue
|
|
154
154
|
* + in-memory session state).
|
|
155
155
|
*
|
|
156
|
-
* transport (WS) → handleMessage → agent.tick(
|
|
156
|
+
* transport (WS) → handleMessage → agent.tick(.. , eff) → ..
|
|
157
157
|
*/
|
|
158
158
|
export class AgentHost {
|
|
159
159
|
options;
|
|
@@ -210,7 +210,7 @@ export class AgentHost {
|
|
|
210
210
|
this.sessionResolver = options.sessionResolver || DEFAULT_SESSION_RESOLVER;
|
|
211
211
|
// A Claude-brain wake legitimately runs long (multi-turn query, prose
|
|
212
212
|
// deliverables) — 120s watchdogged real spec-writing wakes mid-flight,
|
|
213
|
-
// which then redelivered and re-ran (
|
|
213
|
+
// which then redelivered and re-ran (dogfood, 2026-07-26). FSM
|
|
214
214
|
// turns stay on the tight default.
|
|
215
215
|
this.tickTimeoutMs =
|
|
216
216
|
options.tickTimeoutMs ??
|
|
@@ -319,7 +319,7 @@ export class AgentHost {
|
|
|
319
319
|
agentId: this.options.agentId,
|
|
320
320
|
// No taskService/agreementService: Claude-brain tools execute through
|
|
321
321
|
// eff({ kind: 'tool-call' }) → the shared executor, which sources those
|
|
322
|
-
// services from the effect handler's deps like the FSM path
|
|
322
|
+
// services from the effect handler's deps like the FSM path.
|
|
323
323
|
maxTurns: this.options.claudeMaxTurns,
|
|
324
324
|
statelessPerWake: this.options.claudeStatelessPerWake,
|
|
325
325
|
})
|
|
@@ -331,7 +331,7 @@ export class AgentHost {
|
|
|
331
331
|
this.eventQueue = new EventQueue((event, laneKey) => this._processEvent(event, laneKey));
|
|
332
332
|
// A timer frame is not a delivery: there is no watermark waiting on it and
|
|
333
333
|
// nothing to redeliver, so its failure is logged and dropped here rather
|
|
334
|
-
// than left as an unhandled rejection (
|
|
334
|
+
// than left as an unhandled rejection (made enqueue reject).
|
|
335
335
|
this.stateTimers = new StateTimers((ev, laneKey) => {
|
|
336
336
|
void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
|
|
337
337
|
runtimeLog.warn('AgentHost', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
@@ -366,7 +366,7 @@ export class AgentHost {
|
|
|
366
366
|
event: { type: 'message', timestamp: Date.now(), text: '' },
|
|
367
367
|
};
|
|
368
368
|
let sessionId = this.sessionResolver(fakeNormalized, ownAgentId);
|
|
369
|
-
//
|
|
369
|
+
// store claims deliver agreement wakes with no chatId. Do not
|
|
370
370
|
// drop them; lane by agreement the same way task bootstrap does
|
|
371
371
|
// (`agrn-<agreementId>`), never a bare agreement id.
|
|
372
372
|
if (!sessionId) {
|
|
@@ -415,7 +415,7 @@ export class AgentHost {
|
|
|
415
415
|
}
|
|
416
416
|
let enrichedText = typeof text === 'string' ? text : '';
|
|
417
417
|
if (!enrichedText.trim()) {
|
|
418
|
-
//
|
|
418
|
+
// agreement rides under `service` on the inbox-pull wire; fall
|
|
419
419
|
// back to top-level for hand-built metadata.
|
|
420
420
|
const svc = metadata.service;
|
|
421
421
|
const agreement = (svc?.agreement ?? metadata.agreement);
|
|
@@ -441,7 +441,7 @@ export class AgentHost {
|
|
|
441
441
|
// routes the event onto its lane. No live-path watermark ack — there is no
|
|
442
442
|
// separate live/push channel to reconcile against anymore.
|
|
443
443
|
//
|
|
444
|
-
//
|
|
444
|
+
// a failure un-marks the messageId. The mark above is what stops
|
|
445
445
|
// the same message being processed twice; leaving it set on a failed wake
|
|
446
446
|
// would make the redelivery this rejection buys us a no-op.
|
|
447
447
|
try {
|
|
@@ -454,7 +454,7 @@ export class AgentHost {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
/**
|
|
457
|
-
*
|
|
457
|
+
* part 3 — an agreement lane outlives the agreement.
|
|
458
458
|
*
|
|
459
459
|
* A lane named `agrn-<agreementId>` keeps its Claude session for the whole
|
|
460
460
|
* engagement, so a trailing event after the deal ends pays for all of it: on
|
|
@@ -486,11 +486,11 @@ export class AgentHost {
|
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
/**
|
|
489
|
-
*
|
|
489
|
+
* the trailing turn on work that is already reported.
|
|
490
490
|
*
|
|
491
491
|
* A lane session carries the whole engagement, so the LAST wake is the most
|
|
492
492
|
* expensive one: on prod, the frame that followed a completed elicitation cost
|
|
493
|
-
* **158,733 tokens** with the session's input at 366,417 before
|
|
493
|
+
* **158,733 tokens** with the session's input at 366,417 before reset
|
|
494
494
|
* it, and produced nothing — the spec had been written and the task closed one
|
|
495
495
|
* frame earlier.
|
|
496
496
|
*
|
|
@@ -589,7 +589,7 @@ export class AgentHost {
|
|
|
589
589
|
runtimeLog.info('AgentHost', `wake agent=${identity.agentId} lane=${laneKey} llmCalls=${m.llmCalls} ` +
|
|
590
590
|
`tokens_in=${m.tokens.input} tokens_out=${m.tokens.output} total=${m.tokens.total}`);
|
|
591
591
|
}
|
|
592
|
-
// Single source of truth for usage/cost
|
|
592
|
+
// Single source of truth for usage/cost: emit the tick's
|
|
593
593
|
// authoritative token metrics once, for every cognition. Never let an
|
|
594
594
|
// observability failure break the tick.
|
|
595
595
|
if (this.options.metricsTap && result.metrics) {
|
|
@@ -624,7 +624,7 @@ export class AgentHost {
|
|
|
624
624
|
// again unless a real event happens to arrive.
|
|
625
625
|
this._armIfTimedParked(laneKey, session.state ?? this.agent.workflow.initial);
|
|
626
626
|
// Best-effort: also notify the user so the conversation isn't silently
|
|
627
|
-
// stuck
|
|
627
|
+
// stuck. Complements the timer re-arm above — that recovers a
|
|
628
628
|
// parked lane; this tells a waiting human.
|
|
629
629
|
const ev = event;
|
|
630
630
|
const senderType = String(ev?.senderType ?? '').toUpperCase();
|
|
@@ -632,7 +632,7 @@ export class AgentHost {
|
|
|
632
632
|
if (senderId && senderType === 'USER') {
|
|
633
633
|
this.eff({ kind: 'send-message', sessionId: session.sessionId, receiverId: senderId, text: 'Something went wrong. Please try again.' }).catch(() => { });
|
|
634
634
|
}
|
|
635
|
-
//
|
|
635
|
+
// and then say so. Everything above is recovery for the lane —
|
|
636
636
|
// it does not mean the event was handled, and the caller is about to
|
|
637
637
|
// decide whether the delivery that carried it can be acked. Swallowing
|
|
638
638
|
// here is what turned a failed wake into a lost one: two hand-off publish
|
|
@@ -682,7 +682,7 @@ export class AgentHost {
|
|
|
682
682
|
onResourceEvent: (event) => {
|
|
683
683
|
return this.handleResourceEvent(event);
|
|
684
684
|
},
|
|
685
|
-
//
|
|
685
|
+
// work delivered by the inbox's task channel, not by a chat
|
|
686
686
|
// row — this is what reaches a task under a chat-less agreement or one
|
|
687
687
|
// the agent assigned to itself.
|
|
688
688
|
onTask: async (task) => {
|
|
@@ -693,7 +693,7 @@ export class AgentHost {
|
|
|
693
693
|
runtimeLog.warn(this.options.name || 'agent', `inbox task ${task.taskId} could not be woken: ${err.message}`);
|
|
694
694
|
}
|
|
695
695
|
},
|
|
696
|
-
//
|
|
696
|
+
// exact-match triage in code — miss acks with 0 tokens.
|
|
697
697
|
onQuest: async (quest) => {
|
|
698
698
|
await this.triageQuestDelivery(quest);
|
|
699
699
|
},
|
|
@@ -702,7 +702,7 @@ export class AgentHost {
|
|
|
702
702
|
return this;
|
|
703
703
|
}
|
|
704
704
|
/**
|
|
705
|
-
*
|
|
705
|
+
* plain string compare against {@link AgentHostOptions.questMatchTags}
|
|
706
706
|
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
707
707
|
*/
|
|
708
708
|
async triageQuestDelivery(quest) {
|
|
@@ -768,7 +768,7 @@ export class AgentHost {
|
|
|
768
768
|
}, laneKey);
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
|
-
/**
|
|
771
|
+
/** replay task-assigned for an existing active task (demo recovery). */
|
|
772
772
|
async wakeTaskAssignment(taskId) {
|
|
773
773
|
const agentId = this.options.agentId;
|
|
774
774
|
if (!agentId || !this.taskService) {
|
|
@@ -94,7 +94,7 @@ export class AnthropicAdapter {
|
|
|
94
94
|
const final = await stream.finalMessage();
|
|
95
95
|
const u = final.usage;
|
|
96
96
|
// Fold cache read/creation into prompt tokens so the streamed reply is not
|
|
97
|
-
// undercounted (cache traffic was the bulk of the
|
|
97
|
+
// undercounted (cache traffic was the bulk of the drain).
|
|
98
98
|
const usage = u
|
|
99
99
|
? {
|
|
100
100
|
prompt_tokens: (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0),
|
|
@@ -38,7 +38,7 @@ export type McpTransportConfig = {
|
|
|
38
38
|
agentId?: string;
|
|
39
39
|
} | {
|
|
40
40
|
/**
|
|
41
|
-
* Per-user brokered connection
|
|
41
|
+
* Per-user brokered connection: the agent holds NO
|
|
42
42
|
* connectionId/grantId. It speaks StreamableHTTP to
|
|
43
43
|
* `/connections/mcp/relay` and asserts the invoking end-user via
|
|
44
44
|
* `x-on-behalf-of-user`; the gateway resolves that user's own connection
|
|
@@ -57,13 +57,13 @@ export type McpTransportConfig = {
|
|
|
57
57
|
* The MCP server URL this agent fronts. Not used for gateway calls (the
|
|
58
58
|
* gateway derives the server from the resolved connection), but needed to
|
|
59
59
|
* open a connection request when the invoking user hasn't connected yet
|
|
60
|
-
|
|
60
|
+
*. Without it, a NO_USER_CONNECTION surfaces as an error rather
|
|
61
61
|
* than an auto-created consent prompt.
|
|
62
62
|
*/
|
|
63
63
|
serverUrl?: string;
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
|
-
* Typed failure from the Ziggs MCP gateway
|
|
66
|
+
* Typed failure from the Ziggs MCP gateway. `code` is the stable
|
|
67
67
|
* machine-readable reason — e.g. `GRANT_REVOKED`, `GRANT_EXPIRED`,
|
|
68
68
|
* `GRANT_HOLDER_MISMATCH`, `GRANT_CONNECTION_MISMATCH` — so agent code can
|
|
69
69
|
* branch ("revoked → tell the user and stop retrying" vs "network blip →
|
|
@@ -89,7 +89,7 @@ export declare function resolveZiggsGatewayTarget(config: Extract<McpTransportCo
|
|
|
89
89
|
headers: Record<string, string>;
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
92
|
-
* Resolve the per-user gateway URL + headers
|
|
92
|
+
* Resolve the per-user gateway URL + headers. No connectionId/grantId —
|
|
93
93
|
* the gateway resolves the invoking user's own connection + grant from
|
|
94
94
|
* `x-on-behalf-of-user` + the impersonated agent.
|
|
95
95
|
*/
|
|
@@ -109,9 +109,9 @@ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' |
|
|
|
109
109
|
/**
|
|
110
110
|
* Max tools to bind to the LLM per turn. LLM providers cap the `tools` array
|
|
111
111
|
* (OpenAI: 128); an MCP server exposing more (e.g. PostHog: 146) makes every
|
|
112
|
-
* turn fail with `400
|
|
112
|
+
* turn fail with `400 .. array too long`. When the server's tool count
|
|
113
113
|
* exceeds this budget the list is truncated (and logged). Defaults to 128.
|
|
114
|
-
* See
|
|
114
|
+
* See; Phase 2 will replace truncation with query-aware selection.
|
|
115
115
|
*/
|
|
116
116
|
maxTools?: number;
|
|
117
117
|
/**
|
|
@@ -120,12 +120,12 @@ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' |
|
|
|
120
120
|
* MCP server (e.g. PostHog's 146 tools) be narrowed to the handful this agent
|
|
121
121
|
* actually needs — cutting per-turn cost and stopping the model from thrashing
|
|
122
122
|
* across (or never binding) the tool it should call. Empty/undefined = bind
|
|
123
|
-
* all (unchanged behavior).
|
|
123
|
+
* all (unchanged behavior). Phase 2 = name-based selection.
|
|
124
124
|
*/
|
|
125
125
|
allowedTools?: string[];
|
|
126
126
|
/**
|
|
127
127
|
* Pre-discovered MCP tool schemas. REQUIRED for `type: 'ziggs-per-user'`
|
|
128
|
-
*
|
|
128
|
+
* — there is no boot connection to call `tools/list` on, so the
|
|
129
129
|
* schema is baked at provision time and passed here. Ignored for the other
|
|
130
130
|
* transports, which discover tools live at boot.
|
|
131
131
|
*/
|
|
@@ -138,7 +138,7 @@ export interface CreateMcpAgentOptions extends Omit<AgentHostOptions, 'tools' |
|
|
|
138
138
|
/**
|
|
139
139
|
* How many MCP tools may be bound to the LLM, given the provider cap and the
|
|
140
140
|
* number of extra (non-MCP) tools the agent also carries. Always >= 1 so a
|
|
141
|
-
* pathological config still binds something rather than nothing.
|
|
141
|
+
* pathological config still binds something rather than nothing.
|
|
142
142
|
*/
|
|
143
143
|
export declare function mcpToolBudget(maxTools: number, extraToolCount: number): number;
|
|
144
144
|
export interface McpAgentHandle {
|
|
@@ -158,7 +158,7 @@ export declare function buildTransport(config: McpTransportConfig, defaults?: {
|
|
|
158
158
|
* that full block rides every turn. Keep the lead — what the tool does + how to
|
|
159
159
|
* call it — and drop the embedded reference; agents fetch detail via dedicated
|
|
160
160
|
* schema/help tools at call time. Tunable; big enough to preserve real guidance.
|
|
161
|
-
|
|
161
|
+
*.
|
|
162
162
|
*/
|
|
163
163
|
export declare const MAX_TOOL_DESC_CHARS = 2000;
|
|
164
164
|
/** Truncate one description at a sentence/newline boundary near the cap. */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
3
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
-
import { ConnectionsClient } from '@ziggs-ai/api-client';
|
|
4
|
+
import { ConnectionsClient, getBackendUrl } from '@ziggs-ai/api-client';
|
|
5
5
|
import { AgentHost } from '../AgentHost.js';
|
|
6
6
|
import { thinkingDefaults } from '../runtime/defaults.js';
|
|
7
7
|
/**
|
|
8
|
-
* Typed failure from the Ziggs MCP gateway
|
|
8
|
+
* Typed failure from the Ziggs MCP gateway. `code` is the stable
|
|
9
9
|
* machine-readable reason — e.g. `GRANT_REVOKED`, `GRANT_EXPIRED`,
|
|
10
10
|
* `GRANT_HOLDER_MISMATCH`, `GRANT_CONNECTION_MISMATCH` — so agent code can
|
|
11
11
|
* branch ("revoked → tell the user and stop retrying" vs "network blip →
|
|
@@ -56,10 +56,16 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
56
56
|
if (!agentId) {
|
|
57
57
|
throw new Error('ziggs MCP transport requires an agentId to impersonate (X-Agent-Id)');
|
|
58
58
|
}
|
|
59
|
+
// ZIG-1104: explicit overrides still win, but the LAST resort is
|
|
60
|
+
// `getBackendUrl()` — the resolution every other SDK call uses — not
|
|
61
|
+
// `localhost:3000`. The fleet task sets neither BACKEND_URL nor
|
|
62
|
+
// ZIGGS_BACKEND_URL (it sets HTTP_URL), so the old chain silently pointed every
|
|
63
|
+
// gateway call at localhost inside the container: the requests never reached the
|
|
64
|
+
// backend at all, and the agent reported the server as unreachable.
|
|
59
65
|
const baseUrl = (config.baseUrl ||
|
|
60
|
-
process.env.BACKEND_URL ||
|
|
61
66
|
process.env.ZIGGS_BACKEND_URL ||
|
|
62
|
-
|
|
67
|
+
process.env.BACKEND_URL ||
|
|
68
|
+
getBackendUrl()).replace(/\/$/, '');
|
|
63
69
|
return {
|
|
64
70
|
url: new URL(`${baseUrl}/connections/${encodeURIComponent(config.connectionId)}/mcp`),
|
|
65
71
|
headers: {
|
|
@@ -70,7 +76,7 @@ export function resolveZiggsGatewayTarget(config, defaults = {}) {
|
|
|
70
76
|
};
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
|
-
* Resolve the per-user gateway URL + headers
|
|
79
|
+
* Resolve the per-user gateway URL + headers. No connectionId/grantId —
|
|
74
80
|
* the gateway resolves the invoking user's own connection + grant from
|
|
75
81
|
* `x-on-behalf-of-user` + the impersonated agent.
|
|
76
82
|
*/
|
|
@@ -79,17 +85,23 @@ export function resolvePerUserGatewayTarget(config, agentId, userId, onBehalfOfA
|
|
|
79
85
|
if (!operatorKey) {
|
|
80
86
|
throw new Error('ziggs-per-user MCP transport requires an operatorKey (or ZIGGS_OPERATOR_KEY)');
|
|
81
87
|
}
|
|
88
|
+
// ZIG-1104: explicit overrides still win, but the LAST resort is
|
|
89
|
+
// `getBackendUrl()` — the resolution every other SDK call uses — not
|
|
90
|
+
// `localhost:3000`. The fleet task sets neither BACKEND_URL nor
|
|
91
|
+
// ZIGGS_BACKEND_URL (it sets HTTP_URL), so the old chain silently pointed every
|
|
92
|
+
// gateway call at localhost inside the container: the requests never reached the
|
|
93
|
+
// backend at all, and the agent reported the server as unreachable.
|
|
82
94
|
const baseUrl = (config.baseUrl ||
|
|
83
|
-
process.env.BACKEND_URL ||
|
|
84
95
|
process.env.ZIGGS_BACKEND_URL ||
|
|
85
|
-
|
|
96
|
+
process.env.BACKEND_URL ||
|
|
97
|
+
getBackendUrl()).replace(/\/$/, '');
|
|
86
98
|
return {
|
|
87
99
|
url: new URL(`${baseUrl}/connections/mcp/relay`),
|
|
88
100
|
headers: {
|
|
89
101
|
authorization: `Bearer ${operatorKey}`,
|
|
90
102
|
'x-agent-id': agentId,
|
|
91
103
|
'x-on-behalf-of-user': userId,
|
|
92
|
-
//
|
|
104
|
+
// when this call runs under a delegation, userId is the delegating
|
|
93
105
|
// agent (senderId), not the end-user who owns the connection. The gateway
|
|
94
106
|
// uses this agreement to resolve the delegation root's user.
|
|
95
107
|
...(onBehalfOfAgreementId ? { 'x-on-behalf-of-agreement': onBehalfOfAgreementId } : {}),
|
|
@@ -103,7 +115,7 @@ const CONTROL_TOOL_RESERVE = 4;
|
|
|
103
115
|
/**
|
|
104
116
|
* How many MCP tools may be bound to the LLM, given the provider cap and the
|
|
105
117
|
* number of extra (non-MCP) tools the agent also carries. Always >= 1 so a
|
|
106
|
-
* pathological config still binds something rather than nothing.
|
|
118
|
+
* pathological config still binds something rather than nothing.
|
|
107
119
|
*/
|
|
108
120
|
export function mcpToolBudget(maxTools, extraToolCount) {
|
|
109
121
|
return Math.max(1, maxTools - CONTROL_TOOL_RESERVE - extraToolCount);
|
|
@@ -218,7 +230,7 @@ function buildDefaultWorkflow(description, mcpTools) {
|
|
|
218
230
|
* that full block rides every turn. Keep the lead — what the tool does + how to
|
|
219
231
|
* call it — and drop the embedded reference; agents fetch detail via dedicated
|
|
220
232
|
* schema/help tools at call time. Tunable; big enough to preserve real guidance.
|
|
221
|
-
|
|
233
|
+
*.
|
|
222
234
|
*/
|
|
223
235
|
export const MAX_TOOL_DESC_CHARS = 2000;
|
|
224
236
|
/** Truncate one description at a sentence/newline boundary near the cap. */
|
|
@@ -230,7 +242,7 @@ export function trimDescription(desc) {
|
|
|
230
242
|
const head = boundary > MAX_TOOL_DESC_CHARS * 0.6 ? slice.slice(0, boundary + 1) : slice;
|
|
231
243
|
return `${head.trimEnd()} […description truncated; use the server's schema/help tools for full detail]`;
|
|
232
244
|
}
|
|
233
|
-
/** Trim oversized descriptions across the tool set (cost control
|
|
245
|
+
/** Trim oversized descriptions across the tool set (cost control). */
|
|
234
246
|
function trimToolDescriptions(tools, agentId) {
|
|
235
247
|
let trimmedCount = 0;
|
|
236
248
|
let savedChars = 0;
|
|
@@ -243,14 +255,14 @@ function trimToolDescriptions(tools, agentId) {
|
|
|
243
255
|
return { ...t, description: nextDesc };
|
|
244
256
|
});
|
|
245
257
|
if (trimmedCount > 0) {
|
|
246
|
-
console.warn(`[McpAdapter] ${agentId}: trimmed ${trimmedCount} oversized tool description(s) to ${MAX_TOOL_DESC_CHARS} chars, saving ~${Math.round(savedChars / 4 / 1000)}K tokens/turn
|
|
258
|
+
console.warn(`[McpAdapter] ${agentId}: trimmed ${trimmedCount} oversized tool description(s) to ${MAX_TOOL_DESC_CHARS} chars, saving ~${Math.round(savedChars / 4 / 1000)}K tokens/turn.`);
|
|
247
259
|
}
|
|
248
260
|
return out;
|
|
249
261
|
}
|
|
250
262
|
/**
|
|
251
263
|
* Keep only tools whose name matches one of the allowlist substrings
|
|
252
264
|
* (case-insensitive). No allowlist → pass through unchanged. Applied before the
|
|
253
|
-
* maxTools cap so a targeted agent binds exactly the tools it needs.
|
|
265
|
+
* maxTools cap so a targeted agent binds exactly the tools it needs.
|
|
254
266
|
*/
|
|
255
267
|
function filterToolsByAllowlist(tools, allowedTools, agentId) {
|
|
256
268
|
if (!allowedTools || allowedTools.length === 0)
|
|
@@ -263,7 +275,7 @@ function filterToolsByAllowlist(tools, allowedTools, agentId) {
|
|
|
263
275
|
/**
|
|
264
276
|
* Cap the tool set bound to the LLM at the provider limit (OpenAI 128) —
|
|
265
277
|
* truncate and log rather than crash — and trim oversized descriptions so the
|
|
266
|
-
* bound schema doesn't ship a manual every turn.
|
|
278
|
+
* bound schema doesn't ship a manual every turn. Phase 1. Shared by the
|
|
267
279
|
* boot-time (single-client) and per-user paths. When `allowedTools` is set the
|
|
268
280
|
* server's tools are narrowed by name first, so the cap rarely bites.
|
|
269
281
|
*/
|
|
@@ -273,7 +285,7 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
|
|
|
273
285
|
const budget = mcpToolBudget(maxTools, extraToolCount);
|
|
274
286
|
if (trimmed.length <= budget)
|
|
275
287
|
return trimmed;
|
|
276
|
-
console.warn(`[McpAdapter] ${agentId}: MCP server exposes ${trimmed.length} tools, over the ${maxTools}-tool LLM cap; binding the first ${budget}, dropping ${trimmed.length - budget}
|
|
288
|
+
console.warn(`[McpAdapter] ${agentId}: MCP server exposes ${trimmed.length} tools, over the ${maxTools}-tool LLM cap; binding the first ${budget}, dropping ${trimmed.length - budget}.`);
|
|
277
289
|
return trimmed.slice(0, budget);
|
|
278
290
|
}
|
|
279
291
|
/**
|
|
@@ -311,7 +323,7 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
|
|
|
311
323
|
*/
|
|
312
324
|
export async function createMcpAgent(options) {
|
|
313
325
|
const { transport: transportConfig, extraTools = [], workflow, maxTools = DEFAULT_MAX_TOOLS, allowedTools, toolSchema, ...hostOptions } = options;
|
|
314
|
-
// ── Per-user path
|
|
326
|
+
// ── Per-user path: no boot connection; one MCP client per invoking
|
|
315
327
|
// user, each carrying that user's on-behalf-of header for an isolated session.
|
|
316
328
|
if (transportConfig.type === 'ziggs-per-user') {
|
|
317
329
|
const agentId = transportConfig.agentId || hostOptions.agentId;
|
|
@@ -351,12 +363,12 @@ export async function createMcpAgent(options) {
|
|
|
351
363
|
// When the invoking user hasn't connected this server, open a connection
|
|
352
364
|
// request targeting THEM (X-On-Behalf-Of-User) so the consent prompt lands
|
|
353
365
|
// in their inbox. The backend dedups pending requests, so retrying every
|
|
354
|
-
// turn is safe.
|
|
366
|
+
// turn is safe.
|
|
355
367
|
const backendBase = (transportConfig.baseUrl ||
|
|
356
368
|
process.env.BACKEND_URL ||
|
|
357
369
|
process.env.ZIGGS_BACKEND_URL ||
|
|
358
370
|
'http://localhost:3000').replace(/\/$/, '');
|
|
359
|
-
// App URL for the user-facing connect link
|
|
371
|
+
// App URL for the user-facing connect link: api.ziggsai.com → ziggsai.com.
|
|
360
372
|
const appUrl = backendBase.includes('localhost')
|
|
361
373
|
? 'http://localhost:1234'
|
|
362
374
|
: backendBase.replace(/\/\/api\./, '//');
|
|
@@ -364,7 +376,7 @@ export async function createMcpAgent(options) {
|
|
|
364
376
|
? new URL(transportConfig.serverUrl).host
|
|
365
377
|
: 'the external service';
|
|
366
378
|
// Open a connection request for this user; returns its id so the caller can
|
|
367
|
-
// deep-link the user straight to it on the Connections page
|
|
379
|
+
// deep-link the user straight to it on the Connections page.
|
|
368
380
|
const triggerConnectionRequest = async (userId) => {
|
|
369
381
|
if (!transportConfig.serverUrl)
|
|
370
382
|
return null;
|
|
@@ -390,11 +402,11 @@ export async function createMcpAgent(options) {
|
|
|
390
402
|
if (!userId) {
|
|
391
403
|
throw new Error('per-user MCP tool call requires an invoking user (context.senderId is missing)');
|
|
392
404
|
}
|
|
393
|
-
//
|
|
405
|
+
// for a delegated wake, senderId is the delegating agent, not the
|
|
394
406
|
// connection-owning end-user; pass the agreement so the gateway resolves
|
|
395
407
|
// the delegation root's user.
|
|
396
408
|
const onBehalfOfAgreementId = typeof context.onBehalfOfAgreementId === 'string' ? context.onBehalfOfAgreementId : undefined;
|
|
397
|
-
//
|
|
409
|
+
// (B): per-user resolution is intermittently flaky even when the
|
|
398
410
|
// connection is valid and resolvable — the same on-behalf call fails with
|
|
399
411
|
// NO_USER_CONNECTION on one wake and succeeds on the next. clientFor never
|
|
400
412
|
// caches a failed connect, so each attempt re-resolves from scratch. Retry
|
|
@@ -137,7 +137,7 @@ export class OpenAIAdapter {
|
|
|
137
137
|
stream: true,
|
|
138
138
|
// Without this, a streamed completion never returns a usage block — the
|
|
139
139
|
// streamed reply (often the largest output) would bill tokens that are
|
|
140
|
-
// counted nowhere
|
|
140
|
+
// counted nowhere. The usage arrives on a final chunk whose
|
|
141
141
|
// `choices` is empty.
|
|
142
142
|
stream_options: { include_usage: true },
|
|
143
143
|
max_completion_tokens: maxCompletionTokensForModel(this.model),
|