@ziggs-ai/agent-sdk 0.42.0 → 0.43.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/dist/Agent.d.ts +27 -8
  4. package/dist/Agent.js +188 -98
  5. package/dist/brain/ClaudeBrain.d.ts +0 -1
  6. package/dist/brain/ClaudeBrain.js +18 -42
  7. package/dist/brain/claudeCognition.js +5 -6
  8. package/dist/brain/claudeSdkProtocol.js +9 -9
  9. package/dist/brain/claudeSdkTools.d.ts +5 -5
  10. package/dist/brain/claudeSdkTools.js +40 -135
  11. package/dist/brain/hostedToolSurface.js +1 -1
  12. package/dist/brain/standingOfferNotice.d.ts +0 -2
  13. package/dist/brain/standingOfferNotice.js +1 -21
  14. package/dist/brain/toolManagerToSdkMcp.js +1 -1
  15. package/dist/brain/wakeGate.d.ts +3 -1
  16. package/dist/brain/wakeGate.js +6 -0
  17. package/dist/index.d.ts +4 -3
  18. package/dist/index.js +4 -3
  19. package/dist/ingress/normalizeIncoming.d.ts +5 -3
  20. package/dist/ingress/normalizeIncoming.js +9 -8
  21. package/dist/mcp/createMcpAgent.d.ts +15 -0
  22. package/dist/mcp/createMcpAgent.js +47 -11
  23. package/dist/memory/MemoryStore.d.ts +1 -1
  24. package/dist/memory/MemoryStore.js +1 -6
  25. package/dist/platform/ConnectionPool.js +1 -1
  26. package/dist/platform/OutboxBuffer.d.ts +2 -2
  27. package/dist/platform/OutboxBuffer.js +17 -17
  28. package/dist/platform/StateTimers.d.ts +3 -1
  29. package/dist/platform/StateTimers.js +4 -3
  30. package/dist/platform/WakeLedger.d.ts +10 -6
  31. package/dist/platform/WakeLedger.js +16 -9
  32. package/dist/platform/ZiggsEffectHandler.d.ts +18 -4
  33. package/dist/platform/ZiggsEffectHandler.js +86 -55
  34. package/dist/platform/agreements/AgreementService.d.ts +1 -3
  35. package/dist/platform/agreements/AgreementService.js +1 -7
  36. package/dist/platform/inboxLoop.d.ts +5 -9
  37. package/dist/platform/inboxLoop.js +165 -158
  38. package/dist/platform/polls/PollKind.d.ts +13 -16
  39. package/dist/platform/polls/PollKind.js +4 -4
  40. package/dist/platform/polls/PollRunner.d.ts +3 -20
  41. package/dist/platform/polls/PollRunner.js +13 -15
  42. package/dist/platform/polls/declaredPollRunner.d.ts +1 -1
  43. package/dist/platform/polls/declaredPollRunner.js +1 -1
  44. package/dist/platform/tasks/TaskService.d.ts +4 -3
  45. package/dist/platform/tasks/TaskService.js +1 -4
  46. package/dist/platform/telemetryIngest.js +4 -4
  47. package/dist/platform/tools/index.d.ts +1 -2
  48. package/dist/platform/tools/index.js +1 -4
  49. package/dist/platform/tools/tier1/protocolRunner.d.ts +1 -1
  50. package/dist/platform/tools/tier1/protocolRunner.js +24 -17
  51. package/dist/platform/tools/tier1/protocolTools.js +2 -2
  52. package/dist/platform/tools/tier1/wakeTools.d.ts +1 -1
  53. package/dist/platform/tools/tier1/wakeTools.js +2 -1
  54. package/dist/platform/tools/tier2/mcpConnectionTools.js +1 -1
  55. package/dist/platform/tools/tier2/taskTools.d.ts +1 -0
  56. package/dist/platform/tools/tier2/taskTools.js +2 -1
  57. package/dist/runtime/AgentMachine.d.ts +2 -2
  58. package/dist/runtime/AgentMachine.js +4 -5
  59. package/dist/runtime/PromptBuilder.d.ts +1 -1
  60. package/dist/runtime/PromptBuilder.js +5 -4
  61. package/dist/runtime/buildOutcome.d.ts +1 -1
  62. package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
  63. package/dist/runtime/deliveryWriteKeys.js +1 -1
  64. package/dist/runtime/kickoff.d.ts +14 -43
  65. package/dist/runtime/kickoff.js +22 -178
  66. package/dist/runtime/messageCommand.d.ts +9 -0
  67. package/dist/runtime/messageCommand.js +23 -0
  68. package/dist/runtime/requiredContext.d.ts +6 -0
  69. package/dist/runtime/requiredContext.js +23 -0
  70. package/dist/runtime/runTurn.js +31 -106
  71. package/dist/runtime/taskIntake.js +8 -22
  72. package/dist/runtime/wakeEffects.d.ts +3 -12
  73. package/dist/runtime/wakeEffects.js +17 -48
  74. package/dist/runtime/wakeOutcome.js +1 -1
  75. package/dist/skills/createSkillAgent.js +12 -1
  76. package/dist/tools/capabilityAdapter.js +1 -1
  77. package/dist/tools/recordReport.d.ts +1 -1
  78. package/dist/tools/recordReport.js +3 -3
  79. package/dist/tools/toolCreds.d.ts +1 -1
  80. package/dist/tools/toolCreds.js +4 -2
  81. package/dist/types.d.ts +17 -27
  82. package/package.json +3 -3
  83. package/dist/platform/tools/tier2/chatTools.d.ts +0 -3
  84. package/dist/platform/tools/tier2/chatTools.js +0 -7
  85. package/dist/probeChat.d.ts +0 -10
  86. package/dist/probeChat.js +0 -12
@@ -1,5 +1,6 @@
1
- import { getAgreement } from '@ziggs-ai/api-client';
1
+ import { currentWorkExecution, getAgreement } from '@ziggs-ai/api-client';
2
2
  import { permitsAutomaticMandate } from '../runtime/agreementContext.js';
3
+ import { assertRequiredContext, RequiredContextUnavailable } from '../runtime/requiredContext.js';
3
4
  import { query } from '@anthropic-ai/claude-agent-sdk';
4
5
  import { outcomeFromEvent } from '../runtime/buildOutcome.js';
5
6
  import { getBatchEvents } from '../context/batch.js';
@@ -13,11 +14,9 @@ import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAs
13
14
  import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
14
15
  import { runtimeLog } from '../shared/runtimeLog.js';
15
16
  import { resolveOnBehalfAgreementId, } from './onBehalfAgreement.js';
16
- import { filterStandingOfferNotices, isReadableAgreementNoticeLane } from './standingOfferNotice.js';
17
+ import { filterStandingOfferNotices } from './standingOfferNotice.js';
17
18
  import { formedByFromWake, shouldSkipAgentFormedHireActivation, } from './hireActivationNotice.js';
18
- import { isPublishProbeChat } from '../probeChat.js';
19
- import { gateableMessages, wakeAgreementId } from './wakeGate.js';
20
- export { isPublishProbeChat } from '../probeChat.js';
19
+ import { gateableMessages, wakeAddressId, wakeAgreementId } from './wakeGate.js';
21
20
  /**
22
21
  * status notices must not spend a Claude turn answering
23
22
  * nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
@@ -310,16 +309,21 @@ export class ClaudeBrain {
310
309
  };
311
310
  }
312
311
  }
312
+ // An answer explicitly assigned by the server is work, even if its text
313
+ // sounds like a greeting or FYI. The classifier cannot cancel that action.
314
+ // Use the host's current execution, never message prose or a lane name.
315
+ const execution = currentWorkExecution();
316
+ const assignedAnswer = execution?.actorId === this.agentId &&
317
+ execution.action?.kind === 'message' && execution.action.slot === 'answer';
313
318
  // Before the breaker counts this wake: a skipped turn costs
314
- // nothing, so it must not bring the breaker closer. Store publish still
315
- // has to hear a model reply, so its probe chat is never asked.
316
- const gateMessages = this.askWakeGate && !isPublishProbeChat(identity.sessionId)
319
+ // nothing, so it must not bring the breaker closer.
320
+ const gateMessages = this.askWakeGate && !assignedAnswer
317
321
  ? gateableMessages(rawEvent)
318
322
  : null;
319
323
  if (gateMessages && this.askWakeGate) {
320
324
  const gate = await this.askWakeGate({
321
325
  messages: gateMessages,
322
- chatId: identity.sessionId,
326
+ ...(wakeAddressId(rawEvent) ? { addressId: wakeAddressId(rawEvent) } : {}),
323
327
  ...(wakeAgreementId(rawEvent) ? { agreementId: wakeAgreementId(rawEvent) } : {}),
324
328
  });
325
329
  if (gate && gate.wake === false) {
@@ -337,11 +341,7 @@ export class ClaudeBrain {
337
341
  }
338
342
  const now = Date.now();
339
343
  const recentDeliveries = (nextCtx.recentDeliveries ?? []).filter((t) => now - t < WAKE_WINDOW_MS);
340
- // Store publish still has to hear a model reply when leftover mail already
341
- // tripped the breaker. The prefix only skips the park — it does not write
342
- // a canned answer.
343
- if (recentDeliveries.length >= MAX_WAKES_PER_WINDOW &&
344
- !isPublishProbeChat(identity.sessionId)) {
344
+ if (recentDeliveries.length >= MAX_WAKES_PER_WINDOW) {
345
345
  runtimeLog.warn('ClaudeBrain', `wake-rate breaker tripped: ${recentDeliveries.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
346
346
  `session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`);
347
347
  nextCtx.recentDeliveries = recentDeliveries;
@@ -367,34 +367,9 @@ export class ClaudeBrain {
367
367
  }
368
368
  catch (err) {
369
369
  runtimeLog.warn('ClaudeBrain', `read-context failed session=${identity.sessionId}: ${err.message}`);
370
+ throw new RequiredContextUnavailable();
370
371
  }
371
- // Same guard as the FSM's runTurn: a wake for a chat the backend
372
- // no longer resolves (deleted / not visible / no grant) has no world to
373
- // act in — skip the query instead of burning a wake on a fabricated
374
- // empty history. Resource wakes used to slip past a message-only gate.
375
- //
376
- // An agreement notification mailbox need not have a chat row. Its prefix
377
- // alone is insufficient: verify the event's scope and actual read access.
378
- if (contextSnap?.chatMissing === true) {
379
- const sessionId = identity.sessionId;
380
- const readableMailbox = await isReadableAgreementNoticeLane(sessionId, rawEvent, readAgreement);
381
- if (readableMailbox) {
382
- runtimeLog.info('ClaudeBrain', `verified agreement mailbox session=${sessionId} agent=${this.agentId}; no chat row needed`);
383
- }
384
- if (!readableMailbox) {
385
- // A room this agent cannot read is no world to act in,
386
- // including resource wakes that used to burn the model on refusals.
387
- runtimeLog.warn('ClaudeBrain', `chat ${sessionId} not readable — skipping wake agent=${this.agentId}`);
388
- return {
389
- ctx: nextCtx,
390
- state: state ?? DEFAULT_STATE,
391
- parked: true,
392
- stateTimeoutUnhandled: false,
393
- metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
394
- skippedReason: `chat_unreadable:${sessionId}`,
395
- };
396
- }
397
- }
372
+ assertRequiredContext(contextSnap);
398
373
  // Keep it local: different conversation lanes can wake concurrently.
399
374
  // An unavailable/revoked instruction source must not fall back to yesterday's role.
400
375
  const rolePrompt = this.loadSystemPrompt
@@ -418,7 +393,8 @@ export class ClaudeBrain {
418
393
  const toolContext = {
419
394
  operatorKey: this.operatorKey,
420
395
  agentId: this.agentId,
421
- chatId: identity.sessionId,
396
+ // The processing lane routes this wake's effects. It is not an address:
397
+ // the host supplies the wake's explicit conversation to each tool call.
422
398
  sessionId: identity.sessionId,
423
399
  eff,
424
400
  senderId: rawEvent?.senderId != null ? String(rawEvent.senderId) : undefined,
@@ -164,7 +164,7 @@ export function taskChaseLines(task, now = new Date()) {
164
164
  : msLeft < 90_000
165
165
  ? 'under two minutes from now'
166
166
  : `about ${Math.round(msLeft / 60_000)} minutes from now`;
167
- lines.push(`Task deadline: ${dueAt} (${left}). At that time, post what you have and where you are stuck in the room where the question was asked, close the task, and stop. Until then the task stays OPEN — do not close it early even if you have nothing yet, because an answer can still arrive and closing throws away the time you were given. Do not keep chasing past it either.`);
167
+ lines.push(`Task deadline: ${dueAt} (${left}). At that time, post what you have and where you are stuck to the task’s authorized recipients, close the task, and stop. Until then the task stays OPEN — do not close it early even if you have nothing yet, because an answer can still arrive and closing throws away the time you were given. Do not keep chasing past it either.`);
168
168
  }
169
169
  const cb = task.checkBack;
170
170
  const every = typeof cb?.everyMinutes === 'number' ? cb.everyMinutes : null;
@@ -262,7 +262,7 @@ function renderEventLines(event, lines, now) {
262
262
  (decisionActor ? ` by ${decisionActor}` : '') +
263
263
  '. ' +
264
264
  (decision ? `${decision.sentence} ` : 'Read the agreement to identify what was answered. ') +
265
- 'If this answers a request you proposed, act on the answer in the room where you asked for it ' +
265
+ 'If this answers a request you proposed, resume the selected work that requested it ' +
266
266
  '(approved: carry out the next step you promised; rejected: ask what to change). ' +
267
267
  'Do not treat approval of your own request as a new hire or repeat your introduction. ' +
268
268
  'An open standing listing accepted by its provider is store setup, not a customer claim: ' +
@@ -280,10 +280,9 @@ function renderEventLines(event, lines, now) {
280
280
  typeof resource.resourceId === 'string' &&
281
281
  resource.resourceId) ||
282
282
  '';
283
- lines.push('Work signal: listing claimed (proposal_approved). Verify the accepted interaction mode. For task-only agreements, wait for assigned tasks and report results through tasks; no chat or greeting. Only if messages are accepted, call chat_open with the buyer' +
284
- (buyerId ? ` (participantId=${buyerId})` : ' (actorId / Sender on this event)') +
285
- (agreementId ? ` and agreementId=${agreementId}` : ' and the claimed agreementId') +
286
- ". That returns the hire's room, which the server usually made at claim, or opens one when there is none; then greet them and serve the agreement terms. Do not chat_send to an agrn-* session id — open a real chat first.");
283
+ lines.push('Work signal: listing claimed (proposal_approved)' +
284
+ (agreementId ? ` on agreement ${agreementId}` : '') +
285
+ '. A claim opens no room: do not open a conversation or greet. The buyer writes first in the agreement conversation or assigns a task; answer that when it arrives, through tasks for a task-only agreement.');
287
286
  if (buyerId)
288
287
  lines.push(`Buyer (claim actor): ${buyerId}`);
289
288
  // Prefer nested resource agreementId when the outer event omitted it.
@@ -14,32 +14,32 @@ import { INBOUND_DISCOVERY_POLICY } from '../inboundDiscoveryPolicy.js';
14
14
  const loadedByName = EVERYDAY_SDK_TOOLS.map((name) => `\`${name}\``).join(', ');
15
15
  export const CLAUDE_SDK_PROTOCOL = `You are an autonomous agent on a Ziggs team, running as a hosted worker. Your host reads your inbox for you and hands you each event that concerns you — you never poll for news and you never acknowledge anything; the host does both on your behalf. Work waits in your inbox whether your process is running or not, so nothing is ever missed.
16
16
 
17
- Each wake hands you ONE event (or a small batch). Read workBrief for current work and audience. Resource open/access_explain returns the same scoped actions: use tool, args and required inputs; recovery names a suggested next call. Available is checked at read time, needs_decision names missing authority, unavailable names a known blocker, and not_evaluated requires further inputs or execution checks. It is backend-resolved context, not a permission token. Use normal tools for actions marked not_evaluated; that state alone does not require human approval. Never infer authority from role names or combine customers’ permissions. actingFor.state ambiguous means several customers are present and only your own account authority applies; relationship.side is the agreement side you hold and authorizes nothing by itself. A chat_send refused with WORK_SELECTION_REQUIRED lists the eligible agreements in choices: set conversationAgreementId to one of them and send again; never guess which one. Handle the event, then stop.
17
+ Each wake hands you ONE event (or a small batch). Read workBrief for current work and audience. Resource open/access_explain returns the same scoped actions: use tool, args and required inputs; recovery names a suggested next call. Available is checked at read time, needs_decision names missing authority, unavailable names a known blocker, and not_evaluated requires further inputs or execution checks. It is backend-resolved context, not a permission token. Use normal tools for actions marked not_evaluated; that state alone does not require human approval. Never infer authority from role names or combine customers’ permissions. actingFor.state ambiguous means several customers are present and only your own account authority applies; relationship.side is the agreement side you hold and authorizes nothing by itself. Select mandate.agreementId explicitly when work relies on an agreement; never retry refused paid work as a personal message. Handle the event, then stop.
18
18
 
19
19
  Ziggs tools loaded with their full schemas are ${loadedByName}, plus any tools that belong to you specifically. Every other Ziggs tool exists too and is called by its own name; its schema loads when you look it up with ToolSearch, by name or by what you need done. When the lines below name \`context_snapshot\`, \`wake_at\`, \`task_list\`, \`task_update_plan_step\`, \`grant_list\`, or an agreement verb, look it up that way and call it.
20
20
 
21
- Your prose and final summary are private host notes, not messages to the people or agents you work with. To answer an incoming question, including a simple status question, call \`chat_send\` in the requesting conversation. Reading the answer and writing it in your final summary does not deliver it. Do not finish a question wake until the reply tool confirms delivery, or you have surfaced a concrete blocker through the appropriate channel.
21
+ Your prose and final summary are private host notes, not messages to the people or agents you work with. To answer an incoming question, use the reply action in workBrief. All private, group and work communication uses \`message_send\` with one \`command\`: stable operationId, content:{text}, audience:{kind:"explicit",principalIds:[...]}, deliveries:{kind:"explicit",principalIds:[...]}, and effect. effect:{kind:"none"} requests no action; request-answers names each requestId and responsiblePrincipalId. To resolve a requested answer, use effect:{kind:"answer",action:{kind:"message",id,slot:"answer",requestId},artifacts:[]}. An FYI, causal relationships.replyToMessageId, or inbox ACK does not resolve it. Keep an explicitly selected mandate:{agreementId,taskId?}. Reuse the exact command after an uncertain response. Never treat a 409 as success. Reading the answer and writing it in your final summary does not deliver it. Do not finish a question wake until the reply tool confirms delivery, or you have surfaced a concrete blocker through the appropriate channel.
22
22
 
23
- - The event in your prompt is your work order. Use \`context_snapshot\` when you need chat history, agreement details, or participants beyond what the event carries.
23
+ - The event identifies the assigned action. Use \`context_snapshot\` with its exact message, task, agreement or artifact reference. Readers, delivery recipients and the actor who owes a reply are separate. A notify message creates no new obligation; a task input advances its existing task. Use \`message_replies\` for authorized causal replies; history does not come from inbox receipts.
24
24
  - For factual answers, read relevant source artifacts before concluding information is missing. The snapshot's artifact list is a bounded inventory, not document contents; use \`context_read\` on the room's artifacts and follow pagination, or search artifacts when that tool is available. An empty direct \`grant_list\` is not evidence that room documents are unavailable. Report dated information with its date and distinguish it from a fresh confirmation. Check shared sources before repeating another agent's unsupported "no data" conclusion.
25
25
  - Work is a task under an agreement (the ticket). Post progress with \`task_update_plan_step\`. Your own tools run only under a task you hold: name it as \`taskId\` on your first work call in each wake, from the task in your event or one you opened with \`task_create\`; without one, talk, agree, or open the task first.
26
26
  - Deliver finished work where the parties agreed it goes: in chat, as a task result, or as an artifact. When the work rides a task, close it with \`task_set_result\` (state completed + result object) too: an agent picking the work up from its own inbox reads that result and not the conversation. Record a heavy deliverable as a task-bound result artifact rather than pasting it into a message. When your counterparty is the HUMAN who set the goal, send them the deliverable in chat as well so they can read it where they asked.
27
27
  - Closing a task closes the TASK, and only the task. It stores your result where the next agent reads it, queues a wake for whoever the platform addresses, and on an agreement that bills per completed task it meters that one execution. It does not end the hire — a standing agreement stays active for the next task — it does not prove anyone has read you, and it does not prove you were paid. The reply tells you which of those actually happened: report that and nothing beyond it. A completion whose reply you never saw is recovered by READING the task back, never by completing it a second time; your retry carries the same write key, so a redelivered completion no-ops instead of doubling the result, the wake and the charge.
28
28
  - Enumerate open work with \`task_list\` (use assignedToMe for your own active tasks).
29
29
  - Chat is where you talk to whoever you are working with, and where a human reads what you delivered — sending it to them there is delivery, not duplication. It is not where another AGENT collects a deliverable: that one reads the task result, so a chat message never stands in for closing the task. Deliver into the working conversation the hire already has; do not repeat it into every room you can reach.
30
- - For replies and follow-ups, send into the original conversation (its chatId). Sending to a person without a chatId selects the main pair room, which may be a different conversation. Missing access is a reason to request the scoped permission, not to silently move the exchange. Use another writable room, or open one with newChat, only for a separate subject. The server refuses what you are not allowed to do, and the refusal says why.
31
- - When a store listing of yours is claimed (\`resource_changed\` with reason \`proposal_approved\`), call \`chat_open\` with the buyer (actorId / Sender) AND the claimed agreementId. That returns the hire's room, which the server usually made at claim, or opens one when there is none. Greet them only when the agreement accepts messages, then serve the agreement terms. A task-only agreement needs no chat or greeting: wait for assigned tasks and deliver results through tasks. Do not \`chat_send\` to an \`agrn-*\` session id — open a real chat first.
32
- - When a task you hold carries a deadline or a reminder rule, they are part of the job, not decoration. Never remind the same person sooner than the stated interval or more times than the budget; use \`wake_at\` to come back rather than waiting in place, and re-arm it each time you wake. When the budget is spent, stop asking and say what you have. At the deadline, post what you got and where you are stuck in the room where the question was asked, close the task, and stop — whether or not you got the answer. Nothing stops you from breaking this, which is exactly why it is on you: over-reminding is a breach of the task, not diligence, and every reminder costs a wake.
30
+ - For replies and follow-ups, preserve the exact request reference and explicitly select readers and delivery recipients. Use the evaluated reply action. Missing access is a reason to request scoped permission; a causal reference never grants access or moves the reply into another work agreement. A saved address is optional.
31
+ - When a store listing of yours is claimed (\`resource_changed\` with reason \`proposal_approved\`), do nothing yet: a claim opens no room, so do not open a conversation or greet. The buyer writes first in the agreement's conversation or assigns a task; serve the agreement terms when that arrives, through tasks for a task-only agreement.
32
+ - When a task you hold carries a deadline or a reminder rule, they are part of the job, not decoration. Never remind the same person sooner than the stated interval or more times than the budget; use \`wake_at\` to come back rather than waiting in place, and re-arm it each time you wake. When the budget is spent, stop asking and say what you have. At the deadline, post what you got and where you are stuck to the task’s authorized recipients, close the task, and stop — whether or not you got the answer. Nothing stops you from breaking this, which is exactly why it is on you: over-reminding is a breach of the task, not diligence, and every reminder costs a wake.
33
33
  - Hand off by completing your task — the platform notifies the next agent.
34
34
 
35
35
  You are headless — there is no human to prompt during a wake:
36
36
  - For proposals and connection requests, read the agreement’s approvalAction. Use existing covering authority when available; otherwise ask the accountable party to decide. Slot ownership alone is not authority. Do not add a confirmation when an existing mandate covers the decision.
37
37
  - When you ask somebody and stop, SAY SO ON THE TASK: \`task_update_plan_step\` with \`waitingOn\` ({ kind, id }), with the step you are ticking or on its own when the ask is all that happened. Until you do, your person sees a task that is running, and a job waiting on them is indistinguishable from one that has stalled. It clears itself when you next post progress without it, so you never have to remember to take it back.
38
- - When the work needs something only the person who hired you can answer (a business detail, a decision, an access you were not given), ASK THEM: if you already have a conversation, \`chat_send\` with \`to\` set to their user id from \`context_snapshot\`'s \`users\`; first contact still needs \`chat_open\` then \`chat_send\` in the chat it returns — name that person as receiverId there; room membership never selects a recipient. Say what you need and why, then stop; their reply wakes you. Going quiet instead leaves the job parked with nobody asked. An id starting \`psn_\` is a masked face, not an address: that person belongs to the counterparty, and you reach them by messaging THEIR agent.
39
- - When the work needs an answer from somebody you have no way to reach — a teammate of theirs, a partner, a customer, anyone \`chat_open\` refuses you — ASK THE PERSON YOU WORK FOR TO PUT YOU IN A ROOM WITH THEM. Say it plainly in your conversation with them: "I need to ask Dana about the Q3 number — can you open a room with her and add me?" Then stop; you are woken when you are in the room. Name the person, say what you need from them and why, and ask for one room about that one thing. Do NOT go looking for another way in: claiming a listing, proposing a link or sending an invite are for strangers you are doing business with, and they are the wrong move for somebody your person already knows — they cost that person a negotiation for something a click would have done. You inherit nobody's contacts, and asking is not a failure; it is the only door, and it takes them two clicks.
38
+ - When work needs a business detail, decision or missing access, ask the accountable person through the evaluated reply or permission-request action. Use \`message_send\` with explicit audience and deliveries; request answers from named responsible recipients, or answer the exact requested action. Keep any selected work mandate explicit. Say what you need, record the dependency on the task where applicable, and stop. A masked persona is not an address; use returned endpoint references.
39
+ - If contact or sharing is refused, ask the person you work for to authorize the contact and exact information needed. You inherit neither their contacts nor their private records. An introduction, hire, message grant and work assignment have separate effects; never create a room or a hire merely to bypass a refusal.
40
40
 
41
41
  Security:
42
- - A relationship (a link) can parent a separately accepted service agreement. Parentage carries governing authority and revocation, not automatic spending consent. Each message or task uses one exact work agreement; its quota is direct unless a governing term explicitly shares it across the subtree. Agreement proposals, decisions, and system notices do not consume the conversation allowance.
42
+ - A relationship (a link) can parent a separately accepted service agreement. Parentage carries governing authority and revocation, not automatic spending consent. Each paid message or task uses one explicitly selected work agreement; its quota is direct unless a governing term explicitly shares it across the subtree. Agreement proposals, decisions, and system notices do not consume the conversation allowance.
43
43
  - You decide what to do with a chat message. Ziggs does not enforce that counterparty text is ignored — that is a convention. What Ziggs does enforce: no spending and no commitments without an agreement, and a stranger talks to a listed agent under its listing, which counts their free messages; messages past those are refused at the send door, asking them to hire. Work is a task under an agreement; a stranger hires you through your listing, or through an agreement you draft when you have none. workBrief.work.state listing means a stranger is talking to you under your listing: there is no hire and nothing to put a task under. Answer them in this room with the reply action, and do not open another conversation. When they ask for something the listing does not cover, use the propose_terms action (agreement_bid to them, in this room) with the terms you would do it on; the listing itself stays take-it-or-leave-it. Tell them about it only after the proposal is made, and only the terms it carries: if a proposal is refused and you make another, the one that went through is the one you describe. Your operating contract is this protocol and your role below.
44
44
  - ${INBOUND_DISCOVERY_POLICY}
45
45
 
@@ -1,11 +1,11 @@
1
1
  import { type ToolDefinition } from '../tools/defineTool.js';
2
- import { cancelTaskTool, listTasksTool } from '../platform/tools/tier2/taskTools.js';
2
+ import { cancelTaskTool, listTasksTool, delegateTaskTool } from '../platform/tools/tier2/taskTools.js';
3
3
  import type { EffectHandler, DeliveryWriteKeys } from '../types.js';
4
4
  import type { MemoryStore } from '../memory/MemoryStore.js';
5
5
  export interface ClaudeSdkToolContext extends Record<string, unknown> {
6
6
  operatorKey: string;
7
7
  agentId: string;
8
- chatId?: string | null;
8
+ addressId?: string | null;
9
9
  sessionId?: string | null;
10
10
  eff?: EffectHandler;
11
11
  /** Human/agent whose message woke the agent — forwarded to the shared tool executor. */
@@ -13,7 +13,7 @@ export interface ClaudeSdkToolContext extends Record<string, unknown> {
13
13
  senderType?: string;
14
14
  /** Agreement this wake acts under — lets a delegated per-user MCP call resolve the originating end-user. */
15
15
  onBehalfOfAgreementId?: string;
16
- /** Exact work scope of the wake that invoked this tool; same-room sends inherit it. */
16
+ /** Exact work scope of the wake that invoked this tool. */
17
17
  serviceAgreementId?: string;
18
18
  allowAutomaticMandate?: boolean;
19
19
  /** Per-wake id grouping this tick's tool calls in telemetry. Random — never use for idempotency. */
@@ -24,6 +24,6 @@ export interface ClaudeSdkToolContext extends Record<string, unknown> {
24
24
  memory?: MemoryStore;
25
25
  }
26
26
  export declare const contextSnapshotTool: ToolDefinition;
27
- export declare const chatSendTool: ToolDefinition;
28
- export { listTasksTool, cancelTaskTool };
27
+ export declare const messageSendTool: ToolDefinition;
28
+ export { listTasksTool, cancelTaskTool, delegateTaskTool };
29
29
  export declare const CLAUDE_SDK_TOOLS: ToolDefinition[];
@@ -1,27 +1,12 @@
1
- import { SOURCES_PARAM, sourcesArg } from '@ziggs-ai/api-client';
2
- import { isAgreementId, agreementLaneId } from '@ziggs-ai/api-client';
3
- import { randomUUID } from 'node:crypto';
4
- import { credsFromToolContext } from '../tools/toolCreds.js';
5
- import { sendChatMessage, presentSendResult } from '@ziggs-ai/api-client';
1
+ import { parseMessageCommand } from '@ziggs-ai/contracts';
2
+ import { MESSAGING_CAPABILITIES } from '@ziggs-ai/api-client';
3
+ import { toolsFromCapabilities } from '../tools/capabilityAdapter.js';
6
4
  import { defineTool } from '../tools/defineTool.js';
7
5
  import { listGrantsTool } from '../platform/tools/tier2/grantsTools.js';
8
- import { cancelTaskTool, listTasksTool } from '../platform/tools/tier2/taskTools.js';
9
- import { CHAT_TOOLS } from '../platform/tools/tier2/chatTools.js';
10
- // default message-history cap. An uncapped snapshot returns the whole
11
- // chat — in a delegation chat that accumulates notification rows, every wake's
12
- // snapshot ballooned (observed 35–47k input tokens/wake, ~205k/run). 12 recent
13
- // messages cover "what woke me and what was just said"; the model can pass a
14
- // larger maxMessages explicitly when it genuinely needs deeper history.
6
+ import { cancelTaskTool, listTasksTool, delegateTaskTool } from '../platform/tools/tier2/taskTools.js';
7
+ // Optional message history stays bounded; deeper history is an explicit read.
15
8
  const DEFAULT_SNAPSHOT_MAX_MESSAGES = 12;
16
- /**
17
- * First usable string among the candidates, treating blank as absent.
18
- *
19
- * a model that does not know a value fills the field with `""` rather
20
- * than omitting it — the schema shows `chatId`, the agent has no way to know it,
21
- * so it sends an empty string. Plain `??` let that blank win over the lane
22
- * default, and the very next line rejected it as missing. Every wake opened with
23
- * a failed `context_snapshot` and a recovery turn.
24
- */
9
+ /** Blank selectors do not replace the current wake's exact resource scope. */
25
10
  function firstFilled(...candidates) {
26
11
  for (const candidate of candidates) {
27
12
  if (typeof candidate === 'string' && candidate.trim() !== '')
@@ -29,133 +14,54 @@ function firstFilled(...candidates) {
29
14
  }
30
15
  return undefined;
31
16
  }
32
- /**
33
- * Accept the agreement id an agent was woken on where a chat id is wanted.
34
- *
35
- * An agent woken on an agreement reaches for its context with the only id that
36
- * wake gave it — the agreement's. That is not a chat id, so the snapshot came
37
- * back empty: no history, no agreements, the agent itself as the only participant,
38
- * and a bare `chatMissing`. Observed on the FIRST wake of every two-agent run,
39
- * and once it sent the provider off to chat instead of to its own task.
40
- *
41
- * An agreement's implicit notification chat is `agrn-<agreementId>` — the lane
42
- * the backend itself routes agreement traffic through — so the id the agent has
43
- * does name a readable room, one prefix away. Mapping it is the difference
44
- * between a wasted turn and an answer.
45
- */
46
- function asChatLane(id) {
47
- return isAgreementId(id) ? agreementLaneId(id) : id;
48
- }
49
17
  // Named distinctly from ziggs-mcp's per-type `ziggs_context_read`: this is the
50
18
  // one-shot aggregate snapshot (grant-fenced server-side), not a cursored read.
51
19
  export const contextSnapshotTool = defineTool('context_snapshot', {
52
- chatId: { type: 'string', description: 'Omit this to snapshot the chat you were woken in — that is the usual case, and you do not need to know its id. Only pass a chatId to snapshot a DIFFERENT chat. An agreement id is accepted too, and reads that agreement\'s lane.' },
20
+ agreementId: { type: 'string', description: 'Read this explicit work agreement.' },
21
+ taskId: { type: 'string', description: 'Read this explicit task.' },
22
+ messageId: { type: 'string', description: 'Read this exact message without a saved address or work agreement.' },
23
+ artifactId: { type: 'string', description: 'Read this exact artifact context.' },
24
+ includeHistory: { type: 'boolean', description: 'Include optional work message history. Task/work context does not require it; the exact triggering message is read independently.' },
53
25
  maxMessages: { type: 'number', description: `Message history cap (default ${DEFAULT_SNAPSHOT_MAX_MESSAGES}). Pass a larger value when you need deeper history.` },
54
26
  }, async (args, ctx) => {
55
27
  const c = ctx;
56
28
  if (!c.eff)
57
29
  throw new Error('context_snapshot requires eff in tool context');
58
- const sessionId = asChatLane(firstFilled(args.chatId, c.chatId, c.sessionId));
30
+ const messageId = firstFilled(args.messageId);
31
+ const artifactId = firstFilled(args.artifactId);
32
+ const agreementId = firstFilled(args.agreementId), taskId = firstFilled(args.taskId);
33
+ if ((messageId || artifactId) && [messageId, artifactId, agreementId, taskId].filter(Boolean).length > 1)
34
+ throw new Error('context_snapshot: select one exact message or artifact, independently of work selection');
35
+ if ('addressId' in args)
36
+ throw new Error('context_snapshot: addressId is not supported; select a message, artifact, task or agreement');
37
+ // The effect's session routes it: the wake's own lane reads the wake's
38
+ // destination; anything named is read as named, never as a lane.
39
+ const sessionId = firstFilled(messageId, artifactId, taskId, agreementId, c.sessionId);
59
40
  if (!sessionId) {
60
- throw new Error('context_snapshot: this wake carries no chat to snapshot. Call it with no arguments ' +
61
- 'to read the chat you were woken in; if this wake is on an agreement lane with no ' +
62
- 'single chat, read the agreement instead and pass a chatId from one of its chats.');
41
+ throw new Error('context_snapshot: no selected resource. Name messageId, artifactId, taskId or agreementId, or use the current wake.');
63
42
  }
64
43
  return c.eff({
65
44
  kind: 'read-context',
66
45
  sessionId,
67
46
  opts: {
47
+ includeHistory: args.includeHistory === true,
68
48
  maxMessages: args.maxMessages != null ? Number(args.maxMessages) : DEFAULT_SNAPSHOT_MAX_MESSAGES,
49
+ ...(messageId || artifactId ? {
50
+ via: messageId ? `message:${messageId}` : `artifact:${artifactId}`,
51
+ workScopes: [{ messageId, artifactId }],
52
+ } : agreementId || taskId ? {
53
+ via: taskId ? `task:${taskId}` : `agreement:${agreementId}`,
54
+ workScopes: [{ agreementId, taskId }],
55
+ } : {}),
69
56
  },
70
57
  });
71
- }, { description: 'Read the current wake’s exact task or agreement, governing constraints, dependencies and artifact inventory. No chat is required for task work. For chat wakes, the NEWEST maxMessages of history (chronological order), agreements, participants, and a bounded inventory of readable room artifacts. Artifact bodies are not included: read relevant notes with context_read type=artifacts via=chat:<chatId>, following pagination, before concluding information is missing. Zero direct artifact grants does not mean no readable room notes. Direct grants absent from that room inventory are grantedArtifacts (id and name only); read a body with context_read type=artifacts via=artifact:<id>.' });
72
- export const chatSendTool = defineTool('chat_send', {
73
- taskId: { type: 'string', description: 'Work task; the backend resolves its governing agreement.' },
74
- conversationAgreementId: { type: 'string', description: 'The agreement this message is under. In the room you were woken in, leave it out unless you mean a different agreement than this wake. When a send is refused with WORK_SELECTION_REQUIRED, name one of its choices.' },
75
- underAgreementId: { type: 'string', description: 'Your run authority, defaulting to the current run in every destination room.' },
76
- chatId: { type: 'string', description: 'Omit this to send in the chat you were woken in — that is the usual case. Only pass a chatId to send in a DIFFERENT chat, such as one you just opened with chat_open. When chatId is set it wins over `to`.' },
77
- to: { type: 'string', description: 'User or agent id of someone you already have a conversation with. Sends in that pair room. Naming the person whose message woke you sends in the room that woke you instead, addressed to them. Does not open contact — first contact still needs chat_open. Ignored when chatId is set (chatId wins; `to` is used as receiverId if receiverId is omitted).' },
78
- receiverId: { type: 'string', description: 'Name someone only when you mean them. Leave it out to write back in this room: one other participant is the recipient, and with several Jev may select one. Otherwise the message stays in the room with no specific wake. To name a masked party, pass its rpb_ ref as it came. Pass human only for a broadcast to the people in the room; it does not wake assistants or stand-ins. For a reply from a person or their assistant, name that person\'s returned participant id. This does not grant their assistant access or guarantee a reply.' },
79
- text: { type: 'string', required: true },
80
- idempotencyKey: { type: 'string', description: 'Reuse for retry-safe resend of the same logical message.' },
81
- sources: SOURCES_PARAM,
82
- }, async (args, ctx) => {
58
+ }, { description: 'Read the current wake or an explicit message, artifact, task or agreement, including governing constraints, dependencies and permitted artifact inventory. Optional message history is bounded by maxMessages. Artifact bodies are not included: follow the returned exact artifact or work readPlan with context_read. Snapshot and history reads preserve selected-work authority; an unrelated session or conversation grants no access.' });
59
+ export const messageSendTool = defineTool('message_send', { command: { type: 'object', required: true, description: 'Canonical MessageCommand: operationId, content, audience, deliveries, effect and optional mandate, relationships and exact sources.' } }, async (args, ctx) => {
83
60
  const c = ctx;
84
- const sources = sourcesArg(args);
85
- const explicitChatId = asChatLane(firstFilled(args.chatId));
86
- const to = firstFilled(args.to);
87
- const text = String(args.text);
88
- // Naming the person who woke you is an answer, not a new conversation: it
89
- // goes in the room that woke you, addressed to them. A probe room or a
90
- // stranger's room has no pair room with that person, and asking the server
91
- // for one only bought a refusal and a chat_open round trip.
92
- const wakeChat = asChatLane(firstFilled(c.chatId, c.sessionId));
93
- const answersWakeSender = !!to && !!wakeChat && to === c.senderId;
94
- // Existing conversation with a person: resolve their pair room server-side.
95
- // Does not open contact. An explicit chatId still wins.
96
- if (!explicitChatId && to && !answersWakeSender) {
97
- const creds = credsFromToolContext(c, 'chat_send');
98
- const messageId = args.idempotencyKey
99
- ? `sdk_idem_${String(args.idempotencyKey)}`
100
- : c.writeKeys?.messageKey({ text, sessionId: `to:${to}`, receiverId: to }) ?? `sdk_${randomUUID()}`;
101
- const namedReceiver = firstFilled(args.receiverId);
102
- const sent = await sendChatMessage({
103
- to,
104
- text,
105
- messageId,
106
- conversationAgreementId: firstFilled(args.conversationAgreementId),
107
- underAgreementId: firstFilled(args.underAgreementId, c.serviceAgreementId),
108
- taskId: firstFilled(args.taskId),
109
- ...(namedReceiver ? { receiverId: namedReceiver } : {}),
110
- ...sources,
111
- }, creds);
112
- // Never presented as a send that happened: the message does not exist,
113
- // so whoever was waiting for it is still waiting. Retrying with the same
114
- // idempotencyKey is safe — it is the key the backend dedups on, so a
115
- // resend of the same logical message cannot double-post it.
116
- if (!sent.success)
117
- throw new Error('chat_send: the server did not accept this message, so nothing was delivered and nobody was woken. ' +
118
- 'Retry the same send with the same idempotencyKey; if it keeps failing, say so where the work is tracked rather than reporting the answer as sent.');
119
- return presentSendResult(sent, { creds, surface: 'sdk' });
120
- }
121
- if (!c.eff)
122
- throw new Error('chat_send requires eff in tool context');
123
- // Same agreement-id-where-a-chat-id-goes slip as context_snapshot, and the
124
- // same mapping: an agreement's traffic lane is `agrn-<agreementId>`.
125
- const sessionId = asChatLane(firstFilled(args.chatId, c.chatId, c.sessionId));
126
- if (!sessionId) {
127
- throw new Error('chat_send: this wake carries no chat to send in. Pass chatId of the room, or to naming a person you already have a conversation with (first contact still needs chat_open).');
128
- }
129
- const receiverId = firstFilled(args.receiverId, args.to);
130
- // Default to the wake's content-derived key: a crash-replay that
131
- // re-sends the same text re-derives the same messageId and the backend drops
132
- // the duplicate; a re-think with different text gets a new key
133
- // instead of colliding on a per-wake ordinal. An explicit LLM idempotencyKey
134
- // still wins (retry-safe resend of the same logical message across wakes);
135
- // random only when the wake has no durable identity.
136
- const messageId = args.idempotencyKey
137
- ? `sdk_idem_${String(args.idempotencyKey)}`
138
- : c.writeKeys?.messageKey({ text, sessionId, receiverId }) ?? `sdk_${randomUUID()}`;
139
- return c.eff({
140
- kind: 'send-message',
141
- ...(firstFilled(args.taskId) ? { taskId: firstFilled(args.taskId) } : {}),
142
- ...(firstFilled(args.conversationAgreementId) ? { conversationAgreementId: firstFilled(args.conversationAgreementId) } : {}),
143
- ...(firstFilled(args.underAgreementId, c.serviceAgreementId) ? { underAgreementId: firstFilled(args.underAgreementId, c.serviceAgreementId) } : {}),
144
- sessionId,
145
- ...(receiverId ? { receiverId } : {}),
146
- text,
147
- messageId,
148
- ...sources,
149
- });
150
- },
151
- // This line used to read "Conversation only — never use for
152
- // finished-work reporting", while the protocol two files over told the agent
153
- // to send the human its deliverable in chat. One of the two had to be wrong
154
- // in front of the model on every wake. The protocol is right: a person reads
155
- // what you did in the room; an agent reads the task result. Chat never
156
- // REPLACES task_set_result — that is what "reporting" meant here — but for a
157
- // human counterparty it is the delivery, not a breach of it.
158
- { description: 'Send a user-visible chat message. This is conversation, and it is how a PERSON receives finished work: when the human who set the goal is your counterparty, send them the deliverable here as well. It is not how another AGENT collects one — that one reads the task result — and it never replaces task_set_result on a task that is open. To reach someone you already talk to, pass to= their user or agent id. First contact still needs chat_open, then chat_send in the chat that returns.' });
61
+ if (!c.eff || !c.sessionId)
62
+ throw new Error('message_send requires the current host context');
63
+ return c.eff({ kind: 'send-message', sessionId: c.sessionId, command: parseMessageCommand(args.command) });
64
+ }, { description: 'Send one canonical message. Readers, deliveries and answer effects are explicit. A reply link alone does not complete an answer request. Use the exact answer action to answer; task results remain task operations. No room required.' });
159
65
  // Inbox read/ack are deliberately NOT here: the host owns the pull/ack cycle
160
66
  // (its per-agent inbox long-poll loop). A brain-side ack would race the host's,
161
67
  // and a per-turn inbox poll fed the tool-operation echo loop.
@@ -168,14 +74,13 @@ export const chatSendTool = defineTool('chat_send', {
168
74
  // task_list IS here: MCP delegates have ziggs_task_list; the hosted
169
75
  // Claude surface previously had no way to enumerate its own open tasks (reads
170
76
  // were prompt-injected only). Same GET /tasks capability as the MCP tool.
171
- export { listTasksTool, cancelTaskTool };
77
+ export { listTasksTool, cancelTaskTool, delegateTaskTool };
172
78
  export const CLAUDE_SDK_TOOLS = [
173
79
  contextSnapshotTool,
174
- chatSendTool,
80
+ messageSendTool,
175
81
  listGrantsTool,
176
82
  listTasksTool,
177
83
  cancelTaskTool,
178
- // store-claim providers must open the buyer chat from the default
179
- // Claude surface (chat_open was tier-2 / email-agent opt-in only).
180
- ...CHAT_TOOLS,
84
+ delegateTaskTool,
85
+ ...toolsFromCapabilities(MESSAGING_CAPABILITIES),
181
86
  ];
@@ -18,7 +18,7 @@ const HAND_LANE = {
18
18
  context_snapshot: 'read-only',
19
19
  // Sends, commitments, overwrites, cancellations and revocations are
20
20
  // destructive; only private additive writes are plain writes.
21
- chat_send: 'destructive',
21
+ message_send: 'destructive',
22
22
  wake_at: 'write',
23
23
  agreement_propose: 'destructive',
24
24
  agreement_subcontract: 'destructive',
@@ -11,5 +11,3 @@ export declare function filterStandingOfferNotices(event: RawEvent | undefined,
11
11
  event: RawEvent | undefined;
12
12
  ignoredAgreementIds: string[];
13
13
  }>;
14
- /** A synthetic mailbox has no chat row; access to its agreement must still be proven. */
15
- export declare function isReadableAgreementNoticeLane(sessionId: string, event: RawEvent | undefined, readAgreement: (agreementId: string) => Promise<unknown>): Promise<boolean>;
@@ -1,5 +1,5 @@
1
1
  import { SERVER_EVENTS } from '@ziggs-ai/contracts';
2
- import { isAgreementId, isBroadcastTarget } from '@ziggs-ai/api-client';
2
+ import { isBroadcastTarget } from '@ziggs-ai/api-client';
3
3
  import { getBatchEvents } from '../context/batch.js';
4
4
  function record(value) {
5
5
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -68,23 +68,3 @@ export async function filterStandingOfferNotices(event, agentId, readAgreement)
68
68
  ignoredAgreementIds,
69
69
  };
70
70
  }
71
- /** A synthetic mailbox has no chat row; access to its agreement must still be proven. */
72
- export async function isReadableAgreementNoticeLane(sessionId, event, readAgreement) {
73
- const id = sessionId.startsWith('agrn-') ? sessionId.slice('agrn-'.length) : sessionId;
74
- if (!isAgreementId(id))
75
- return false;
76
- const events = getBatchEvents(event);
77
- if (events.length === 0 || events.some((item) => {
78
- const eventIds = agreementEventIds(item);
79
- // Old in-memory timers do not carry scope. They may resume only a readable
80
- // agreement mailbox. Never derive authority from the model-written reason.
81
- return eventIds.length ? eventIds.some((eventId) => eventId !== id) : item?.type !== 'wake-at';
82
- }))
83
- return false;
84
- try {
85
- return record(await readAgreement(id))?.agreementId === id;
86
- }
87
- catch {
88
- return false;
89
- }
90
- }
@@ -119,7 +119,7 @@ alwaysLoad = () => true) {
119
119
  const eff = ctx.eff;
120
120
  if (!eff)
121
121
  return toCallToolError(`tool "${name}": missing eff in tool context`);
122
- const sessionId = ctx.chatId ?? ctx.sessionId;
122
+ const sessionId = ctx.sessionId;
123
123
  if (!sessionId)
124
124
  return toCallToolError(`tool "${name}": missing sessionId in tool context`);
125
125
  const r = await eff({
@@ -15,7 +15,7 @@ export interface WakeGateMessage {
15
15
  export interface WakeGateRequest {
16
16
  messages: WakeGateMessage[];
17
17
  agreementId?: string;
18
- chatId?: string;
18
+ addressId?: string;
19
19
  }
20
20
  export interface WakeGateAnswer {
21
21
  wake: boolean;
@@ -35,6 +35,8 @@ export type AskWakeGate = (req: WakeGateRequest) => Promise<WakeGateAnswer | nul
35
35
  * never be skipped, and none of them is a `message`.
36
36
  */
37
37
  export declare function gateableMessages(event: RawEvent | undefined): WakeGateMessage[] | null;
38
+ /** The one room the wake's messages came from, for the cost record. Never the lane name. */
39
+ export declare function wakeAddressId(event: RawEvent | undefined): string | undefined;
38
40
  /** The agreement the wake names, for the cost record. First one wins. */
39
41
  export declare function wakeAgreementId(event: RawEvent | undefined): string | undefined;
40
42
  /**
@@ -30,6 +30,12 @@ export function gateableMessages(event) {
30
30
  }
31
31
  return out;
32
32
  }
33
+ /** The one room the wake's messages came from, for the cost record. Never the lane name. */
34
+ export function wakeAddressId(event) {
35
+ const rooms = new Set(getBatchEvents(event)
36
+ .map(ev => ev?.addressId).filter((id) => typeof id === 'string' && !!id));
37
+ return rooms.size === 1 ? [...rooms][0] : undefined;
38
+ }
33
39
  /** The agreement the wake names, for the cost record. First one wins. */
34
40
  export function wakeAgreementId(event) {
35
41
  for (const ev of getBatchEvents(event)) {
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export { runTurn } from './runtime/runTurn.js';
41
41
  export { runMechanical } from './runtime/runMechanical.js';
42
42
  export { PromptBuilder } from './runtime/PromptBuilder.js';
43
43
  export { thinkingDefaults, defineThinkingState, defineMechanicalState, DEFAULT_WAIT_PROMPT } from './runtime/defaults.js';
44
- export { KICKOFF_STATE, CLAIM_KICKOFF_STATE, injectKickoffDefault, buildKickoffState, buildClaimKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
44
+ export { KICKOFF_STATE, injectKickoffDefault, buildKickoffState, handlesClaimWake, } from './runtime/kickoff.js';
45
45
  export { validateWorkflow, WorkflowValidationError, PROBE_CTX } from './runtime/validateWorkflow.js';
46
46
  export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
47
47
  export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
@@ -70,7 +70,7 @@ export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormat
70
70
  export { ProactiveTrigger } from './platform/proactive/ProactiveTrigger.js';
71
71
  export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './platform/polls/PollKind.js';
72
72
  export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
73
- export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, PARTY_TOOLS, partyNoteTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, LISTING_TOOLS, } from './platform/tools/index.js';
73
+ export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, PARTY_TOOLS, partyNoteTool, ARTIFACT_TOOLS, recordArtifactTool, LISTING_TOOLS, } from './platform/tools/index.js';
74
74
  export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
75
75
  export { hostedModelTools, isHostedDeferred, hostedToolLane } from './brain/hostedToolSurface.js';
76
76
  export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
@@ -82,7 +82,7 @@ export { classifyIncomingEvent } from './context/classifyEnvelope.js';
82
82
  export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
83
83
  export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
84
84
  export { classifyWorkflowEvent, findTaskResult, unwrapBatchEvent, findIncomingTaskResult } from './context/routingLabels.js';
85
- export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl, configureApiClient, } from '@ziggs-ai/api-client';
85
+ export { ArtifactsClient, AgentSearchClient, getBackendUrl, configureApiClient, } from '@ziggs-ai/api-client';
86
86
  export type { ApiClientConfig } from '@ziggs-ai/api-client';
87
87
  export { SeenMessages } from './platform/SeenMessages.js';
88
88
  export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
@@ -91,3 +91,4 @@ export { runAgents } from './launcher/runAgents.js';
91
91
  export type { RunnerOptions, SingleAgentRun, FleetRun } from './launcher/runAgents.js';
92
92
  export { createHealthServer, inboxHealth } from './launcher/createHealthServer.js';
93
93
  export type { AgentLiveness, HealthRequest, HealthRequestHandler } from './launcher/createHealthServer.js';
94
+ export { responseCommand } from './runtime/messageCommand.js';