@ziggs-ai/agent-sdk 0.10.8 → 0.11.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.
Files changed (104) hide show
  1. package/CHANGELOG.md +11 -11
  2. package/README.md +1 -1
  3. package/dist/AgentHost.d.ts +96 -16
  4. package/dist/AgentHost.js +205 -25
  5. package/dist/adapters/AnthropicAdapter.js +1 -1
  6. package/dist/adapters/McpAdapter.d.ts +10 -10
  7. package/dist/adapters/McpAdapter.js +31 -21
  8. package/dist/adapters/OpenAIAdapter.js +1 -1
  9. package/dist/agent/Agent.d.ts +2 -2
  10. package/dist/agent/ClaudeSdkAgent.d.ts +15 -6
  11. package/dist/agent/ClaudeSdkAgent.js +149 -48
  12. package/dist/agent/claudeCognition.d.ts +3 -3
  13. package/dist/agent/claudeCognition.js +7 -7
  14. package/dist/agent/claudeSdkTools.d.ts +3 -3
  15. package/dist/agent/claudeSdkTools.js +34 -15
  16. package/dist/agent/hostedAgentConnection.d.ts +1 -1
  17. package/dist/agent/hostedAgentConnection.js +1 -1
  18. package/dist/agent/toolManagerToSdkMcp.d.ts +1 -1
  19. package/dist/agent/toolManagerToSdkMcp.js +2 -2
  20. package/dist/context/applyEffects.js +1 -1
  21. package/dist/defineAgent.js +33 -4
  22. package/dist/formatters/HistoryFormatter.js +2 -2
  23. package/dist/index.d.ts +4 -2
  24. package/dist/index.js +8 -5
  25. package/dist/ingress/normalizeIncoming.d.ts +1 -1
  26. package/dist/ingress/normalizeIncoming.js +7 -7
  27. package/dist/pricing/fleetDefaults.d.ts +3 -3
  28. package/dist/pricing/fleetDefaults.js +3 -3
  29. package/dist/pricing/fleetEvalFree.d.ts +1 -1
  30. package/dist/pricing/fleetEvalFree.js +1 -1
  31. package/dist/pricing/fleetFreeTierA.gen.d.ts +1 -1
  32. package/dist/pricing/fleetFreeTierA.gen.js +1 -1
  33. package/dist/pricing/fleetTierByAgentId.gen.d.ts +2 -2
  34. package/dist/pricing/fleetTierByAgentId.gen.js +9 -2
  35. package/dist/runtime/AgentMachine.d.ts +1 -1
  36. package/dist/runtime/AgentMachine.js +8 -8
  37. package/dist/runtime/PromptBuilder.d.ts +2 -2
  38. package/dist/runtime/PromptBuilder.js +3 -3
  39. package/dist/runtime/bootstrapCtx.d.ts +1 -1
  40. package/dist/runtime/bootstrapCtx.js +2 -2
  41. package/dist/runtime/buildOutcome.d.ts +1 -1
  42. package/dist/runtime/buildOutcome.js +4 -4
  43. package/dist/runtime/defaults.d.ts +5 -5
  44. package/dist/runtime/defaults.js +4 -4
  45. package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
  46. package/dist/runtime/deliveryWriteKeys.js +3 -3
  47. package/dist/runtime/kickoff.d.ts +3 -3
  48. package/dist/runtime/kickoff.js +3 -3
  49. package/dist/runtime/runMechanical.d.ts +1 -1
  50. package/dist/runtime/runMechanical.js +1 -1
  51. package/dist/runtime/runTurn.d.ts +1 -1
  52. package/dist/runtime/runTurn.js +10 -10
  53. package/dist/runtime/taskBookkeeping.d.ts +3 -3
  54. package/dist/runtime/taskBookkeeping.js +3 -3
  55. package/dist/runtime/taskIntake.js +2 -2
  56. package/dist/server/ConnectionPool.js +1 -1
  57. package/dist/server/EventQueue.d.ts +1 -1
  58. package/dist/server/EventQueue.js +1 -1
  59. package/dist/server/SeenMessages.d.ts +2 -2
  60. package/dist/server/SeenMessages.js +2 -2
  61. package/dist/server/ZiggsEffectHandler.d.ts +2 -2
  62. package/dist/server/ZiggsEffectHandler.js +18 -9
  63. package/dist/server/agreements/AgreementService.d.ts +3 -8
  64. package/dist/server/agreements/AgreementService.js +4 -7
  65. package/dist/server/inboxLoop.d.ts +5 -5
  66. package/dist/server/inboxLoop.js +27 -11
  67. package/dist/server/polls/PollRunner.d.ts +36 -1
  68. package/dist/server/polls/PollRunner.js +42 -2
  69. package/dist/server/questMatch.d.ts +1 -1
  70. package/dist/server/questMatch.js +1 -1
  71. package/dist/server/runLauncher.js +3 -3
  72. package/dist/server/tasks/TaskService.d.ts +5 -5
  73. package/dist/server/tools/index.d.ts +1 -0
  74. package/dist/server/tools/index.js +7 -6
  75. package/dist/server/tools/tier1/protocolRunner.d.ts +1 -1
  76. package/dist/server/tools/tier1/protocolRunner.js +19 -19
  77. package/dist/server/tools/tier1/protocolTools.js +4 -4
  78. package/dist/server/tools/tier2/artifactTools.js +2 -2
  79. package/dist/server/tools/tier2/chatTools.js +2 -2
  80. package/dist/server/tools/tier2/connectionTools.js +3 -3
  81. package/dist/server/tools/tier2/contextTools.js +1 -1
  82. package/dist/server/tools/tier2/discoveryTools.js +2 -2
  83. package/dist/server/tools/tier2/grantsTools.js +1 -1
  84. package/dist/server/tools/tier2/linkTools.js +3 -3
  85. package/dist/server/tools/tier2/marketplaceTools.js +1 -1
  86. package/dist/server/tools/tier2/mcpConnectionTools.d.ts +4 -0
  87. package/dist/server/tools/tier2/mcpConnectionTools.js +136 -0
  88. package/dist/server/tools/tier2/paymentTools.js +1 -1
  89. package/dist/shared/operatorKeyClaims.d.ts +2 -2
  90. package/dist/shared/operatorKeyClaims.js +1 -1
  91. package/dist/tasks/protocolRegistry.d.ts +1 -1
  92. package/dist/tasks/protocolRegistry.js +1 -1
  93. package/dist/tools/ToolManager.d.ts +1 -1
  94. package/dist/tools/ToolManager.js +3 -2
  95. package/dist/tools/capabilityAdapter.d.ts +1 -1
  96. package/dist/tools/capabilityAdapter.js +3 -3
  97. package/dist/tools/defineTool.d.ts +7 -0
  98. package/dist/tools/defineTool.js +13 -1
  99. package/dist/tools/needs.d.ts +54 -0
  100. package/dist/tools/needs.js +84 -0
  101. package/dist/tools/recordReport.d.ts +3 -3
  102. package/dist/tools/recordReport.js +3 -3
  103. package/dist/types.d.ts +28 -18
  104. package/package.json +3 -3
package/dist/AgentHost.js CHANGED
@@ -16,7 +16,8 @@ import { PROTOCOL_TOOLS } from './server/tools/tier1/protocolTools.js';
16
16
  import { marketplaceViewTool } from './server/tools/tier2/marketplaceTools.js';
17
17
  import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
18
18
  import { AnthropicAdapter } from './adapters/AnthropicAdapter.js';
19
- import { ArtifactsClient, ContextReadClient, TelemetryClient, sendChatMessage, } from '@ziggs-ai/api-client';
19
+ import { ArtifactsClient, ConnectionsClient, ContextReadClient, TelemetryClient, sendChatMessage, } from '@ziggs-ai/api-client';
20
+ import { normalizeNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
20
21
  import { TaskService } from './server/tasks/TaskService.js';
21
22
  import { AgreementService } from './server/agreements/AgreementService.js';
22
23
  import { runInboxLoop } from './server/inboxLoop.js';
@@ -40,13 +41,13 @@ async function loadConnectionPool() {
40
41
  }
41
42
  const DEFAULT_SESSION_RESOLVER = (normalized) => normalized.chatId;
42
43
  /**
43
- * ZIG-975: one lane key per unit of work, derived the same way as inbox wakes.
44
+ * one lane key per unit of work, derived the same way as inbox wakes.
44
45
  *
45
46
  * Inbox deliveries key on `chatId` (DEFAULT_SESSION_RESOLVER). Bootstrap used to
46
47
  * prefer bare `agreementId`, which opened a second lane for the same task and
47
48
  * made the snapshot read treat the agreement id as a chat (`chatMissing: true`,
48
49
  * empty context). Prefer the origin chat when present; otherwise the agreement's
49
- * notification chat (`agrn-<agreementId>`), which is always linked after ZIG-966.
50
+ * notification chat (`agrn-<agreementId>`), which is always linked after.
50
51
  */
51
52
  export function laneSessionIdForTask(task) {
52
53
  if (typeof task.originChatId === 'string' && task.originChatId) {
@@ -69,13 +70,13 @@ function createToolManager(options) {
69
70
  taskTools = PROTOCOL_TOOLS; // 'all' or undefined
70
71
  const claudeSdkTools = isClaudeCognition(options.cognition) ? CLAUDE_SDK_TOOLS : [];
71
72
  const ownTools = Array.isArray(userTools) ? userTools : [];
72
- // ZIG-1280 — browse rides wherever claim rides. Posted-first engagements
73
+ // browse rides wherever claim rides. Posted-first engagements
73
74
  // START at marketplace_view (find the listing), so an agent that can
74
75
  // agreement_claim but cannot browse holds a default pathway it cannot begin.
75
76
  // Both directions: shedding claim (taskTools array without it) sheds browse.
76
77
  const canClaim = taskTools.some((t) => t.schema.function.name === 'agreement_claim');
77
78
  const browseTools = canClaim ? [marketplaceViewTool] : [];
78
- // ZIG-1278 — chat_open is always-on so claim/provider kickoff (FSM) and the
79
+ // chat_open is always-on so claim/provider kickoff (FSM) and the
79
80
  // Claude always-on set can open the buyer chat after a store claim. CHAT_TOOLS
80
81
  // is also folded into CLAUDE_SDK_TOOLS; Map registration makes duplicates a no-op.
81
82
  const platformTools = [...taskTools, ...CHAT_TOOLS, ...browseTools, ...claudeSdkTools];
@@ -128,7 +129,7 @@ function createBackendServices(options) {
128
129
  const agentId = options.agentId;
129
130
  const artifactsClient = new ArtifactsClient(operatorKey, agentId);
130
131
  const contextReadClient = new ContextReadClient(operatorKey, agentId);
131
- // ZIG-1080: the role text is the prompt when the author split the two; the
132
+ // the role text is the prompt when the author split the two; the
132
133
  // store-facing `description` is only the fallback for agents that never did.
133
134
  const promptBuilder = new PromptBuilder({
134
135
  description: options.systemPrompt || options.description,
@@ -153,7 +154,7 @@ function createBackendServices(options) {
153
154
  * a transport (Ziggs WebSocket), and the lane router (per-session FIFO queue
154
155
  * + in-memory session state).
155
156
  *
156
- * transport (WS) → handleMessage → agent.tick(... , eff) → ...
157
+ * transport (WS) → handleMessage → agent.tick(.. , eff) → ..
157
158
  */
158
159
  export class AgentHost {
159
160
  options;
@@ -177,6 +178,8 @@ export class AgentHost {
177
178
  /** The per-agent inbox long-poll loop — this agent's sole wake source. */
178
179
  inboxLoop = null;
179
180
  inboxLoopEnabled;
181
+ /** Short-lived grant-provider cache for the declared-needs gate. */
182
+ grantProviderCache = null;
180
183
  constructor(options = {}) {
181
184
  const resolved = applyBrainToHostOptions(options);
182
185
  this.options = {
@@ -190,6 +193,11 @@ export class AgentHost {
190
193
  systemPrompt: resolved.systemPrompt,
191
194
  specialization: resolved.specialization ?? null,
192
195
  tools: resolved.tools || [],
196
+ // Copied deliberately: this object is an allow-list, so a field left out
197
+ // here is silently dropped between the manifest and the runtime. That is
198
+ // exactly how `respondsToProposals` reached the store as null for 133
199
+ // agents — the declaration existed and nothing carried it.
200
+ needs: resolved.needs,
193
201
  taskTools: resolved.taskTools,
194
202
  states: resolved.states,
195
203
  initial: resolved.initial,
@@ -200,6 +208,7 @@ export class AgentHost {
200
208
  effectTap: resolved.effectTap,
201
209
  transitionTap: resolved.transitionTap,
202
210
  metricsTap: resolved.metricsTap,
211
+ wakeTap: resolved.wakeTap,
203
212
  inboxCatchUp: resolved.inboxCatchUp,
204
213
  brain: resolved.brain,
205
214
  cognition: resolved.cognition ?? 'fsm',
@@ -209,9 +218,9 @@ export class AgentHost {
209
218
  this.inboxLoopEnabled = options.inboxCatchUp !== false;
210
219
  this.sessionResolver = options.sessionResolver || DEFAULT_SESSION_RESOLVER;
211
220
  // A Claude-brain wake legitimately runs long (multi-turn query, prose
212
- // deliverables) — 120s watchdogged real spec-writing wakes mid-flight,
213
- // which then redelivered and re-ran (ZIG-924 dogfood, 2026-07-26). FSM
214
- // turns stay on the tight default.
221
+ // deliverables). At 120s the watchdog cuts real spec-writing wakes
222
+ // mid-flight, which then redeliver and re-run. FSM turns stay on the tight
223
+ // default.
215
224
  this.tickTimeoutMs =
216
225
  options.tickTimeoutMs ??
217
226
  (isClaudeCognition(options.cognition)
@@ -319,7 +328,7 @@ export class AgentHost {
319
328
  agentId: this.options.agentId,
320
329
  // No taskService/agreementService: Claude-brain tools execute through
321
330
  // eff({ kind: 'tool-call' }) → the shared executor, which sources those
322
- // services from the effect handler's deps like the FSM path (ZIG-775).
331
+ // services from the effect handler's deps like the FSM path.
323
332
  maxTurns: this.options.claudeMaxTurns,
324
333
  statelessPerWake: this.options.claudeStatelessPerWake,
325
334
  })
@@ -331,7 +340,7 @@ export class AgentHost {
331
340
  this.eventQueue = new EventQueue((event, laneKey) => this._processEvent(event, laneKey));
332
341
  // A timer frame is not a delivery: there is no watermark waiting on it and
333
342
  // nothing to redeliver, so its failure is logged and dropped here rather
334
- // than left as an unhandled rejection (ZIG-1191 made enqueue reject).
343
+ // than left as an unhandled rejection (made enqueue reject).
335
344
  this.stateTimers = new StateTimers((ev, laneKey) => {
336
345
  void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
337
346
  runtimeLog.warn('AgentHost', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
@@ -366,7 +375,7 @@ export class AgentHost {
366
375
  event: { type: 'message', timestamp: Date.now(), text: '' },
367
376
  };
368
377
  let sessionId = this.sessionResolver(fakeNormalized, ownAgentId);
369
- // ZIG-1278 — store claims deliver agreement wakes with no chatId. Do not
378
+ // store claims deliver agreement wakes with no chatId. Do not
370
379
  // drop them; lane by agreement the same way task bootstrap does
371
380
  // (`agrn-<agreementId>`), never a bare agreement id.
372
381
  if (!sessionId) {
@@ -415,7 +424,7 @@ export class AgentHost {
415
424
  }
416
425
  let enrichedText = typeof text === 'string' ? text : '';
417
426
  if (!enrichedText.trim()) {
418
- // ZIG-936: agreement rides under `service` on the inbox-pull wire; fall
427
+ // agreement rides under `service` on the inbox-pull wire; fall
419
428
  // back to top-level for hand-built metadata.
420
429
  const svc = metadata.service;
421
430
  const agreement = (svc?.agreement ?? metadata.agreement);
@@ -441,7 +450,7 @@ export class AgentHost {
441
450
  // routes the event onto its lane. No live-path watermark ack — there is no
442
451
  // separate live/push channel to reconcile against anymore.
443
452
  //
444
- // ZIG-1191: a failure un-marks the messageId. The mark above is what stops
453
+ // a failure un-marks the messageId. The mark above is what stops
445
454
  // the same message being processed twice; leaving it set on a failed wake
446
455
  // would make the redelivery this rejection buys us a no-op.
447
456
  try {
@@ -454,7 +463,7 @@ export class AgentHost {
454
463
  }
455
464
  }
456
465
  /**
457
- * ZIG-1144 part 3 — an agreement lane outlives the agreement.
466
+ * part 3 — an agreement lane outlives the agreement.
458
467
  *
459
468
  * A lane named `agrn-<agreementId>` keeps its Claude session for the whole
460
469
  * engagement, so a trailing event after the deal ends pays for all of it: on
@@ -486,11 +495,11 @@ export class AgentHost {
486
495
  }
487
496
  }
488
497
  /**
489
- * ZIG-1153 — the trailing turn on work that is already reported.
498
+ * the trailing turn on work that is already reported.
490
499
  *
491
500
  * A lane session carries the whole engagement, so the LAST wake is the most
492
501
  * 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 ZIG-816 reset
502
+ * **158,733 tokens** with the session's input at 366,417 before reset
494
503
  * it, and produced nothing — the spec had been written and the task closed one
495
504
  * frame earlier.
496
505
  *
@@ -518,6 +527,90 @@ export class AgentHost {
518
527
  const reported = result.result;
519
528
  return reported !== undefined && reported !== null && reported !== '';
520
529
  }
530
+ /** Providers of this agent's live connection grants. Its own seam, so the gate below is testable without a backend. */
531
+ async _listGrantProviders(operatorKey, agentId) {
532
+ const groups = await new ConnectionsClient(operatorKey, agentId).listForHolder();
533
+ return groups.filter((g) => (g.grants ?? []).length > 0).map((g) => g.provider);
534
+ }
535
+ /**
536
+ * Short TTL on purpose: the gate runs on every wake, and a claim produces
537
+ * several in seconds — but hire comes before grant, so a grant arriving
538
+ * mid-engagement has to take effect without a restart.
539
+ */
540
+ async _grantProvidersCached(operatorKey, agentId) {
541
+ const now = Date.now();
542
+ if (this.grantProviderCache && now - this.grantProviderCache.at < 30_000) {
543
+ return this.grantProviderCache.providers;
544
+ }
545
+ const providers = await this._listGrantProviders(operatorKey, agentId);
546
+ this.grantProviderCache = { at: now, providers };
547
+ return providers;
548
+ }
549
+ /**
550
+ * Refuse work this agent has no way to do, before it costs a turn.
551
+ * Returns true when work was refused and the tick must not run.
552
+ *
553
+ * Enforced at wake for an agent holding assigned work, not at hire: hire comes
554
+ * first and the grant second, so a hire-time gate would refuse every honest
555
+ * one. Not limited to the task frame either — a store claim wakes the agent on
556
+ * the agreement first, and a task-only gate let the model take that turn and
557
+ * reason its own way to "I have no grant" (measured on linear-janitor: 8 calls,
558
+ * 114,025 tokens, for what one HTTP call answers).
559
+ *
560
+ * It fails the task rather than skipping quietly, which would leave it active
561
+ * with nothing working it — the same invisible stall from the buyer's side.
562
+ */
563
+ async _refuseIfNeedsUnmet(_event, _laneKey) {
564
+ const needs = normalizeNeeds(this.options.needs);
565
+ if (!needs.length)
566
+ return false;
567
+ const operatorKey = this.options.operatorKey;
568
+ const agentId = this.options.agentId;
569
+ if (!operatorKey || !agentId || !this.taskService)
570
+ return false;
571
+ let providers;
572
+ try {
573
+ providers = await this._grantProvidersCached(operatorKey, agentId);
574
+ }
575
+ catch (err) {
576
+ // A grants read that failed is not proof the grant is missing, and failing
577
+ // the task on our own outage would be worse than letting the agent try.
578
+ runtimeLog.warn(this.options.name || 'agent', `needs check skipped: ${err?.message ?? err}`);
579
+ return false;
580
+ }
581
+ const unmet = unmetNeeds(needs, providers);
582
+ if (!unmet.length)
583
+ return false;
584
+ // Only work already on this agent's plate is refused. A wake with no assigned
585
+ // task is someone talking to it — an agent that cannot reach Linear can still
586
+ // say so in a sentence, and muting it would be a worse silence.
587
+ let assigned;
588
+ try {
589
+ assigned = await this.taskService.getActiveTasksForAgent(agentId);
590
+ }
591
+ catch (err) {
592
+ runtimeLog.warn(this.options.name || 'agent', `needs check could not read assigned work: ${err?.message ?? err}`);
593
+ return false;
594
+ }
595
+ const mine = assigned.filter((t) => t.taskId && (!t.assigneeId || t.assigneeId === agentId));
596
+ if (!mine.length)
597
+ return false;
598
+ const message = unmetNeedsMessage(unmet);
599
+ for (const task of mine) {
600
+ const taskId = String(task.taskId);
601
+ runtimeLog.warn(this.options.name || 'agent', `refusing task ${taskId} — ${message}`);
602
+ try {
603
+ await this.taskService.updateState(taskId, 'failed', {
604
+ summary: message,
605
+ status: 'failed',
606
+ });
607
+ }
608
+ catch (err) {
609
+ runtimeLog.warn(this.options.name || 'agent', `could not fail task ${taskId} after refusing it: ${err?.message ?? err}`);
610
+ }
611
+ }
612
+ return true;
613
+ }
521
614
  async _processEvent(event, laneKey) {
522
615
  const session = this.sessions.get(laneKey);
523
616
  if (!session?.sessionId)
@@ -530,6 +623,10 @@ export class AgentHost {
530
623
  runtimeLog.info(this.options.name || 'agent', `skipping wake on ${session.sessionId} — its task already reported a result`);
531
624
  return;
532
625
  }
626
+ // Before the tick spends anything: work this agent declared it cannot do
627
+ // without access it does not hold.
628
+ if (await this._refuseIfNeedsUnmet(event, laneKey))
629
+ return;
533
630
  // Stale timer frames (superseded epoch) are dropped BEFORE touching the
534
631
  // live timer — a pure-stale frame must not reset a freshly armed countdown.
535
632
  const frame = this.stateTimers.stripStale(event, laneKey);
@@ -557,6 +654,24 @@ export class AgentHost {
557
654
  sessionId: session.sessionId,
558
655
  laneKey,
559
656
  };
657
+ // Recorded here rather than after the tick: this is the last point where the
658
+ // input is still exactly what the brain will see, and a wake that goes on to
659
+ // throw is the one most worth replaying.
660
+ if (this.options.wakeTap) {
661
+ try {
662
+ this.options.wakeTap({
663
+ agentId: identity.agentId,
664
+ sessionId: identity.sessionId,
665
+ laneKey,
666
+ event: frame,
667
+ ctx: session.ctx,
668
+ state: session.state,
669
+ });
670
+ }
671
+ catch (err) {
672
+ runtimeLog.warn('AgentHost', `wakeTap failed: ${err?.message || err}`);
673
+ }
674
+ }
560
675
  // The watchdog timer must be cleared once the tick settles — leaving it
561
676
  // armed pinned one live 2-minute timer per tick (and kept short-lived
562
677
  // processes/tests alive until it fired).
@@ -589,7 +704,7 @@ export class AgentHost {
589
704
  runtimeLog.info('AgentHost', `wake agent=${identity.agentId} lane=${laneKey} llmCalls=${m.llmCalls} ` +
590
705
  `tokens_in=${m.tokens.input} tokens_out=${m.tokens.output} total=${m.tokens.total}`);
591
706
  }
592
- // Single source of truth for usage/cost (ZIG-813): emit the tick's
707
+ // Single source of truth for usage/cost: emit the tick's
593
708
  // authoritative token metrics once, for every cognition. Never let an
594
709
  // observability failure break the tick.
595
710
  if (this.options.metricsTap && result.metrics) {
@@ -624,7 +739,7 @@ export class AgentHost {
624
739
  // again unless a real event happens to arrive.
625
740
  this._armIfTimedParked(laneKey, session.state ?? this.agent.workflow.initial);
626
741
  // Best-effort: also notify the user so the conversation isn't silently
627
- // stuck (ZIG-607). Complements the timer re-arm above — that recovers a
742
+ // stuck. Complements the timer re-arm above — that recovers a
628
743
  // parked lane; this tells a waiting human.
629
744
  const ev = event;
630
745
  const senderType = String(ev?.senderType ?? '').toUpperCase();
@@ -632,7 +747,7 @@ export class AgentHost {
632
747
  if (senderId && senderType === 'USER') {
633
748
  this.eff({ kind: 'send-message', sessionId: session.sessionId, receiverId: senderId, text: 'Something went wrong. Please try again.' }).catch(() => { });
634
749
  }
635
- // ZIG-1191: and then say so. Everything above is recovery for the lane —
750
+ // and then say so. Everything above is recovery for the lane —
636
751
  // it does not mean the event was handled, and the caller is about to
637
752
  // decide whether the delivery that carried it can be acked. Swallowing
638
753
  // here is what turned a failed wake into a lost one: two hand-off publish
@@ -644,6 +759,71 @@ export class AgentHost {
644
759
  clearTimeout(tickWatchdog);
645
760
  }
646
761
  }
762
+ /**
763
+ * Re-run one recorded wake against THIS host's agent, and nothing else.
764
+ *
765
+ * The dev loop for a prompt: record a wake off a live run, edit the agent, and
766
+ * see what it does differently in seconds instead of standing a world back up
767
+ * for one turn. The brain, tools and effect handler are the real ones, so the
768
+ * turn is real — its tool calls hit the backend this host is pointed at, which
769
+ * is why it belongs against a disposable world and not a shared one.
770
+ *
771
+ * Deliberately NOT routed through `_processEvent`: everything that method does
772
+ * around the tick is lane bookkeeping — session state, liveness timers, session
773
+ * persistence, ack decisions, failure re-arm — and a replay has no lane to keep.
774
+ * Borrowing that path would make a dev tool able to disturb a live one.
775
+ */
776
+ async replayWake(record) {
777
+ if (!record?.sessionId)
778
+ throw new Error('replayWake: record.sessionId is required');
779
+ const identity = {
780
+ agentId: record.agentId || this.options.agentId,
781
+ sessionId: record.sessionId,
782
+ laneKey: record.laneKey || record.sessionId,
783
+ };
784
+ // Same watchdog as a live wake, for the same reason: an unaborted Claude
785
+ // query outlives the promise that abandoned it and keeps spending.
786
+ const abort = new AbortController();
787
+ let watchdog;
788
+ try {
789
+ const result = await Promise.race([
790
+ this.agent.tick({
791
+ event: record.event,
792
+ ctx: record.ctx,
793
+ state: record.state,
794
+ identity,
795
+ eff: this.eff,
796
+ memory: this.memory,
797
+ transitionTap: this.options.transitionTap,
798
+ signal: abort.signal,
799
+ }),
800
+ new Promise((_, reject) => {
801
+ watchdog = setTimeout(() => {
802
+ abort.abort(new Error('tick timeout'));
803
+ reject(new Error('tick timeout'));
804
+ }, this.tickTimeoutMs);
805
+ }),
806
+ ]);
807
+ if (this.options.metricsTap && result.metrics) {
808
+ try {
809
+ this.options.metricsTap({
810
+ agentId: identity.agentId,
811
+ sessionId: identity.sessionId,
812
+ model: this.options.model ?? 'unknown',
813
+ cognition: this.options.cognition ?? 'fsm',
814
+ metrics: result.metrics,
815
+ });
816
+ }
817
+ catch (err) {
818
+ runtimeLog.warn('AgentHost', `metricsTap failed: ${err?.message || err}`);
819
+ }
820
+ }
821
+ return result;
822
+ }
823
+ finally {
824
+ clearTimeout(watchdog);
825
+ }
826
+ }
647
827
  /** Arms the lane's liveness timer iff stateName is a parked state declaring timeoutMs. */
648
828
  _armIfTimedParked(laneKey, stateName) {
649
829
  if (!stateName)
@@ -682,7 +862,7 @@ export class AgentHost {
682
862
  onResourceEvent: (event) => {
683
863
  return this.handleResourceEvent(event);
684
864
  },
685
- // ZIG-973: work delivered by the inbox's task channel, not by a chat
865
+ // work delivered by the inbox's task channel, not by a chat
686
866
  // row — this is what reaches a task under a chat-less agreement or one
687
867
  // the agent assigned to itself.
688
868
  onTask: async (task) => {
@@ -693,7 +873,7 @@ export class AgentHost {
693
873
  runtimeLog.warn(this.options.name || 'agent', `inbox task ${task.taskId} could not be woken: ${err.message}`);
694
874
  }
695
875
  },
696
- // ZIG-1185: exact-match triage in code — miss acks with 0 tokens.
876
+ // exact-match triage in code — miss acks with 0 tokens.
697
877
  onQuest: async (quest) => {
698
878
  await this.triageQuestDelivery(quest);
699
879
  },
@@ -702,7 +882,7 @@ export class AgentHost {
702
882
  return this;
703
883
  }
704
884
  /**
705
- * ZIG-1185 — plain string compare against {@link AgentHostOptions.questMatchTags}
885
+ * plain string compare against {@link AgentHostOptions.questMatchTags}
706
886
  * and `specialization`. No match → return (caller acks). Match → normal wake.
707
887
  */
708
888
  async triageQuestDelivery(quest) {
@@ -768,7 +948,7 @@ export class AgentHost {
768
948
  }, laneKey);
769
949
  }
770
950
  }
771
- /** ZIG-802: replay task-assigned for an existing active task (demo recovery). */
951
+ /** replay task-assigned for an existing active task (demo recovery). */
772
952
  async wakeTaskAssignment(taskId) {
773
953
  const agentId = this.options.agentId;
774
954
  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 ZIG-759 drain).
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 (ZIG-771): the agent holds NO
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
- * (ZIG-771). Without it, a NO_USER_CONNECTION surfaces as an error rather
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 (ZIG-702). `code` is the stable
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 (ZIG-771). No connectionId/grantId —
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 ... array too long`. When the server's tool count
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 ZIG-759; Phase 2 will replace truncation with query-aware selection.
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). ZIG-759 Phase 2 = name-based selection.
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
- * (ZIG-771) — there is no boot connection to call `tools/list` on, so the
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. ZIG-759.
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
- * ZIG-759.
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. */