@ziggs-ai/agent-sdk 0.9.0 → 0.10.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 +23 -0
- package/dist/AgentHost.d.ts +53 -14
- package/dist/AgentHost.js +137 -8
- package/dist/agent/ClaudeSdkAgent.d.ts +27 -2
- package/dist/agent/ClaudeSdkAgent.js +151 -78
- package/dist/agent/claudeSdkTools.js +39 -11
- package/dist/context/applyEffects.js +4 -3
- package/dist/defineAgent.js +4 -1
- package/dist/formatters/HistoryFormatter.js +11 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/ingress/normalizeIncoming.d.ts +5 -0
- package/dist/ingress/normalizeIncoming.js +23 -1
- package/dist/pricing/fleetDefaults.d.ts +0 -3
- package/dist/pricing/fleetDefaults.js +0 -6
- package/dist/runtime/PromptBuilder.js +9 -5
- package/dist/runtime/bootstrapCtx.js +6 -8
- package/dist/runtime/buildOutcome.d.ts +9 -1
- package/dist/runtime/buildOutcome.js +29 -6
- package/dist/runtime/runTurn.js +1 -1
- package/dist/runtime/taskBookkeeping.d.ts +0 -2
- package/dist/runtime/taskBookkeeping.js +0 -6
- package/dist/runtime/taskIntake.js +6 -4
- package/dist/runtime/wakeWriteKeys.js +23 -21
- package/dist/server/ConnectionPool.d.ts +10 -52
- package/dist/server/ConnectionPool.js +26 -57
- package/dist/server/EventQueue.d.ts +6 -0
- package/dist/server/EventQueue.js +21 -6
- package/dist/server/SeenMessages.d.ts +7 -0
- package/dist/server/SeenMessages.js +15 -0
- package/dist/server/ZiggsEffectHandler.d.ts +1 -0
- package/dist/server/ZiggsEffectHandler.js +21 -11
- package/dist/server/inboxLoop.d.ts +17 -2
- package/dist/server/inboxLoop.js +92 -16
- package/dist/server/polls/PollKind.d.ts +72 -0
- package/dist/server/polls/PollKind.js +53 -0
- package/dist/server/polls/PollRunner.d.ts +62 -0
- package/dist/server/polls/PollRunner.js +138 -0
- package/dist/server/questMatch.d.ts +5 -0
- package/dist/server/questMatch.js +14 -0
- package/dist/server/runLauncher.d.ts +0 -6
- package/dist/server/runLauncher.js +54 -27
- package/dist/server/tasks/TaskService.d.ts +2 -11
- package/dist/server/tools/tier1/protocolRunner.d.ts +3 -1
- package/dist/server/tools/tier1/protocolRunner.js +40 -41
- package/dist/server/tools/tier1/protocolTools.js +23 -13
- package/dist/server/tools/tier2/connectionTools.js +12 -1
- package/dist/tasks/protocolRegistry.d.ts +27 -0
- package/dist/tasks/protocolRegistry.js +36 -0
- package/dist/tools/capabilityAdapter.js +12 -1
- package/dist/tools/recordReport.js +8 -4
- package/dist/types.d.ts +24 -11
- package/package.json +2 -2
- package/dist/server/staleBacklogGate.d.ts +0 -57
- package/dist/server/staleBacklogGate.js +0 -100
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 — unreleased
|
|
4
|
+
|
|
5
|
+
### Removed (breaking)
|
|
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 (ZIG-993). 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
|
+
- `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`. ZIG-977 moved the real bookkeeping into `Ctx.tasks` (keyed by taskId) and ZIG-1057 stopped syncing these mirrors, 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. ZIG-1057 cut `status` from the wire in favour of `state`; four readers kept a `|| r.status` tail, which would have absorbed a backend regression instead of failing on it. Task state resolves as `task?.state || r.state`.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Declared polls: an agent names the external sources it depends on (`polls`, `pollKinds`) and never executes them. New exports `PollRunner`, `PollRegistry`, `declaredPolls`, and types `PollBinding`, `PollContext`, `PollKind`, `PollResult`, `PollRunnerOptions`, `WakeTarget`. `runLauncher` runs declared polls at fleet level — outside any `AgentHost`, because a host is evicted when idle and that is exactly when its source still needs watching.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Attention gate: only the addressed agent spends a turn. A message addressed elsewhere in a shared room no longer wakes every participant.
|
|
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
|
+
- Persona wire shapes are understood on inbound frames.
|
|
21
|
+
- A wake declares its lane so the backend can fence reach (ZIG-1092).
|
|
22
|
+
- A proposal awaiting the agent's own consent counts as actionable rather than a notice to park (ZIG-1078).
|
|
23
|
+
- Artifact idempotency keys are derived from content (ZIG-1083).
|
|
24
|
+
- Blank tool arguments fall back to the wake's lane (ZIG-1081).
|
|
25
|
+
|
|
3
26
|
## 0.9.0 — 2026-07-30
|
|
4
27
|
|
|
5
28
|
### Changed (breaking)
|
package/dist/AgentHost.d.ts
CHANGED
|
@@ -71,8 +71,17 @@ export interface AgentHostOptions {
|
|
|
71
71
|
agentId?: string;
|
|
72
72
|
name?: string | null;
|
|
73
73
|
wsUrl?: string;
|
|
74
|
+
/** Store-facing one-liner. See DefineAgentInput.description. */
|
|
74
75
|
description?: string;
|
|
76
|
+
/** Claude-brain system prompt (ZIG-1080); falls back to `description`. */
|
|
77
|
+
systemPrompt?: string;
|
|
75
78
|
specialization?: string | null;
|
|
79
|
+
/**
|
|
80
|
+
* ZIG-1185 — strings compared exactly against a quest's `match` field.
|
|
81
|
+
* Miss → ack with zero LLM. Match → normal wake. `specialization` is also
|
|
82
|
+
* checked when set.
|
|
83
|
+
*/
|
|
84
|
+
questMatchTags?: string[];
|
|
76
85
|
tools?: ToolDefinition[];
|
|
77
86
|
taskTools?: 'all' | 'none' | string[];
|
|
78
87
|
/**
|
|
@@ -117,14 +126,6 @@ export interface AgentHostOptions {
|
|
|
117
126
|
sessionStore?: SessionStore;
|
|
118
127
|
/** Observability tap: called after every effect with the effect and its response. */
|
|
119
128
|
effectTap?: (effect: import('./types.js').Effect, response: unknown) => void;
|
|
120
|
-
/**
|
|
121
|
-
* Liveness signal: called whenever the host receives or processes work (an
|
|
122
|
-
* inbox message or resource event delivered to a lane). Hosting pools use it
|
|
123
|
-
* to reset their idle-eviction timer — without it a host that is actively
|
|
124
|
-
* working (but never addressed via pool.sendTo) is torn down mid-stream at
|
|
125
|
-
* the idle timeout, discarding lane state, Claude session ids, and timers.
|
|
126
|
-
*/
|
|
127
|
-
onActivity?: () => void;
|
|
128
129
|
/** Observability tap: called on every FSM state transition, no-match, depth-guard, or error-escape (ZIG-442). */
|
|
129
130
|
transitionTap?: (event: import('./runtime/AgentMachine.js').TransitionEvent) => void;
|
|
130
131
|
/**
|
|
@@ -195,11 +196,54 @@ export declare class AgentHost {
|
|
|
195
196
|
handleResourceEvent(event: Record<string, unknown>): Promise<void>;
|
|
196
197
|
/** Wire entry. Normalize → resolve session → enqueue on the right lane. */
|
|
197
198
|
handleMessage(text: string, metadata?: Record<string, unknown>): Promise<void>;
|
|
199
|
+
/**
|
|
200
|
+
* ZIG-1144 part 3 — an agreement lane outlives the agreement.
|
|
201
|
+
*
|
|
202
|
+
* A lane named `agrn-<agreementId>` keeps its Claude session for the whole
|
|
203
|
+
* engagement, so a trailing event after the deal ends pays for all of it: on
|
|
204
|
+
* 2026-08-03 a cancelled hand-off woke its lane once for 92k tokens, and on
|
|
205
|
+
* 08-04 the same shape cost 147k with the session reaching 399,864 input tokens
|
|
206
|
+
* before it reset. Nothing was produced either time — the work had finished and
|
|
207
|
+
* the contract was gone.
|
|
208
|
+
*
|
|
209
|
+
* So a lane whose agreement is terminal does not get a turn. The event is still
|
|
210
|
+
* consumed (and acked by the inbox loop), the FSM is untouched, and a chat lane
|
|
211
|
+
* is never affected: only the agreement-scoped routing key is checked.
|
|
212
|
+
*
|
|
213
|
+
* Failing open is deliberate. If the lookup errors we run the turn — a missed
|
|
214
|
+
* suppression costs tokens, while a wrongly suppressed turn drops work.
|
|
215
|
+
*/
|
|
216
|
+
private laneAgreementIsTerminal;
|
|
217
|
+
/**
|
|
218
|
+
* ZIG-1153 — the trailing turn on work that is already reported.
|
|
219
|
+
*
|
|
220
|
+
* A lane session carries the whole engagement, so the LAST wake is the most
|
|
221
|
+
* expensive one: on prod, the frame that followed a completed elicitation cost
|
|
222
|
+
* **158,733 tokens** with the session's input at 366,417 before ZIG-816 reset
|
|
223
|
+
* it, and produced nothing — the spec had been written and the task closed one
|
|
224
|
+
* frame earlier.
|
|
225
|
+
*
|
|
226
|
+
* A task frame that carries a terminal state AND a result is an echo of work
|
|
227
|
+
* this agent has already finished: there is no decision left in it. Dropping it
|
|
228
|
+
* is not the same suppression as {@link laneAgreementIsTerminal}, which waits
|
|
229
|
+
* for the agreement to end — this fires while the engagement is still live,
|
|
230
|
+
* which is where the cost actually was.
|
|
231
|
+
*
|
|
232
|
+
* Deliberately narrow: only a `task_result` frame, only with a terminal state,
|
|
233
|
+
* and only when a result is attached. A completed task with no result recorded
|
|
234
|
+
* still gets its turn, because that is an agent with something left to say.
|
|
235
|
+
*/
|
|
236
|
+
private isSettledTaskEcho;
|
|
198
237
|
private _processEvent;
|
|
199
238
|
/** Arms the lane's liveness timer iff stateName is a parked state declaring timeoutMs. */
|
|
200
239
|
private _armIfTimedParked;
|
|
201
240
|
private _resolveOwnAgentId;
|
|
202
241
|
connectAsync(_timeout?: number): Promise<this>;
|
|
242
|
+
/**
|
|
243
|
+
* ZIG-1185 — plain string compare against {@link AgentHostOptions.questMatchTags}
|
|
244
|
+
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
245
|
+
*/
|
|
246
|
+
private triageQuestDelivery;
|
|
203
247
|
private _bootstrapAssignedActiveTasks;
|
|
204
248
|
/** ZIG-802: replay task-assigned for an existing active task (demo recovery). */
|
|
205
249
|
wakeTaskAssignment(taskId: string): Promise<void>;
|
|
@@ -214,10 +258,5 @@ export declare class AgentHost {
|
|
|
214
258
|
* createAgentPool([cfg, ...]) → ConnectionPool (lazy fleet, async-loaded)
|
|
215
259
|
*/
|
|
216
260
|
export declare function createAgent(options?: AgentHostOptions | AgentConfig): AgentHost;
|
|
217
|
-
export declare function createAgentPool(configs: AgentHostOptions[] | AgentConfig[]
|
|
218
|
-
poolOptions?: {
|
|
219
|
-
maxActive?: number;
|
|
220
|
-
idleTimeoutMs?: number;
|
|
221
|
-
};
|
|
222
|
-
}): Promise<unknown>;
|
|
261
|
+
export declare function createAgentPool(configs: AgentHostOptions[] | AgentConfig[]): Promise<unknown>;
|
|
223
262
|
export default AgentHost;
|
package/dist/AgentHost.js
CHANGED
|
@@ -18,6 +18,7 @@ import { ArtifactsClient, ContextReadClient, TelemetryClient, sendChatMessage, }
|
|
|
18
18
|
import { TaskService } from './server/tasks/TaskService.js';
|
|
19
19
|
import { AgreementService } from './server/agreements/AgreementService.js';
|
|
20
20
|
import { runInboxLoop } from './server/inboxLoop.js';
|
|
21
|
+
import { questExactMatch } from './server/questMatch.js';
|
|
21
22
|
import { SeenMessages } from './server/SeenMessages.js';
|
|
22
23
|
import { runtimeLog } from './shared/runtimeLog.js';
|
|
23
24
|
/**
|
|
@@ -90,7 +91,12 @@ function createBackendServices(options) {
|
|
|
90
91
|
const agentId = options.agentId;
|
|
91
92
|
const artifactsClient = new ArtifactsClient(operatorKey, agentId);
|
|
92
93
|
const contextReadClient = new ContextReadClient(operatorKey, agentId);
|
|
93
|
-
|
|
94
|
+
// ZIG-1080: the role text is the prompt when the author split the two; the
|
|
95
|
+
// store-facing `description` is only the fallback for agents that never did.
|
|
96
|
+
const promptBuilder = new PromptBuilder({
|
|
97
|
+
description: options.systemPrompt || options.description,
|
|
98
|
+
specialization: options.specialization,
|
|
99
|
+
});
|
|
94
100
|
const taskService = new TaskService(operatorKey, agentId);
|
|
95
101
|
const agreementService = new AgreementService(operatorKey, agentId);
|
|
96
102
|
const telemetryClient = new TelemetryClient(operatorKey, agentId);
|
|
@@ -145,6 +151,7 @@ export class AgentHost {
|
|
|
145
151
|
name: resolved.name || null,
|
|
146
152
|
wsUrl: resolved.wsUrl,
|
|
147
153
|
description: resolved.description || 'A helpful assistant',
|
|
154
|
+
systemPrompt: resolved.systemPrompt,
|
|
148
155
|
specialization: resolved.specialization ?? null,
|
|
149
156
|
tools: resolved.tools || [],
|
|
150
157
|
taskTools: resolved.taskTools,
|
|
@@ -268,6 +275,7 @@ export class AgentHost {
|
|
|
268
275
|
workflow,
|
|
269
276
|
toolManager: services.toolManager,
|
|
270
277
|
description: this.options.description,
|
|
278
|
+
systemPrompt: this.options.systemPrompt,
|
|
271
279
|
specialization: this.options.specialization,
|
|
272
280
|
model: resolveClaudeModel(this.options.model, this.options.services?.llm?.model),
|
|
273
281
|
anthropicKey: this.options.anthropicKey,
|
|
@@ -285,7 +293,14 @@ export class AgentHost {
|
|
|
285
293
|
promptBuilder: services.promptBuilder,
|
|
286
294
|
});
|
|
287
295
|
this.eventQueue = new EventQueue((event, laneKey) => this._processEvent(event, laneKey));
|
|
288
|
-
|
|
296
|
+
// A timer frame is not a delivery: there is no watermark waiting on it and
|
|
297
|
+
// nothing to redeliver, so its failure is logged and dropped here rather
|
|
298
|
+
// than left as an unhandled rejection (ZIG-1191 made enqueue reject).
|
|
299
|
+
this.stateTimers = new StateTimers((ev, laneKey) => {
|
|
300
|
+
void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
|
|
301
|
+
runtimeLog.warn('AgentHost', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
289
304
|
}
|
|
290
305
|
/**
|
|
291
306
|
* Handler for a resource change (artifact created, task state transition,
|
|
@@ -364,12 +379,96 @@ export class AgentHost {
|
|
|
364
379
|
// The inbox loop owns ack (act-before-ack, delivery law); the host only
|
|
365
380
|
// routes the event onto its lane. No live-path watermark ack — there is no
|
|
366
381
|
// separate live/push channel to reconcile against anymore.
|
|
367
|
-
|
|
382
|
+
//
|
|
383
|
+
// ZIG-1191: a failure un-marks the messageId. The mark above is what stops
|
|
384
|
+
// the same message being processed twice; leaving it set on a failed wake
|
|
385
|
+
// would make the redelivery this rejection buys us a no-op.
|
|
386
|
+
try {
|
|
387
|
+
await this.eventQueue.enqueue(normalized.event, laneKey);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
if (messageId)
|
|
391
|
+
this.seenMessages.unmark(messageId);
|
|
392
|
+
throw err;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* ZIG-1144 part 3 — an agreement lane outlives the agreement.
|
|
397
|
+
*
|
|
398
|
+
* A lane named `agrn-<agreementId>` keeps its Claude session for the whole
|
|
399
|
+
* engagement, so a trailing event after the deal ends pays for all of it: on
|
|
400
|
+
* 2026-08-03 a cancelled hand-off woke its lane once for 92k tokens, and on
|
|
401
|
+
* 08-04 the same shape cost 147k with the session reaching 399,864 input tokens
|
|
402
|
+
* before it reset. Nothing was produced either time — the work had finished and
|
|
403
|
+
* the contract was gone.
|
|
404
|
+
*
|
|
405
|
+
* So a lane whose agreement is terminal does not get a turn. The event is still
|
|
406
|
+
* consumed (and acked by the inbox loop), the FSM is untouched, and a chat lane
|
|
407
|
+
* is never affected: only the agreement-scoped routing key is checked.
|
|
408
|
+
*
|
|
409
|
+
* Failing open is deliberate. If the lookup errors we run the turn — a missed
|
|
410
|
+
* suppression costs tokens, while a wrongly suppressed turn drops work.
|
|
411
|
+
*/
|
|
412
|
+
async laneAgreementIsTerminal(sessionId) {
|
|
413
|
+
if (!sessionId.startsWith('agrn-'))
|
|
414
|
+
return false;
|
|
415
|
+
const agreementId = sessionId.slice('agrn-'.length);
|
|
416
|
+
if (!agreementId || !this.agreementService)
|
|
417
|
+
return false;
|
|
418
|
+
try {
|
|
419
|
+
const agreement = await this.agreementService.get(agreementId);
|
|
420
|
+
const status = String(agreement?.status ?? '');
|
|
421
|
+
return (status === 'cancelled' || status === 'refunded' || status === 'fulfilled');
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* ZIG-1153 — the trailing turn on work that is already reported.
|
|
429
|
+
*
|
|
430
|
+
* A lane session carries the whole engagement, so the LAST wake is the most
|
|
431
|
+
* expensive one: on prod, the frame that followed a completed elicitation cost
|
|
432
|
+
* **158,733 tokens** with the session's input at 366,417 before ZIG-816 reset
|
|
433
|
+
* it, and produced nothing — the spec had been written and the task closed one
|
|
434
|
+
* frame earlier.
|
|
435
|
+
*
|
|
436
|
+
* A task frame that carries a terminal state AND a result is an echo of work
|
|
437
|
+
* this agent has already finished: there is no decision left in it. Dropping it
|
|
438
|
+
* is not the same suppression as {@link laneAgreementIsTerminal}, which waits
|
|
439
|
+
* for the agreement to end — this fires while the engagement is still live,
|
|
440
|
+
* which is where the cost actually was.
|
|
441
|
+
*
|
|
442
|
+
* Deliberately narrow: only a `task_result` frame, only with a terminal state,
|
|
443
|
+
* and only when a result is attached. A completed task with no result recorded
|
|
444
|
+
* still gets its turn, because that is an agent with something left to say.
|
|
445
|
+
*/
|
|
446
|
+
isSettledTaskEcho(event) {
|
|
447
|
+
const frame = event;
|
|
448
|
+
if (frame?.type !== 'task_result')
|
|
449
|
+
return false;
|
|
450
|
+
const result = frame.result;
|
|
451
|
+
if (!result)
|
|
452
|
+
return false;
|
|
453
|
+
const state = String(result.state ?? '');
|
|
454
|
+
if (state !== 'completed' && state !== 'cancelled' && state !== 'failed') {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const reported = result.result;
|
|
458
|
+
return reported !== undefined && reported !== null && reported !== '';
|
|
368
459
|
}
|
|
369
460
|
async _processEvent(event, laneKey) {
|
|
370
461
|
const session = this.sessions.get(laneKey);
|
|
371
462
|
if (!session?.sessionId)
|
|
372
463
|
return;
|
|
464
|
+
if (await this.laneAgreementIsTerminal(session.sessionId)) {
|
|
465
|
+
runtimeLog.info(this.options.name || 'agent', `skipping wake on ${session.sessionId} — its agreement is terminal`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (this.isSettledTaskEcho(event)) {
|
|
469
|
+
runtimeLog.info(this.options.name || 'agent', `skipping wake on ${session.sessionId} — its task already reported a result`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
373
472
|
// Stale timer frames (superseded epoch) are dropped BEFORE touching the
|
|
374
473
|
// live timer — a pure-stale frame must not reset a freshly armed countdown.
|
|
375
474
|
const frame = this.stateTimers.stripStale(event, laneKey);
|
|
@@ -472,6 +571,13 @@ export class AgentHost {
|
|
|
472
571
|
if (senderId && senderType === 'USER') {
|
|
473
572
|
this.eff({ kind: 'send-message', sessionId: session.sessionId, receiverId: senderId, text: 'Something went wrong. Please try again.' }).catch(() => { });
|
|
474
573
|
}
|
|
574
|
+
// ZIG-1191: and then say so. Everything above is recovery for the lane —
|
|
575
|
+
// it does not mean the event was handled, and the caller is about to
|
|
576
|
+
// decide whether the delivery that carried it can be acked. Swallowing
|
|
577
|
+
// here is what turned a failed wake into a lost one: two hand-off publish
|
|
578
|
+
// notifications were acked after failing, so nothing ever redelivered
|
|
579
|
+
// them and a claim stayed blocked on consent that no wake would give.
|
|
580
|
+
throw err;
|
|
475
581
|
}
|
|
476
582
|
finally {
|
|
477
583
|
clearTimeout(tickWatchdog);
|
|
@@ -510,18 +616,15 @@ export class AgentHost {
|
|
|
510
616
|
seen: this.seenMessages,
|
|
511
617
|
label,
|
|
512
618
|
onMessage: (text, metadata) => {
|
|
513
|
-
this.options.onActivity?.();
|
|
514
619
|
return this.handleMessage(text, metadata);
|
|
515
620
|
},
|
|
516
621
|
onResourceEvent: (event) => {
|
|
517
|
-
this.options.onActivity?.();
|
|
518
622
|
return this.handleResourceEvent(event);
|
|
519
623
|
},
|
|
520
624
|
// ZIG-973: work delivered by the inbox's task channel, not by a chat
|
|
521
625
|
// row — this is what reaches a task under a chat-less agreement or one
|
|
522
626
|
// the agent assigned to itself.
|
|
523
627
|
onTask: async (task) => {
|
|
524
|
-
this.options.onActivity?.();
|
|
525
628
|
try {
|
|
526
629
|
await this.wakeTaskAssignment(task.taskId);
|
|
527
630
|
}
|
|
@@ -529,10 +632,35 @@ export class AgentHost {
|
|
|
529
632
|
runtimeLog.warn(this.options.name || 'agent', `inbox task ${task.taskId} could not be woken: ${err.message}`);
|
|
530
633
|
}
|
|
531
634
|
},
|
|
635
|
+
// ZIG-1185: exact-match triage in code — miss acks with 0 tokens.
|
|
636
|
+
onQuest: async (quest) => {
|
|
637
|
+
await this.triageQuestDelivery(quest);
|
|
638
|
+
},
|
|
532
639
|
});
|
|
533
640
|
}
|
|
534
641
|
return this;
|
|
535
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* ZIG-1185 — plain string compare against {@link AgentHostOptions.questMatchTags}
|
|
645
|
+
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
646
|
+
*/
|
|
647
|
+
async triageQuestDelivery(quest) {
|
|
648
|
+
const tags = [
|
|
649
|
+
...(this.options.questMatchTags ?? []),
|
|
650
|
+
...(this.options.specialization ? [this.options.specialization] : []),
|
|
651
|
+
];
|
|
652
|
+
if (!questExactMatch(quest.match, tags)) {
|
|
653
|
+
runtimeLog.debug(this.options.name || 'agent', `quest ${quest.agreementId}: no tag match for ${JSON.stringify(quest.match)} — ack without wake`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const needle = quest.match.trim();
|
|
657
|
+
const title = quest.title?.trim() || needle;
|
|
658
|
+
await this.handleMessage(`New marketplace quest matching your tags (${needle}): ${title}. Claim with agreement_claim if you will take it.`, {
|
|
659
|
+
messageId: `quest-wake:${quest.agreementId}:${quest.ts}`,
|
|
660
|
+
chatId: `marketplace:${quest.agreementId}`,
|
|
661
|
+
agreementId: quest.agreementId,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
536
664
|
async _bootstrapAssignedActiveTasks() {
|
|
537
665
|
const agentId = this.options.agentId;
|
|
538
666
|
if (!agentId || !this.taskService)
|
|
@@ -619,10 +747,11 @@ export class AgentHost {
|
|
|
619
747
|
export function createAgent(options = {}) {
|
|
620
748
|
return new AgentHost(options);
|
|
621
749
|
}
|
|
622
|
-
export async function createAgentPool(configs
|
|
750
|
+
export async function createAgentPool(configs) {
|
|
623
751
|
const ConnectionPool = await loadConnectionPool();
|
|
624
|
-
const pool = new ConnectionPool(
|
|
752
|
+
const pool = new ConnectionPool();
|
|
625
753
|
pool.register(configs);
|
|
754
|
+
await pool.connectAll();
|
|
626
755
|
return pool;
|
|
627
756
|
}
|
|
628
757
|
export default AgentHost;
|
|
@@ -15,13 +15,21 @@ import type { TickInput, TickOutput } from './Agent.js';
|
|
|
15
15
|
* `notification` only.
|
|
16
16
|
*
|
|
17
17
|
* Empty / missing batch → notice-only (do not burn a turn on a malformed wake).
|
|
18
|
+
*
|
|
19
|
+
* A message addressed to a DIFFERENT party is notice-only too (attention
|
|
20
|
+
* gate): the sender named who should act, and it wasn't this agent. The
|
|
21
|
+
* message still lands in chat history and arrives as context on the next
|
|
22
|
+
* real wake — skipped here is the LLM turn, not the information.
|
|
18
23
|
*/
|
|
19
|
-
export declare function isNoticeOnlyWake(event: RawEvent | undefined): boolean;
|
|
24
|
+
export declare function isNoticeOnlyWake(event: RawEvent | undefined, ownAgentId?: string | null): boolean;
|
|
20
25
|
export type CognitionKind = 'fsm' | 'claude-sdk';
|
|
21
26
|
export interface ClaudeSdkAgentOptions {
|
|
22
27
|
workflow: Workflow;
|
|
23
28
|
toolManager: ToolManager;
|
|
29
|
+
/** Store-facing one-liner. Used as the role block only when `systemPrompt` is absent. */
|
|
24
30
|
description: string;
|
|
31
|
+
/** The agent's system prompt (ZIG-1080). Preferred over `description` for the role block. */
|
|
32
|
+
systemPrompt?: string;
|
|
25
33
|
specialization?: string | null;
|
|
26
34
|
model: string;
|
|
27
35
|
anthropicKey: string;
|
|
@@ -35,6 +43,16 @@ export interface ClaudeSdkAgentOptions {
|
|
|
35
43
|
/** Injectable for tests. Defaults to @anthropic-ai/claude-agent-sdk query(). */
|
|
36
44
|
queryFn?: typeof query;
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* ZIG-1191 — "the transcript you asked me to resume is not here".
|
|
48
|
+
*
|
|
49
|
+
* Claude Code answers a `resume` of an unknown id with this, and it is the only
|
|
50
|
+
* failure that a fresh session reliably fixes: the id is valid-looking but its
|
|
51
|
+
* transcript lived on a container that is gone. Matched on the message because
|
|
52
|
+
* that is all the CLI gives us — no code, no typed error — so keep it narrow
|
|
53
|
+
* enough that a different failure never claims the retry.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isMissingSessionError(err: unknown): boolean;
|
|
38
56
|
/**
|
|
39
57
|
* A Ziggs agent whose brain is the Claude Agent SDK. Same host, same `eff`
|
|
40
58
|
* side channel, same tool surface as the FSM `Agent` — the ONLY difference is
|
|
@@ -44,7 +62,7 @@ export interface ClaudeSdkAgentOptions {
|
|
|
44
62
|
export declare class ClaudeSdkAgent {
|
|
45
63
|
workflow: Workflow;
|
|
46
64
|
toolManager: ToolManager;
|
|
47
|
-
private
|
|
65
|
+
private rolePrompt;
|
|
48
66
|
private specialization;
|
|
49
67
|
private model;
|
|
50
68
|
private anthropicKey;
|
|
@@ -53,6 +71,13 @@ export declare class ClaudeSdkAgent {
|
|
|
53
71
|
private maxTurns;
|
|
54
72
|
private statelessPerWake;
|
|
55
73
|
private queryFn;
|
|
74
|
+
/**
|
|
75
|
+
* ZIG-1120 #7 — tool set is fixed at host construction; rebuilding the
|
|
76
|
+
* in-process MCP server (+ zod schema tree) every wake was pure waste.
|
|
77
|
+
* A mutable holder supplies per-wake context without closing over `tick`.
|
|
78
|
+
*/
|
|
79
|
+
private toolContextHolder;
|
|
80
|
+
private sdkMcpServer;
|
|
56
81
|
constructor(opts: ClaudeSdkAgentOptions);
|
|
57
82
|
tick({ event, ctx, state, identity, eff, signal }: TickInput): Promise<TickOutput>;
|
|
58
83
|
}
|