@ziggs-ai/agent-sdk 0.7.1 → 0.7.2
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/ClaudeSdkAgent.js +27 -0
- package/dist/agent/claudeCognition.js +7 -2
- package/dist/agent/claudeSdkTools.js +9 -7
- package/dist/formatters/HistoryFormatter.js +5 -0
- package/dist/ingress/normalizeIncoming.d.ts +11 -4
- package/dist/ingress/normalizeIncoming.js +41 -3
- package/dist/runtime/AgentMachine.js +11 -3
- package/dist/runtime/runTurn.js +25 -11
- package/dist/runtime/wakeWriteKeys.d.ts +7 -5
- package/dist/runtime/wakeWriteKeys.js +10 -7
- package/dist/types.d.ts +12 -2
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { outcomeFromEvent } from '../runtime/buildOutcome.js';
|
|
3
|
+
import { getBatchEvents } from '../context/batch.js';
|
|
3
4
|
import { bootstrapCtx } from '../runtime/bootstrapCtx.js';
|
|
4
5
|
import { createWakeWriteKeys } from '../runtime/wakeWriteKeys.js';
|
|
5
6
|
import { CLAUDE_SDK_PROTOCOL } from './claudeSdkProtocol.js';
|
|
@@ -7,6 +8,21 @@ import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
|
|
|
7
8
|
import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAssistantText, } from './claudeCognition.js';
|
|
8
9
|
import { assertHostedAgentZiggsTools } from './hostedAgentConnection.js';
|
|
9
10
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
11
|
+
/**
|
|
12
|
+
* ZIG-1035: lifecycle / status notices must not spend a Claude turn answering
|
|
13
|
+
* nobody. Real work wakes arrive as chat `message` or as synthetic `task_result`
|
|
14
|
+
* from `wakeTaskAssignment` (task channel). Terminal task_result notices still
|
|
15
|
+
* run so orchestrators can consume completions.
|
|
16
|
+
*/
|
|
17
|
+
function isNoticeOnlyWake(event) {
|
|
18
|
+
const events = getBatchEvents(event);
|
|
19
|
+
if (events.length === 0)
|
|
20
|
+
return false;
|
|
21
|
+
return events.every((ev) => {
|
|
22
|
+
const t = ev?.type;
|
|
23
|
+
return t === 'notification' || t === 'agreement_lifecycle';
|
|
24
|
+
});
|
|
25
|
+
}
|
|
10
26
|
const DEFAULT_STATE = 'ready';
|
|
11
27
|
/**
|
|
12
28
|
* Per-task turn cap. A wake drives one `query()` that may loop up to this many
|
|
@@ -186,6 +202,17 @@ export class ClaudeSdkAgent {
|
|
|
186
202
|
};
|
|
187
203
|
}
|
|
188
204
|
nextCtx.recentWakes = [...recentWakes, now];
|
|
205
|
+
if (isNoticeOnlyWake(rawEvent)) {
|
|
206
|
+
runtimeLog.info('ClaudeSdkAgent', `notice-only wake — skipping LLM session=${identity.sessionId} agent=${this.agentId} ` +
|
|
207
|
+
`type=${rawEvent?.type ?? 'unknown'}`);
|
|
208
|
+
return {
|
|
209
|
+
ctx: nextCtx,
|
|
210
|
+
state: state ?? DEFAULT_STATE,
|
|
211
|
+
parked: true,
|
|
212
|
+
stateTimeoutUnhandled: false,
|
|
213
|
+
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
189
216
|
let contextSnap = null;
|
|
190
217
|
try {
|
|
191
218
|
contextSnap = await eff({
|
|
@@ -72,8 +72,13 @@ export function summarizeContext(snap) {
|
|
|
72
72
|
function renderEventLines(event, lines) {
|
|
73
73
|
if (event?.type)
|
|
74
74
|
lines.push(`Event type: ${event.type}`);
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// ZIG-1035: lifecycle notices are events, not counterparty speech.
|
|
76
|
+
if (event?.text) {
|
|
77
|
+
const notice = event.type === 'notification' ||
|
|
78
|
+
event.type === 'agreement_lifecycle' ||
|
|
79
|
+
event.entryType === 'notification';
|
|
80
|
+
lines.push(`${notice ? 'Notification' : 'Message'}: ${event.text}`);
|
|
81
|
+
}
|
|
77
82
|
if (event?.senderId)
|
|
78
83
|
lines.push(`Sender: ${event.senderId}`);
|
|
79
84
|
if (event?.operation)
|
|
@@ -47,19 +47,21 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
47
47
|
const receiverId = args.receiverId ?? c.senderId;
|
|
48
48
|
if (!receiverId)
|
|
49
49
|
throw new Error('chat_send: receiverId is required (this wake has no sender to default to)');
|
|
50
|
-
// Default to the wake's
|
|
51
|
-
// re-derives the same messageId and the backend drops
|
|
52
|
-
// (ZIG-718)
|
|
53
|
-
// of
|
|
54
|
-
//
|
|
50
|
+
// Default to the wake's content-derived key (ZIG-1045): a crash-replay that
|
|
51
|
+
// re-sends the same text re-derives the same messageId and the backend drops
|
|
52
|
+
// the duplicate (ZIG-718); a re-think with different text gets a new key
|
|
53
|
+
// instead of colliding on a per-wake ordinal. An explicit LLM idempotencyKey
|
|
54
|
+
// still wins (retry-safe resend of the same logical message across wakes);
|
|
55
|
+
// random only when the wake has no durable identity.
|
|
56
|
+
const text = String(args.text);
|
|
55
57
|
const messageId = args.idempotencyKey
|
|
56
58
|
? `sdk_idem_${String(args.idempotencyKey)}`
|
|
57
|
-
: c.wakeKeys?.messageKey() ?? `sdk_${randomUUID()}`;
|
|
59
|
+
: c.wakeKeys?.messageKey({ text, sessionId, receiverId }) ?? `sdk_${randomUUID()}`;
|
|
58
60
|
return c.eff({
|
|
59
61
|
kind: 'send-message',
|
|
60
62
|
sessionId,
|
|
61
63
|
receiverId,
|
|
62
|
-
text
|
|
64
|
+
text,
|
|
63
65
|
messageId,
|
|
64
66
|
});
|
|
65
67
|
}, { description: 'Send a user-visible chat message. Conversation only — never use for finished-work reporting.' });
|
|
@@ -24,6 +24,11 @@ export class HistoryFormatter {
|
|
|
24
24
|
const label = labelTypes ? '[CHAT] ' : '';
|
|
25
25
|
return `${label}${this.formatMessage(entry, agentId, timePrefix)}`;
|
|
26
26
|
}
|
|
27
|
+
// ZIG-1035: notices are not counterparty turns — no "from → to:" speech framing.
|
|
28
|
+
if (entryType === 'notification') {
|
|
29
|
+
const label = labelTypes ? '[NOTICE] ' : '';
|
|
30
|
+
return `${label}${timePrefix}[notice] ${entry['text'] || ''}`;
|
|
31
|
+
}
|
|
27
32
|
if (entryType === 'task_history') {
|
|
28
33
|
const label = labelTypes ? '[TASK] ' : '';
|
|
29
34
|
return `${label}${this.formatTaskHistory(entry, agentId, timePrefix)}`;
|
|
@@ -11,7 +11,14 @@ export interface NormalizedEvent {
|
|
|
11
11
|
*/
|
|
12
12
|
messageId?: string;
|
|
13
13
|
timestamp: number;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `notification` = lifecycle / status notice (ZIG-1035). Not counterparty
|
|
16
|
+
* speech; Claude parks without a model turn. Terminal task notices still
|
|
17
|
+
* use `task_result` so orchestrators see completions.
|
|
18
|
+
*/
|
|
19
|
+
type: 'task_result' | 'message' | 'agreement_lifecycle' | 'notification';
|
|
20
|
+
/** Wire `entryType` when present (kept so brains can tell notice vs speech). */
|
|
21
|
+
entryType?: string;
|
|
15
22
|
operation?: string;
|
|
16
23
|
agreementId?: string;
|
|
17
24
|
result?: unknown;
|
|
@@ -72,9 +79,9 @@ interface NormalizeArgs {
|
|
|
72
79
|
* backend policy now (delivery law, phase 4): the inbox loop only ever delivers
|
|
73
80
|
* what is fenced to this agent, so there is no client-side "is this for me"
|
|
74
81
|
* relevance gate — this function decides SHAPE (message vs task_result vs
|
|
75
|
-
* agreement_lifecycle) and the fields the FSM / SessionResolver /
|
|
76
|
-
* depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
77
|
-
* but no longer gates anything.
|
|
82
|
+
* agreement_lifecycle vs notification) and the fields the FSM / SessionResolver /
|
|
83
|
+
* wakeWriteKeys depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
84
|
+
* compatibility but no longer gates anything.
|
|
78
85
|
*/
|
|
79
86
|
export declare function normalizeIncomingEvent({ text, metadata }: NormalizeArgs): NormalizedEvent;
|
|
80
87
|
export {};
|
|
@@ -26,14 +26,17 @@ function isAgreementLifecycleNotification(metadata) {
|
|
|
26
26
|
typeof operation === 'string' &&
|
|
27
27
|
PROPOSAL_RESOLVED_OPERATIONS.has(operation));
|
|
28
28
|
}
|
|
29
|
+
function isTerminalTaskState(state) {
|
|
30
|
+
return state === 'completed' || state === 'failed' || state === 'cancelled';
|
|
31
|
+
}
|
|
29
32
|
/**
|
|
30
33
|
* Classify + shape a wire delivery into a lane-routable event. Targeting is
|
|
31
34
|
* backend policy now (delivery law, phase 4): the inbox loop only ever delivers
|
|
32
35
|
* what is fenced to this agent, so there is no client-side "is this for me"
|
|
33
36
|
* relevance gate — this function decides SHAPE (message vs task_result vs
|
|
34
|
-
* agreement_lifecycle) and the fields the FSM / SessionResolver /
|
|
35
|
-
* depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
36
|
-
* but no longer gates anything.
|
|
37
|
+
* agreement_lifecycle vs notification) and the fields the FSM / SessionResolver /
|
|
38
|
+
* wakeWriteKeys depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
39
|
+
* compatibility but no longer gates anything.
|
|
37
40
|
*/
|
|
38
41
|
export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
39
42
|
// ZIG-936: classifier data lives under `service` on the inbox-pull wire;
|
|
@@ -42,12 +45,16 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
42
45
|
const chatId = normalizeChatId(metadata);
|
|
43
46
|
const receiverId = normalizeReceiverId(metadata, taskData);
|
|
44
47
|
const senderType = metadata.sender?.type ? String(metadata.sender.type).toUpperCase() : undefined;
|
|
48
|
+
const entryType = typeof metadata.entryType === 'string' && metadata.entryType
|
|
49
|
+
? metadata.entryType
|
|
50
|
+
: undefined;
|
|
45
51
|
const base = {
|
|
46
52
|
senderId: metadata.sender?.id,
|
|
47
53
|
senderType,
|
|
48
54
|
receiverId: receiverId || undefined,
|
|
49
55
|
messageId: typeof metadata.messageId === 'string' && metadata.messageId ? metadata.messageId : undefined,
|
|
50
56
|
timestamp: Date.now(),
|
|
57
|
+
...(entryType ? { entryType } : {}),
|
|
51
58
|
};
|
|
52
59
|
if (isAgreementLifecycleNotification(metadata)) {
|
|
53
60
|
const operation = metadata.service?.operation ?? metadata.operation;
|
|
@@ -65,11 +72,42 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
65
72
|
};
|
|
66
73
|
}
|
|
67
74
|
if (isTaskRelated(metadata, taskData)) {
|
|
75
|
+
// ZIG-1035: active/spawn chat notices are not work — real assignment arrives
|
|
76
|
+
// on the task channel (`tasksAwaitingMe` → wakeTaskAssignment). Keep terminal
|
|
77
|
+
// task notices as `task_result` so orchestrators still see completions.
|
|
78
|
+
if (entryType === 'notification' && !isTerminalTaskState(taskData?.state)) {
|
|
79
|
+
return {
|
|
80
|
+
chatId,
|
|
81
|
+
event: {
|
|
82
|
+
...base,
|
|
83
|
+
type: 'notification',
|
|
84
|
+
result: taskData,
|
|
85
|
+
text,
|
|
86
|
+
operation: metadata.service?.operation ?? metadata.operation,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
68
90
|
return {
|
|
69
91
|
chatId,
|
|
70
92
|
event: { ...base, type: 'task_result', result: taskData, text },
|
|
71
93
|
};
|
|
72
94
|
}
|
|
95
|
+
// ZIG-1035: residual lifecycle notices (proposal_created, agreement active, …)
|
|
96
|
+
// must not fall through as counterparty `message` speech.
|
|
97
|
+
if (entryType === 'notification') {
|
|
98
|
+
const operation = metadata.service?.operation ?? metadata.operation;
|
|
99
|
+
const rawAgreementId = metadata.service?.agreementId ?? metadata.agreementId;
|
|
100
|
+
return {
|
|
101
|
+
chatId,
|
|
102
|
+
event: {
|
|
103
|
+
...base,
|
|
104
|
+
type: 'notification',
|
|
105
|
+
text,
|
|
106
|
+
...(typeof operation === 'string' ? { operation } : {}),
|
|
107
|
+
...(typeof rawAgreementId === 'string' ? { agreementId: rawAgreementId } : {}),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
73
111
|
return {
|
|
74
112
|
chatId,
|
|
75
113
|
event: { ...base, type: 'message', text },
|
|
@@ -356,9 +356,17 @@ export class AgentMachine {
|
|
|
356
356
|
if (!userId)
|
|
357
357
|
return;
|
|
358
358
|
try {
|
|
359
|
-
//
|
|
360
|
-
// again and re-sends this apology — same key, backend
|
|
361
|
-
|
|
359
|
+
// Content-derived wake messageId (ZIG-1045): a crash-replay that errors
|
|
360
|
+
// the same way parks again and re-sends this apology — same key, backend
|
|
361
|
+
// drops the dupe.
|
|
362
|
+
const sessionId = this.ctx.identity.sessionId;
|
|
363
|
+
await this.eff({
|
|
364
|
+
kind: 'send-message',
|
|
365
|
+
sessionId,
|
|
366
|
+
receiverId: userId,
|
|
367
|
+
text,
|
|
368
|
+
messageId: this.wakeKeys?.messageKey({ text, sessionId, receiverId: userId }),
|
|
369
|
+
});
|
|
362
370
|
}
|
|
363
371
|
catch { /* best-effort */ }
|
|
364
372
|
}
|
package/dist/runtime/runTurn.js
CHANGED
|
@@ -65,7 +65,15 @@ export async function runTurn(input) {
|
|
|
65
65
|
return eff({ ...effect, runId, stateId, wakeKeys });
|
|
66
66
|
}
|
|
67
67
|
if (effect.kind === 'send-message' && !effect.messageId) {
|
|
68
|
-
|
|
68
|
+
const send = effect;
|
|
69
|
+
return eff({
|
|
70
|
+
...effect,
|
|
71
|
+
messageId: wakeKeys?.messageKey({
|
|
72
|
+
text: send.text,
|
|
73
|
+
sessionId: send.sessionId,
|
|
74
|
+
receiverId: send.receiverId,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
69
77
|
}
|
|
70
78
|
return eff(effect);
|
|
71
79
|
};
|
|
@@ -316,11 +324,12 @@ export async function runTurn(input) {
|
|
|
316
324
|
const inboundFromAgent = String(incomingEvent?.senderType || '').toUpperCase() === 'AGENT';
|
|
317
325
|
runtimeLog.info('runTurn', `streaming check: enabled=${streamRespondActions} receiverId=${decision.receiverId ?? 'none'} tool=${actionDef?.tool ?? 'none'} action=${chosenAction} inboundFromAgent=${inboundFromAgent}`);
|
|
318
326
|
if (streamRespondActions && !inboundFromAgent && decision.receiverId && !actionDef?.tool && chosenAction !== 'wait') {
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
327
|
+
// Live chunks use a provisional id (text not known yet). The persisted
|
|
328
|
+
// send below switches to a content-derived wake key (ZIG-1045) once the
|
|
329
|
+
// reply text exists — a crash-replay of the same text reuses that key
|
|
330
|
+
// (ZIG-718), and a re-think with different text does not collide.
|
|
331
|
+
const chunkMessageId = crypto.randomUUID();
|
|
332
|
+
runtimeLog.info('runTurn', `stream-text start action=${chosenAction} receiverId=${decision.receiverId} msgId=${chunkMessageId}`);
|
|
324
333
|
const streamMessages = [
|
|
325
334
|
...messages,
|
|
326
335
|
{
|
|
@@ -334,7 +343,7 @@ export async function runTurn(input) {
|
|
|
334
343
|
];
|
|
335
344
|
let replyText = '';
|
|
336
345
|
try {
|
|
337
|
-
const streamResult = await eff({ kind: 'stream-text', sessionId, receiverId: decision.receiverId, messageId:
|
|
346
|
+
const streamResult = await eff({ kind: 'stream-text', sessionId, receiverId: decision.receiverId, messageId: chunkMessageId, messages: streamMessages });
|
|
338
347
|
replyText = streamResult.text ?? '';
|
|
339
348
|
// Count the streamed reply only when it actually produced the text we
|
|
340
349
|
// send (an empty resolve falls through to the llm-call fallback below,
|
|
@@ -356,6 +365,7 @@ export async function runTurn(input) {
|
|
|
356
365
|
// llm-call + send-message below so the user still gets a reply.
|
|
357
366
|
runtimeLog.error('runTurn', `stream-text failed: ${err.message} — falling back to non-streaming send`);
|
|
358
367
|
}
|
|
368
|
+
let persistMessageId = chunkMessageId;
|
|
359
369
|
try {
|
|
360
370
|
if (!replyText) {
|
|
361
371
|
const fallbackResponse = await effWithTrace({ kind: 'llm-call', messages: streamMessages, tools: [], sessionId });
|
|
@@ -369,16 +379,20 @@ export async function runTurn(input) {
|
|
|
369
379
|
throw new Error('fallback llm-call returned empty reply text');
|
|
370
380
|
}
|
|
371
381
|
const safeText = truncateMsg(replyText);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
382
|
+
persistMessageId =
|
|
383
|
+
wakeKeys?.messageKey({
|
|
384
|
+
text: safeText,
|
|
385
|
+
sessionId,
|
|
386
|
+
receiverId: decision.receiverId,
|
|
387
|
+
}) ?? chunkMessageId;
|
|
388
|
+
await eff({ kind: 'send-message', sessionId, receiverId: decision.receiverId, text: safeText, messageId: persistMessageId });
|
|
375
389
|
allEmittedEvents.push({ type: 'message_sent', receiverId: decision.receiverId, chatId: sessionId, message: safeText, senderId: 'system' });
|
|
376
390
|
lastAction = chosenAction;
|
|
377
391
|
lastActionDef = actionDef;
|
|
378
392
|
}
|
|
379
393
|
catch (err) {
|
|
380
394
|
if (isReplySlotTaken(err)) {
|
|
381
|
-
runtimeLog.warn('runTurn', `reply already persisted under ${
|
|
395
|
+
runtimeLog.warn('runTurn', `reply already persisted under ${persistMessageId} (idempotency 409) — a replay or second host answered this wake first; dropping duplicate`);
|
|
382
396
|
allEmittedEvents.push({ type: 'message_duplicate_skipped', receiverId: decision.receiverId, chatId: sessionId, senderId: 'system' });
|
|
383
397
|
lastAction = chosenAction;
|
|
384
398
|
lastActionDef = actionDef;
|
|
@@ -9,11 +9,13 @@ import type { WakeWriteKeys } from '../types.js';
|
|
|
9
9
|
* - task transitions: `wk_<wake-hash>_t_<hash(taskId, state)>` — derived from
|
|
10
10
|
* the transition itself, so it dedups regardless of the order in which the
|
|
11
11
|
* replayed wake happens to re-issue its tool calls.
|
|
12
|
-
* - chat messages: `sdk_idem_wk_<wake-hash>
|
|
13
|
-
* the messageId (the backend dedups on (chatId, messageId),
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* - chat messages: `sdk_idem_wk_<wake-hash>_m_<hash(chat, receiver, text)>`
|
|
13
|
+
* — used directly as the messageId (the backend dedups on (chatId, messageId),
|
|
14
|
+
* ZIG-718). Content-derived (ZIG-1045), not ordinal: a wake may emit several
|
|
15
|
+
* distinct messages without colliding, and a crash-replay that re-sends the
|
|
16
|
+
* same text reuses the key; a re-think with different text gets a new key
|
|
17
|
+
* instead of 409-dropping on the old ordinal slot. The prefix matches the
|
|
18
|
+
* tool-layer convention for deterministic ids (`sdk_idem_<llm-key>`).
|
|
17
19
|
* - task spawns: `wk_<wake-hash>_s_<hash(agreement, description, …)>` —
|
|
18
20
|
* derived from what the spawn writes, so it dedups regardless of re-issue
|
|
19
21
|
* order while still distinguishing multiple spawns in one wake.
|
|
@@ -78,11 +78,13 @@ function sha(input) {
|
|
|
78
78
|
* - task transitions: `wk_<wake-hash>_t_<hash(taskId, state)>` — derived from
|
|
79
79
|
* the transition itself, so it dedups regardless of the order in which the
|
|
80
80
|
* replayed wake happens to re-issue its tool calls.
|
|
81
|
-
* - chat messages: `sdk_idem_wk_<wake-hash>
|
|
82
|
-
* the messageId (the backend dedups on (chatId, messageId),
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
81
|
+
* - chat messages: `sdk_idem_wk_<wake-hash>_m_<hash(chat, receiver, text)>`
|
|
82
|
+
* — used directly as the messageId (the backend dedups on (chatId, messageId),
|
|
83
|
+
* ZIG-718). Content-derived (ZIG-1045), not ordinal: a wake may emit several
|
|
84
|
+
* distinct messages without colliding, and a crash-replay that re-sends the
|
|
85
|
+
* same text reuses the key; a re-think with different text gets a new key
|
|
86
|
+
* instead of 409-dropping on the old ordinal slot. The prefix matches the
|
|
87
|
+
* tool-layer convention for deterministic ids (`sdk_idem_<llm-key>`).
|
|
86
88
|
* - task spawns: `wk_<wake-hash>_s_<hash(agreement, description, …)>` —
|
|
87
89
|
* derived from what the spawn writes, so it dedups regardless of re-issue
|
|
88
90
|
* order while still distinguishing multiple spawns in one wake.
|
|
@@ -107,10 +109,11 @@ export function createWakeWriteKeys(laneKey, event) {
|
|
|
107
109
|
return undefined;
|
|
108
110
|
return `wk_${wakeHash}_t_${sha(`${taskId}\n${state}`).slice(0, 16)}`;
|
|
109
111
|
},
|
|
110
|
-
messageKey() {
|
|
112
|
+
messageKey(fields) {
|
|
111
113
|
if (wakeHash === null)
|
|
112
114
|
return undefined;
|
|
113
|
-
|
|
115
|
+
const msgHash = sha(`${fields.sessionId ?? ''}\n${fields.receiverId ?? ''}\n${fields.text}`).slice(0, 16);
|
|
116
|
+
return `sdk_idem_wk_${wakeHash}_m_${msgHash}`;
|
|
114
117
|
},
|
|
115
118
|
taskSpawnKey(fields) {
|
|
116
119
|
if (wakeHash === null)
|
package/dist/types.d.ts
CHANGED
|
@@ -389,8 +389,18 @@ export interface WakeWriteKeys {
|
|
|
389
389
|
artifactKey(): string | undefined;
|
|
390
390
|
/** Key for a task state transition. Derived from (taskId, state), so it is stable regardless of tool-call order within the wake. */
|
|
391
391
|
taskStateKey(taskId: string, state: string): string | undefined;
|
|
392
|
-
/**
|
|
393
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Default messageId for a chat-message send of this wake — the backend dedups
|
|
394
|
+
* on (chatId, messageId). Derived from the emission's content (text + chat +
|
|
395
|
+
* receiver), not a per-wake ordinal (ZIG-1045): a wake that says two things
|
|
396
|
+
* gets two keys, and a redelivered wake that re-says the same text reuses the
|
|
397
|
+
* same key, while a re-think with different text does not collide and drop.
|
|
398
|
+
*/
|
|
399
|
+
messageKey(fields: {
|
|
400
|
+
text: string;
|
|
401
|
+
sessionId?: string;
|
|
402
|
+
receiverId?: string;
|
|
403
|
+
}): string | undefined;
|
|
394
404
|
/** Key for a task spawn. Derived from what the spawn writes (agreement, description, assignee, parent), so it is stable regardless of tool-call order within the wake. */
|
|
395
405
|
taskSpawnKey(fields: {
|
|
396
406
|
agreementId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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.6.
|
|
29
|
+
"@ziggs-ai/api-client": "^0.6.3",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|