@ziggs-ai/agent-sdk 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Agent.js +6 -4
- package/dist/brain/claudeSdkProtocol.js +1 -1
- package/dist/brain/claudeSdkTools.js +8 -45
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ingress/normalizeIncoming.d.ts +2 -0
- package/dist/ingress/normalizeIncoming.js +1 -0
- package/dist/platform/ZiggsEffectHandler.d.ts +1 -1
- package/dist/platform/ZiggsEffectHandler.js +1 -1
- package/dist/platform/inboxLoop.d.ts +3 -13
- package/dist/platform/inboxLoop.js +66 -115
- package/dist/platform/tools/index.d.ts +1 -1
- package/dist/platform/tools/index.js +1 -1
- package/dist/platform/tools/tier2/contextTools.d.ts +1 -0
- package/dist/platform/tools/tier2/contextTools.js +2 -1
- package/dist/runtime/PromptBuilder.js +2 -18
- package/dist/runtime/buildOutcome.d.ts +8 -0
- package/dist/runtime/buildOutcome.js +1 -14
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
package/dist/Agent.js
CHANGED
|
@@ -331,7 +331,7 @@ export class Agent {
|
|
|
331
331
|
operatorKey: this.options.operatorKey,
|
|
332
332
|
agentId: this.options.agentId,
|
|
333
333
|
};
|
|
334
|
-
const messageSender = async (message, receiverId, sessionId, messageId, conversationAgreementId) => {
|
|
334
|
+
const messageSender = async (message, receiverId, sessionId, messageId, conversationAgreementId, underAgreementId, taskId) => {
|
|
335
335
|
const sentId = messageId ?? `sdk_${randomUUID()}`;
|
|
336
336
|
const result = await sendChatMessage({
|
|
337
337
|
chatId: sessionId,
|
|
@@ -345,6 +345,8 @@ export class Agent {
|
|
|
345
345
|
// only when absent (matches the old WS path letting the server assign).
|
|
346
346
|
messageId: sentId,
|
|
347
347
|
conversationAgreementId,
|
|
348
|
+
underAgreementId,
|
|
349
|
+
taskId,
|
|
348
350
|
}, creds);
|
|
349
351
|
if (!result.success)
|
|
350
352
|
throw new Error('sendChatMessage: the server did not accept this message — nothing was delivered and nobody was woken. Retry with the same messageId, which the backend dedups on.');
|
|
@@ -1121,9 +1123,9 @@ export class Agent {
|
|
|
1121
1123
|
const sameRoom = 'sessionId' in effect && effect.sessionId === session.sessionId;
|
|
1122
1124
|
const scopedEffect = sameRoom && effect.kind === 'read-context'
|
|
1123
1125
|
? { ...effect, opts: { ...effect.opts, workScopes: contextScopesFromWake(frame, session.sessionId) } }
|
|
1124
|
-
:
|
|
1125
|
-
? { ...effect,
|
|
1126
|
-
:
|
|
1126
|
+
: agreementId && effect.kind === 'send-message'
|
|
1127
|
+
? { ...effect, underAgreementId: effect.underAgreementId ?? agreementId }
|
|
1128
|
+
: agreementId && (effect.kind === 'tool-call' || effect.kind === 'record-event')
|
|
1127
1129
|
? { ...effect, serviceAgreementId: effect.serviceAgreementId ?? agreementId }
|
|
1128
1130
|
: effect;
|
|
1129
1131
|
const operation = this.eff(scopedEffect);
|
|
@@ -34,7 +34,7 @@ Your prose and final summary are private host notes, not messages to the people
|
|
|
34
34
|
You are headless — there is no human to prompt during a wake:
|
|
35
35
|
- When proposals or connection requests need a human decision, DO NOT approve or reject on their behalf. Summarize what needs the operator's attention and stop.
|
|
36
36
|
- 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.
|
|
37
|
-
- 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 —
|
|
37
|
+
- 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.
|
|
38
38
|
- 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.
|
|
39
39
|
|
|
40
40
|
Security:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAgreementId, agreementLaneId
|
|
1
|
+
import { isAgreementId, agreementLaneId } from '@ziggs-ai/api-client';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { credsFromToolContext } from '../tools/toolCreds.js';
|
|
4
4
|
import { sendChatMessage, presentSendResult } from '@ziggs-ai/api-client';
|
|
@@ -70,11 +70,11 @@ export const contextSnapshotTool = defineTool('context_snapshot', {
|
|
|
70
70
|
}, { 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>.' });
|
|
71
71
|
export const chatSendTool = defineTool('chat_send', {
|
|
72
72
|
taskId: { type: 'string', description: 'Work task; the backend resolves its governing agreement.' },
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
conversationAgreementId: { type: 'string', description: 'Explicit service work requested from the recipient, separate from your own run authority.' },
|
|
74
|
+
underAgreementId: { type: 'string', description: 'Your run authority, defaulting to the current run in every destination room.' },
|
|
75
75
|
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`.' },
|
|
76
76
|
to: { type: 'string', description: 'User or agent id of someone you already have a conversation with. Sends in that pair room. 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).' },
|
|
77
|
-
receiverId: { type: 'string', description: '
|
|
77
|
+
receiverId: { type: 'string', description: 'Explicit recipient user or agent id, or a participant reference from this destination room. Omit it when the room has one other participant: they are the recipient. With several, Jev chooses when confident; otherwise this is room context with no specific wake. The wake sender and last writer are never defaults. Pass human for an intentional broadcast to people.' },
|
|
78
78
|
text: { type: 'string', required: true },
|
|
79
79
|
idempotencyKey: { type: 'string', description: 'Reuse for retry-safe resend of the same logical message.' },
|
|
80
80
|
}, async (args, ctx) => {
|
|
@@ -95,8 +95,8 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
95
95
|
text,
|
|
96
96
|
messageId,
|
|
97
97
|
conversationAgreementId: firstFilled(args.conversationAgreementId),
|
|
98
|
+
underAgreementId: firstFilled(args.underAgreementId, c.serviceAgreementId),
|
|
98
99
|
taskId: firstFilled(args.taskId),
|
|
99
|
-
replyToMessageId: firstFilled(args.replyToMessageId),
|
|
100
100
|
...(namedReceiver ? { receiverId: namedReceiver } : {}),
|
|
101
101
|
}, creds);
|
|
102
102
|
// Never presented as a send that happened: the message does not exist,
|
|
@@ -112,48 +112,11 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
112
112
|
throw new Error('chat_send requires eff in tool context');
|
|
113
113
|
// Same agreement-id-where-a-chat-id-goes slip as context_snapshot, and the
|
|
114
114
|
// same mapping: an agreement's traffic lane is `agrn-<agreementId>`.
|
|
115
|
-
const wakeChatId = asChatLane(firstFilled(c.chatId, c.sessionId));
|
|
116
115
|
const sessionId = asChatLane(firstFilled(args.chatId, c.chatId, c.sessionId));
|
|
117
116
|
if (!sessionId) {
|
|
118
117
|
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).');
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
// the model spell it out cost a failed tool call + a full extra LLM turn
|
|
122
|
-
// per reply.
|
|
123
|
-
//
|
|
124
|
-
// But ONLY in the room that wake belongs to. The sender an
|
|
125
|
-
// event carries is usually an `rpb_*` room participant reference, which is
|
|
126
|
-
// an address inside its own room and a dead id in any other. Defaulting it
|
|
127
|
-
// into a different chat is how an agent that opened a chat with its own
|
|
128
|
-
// human — `chat_open` accepted the principal and minted the room — had the
|
|
129
|
-
// very next `chat_send` refused with INVALID_ROOM_PARTICIPANT_REF, and gave
|
|
130
|
-
// up on reaching the person who hired it. An explicitly passed foreign-room
|
|
131
|
-
// ref is dropped only when it is the wake sender carried across rooms.
|
|
132
|
-
// A DIFFERENT explicit ref can come from the destination's snapshot and
|
|
133
|
-
// must reach the server, which validates it in that destination room.
|
|
134
|
-
const sameRoom = sessionId === wakeChatId;
|
|
135
|
-
const explicit = firstFilled(args.receiverId, args.to);
|
|
136
|
-
const receiverId = explicit && !(isRoomParticipantRef(explicit) && !sameRoom && explicit === c.senderId)
|
|
137
|
-
? explicit
|
|
138
|
-
: sameRoom
|
|
139
|
-
? firstFilled(c.senderId)
|
|
140
|
-
: undefined;
|
|
141
|
-
if (!sameRoom && !receiverId) {
|
|
142
|
-
// A room that also holds the person's old delegate is no longer 1:1.
|
|
143
|
-
// An omitted recipient there is refused, not a broadcast; name the
|
|
144
|
-
// person or agent, or pass human for a room broadcast.
|
|
145
|
-
const snapshot = await c.eff({
|
|
146
|
-
kind: 'read-context', sessionId, opts: { maxMessages: 1 },
|
|
147
|
-
});
|
|
148
|
-
const participantCount = (snapshot?.users?.length ?? 0) + (snapshot?.agents?.length ?? 0);
|
|
149
|
-
if (participantCount > 2) {
|
|
150
|
-
throw new Error('chat_send: this destination has several participants. Set receiverId to the person or agent you are asking, using their account id or a participant reference from context_snapshot of this destination. Omit no recipient when expecting a reply. For an intentional human broadcast, explicitly set receiverId="human". No message was sent.');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// No receiver is a legitimate send, not a failure: the backend resolves the
|
|
154
|
-
// recipient from the room's own participants and answers with a 1:1 chat's
|
|
155
|
-
// only other member. That is exactly the case a freshly opened chat is in,
|
|
156
|
-
// and it is the one case the wake's sender cannot answer.
|
|
119
|
+
const receiverId = firstFilled(args.receiverId, args.to);
|
|
157
120
|
// Default to the wake's content-derived key: a crash-replay that
|
|
158
121
|
// re-sends the same text re-derives the same messageId and the backend drops
|
|
159
122
|
// the duplicate; a re-think with different text gets a new key
|
|
@@ -166,8 +129,8 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
166
129
|
return c.eff({
|
|
167
130
|
kind: 'send-message',
|
|
168
131
|
...(firstFilled(args.taskId) ? { taskId: firstFilled(args.taskId) } : {}),
|
|
169
|
-
...(firstFilled(args.
|
|
170
|
-
...(firstFilled(args.
|
|
132
|
+
...(firstFilled(args.conversationAgreementId) ? { conversationAgreementId: firstFilled(args.conversationAgreementId) } : {}),
|
|
133
|
+
...(firstFilled(args.underAgreementId, c.serviceAgreementId) ? { underAgreementId: firstFilled(args.underAgreementId, c.serviceAgreementId) } : {}),
|
|
171
134
|
sessionId,
|
|
172
135
|
...(receiverId ? { receiverId } : {}),
|
|
173
136
|
text,
|
package/dist/index.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormat
|
|
|
67
67
|
export { ProactiveTrigger } from './platform/proactive/ProactiveTrigger.js';
|
|
68
68
|
export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './platform/polls/PollKind.js';
|
|
69
69
|
export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
|
|
70
|
-
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, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
70
|
+
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, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
71
71
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
72
72
|
export { hostedModelTools, isHostedCatalogOnly, hostedToolLane } from './brain/hostedToolSurface.js';
|
|
73
73
|
export type { HostedToolExecute } from './brain/hostedToolSurface.js';
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ export {
|
|
|
79
79
|
// Tier 1 — protocol grammar (framework-managed via taskTools).
|
|
80
80
|
PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp,
|
|
81
81
|
// Tier 2 — opt-in capability bundles (spread into `tools:`).
|
|
82
|
-
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
82
|
+
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
83
83
|
// The always-on toolset for the hosted Claude cognition (parity guard
|
|
84
84
|
// reads this as part of the real SDK registry).
|
|
85
85
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
@@ -5,6 +5,7 @@ export interface NormalizedEvent {
|
|
|
5
5
|
senderId?: string;
|
|
6
6
|
senderType?: string;
|
|
7
7
|
receiverId?: string;
|
|
8
|
+
assigneeId?: string;
|
|
8
9
|
/**
|
|
9
10
|
* Backend per-delivery id of the chat row this event came from. The one
|
|
10
11
|
* field of the event that is identical on live push and on catch-up
|
|
@@ -58,6 +59,7 @@ interface WireMetadata {
|
|
|
58
59
|
};
|
|
59
60
|
/** Inbox rewrite: assigned mail is this agent's even when the chat row names a human. */
|
|
60
61
|
receiverId?: string;
|
|
62
|
+
assigneeId?: string;
|
|
61
63
|
task?: WireTask;
|
|
62
64
|
service?: {
|
|
63
65
|
conversationAgreementId?: string;
|
|
@@ -113,6 +113,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
113
113
|
senderId: metadata.sender?.id,
|
|
114
114
|
senderType,
|
|
115
115
|
receiverId: receiverId || undefined,
|
|
116
|
+
assigneeId: typeof metadata.assigneeId === 'string' ? metadata.assigneeId : undefined,
|
|
116
117
|
messageId: typeof metadata.messageId === 'string' && metadata.messageId ? metadata.messageId : undefined,
|
|
117
118
|
timestamp: Date.now(),
|
|
118
119
|
...(entryType ? { entryType } : {}),
|
|
@@ -36,7 +36,7 @@ export interface ZiggsEffectDeps {
|
|
|
36
36
|
contextReadClient: ContextReadClient;
|
|
37
37
|
taskService: TaskService;
|
|
38
38
|
agreementService: AgreementService;
|
|
39
|
-
messageSender: (text: string, receiverId: string | undefined, sessionId: string, messageId?: string, conversationAgreementId?: string) => Promise<unknown>;
|
|
39
|
+
messageSender: (text: string, receiverId: string | undefined, sessionId: string, messageId?: string, conversationAgreementId?: string, underAgreementId?: string, taskId?: string) => Promise<unknown>;
|
|
40
40
|
/** Sends a streaming chunk to a client. Called per-token during stream-text effects. */
|
|
41
41
|
chunkSender?: (sessionId: string, receiverId: string, text: string, messageId: string) => void;
|
|
42
42
|
toolManager: ToolManager;
|
|
@@ -313,7 +313,7 @@ export function createZiggsEffectHandler(deps) {
|
|
|
313
313
|
});
|
|
314
314
|
}
|
|
315
315
|
case 'send-message': {
|
|
316
|
-
const sent = await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId, effect.conversationAgreementId);
|
|
316
|
+
const sent = await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId, effect.conversationAgreementId, effect.underAgreementId, effect.taskId);
|
|
317
317
|
outbox.track(effect.sessionId, { text: effect.text, receiverId: effect.receiverId });
|
|
318
318
|
// Carry the send's own report back to whoever asked for the send. A
|
|
319
319
|
// sender that cannot see where its message went cannot tell a delivered
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InboxChatNews, type
|
|
1
|
+
import { type InboxChatNews, type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
import { SeenMessages } from './SeenMessages.js';
|
|
3
3
|
/**
|
|
4
4
|
* Per-agent inbox long-poll loop (delivery law, phase 4). This is the ONLY way
|
|
@@ -60,19 +60,13 @@ export interface InboxPassResult {
|
|
|
60
60
|
* the wake — it does not buy a canned answer.
|
|
61
61
|
*/
|
|
62
62
|
export declare function chatsInWakeOrder(chats: InboxChatNews[]): InboxChatNews[];
|
|
63
|
-
/**
|
|
64
|
-
* The one hire a chat's assigned deliveries name, or null when the room
|
|
65
|
-
* has none or more than one. Memory (and acting-for) treat "several
|
|
66
|
-
* customers in one room" as the shared section.
|
|
67
|
-
*/
|
|
68
|
-
export declare function soleHireAgreementIdForChat(deliveries: readonly Pick<InboxDeliveryRef, 'chatId' | 'agreementId' | 'assigneeId'>[], chatId: string, ownAgentId: string): string | null;
|
|
69
63
|
/**
|
|
70
64
|
* One long-poll pass: hold up to `waitSeconds` for new inbox content, then read
|
|
71
65
|
* the deltas, deliver them into the host, and ack. Returns what it delivered.
|
|
72
66
|
* Reused each iteration of {@link runInboxLoop}; also usable as a single catch-up
|
|
73
67
|
* read (`waitSeconds: 0`) at bootstrap.
|
|
74
68
|
*/
|
|
75
|
-
export declare function pollInboxOnce(deps: InboxLoopDeps, waitSeconds?: number,
|
|
69
|
+
export declare function pollInboxOnce(deps: InboxLoopDeps, waitSeconds?: number, _opts?: {
|
|
76
70
|
forceAck?: boolean;
|
|
77
71
|
}): Promise<InboxPassResult>;
|
|
78
72
|
export interface InboxLoopHandle {
|
|
@@ -94,11 +88,7 @@ export interface InboxLoopHandle {
|
|
|
94
88
|
*/
|
|
95
89
|
readonly lastAttemptAt: number | null;
|
|
96
90
|
}
|
|
97
|
-
/**
|
|
98
|
-
* How many consecutive passes have refused to advance the watermark.
|
|
99
|
-
* A capped envelope used to reset the counter (zero handover failures),
|
|
100
|
-
* so `forceAck` never fired on the one hold that lasts forever.
|
|
101
|
-
*/
|
|
91
|
+
/** Count held windows so retries can back off without discarding mail. */
|
|
102
92
|
export declare function heldPassAfter(prev: number, pass: Pick<InboxPassResult, 'acked' | 'failures' | 'truncated'>): number;
|
|
103
93
|
/**
|
|
104
94
|
* Run {@link pollInboxOnce} forever (until stopped). Each iteration long-polls
|
|
@@ -49,90 +49,79 @@ function proposalIsMine(proposal, ownAgentId) {
|
|
|
49
49
|
return (proposal.pendingApprovalPartyIds?.includes(ownAgentId) === true ||
|
|
50
50
|
proposal.proposedTo === ownAgentId);
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
* The one hire a chat's assigned deliveries name, or null when the room
|
|
54
|
-
* has none or more than one. Memory (and acting-for) treat "several
|
|
55
|
-
* customers in one room" as the shared section.
|
|
56
|
-
*/
|
|
57
|
-
export function soleHireAgreementIdForChat(deliveries, chatId, ownAgentId) {
|
|
58
|
-
const ids = new Set();
|
|
59
|
-
for (const d of deliveries) {
|
|
60
|
-
if (d.chatId !== chatId || !d.agreementId)
|
|
61
|
-
continue;
|
|
62
|
-
if (d.assigneeId && d.assigneeId !== ownAgentId)
|
|
63
|
-
continue;
|
|
64
|
-
ids.add(d.agreementId);
|
|
65
|
-
}
|
|
66
|
-
return ids.size === 1 ? [...ids][0] : null;
|
|
67
|
-
}
|
|
68
52
|
/**
|
|
69
53
|
* Read the messages behind one chat's deliveries and hand them to the host.
|
|
70
54
|
*
|
|
71
|
-
* The envelope
|
|
55
|
+
* The envelope names exact assigned message ids, not their bodies — so this still
|
|
72
56
|
* reads through `/context/read`, where the agent's own grants fence what it is
|
|
73
57
|
* allowed to see. A delivery reference is an address, never an authorisation.
|
|
74
58
|
*/
|
|
75
|
-
async function deliverMessagesForChat(deps, news,
|
|
59
|
+
async function deliverMessagesForChat(deps, news, refs, reader, attempt, handledIds) {
|
|
60
|
+
const pending = new Map(refs.map(ref => [ref.resourceId, ref]));
|
|
61
|
+
for (const id of pending.keys()) {
|
|
62
|
+
if (deps.seen.has(id)) {
|
|
63
|
+
pending.delete(id);
|
|
64
|
+
handledIds.add(id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!pending.size)
|
|
68
|
+
return 0;
|
|
76
69
|
let delivered = 0;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
let after = new Date(Date.parse(earliestTs) - 1).toISOString();
|
|
82
|
-
let pages = 0;
|
|
83
|
-
const maxPages = 20;
|
|
84
|
-
while (remaining > 0 && pages < maxPages) {
|
|
85
|
-
const limit = Math.min(READ_PAGE, remaining);
|
|
70
|
+
const earliest = refs.map(ref => ref.ts).sort()[0];
|
|
71
|
+
let after = new Date(Date.parse(earliest) - 1).toISOString();
|
|
72
|
+
let cursor;
|
|
73
|
+
for (let pages = 0; pending.size && pages < 100; pages++) {
|
|
86
74
|
let page;
|
|
87
75
|
try {
|
|
88
76
|
page = await reader.read('messages', {
|
|
89
|
-
via: `chat:${news.chatId}`,
|
|
90
|
-
|
|
91
|
-
direction: 'forward',
|
|
92
|
-
limit,
|
|
77
|
+
via: `chat:${news.chatId}`, after, direction: 'forward', limit: READ_PAGE,
|
|
78
|
+
...(cursor ? { cursor } : {}),
|
|
93
79
|
});
|
|
94
80
|
}
|
|
95
81
|
catch (err) {
|
|
96
82
|
const status = err.status;
|
|
97
83
|
if (status !== 403 && status !== 404)
|
|
98
84
|
throw err;
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// failing here would wedge the agent on one unreadable item forever,
|
|
104
|
-
// re-woken by the same delivery on every pass.
|
|
105
|
-
warn(deps.label ?? 'inboxLoop', `chat ${news.chatId}: addressed but not readable (${status}) — skipping ${remaining} delivery/deliveries`);
|
|
85
|
+
// This read explicitly establishes that these deliveries are inaccessible.
|
|
86
|
+
// The server also excludes out-of-reach rows from its acknowledgment guard.
|
|
87
|
+
for (const id of pending.keys())
|
|
88
|
+
handledIds.add(id);
|
|
106
89
|
return delivered;
|
|
107
90
|
}
|
|
108
91
|
for (const row of page.items) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const text = typeof row.text === 'string' ? row.text : '';
|
|
113
|
-
if (!text.trim())
|
|
92
|
+
const id = String(row.messageId ?? '');
|
|
93
|
+
const ref = pending.get(id);
|
|
94
|
+
if (!ref)
|
|
114
95
|
continue;
|
|
115
|
-
|
|
116
|
-
// nothing to forget here — but the failure still holds the watermark.
|
|
117
|
-
const handled = await attempt(`chat ${news.chatId} message ${messageId}`, () => deps.onMessage(text, {
|
|
96
|
+
const handled = await attempt(`chat ${news.chatId} message ${id}`, () => deps.onMessage(typeof row.text === 'string' ? row.text : '', {
|
|
118
97
|
...row,
|
|
119
98
|
chatId: news.chatId,
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
receiver: { id: deps.ownAgentId },
|
|
125
|
-
...(hireAgreementId ? { agreementId: hireAgreementId } : {}),
|
|
99
|
+
// Preserve the message's addressee. Assignment separately authorizes
|
|
100
|
+
// this carrier to handle it, including a person's stand-in.
|
|
101
|
+
assigneeId: deps.ownAgentId,
|
|
102
|
+
...(ref.agreementId ? { agreementId: ref.agreementId } : {}),
|
|
126
103
|
}));
|
|
127
|
-
if (handled)
|
|
104
|
+
if (handled) {
|
|
128
105
|
delivered++;
|
|
129
|
-
|
|
106
|
+
handledIds.add(id);
|
|
107
|
+
}
|
|
108
|
+
pending.delete(id);
|
|
130
109
|
}
|
|
131
|
-
if (!page.hasMore
|
|
110
|
+
if (!page.hasMore)
|
|
111
|
+
break;
|
|
112
|
+
if (page.nextCursor && page.nextCursor !== cursor)
|
|
113
|
+
cursor = page.nextCursor;
|
|
114
|
+
else if (page.latestSequence && page.latestSequence > after) {
|
|
115
|
+
after = page.latestSequence;
|
|
116
|
+
cursor = undefined;
|
|
117
|
+
}
|
|
118
|
+
else
|
|
132
119
|
break;
|
|
133
|
-
after = page.latestSequence;
|
|
134
|
-
pages++;
|
|
135
120
|
}
|
|
121
|
+
if (pending.size)
|
|
122
|
+
await attempt(`chat ${news.chatId} missing assigned messages`, async () => {
|
|
123
|
+
throw new Error(`Assigned message(s) not read: ${[...pending.keys()].join(', ')}`);
|
|
124
|
+
});
|
|
136
125
|
return delivered;
|
|
137
126
|
}
|
|
138
127
|
/**
|
|
@@ -141,7 +130,7 @@ async function deliverMessagesForChat(deps, news, earliestTs, reader, attempt, h
|
|
|
141
130
|
* Reused each iteration of {@link runInboxLoop}; also usable as a single catch-up
|
|
142
131
|
* read (`waitSeconds: 0`) at bootstrap.
|
|
143
132
|
*/
|
|
144
|
-
export async function pollInboxOnce(deps, waitSeconds = 0,
|
|
133
|
+
export async function pollInboxOnce(deps, waitSeconds = 0, _opts = {}) {
|
|
145
134
|
const label = deps.label ?? 'inboxLoop';
|
|
146
135
|
const inbox = inboxClient(deps);
|
|
147
136
|
const reader = new ContextReadClient(deps.creds.operatorKey, deps.creds.agentId, deps.baseUrl);
|
|
@@ -213,37 +202,21 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
213
202
|
warn(label, `mid-pass ack did not land: ${err?.message ?? err}`);
|
|
214
203
|
}
|
|
215
204
|
};
|
|
216
|
-
|
|
217
|
-
// messages. Ambient chat rows must not drag the read window earlier.
|
|
218
|
-
const earliestByChat = new Map();
|
|
219
|
-
const assignedRowsByChat = new Map();
|
|
205
|
+
const assignedByChat = new Map();
|
|
220
206
|
for (const d of envelope.deliveries) {
|
|
221
|
-
if (!d.chatId || !mine(d))
|
|
207
|
+
if (d.kind !== 'message' || !d.chatId || !mine(d) || d.outOfReach)
|
|
222
208
|
continue;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const rows = assignedRowsByChat.get(d.chatId);
|
|
227
|
-
if (rows)
|
|
228
|
-
rows.push(d.resourceId);
|
|
229
|
-
else
|
|
230
|
-
assignedRowsByChat.set(d.chatId, [d.resourceId]);
|
|
209
|
+
const rows = assignedByChat.get(d.chatId) ?? [];
|
|
210
|
+
rows.push(d);
|
|
211
|
+
assignedByChat.set(d.chatId, rows);
|
|
231
212
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
213
|
+
const chats = [...assignedByChat].map(([chatId, refs]) => ({
|
|
214
|
+
chatId, count: refs.length, latestAt: refs.map(ref => ref.ts).sort().at(-1),
|
|
215
|
+
}));
|
|
216
|
+
for (const news of chatsInWakeOrder(chats)) {
|
|
217
|
+
const n = await deliverMessagesForChat(deps, news, assignedByChat.get(news.chatId), reader, attempt, handledSoFar);
|
|
236
218
|
messagesDelivered += n;
|
|
237
|
-
|
|
238
|
-
log(label, `chat ${news.chatId} delivered ${n} message(s)`);
|
|
239
|
-
// A fold that dropped a handover has not finished its rows, and the
|
|
240
|
-
// watermark must wait for the redelivery it is holding open.
|
|
241
|
-
if (failures === failuresBefore) {
|
|
242
|
-
for (const id of assignedRowsByChat.get(news.chatId) ?? []) {
|
|
243
|
-
handledSoFar.add(id);
|
|
244
|
-
}
|
|
245
|
-
await ackWhatIsFinished();
|
|
246
|
-
}
|
|
219
|
+
await ackWhatIsFinished();
|
|
247
220
|
}
|
|
248
221
|
// Everything that is not chat text is handed over as a resource event, from
|
|
249
222
|
// the delivery itself — the reference already carries the ids the host needs,
|
|
@@ -355,23 +328,17 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
355
328
|
// Act, then ack: everything above has been handed to the host, so the
|
|
356
329
|
// watermark may now move past it. A crash before this point redelivers.
|
|
357
330
|
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
// this window has failed too many times in a row and is now poison, in which
|
|
361
|
-
// case it is dropped loudly rather than wedging the agent forever.
|
|
331
|
+
// Advance only after every assigned id has an explicit disposition.
|
|
332
|
+
// Repeated failures never turn unread mail into an acknowledged delivery.
|
|
362
333
|
// One function, shared with the MCP inbox tool — do not restate the list
|
|
363
334
|
// or the allow-gate here.
|
|
364
335
|
let acked = false;
|
|
365
336
|
const ack = planInboxAck(envelope, {
|
|
366
337
|
ownAgentId: deps.ownAgentId,
|
|
367
338
|
failures,
|
|
368
|
-
forceAck:
|
|
339
|
+
forceAck: false,
|
|
369
340
|
});
|
|
370
|
-
if (ack.allowed) {
|
|
371
|
-
if (failures > 0) {
|
|
372
|
-
runtimeLog.error(label, `acking a window that failed ${failures} handover(s) — giving up on redelivery to keep the ` +
|
|
373
|
-
`agent alive; this mail is lost (ackTo=${envelope.ackTo})`);
|
|
374
|
-
}
|
|
341
|
+
if (ack.allowed && ack.handledResourceIds.every(id => handledSoFar.has(id))) {
|
|
375
342
|
await inbox.ack(envelope.ackTo, { handledResourceIds: ack.handledResourceIds });
|
|
376
343
|
acked = true;
|
|
377
344
|
log(label, `acked up to ${envelope.ackTo}`);
|
|
@@ -399,19 +366,7 @@ export async function pollInboxOnce(deps, waitSeconds = 0, opts = {}) {
|
|
|
399
366
|
const DEFAULT_WAIT_SECONDS = 110;
|
|
400
367
|
const ERROR_BACKOFF_BASE_MS = 1_000;
|
|
401
368
|
const ERROR_BACKOFF_MAX_MS = 30_000;
|
|
402
|
-
/**
|
|
403
|
-
* How many passes in a row may hold the watermark before the window is treated
|
|
404
|
-
* as poison and acked away. Three is enough for the failures we have
|
|
405
|
-
* actually seen — a redeployed container, a wedged backend, a transient throw —
|
|
406
|
-
* and short enough that a frame the host can never handle costs three wakes
|
|
407
|
-
* rather than an unbounded loop.
|
|
408
|
-
*/
|
|
409
|
-
const MAX_HELD_PASSES = 3;
|
|
410
|
-
/**
|
|
411
|
-
* How many consecutive passes have refused to advance the watermark.
|
|
412
|
-
* A capped envelope used to reset the counter (zero handover failures),
|
|
413
|
-
* so `forceAck` never fired on the one hold that lasts forever.
|
|
414
|
-
*/
|
|
369
|
+
/** Count held windows so retries can back off without discarding mail. */
|
|
415
370
|
export function heldPassAfter(prev, pass) {
|
|
416
371
|
return !pass.acked && (pass.failures > 0 || pass.truncated) ? prev + 1 : 0;
|
|
417
372
|
}
|
|
@@ -443,17 +398,13 @@ export function runInboxLoop(deps, opts = {}) {
|
|
|
443
398
|
while (running) {
|
|
444
399
|
try {
|
|
445
400
|
lastAttemptAt = Date.now();
|
|
446
|
-
const pass = await pollInboxOnce(deps, first ? 0 : waitSeconds
|
|
447
|
-
forceAck: heldPasses >= MAX_HELD_PASSES,
|
|
448
|
-
});
|
|
401
|
+
const pass = await pollInboxOnce(deps, first ? 0 : waitSeconds);
|
|
449
402
|
lastPassAt = Date.now();
|
|
450
403
|
first = false;
|
|
451
404
|
consecutiveErrors = 0;
|
|
452
|
-
// A held pass means the same window comes back; count how many times we
|
|
453
|
-
// have refused it so a permanently unhandleable frame cannot spin here.
|
|
454
|
-
// Capped holds used to reset this: failures === 0, so the poison
|
|
455
|
-
// escape never climbed. Count any pass that refused to advance.
|
|
456
405
|
heldPasses = heldPassAfter(heldPasses, pass);
|
|
406
|
+
if (heldPasses)
|
|
407
|
+
await sleep(Math.min(ERROR_BACKOFF_MAX_MS, ERROR_BACKOFF_BASE_MS * 2 ** Math.min(heldPasses - 1, 5)));
|
|
457
408
|
}
|
|
458
409
|
catch (err) {
|
|
459
410
|
if (!running)
|
|
@@ -21,7 +21,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
|
|
|
21
21
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
22
22
|
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
23
23
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
24
|
-
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
24
|
+
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
25
25
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
26
26
|
export { TASK_TOOLS, listTasksTool, cancelTaskTool } from './tier2/taskTools.js';
|
|
27
27
|
export { PLAN_TOOLS, taskReplacePlanTool } from './tier2/planTools.js';
|
|
@@ -23,7 +23,7 @@ export { DISCOVERY_TOOLS, agentSearchTool, agentGetTool } from './tier2/discover
|
|
|
23
23
|
export { MARKETPLACE_TOOLS, marketplaceViewTool } from './tier2/marketplaceTools.js';
|
|
24
24
|
export { PAYMENT_TOOLS, paymentBalanceTool } from './tier2/paymentTools.js';
|
|
25
25
|
export { CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, } from './tier2/connectionTools.js';
|
|
26
|
-
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
26
|
+
export { CONTEXT_TOOLS, contextReadTool, contextRequestTool, accessRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, } from './tier2/contextTools.js';
|
|
27
27
|
// one grant lister across every rail (retires context_discover +
|
|
28
28
|
// payment_list_grants). Always-available on the hosted agent — see CLAUDE_SDK_TOOLS.
|
|
29
29
|
export { GRANTS_TOOLS, listGrantsTool } from './tier2/grantsTools.js';
|
|
@@ -2,6 +2,7 @@ import type { ToolDefinition } from '../../../tools/defineTool.js';
|
|
|
2
2
|
export declare const contextReadTool: ToolDefinition;
|
|
3
3
|
export declare const openTool: ToolDefinition;
|
|
4
4
|
export declare const accessExplainTool: ToolDefinition;
|
|
5
|
+
export declare const accessRequestTool: ToolDefinition;
|
|
5
6
|
export declare const contextRequestTool: ToolDefinition;
|
|
6
7
|
export declare const contextDelegateTool: ToolDefinition;
|
|
7
8
|
export declare const contextExpandReachTool: ToolDefinition;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextRequestCapability, } from '@ziggs-ai/api-client';
|
|
1
|
+
import { CONTEXT_CAPABILITIES, contextReadCapability, openCapability, accessExplainCapability, contextDelegateCapability, contextExpandReachCapability, contextDiscoverGrantableCapability, contextRequestCapability, accessRequestCapability, } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
3
|
// context grant tools ride the same api-client
|
|
4
4
|
// clients as ziggs-mcp; the whole definition (schema + handler) is now shared
|
|
@@ -6,6 +6,7 @@ import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabi
|
|
|
6
6
|
export const contextReadTool = toolFromCapability(contextReadCapability);
|
|
7
7
|
export const openTool = toolFromCapability(openCapability);
|
|
8
8
|
export const accessExplainTool = toolFromCapability(accessExplainCapability);
|
|
9
|
+
export const accessRequestTool = toolFromCapability(accessRequestCapability);
|
|
9
10
|
export const contextRequestTool = toolFromCapability(contextRequestCapability);
|
|
10
11
|
export const contextDelegateTool = toolFromCapability(contextDelegateCapability);
|
|
11
12
|
export const contextExpandReachTool = toolFromCapability(contextExpandReachCapability);
|
|
@@ -382,7 +382,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
382
382
|
lines.push('Return ONE JSON object. ALWAYS include "thought" as the FIRST field — briefly explain your reasoning before choosing an action.');
|
|
383
383
|
lines.push('Set "action" to exactly one of the action names below.');
|
|
384
384
|
lines.push('Message text is rendered as markdown (CommonMark + GFM). You MAY use **bold**, *italic*, `code`, fenced code blocks, lists, links, and tables when they improve clarity — plain text is also fine.\n');
|
|
385
|
-
const replyTo =
|
|
385
|
+
const replyTo = '<receiverId from <recipients>>';
|
|
386
386
|
const pendingProposals = (ctxObj.agreements || []).filter((a) => a.proposal?.status === 'pending' && (a.proposedToIsYou || isBroadcastTarget(a.parties?.proposedTo?.principal)));
|
|
387
387
|
if (pendingProposals.length > 0) {
|
|
388
388
|
lines.push(`PRIORITY: You have ${pendingProposals.length} pending proposal(s) directed at you. Respond BEFORE other actions.`);
|
|
@@ -439,23 +439,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
439
439
|
const senderId = String(ev?.senderId || 'system');
|
|
440
440
|
const senderType = ev?.senderType ? String(ev.senderType).toUpperCase() : 'SYSTEM';
|
|
441
441
|
const myId = this._getMyAgentId(ctxObj);
|
|
442
|
-
|
|
443
|
-
let defaultReplyReceiverId = senderId;
|
|
444
|
-
const isNonRoutable = isNonRoutableSenderType(senderType);
|
|
445
|
-
if (isNonRoutable) {
|
|
446
|
-
const history = ctxObj.history || [];
|
|
447
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
448
|
-
const row = history[i];
|
|
449
|
-
const sender = row.sender;
|
|
450
|
-
const id = String(sender?.id || '');
|
|
451
|
-
const type = String(sender?.type || '').toUpperCase();
|
|
452
|
-
if (id && id !== myId && !isNonRoutableSenderType(type)) {
|
|
453
|
-
defaultReplyReceiverId = id;
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return { senderId, senderType, myAgentId: myId, defaultReplyReceiverId: defaultReplyReceiverId || senderId };
|
|
442
|
+
return { senderId, senderType, myAgentId: myId };
|
|
459
443
|
}
|
|
460
444
|
_getMyAgentId(ctxObj) {
|
|
461
445
|
const agents = ctxObj.agents || [];
|
|
@@ -36,6 +36,14 @@ export interface EmittedEvent {
|
|
|
36
36
|
senderId?: string;
|
|
37
37
|
[key: string]: unknown;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A chat message whose stated receiver is a DIFFERENT party. `receiverId` is
|
|
41
|
+
* the sender's addressing decision, carried on every wire frame; the
|
|
42
|
+
* human-broadcast sentinel counts as "elsewhere" for an agent (a broadcast is
|
|
43
|
+
* addressed to the room's humans). A frame with NO receiver keeps the turn —
|
|
44
|
+
* we cannot know it is not ours, and skipping on absence would deafen agents
|
|
45
|
+
* to malformed or legacy frames.
|
|
46
|
+
*/
|
|
39
47
|
export declare function isMessageAddressedElsewhere(ev: RawEvent | null | undefined, ownAgentId: string | null | undefined): boolean;
|
|
40
48
|
/**
|
|
41
49
|
* Converts a raw incoming event into the single Outcome that re-enters the
|
|
@@ -9,24 +9,11 @@ import { bookForDelegation, ensureTaskBook, findBookHoldingPending, removeTaskBo
|
|
|
9
9
|
* we cannot know it is not ours, and skipping on absence would deafen agents
|
|
10
10
|
* to malformed or legacy frames.
|
|
11
11
|
*/
|
|
12
|
-
function isSyntheticOrgReceiver(id) {
|
|
13
|
-
if (/^([a-z0-9][a-z0-9-]*)\1@ziggs\.local$/i.test(id))
|
|
14
|
-
return true;
|
|
15
|
-
const sep = id.indexOf(':::');
|
|
16
|
-
if (sep > 0)
|
|
17
|
-
return /@ziggs\.local$/i.test(id.slice(sep + 3));
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
12
|
export function isMessageAddressedElsewhere(ev, ownAgentId) {
|
|
21
13
|
if (!ev || ev.type !== 'message' || !ownAgentId || !ev.receiverId) {
|
|
22
14
|
return false;
|
|
23
15
|
}
|
|
24
|
-
if (ev.receiverId === ownAgentId)
|
|
25
|
-
return false;
|
|
26
|
-
// Fleet synthetic users are not a party that will answer. A hire room
|
|
27
|
-
// born with the org ghost as the other face used to address the human's
|
|
28
|
-
// message there, and the agent skipped the turn — claimed, acked, silence.
|
|
29
|
-
if (isSyntheticOrgReceiver(String(ev.receiverId)))
|
|
16
|
+
if (ev.assigneeId === ownAgentId || ev.receiverId === ownAgentId)
|
|
30
17
|
return false;
|
|
31
18
|
return true;
|
|
32
19
|
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "0.
|
|
29
|
+
"@ziggs-ai/api-client": "0.26.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|