@ziggs-ai/agent-sdk 0.23.0 → 0.24.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 (39) hide show
  1. package/dist/Agent.d.ts +8 -2
  2. package/dist/Agent.js +232 -53
  3. package/dist/brain/ClaudeBrain.d.ts +1 -8
  4. package/dist/brain/ClaudeBrain.js +13 -42
  5. package/dist/brain/ZiggsBrain.d.ts +1 -1
  6. package/dist/brain/ZiggsBrain.js +9 -2
  7. package/dist/brain/claudeCognition.js +6 -2
  8. package/dist/brain/claudeSdkProtocol.d.ts +1 -1
  9. package/dist/brain/claudeSdkProtocol.js +3 -2
  10. package/dist/brain/claudeSdkTools.d.ts +3 -0
  11. package/dist/brain/claudeSdkTools.js +10 -1
  12. package/dist/brain/index.d.ts +9 -1
  13. package/dist/brain/index.js +5 -1
  14. package/dist/brain/toolManagerToSdkMcp.js +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/ingress/normalizeIncoming.d.ts +1 -0
  18. package/dist/ingress/normalizeIncoming.js +2 -1
  19. package/dist/platform/ZiggsEffectHandler.d.ts +1 -1
  20. package/dist/platform/ZiggsEffectHandler.js +20 -10
  21. package/dist/platform/inboxLoop.d.ts +4 -3
  22. package/dist/platform/inboxLoop.js +5 -6
  23. package/dist/platform/tasks/TaskService.d.ts +1 -0
  24. package/dist/platform/tasks/TaskService.js +4 -1
  25. package/dist/platform/tools/tier1/protocolRunner.js +1 -0
  26. package/dist/platform/tools/tier1/protocolTools.js +2 -2
  27. package/dist/probeChat.d.ts +12 -0
  28. package/dist/probeChat.js +14 -0
  29. package/dist/runtime/PromptBuilder.js +4 -1
  30. package/dist/runtime/agreementContext.d.ts +5 -0
  31. package/dist/runtime/agreementContext.js +75 -0
  32. package/dist/runtime/mandateDeclaration.d.ts +1 -1
  33. package/dist/runtime/mandateDeclaration.js +4 -4
  34. package/dist/runtime/runTurn.js +3 -2
  35. package/dist/runtime/wakeEffects.d.ts +5 -0
  36. package/dist/runtime/wakeEffects.js +14 -1
  37. package/dist/tools/capabilityAdapter.js +1 -0
  38. package/dist/types.d.ts +10 -0
  39. package/package.json +2 -2
@@ -0,0 +1,75 @@
1
+ const row = (value) => value && typeof value === 'object' ? value : {};
2
+ /** Enrich each wake from the agreement it actually names, never from the room's
3
+ * latest deal. Reads use the agent's credentials; no grants are inferred here. */
4
+ export async function withAgreementContext(event, read) {
5
+ const reads = new Map();
6
+ const context = (id) => {
7
+ if (!reads.has(id))
8
+ reads.set(id, (async () => {
9
+ try {
10
+ let timer;
11
+ const agreement = row(await Promise.race([
12
+ read(id),
13
+ new Promise((_, reject) => {
14
+ timer = setTimeout(() => reject(new Error('Agreement read timed out')), 5_000);
15
+ }),
16
+ ]).finally(() => { if (timer)
17
+ clearTimeout(timer); }));
18
+ if (agreement.agreementId !== id)
19
+ throw new Error('Agreement is unavailable');
20
+ const terms = row(agreement.terms);
21
+ const usage = row(agreement.conversationUsage);
22
+ const money = row(agreement.money);
23
+ const limit = typeof terms.conversationAllowance === 'number' ? terms.conversationAllowance : null;
24
+ const used = typeof usage.messages === 'number' ? usage.messages : 0;
25
+ return {
26
+ agreementId: id, available: true,
27
+ freeConversation: row(agreement.metadata).freeConversation === true,
28
+ status: agreement.status,
29
+ relationship: agreement.engagementKind === 'link' || row(agreement.metadata).freeConversation === true,
30
+ interactionMode: terms.interactionMode ?? 'both',
31
+ parentAgreementId: agreement.parentAgreementId ?? null,
32
+ allowanceScope: terms.conversationAllowanceScope ?? 'direct',
33
+ scope: terms.description,
34
+ lifecycle: terms.lifecycle,
35
+ expiresAt: terms.expiresAt ?? null,
36
+ maxTasks: terms.maxExecutions ?? null,
37
+ wakeCeiling: terms.wakeCeiling ?? null,
38
+ messageLimit: limit,
39
+ messagesUsed: used,
40
+ messagesRemaining: limit === null ? null : Math.max(0, limit - used),
41
+ price: money.price ?? 0,
42
+ billing: terms.billing ?? 'total',
43
+ paymentStatus: money.paymentStatus,
44
+ access: 'Tool and resource access requires separate grants.',
45
+ };
46
+ }
47
+ catch {
48
+ return { agreementId: id, available: false,
49
+ instruction: 'Agreement terms could not be read. Verify authorization before starting new service work or making commitments.' };
50
+ }
51
+ })());
52
+ return reads.get(id);
53
+ };
54
+ const enrich = async (value) => {
55
+ const frame = row(value);
56
+ if (frame.type === 'batch' && Array.isArray(frame.events)) {
57
+ return { ...frame, events: await Promise.all(frame.events.map(enrich)) };
58
+ }
59
+ const resource = row(frame.resource);
60
+ const result = row(frame.result);
61
+ const id = frame.agreementId ?? resource.agreementId ?? result.agreementId ?? row(result.agreement).agreementId;
62
+ if (typeof id !== 'string' || !id)
63
+ return value;
64
+ return { ...frame, agreementContext: await context(id) };
65
+ };
66
+ return enrich(event);
67
+ }
68
+ /** Free chat supplies conversation context, never consent to another engagement. */
69
+ export function permitsAutomaticMandate(event) {
70
+ const frame = row(event);
71
+ if (frame.type === 'batch' && Array.isArray(frame.events))
72
+ return frame.events.every(permitsAutomaticMandate);
73
+ const context = row(frame.agreementContext);
74
+ return context.relationship !== true && context.freeConversation !== true && context.available !== false;
75
+ }
@@ -6,4 +6,4 @@ import type { ToolManager } from '../tools/ToolManager.js';
6
6
  * declaration, the model already made one, or this wake is not running inside a
7
7
  * job at all.
8
8
  */
9
- export declare function withDeclaredMandate(args: Record<string, unknown>, toolName: string | null | undefined, toolManager: ToolManager | undefined, activeAgreementId: string | null | undefined): Record<string, unknown>;
9
+ export declare function withDeclaredMandate(args: Record<string, unknown>, toolName: string | null | undefined, toolManager: ToolManager | undefined, activeAgreementId: string | null | undefined, allowSpendingMandate?: boolean): Record<string, unknown>;
@@ -30,14 +30,14 @@
30
30
  */
31
31
  const MANDATE_PARAM_NAMES = ['mandateAgreementId', 'parentAgreementId'];
32
32
  /** The mandate parameter this tool declares, if it declares one. */
33
- function mandateParamOf(toolName, toolManager) {
33
+ function mandateParamOf(toolName, toolManager, allowSpendingMandate = true) {
34
34
  if (!toolName)
35
35
  return null;
36
36
  const schema = toolManager?.getTool?.(toolName)?.schema;
37
37
  const properties = schema?.function?.parameters?.properties;
38
38
  if (!properties)
39
39
  return null;
40
- for (const name of MANDATE_PARAM_NAMES) {
40
+ for (const name of (allowSpendingMandate ? MANDATE_PARAM_NAMES : ['parentAgreementId'])) {
41
41
  if (Object.prototype.hasOwnProperty.call(properties, name))
42
42
  return name;
43
43
  }
@@ -50,10 +50,10 @@ function mandateParamOf(toolName, toolManager) {
50
50
  * declaration, the model already made one, or this wake is not running inside a
51
51
  * job at all.
52
52
  */
53
- export function withDeclaredMandate(args, toolName, toolManager, activeAgreementId) {
53
+ export function withDeclaredMandate(args, toolName, toolManager, activeAgreementId, allowSpendingMandate = true) {
54
54
  if (!activeAgreementId)
55
55
  return args;
56
- const param = mandateParamOf(toolName, toolManager);
56
+ const param = mandateParamOf(toolName, toolManager, allowSpendingMandate);
57
57
  if (!param)
58
58
  return args;
59
59
  if (args[param] !== undefined)
@@ -1,3 +1,4 @@
1
+ import { permitsAutomaticMandate } from './agreementContext.js';
1
2
  import { isAgreementId } from '@ziggs-ai/api-client';
2
3
  import { outcomeFromActionResult } from './buildOutcome.js';
3
4
  import { runtimeLog } from '../shared/runtimeLog.js';
@@ -267,7 +268,7 @@ export async function runTurn(input) {
267
268
  catch { }
268
269
  // Say which job this is part of, when the tool asks and the model did
269
270
  // not. The runtime knows; the model would have to be told.
270
- args = withDeclaredMandate(args, toolName, toolManager, ctx.activeAgreementId);
271
+ args = withDeclaredMandate(args, toolName, toolManager, ctx.activeAgreementId, permitsAutomaticMandate(incomingEvent));
271
272
  const decision = { action: matchingAction, tool: toolName, args };
272
273
  const result = await executeDecision(decision, actionDef, effWithTrace, sessionId, memory, incomingEvent, addToolUsage, ctx.identity.agentId, Array.isArray(ctxObj.agreements)
273
274
  ? ctxObj.agreements
@@ -324,7 +325,7 @@ export async function runTurn(input) {
324
325
  // Same mandate declaration as the tool-call path above. Both routes reach
325
326
  // the same tools, so filling it on only one would make the excuse depend on
326
327
  // which shape the model happened to answer in.
327
- decision.args = withDeclaredMandate((decision.args ?? {}), actionDef?.tool, toolManager, ctx.activeAgreementId);
328
+ decision.args = withDeclaredMandate((decision.args ?? {}), actionDef?.tool, toolManager, ctx.activeAgreementId, permitsAutomaticMandate(incomingEvent));
328
329
  if (decision.thought)
329
330
  await emitThought(decision.thought);
330
331
  // 2-call streaming: if this is a respond action (has receiverId, no bound tool),
@@ -7,6 +7,11 @@ export interface WorkScope {
7
7
  export declare function uniqueWorkScope(scopes: WorkScope[]): WorkScope;
8
8
  /** Only structured host/backend fields are authority; message text and timer reasons are not. */
9
9
  export declare function workScopeFromWake(event: unknown, sessionId?: string): WorkScope;
10
+ /** One packet per distinct work item in a batch; never union their authority. */
11
+ export declare function contextScopesFromWake(event: unknown, sessionId: string): Array<WorkScope & {
12
+ messageId?: string;
13
+ artifactId?: string;
14
+ }>;
10
15
  /** Per-wake observation, including effects nested inside tools and cross-room sends. */
11
16
  export declare class WakeEffects {
12
17
  private readonly initialScope;
@@ -25,9 +25,22 @@ export function workScopeFromWake(event, sessionId) {
25
25
  const agreementId = id(rec.agreementId) ?? id(resource.agreementId) ?? id(task.agreementId)
26
26
  ?? agreementIdFromLane(rec.chatId) ?? agreementIdFromLane(resource.chatId)
27
27
  ?? agreementIdFromLane(sessionId) ?? undefined;
28
- const taskId = id(rec.taskId) ?? id(resource.taskId) ?? id(task.taskId);
28
+ const taskId = id(rec.taskId) ?? id(resource.taskId) ?? id(task.taskId) ?? (resource.kind === 'task' ? id(resource.resourceId) : undefined);
29
29
  return { ...(agreementId ? { agreementId } : {}), ...(taskId ? { taskId } : {}) };
30
30
  }
31
+ /** One packet per distinct work item in a batch; never union their authority. */
32
+ export function contextScopesFromWake(event, sessionId) {
33
+ const frame = record(event);
34
+ if (frame.type === 'batch' && Array.isArray(frame.events)) {
35
+ return [...new Map(frame.events.flatMap(e => contextScopesFromWake(e, sessionId)).map(scope => [JSON.stringify(scope), scope])).values()];
36
+ }
37
+ const scope = workScopeFromWake(event, sessionId);
38
+ const messageId = frame.type === 'message' ? id(frame.messageId) : undefined;
39
+ const resource = record(frame.resource);
40
+ const artifactId = resource.kind === 'artifact' ? id(resource.resourceId) : undefined;
41
+ return scope.agreementId || scope.taskId || messageId || artifactId
42
+ ? [{ ...scope, ...(messageId ? { messageId } : {}), ...(artifactId ? { artifactId } : {}) }] : [];
43
+ }
31
44
  const TASK_WRITES = new Set(['task_create', 'task_set_result', 'task_update_plan_step', 'task_replace_plan']);
32
45
  /** Per-wake observation, including effects nested inside tools and cross-room sends. */
33
46
  export class WakeEffects {
@@ -35,6 +35,7 @@ export function toolFromCapability(cap) {
35
35
  baseUrl: process.env.BACKEND_URL || process.env.ZIGGS_BACKEND_URL || undefined,
36
36
  webUrl: process.env.ZIGGS_WEB_URL,
37
37
  surface: 'sdk',
38
+ serviceAgreementId: typeof ctx.serviceAgreementId === 'string' ? ctx.serviceAgreementId : undefined,
38
39
  };
39
40
  return cap.handler(args, env);
40
41
  }, { description: cap.descriptions.sdk, ...(cap.sdkOptions ?? {}) });
package/dist/types.d.ts CHANGED
@@ -370,6 +370,12 @@ export interface ContextSnapshot {
370
370
  [key: string]: unknown;
371
371
  }
372
372
  export interface ReadContextOpts {
373
+ workScopes?: Array<{
374
+ agreementId?: string;
375
+ taskId?: string;
376
+ messageId?: string;
377
+ artifactId?: string;
378
+ }>;
373
379
  maxMessages?: number;
374
380
  }
375
381
  /**
@@ -475,6 +481,8 @@ export type Effect = {
475
481
  } | {
476
482
  kind: 'tool-call';
477
483
  sessionId: string;
484
+ /** Exact work scope from this wake, independent of connection delegation. */
485
+ serviceAgreementId?: string;
478
486
  name: string;
479
487
  args: unknown;
480
488
  memory?: MemoryStore;
@@ -506,11 +514,13 @@ export type Effect = {
506
514
  receiverId?: string;
507
515
  text: string;
508
516
  messageId?: string;
517
+ conversationAgreementId?: string;
509
518
  } | {
510
519
  kind: 'record-event';
511
520
  sessionId: string;
512
521
  entry: RecordedEntry;
513
522
  idempotencyKey?: string;
523
+ serviceAgreementId?: string;
514
524
  };
515
525
  /**
516
526
  * Typed result map. With this shape, `await eff({ kind: 'read-context', .. })`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.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.22.0",
29
+ "@ziggs-ai/api-client": "0.23.0",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",