@ziggs-ai/agent-sdk 0.20.0 → 0.21.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 (51) hide show
  1. package/dist/Agent.d.ts +28 -7
  2. package/dist/Agent.js +176 -71
  3. package/dist/brain/ClaudeBrain.d.ts +11 -1
  4. package/dist/brain/ClaudeBrain.js +71 -18
  5. package/dist/brain/ZiggsBrain.d.ts +2 -0
  6. package/dist/brain/ZiggsBrain.js +1 -0
  7. package/dist/brain/claudeCognition.d.ts +1 -1
  8. package/dist/brain/claudeCognition.js +47 -7
  9. package/dist/brain/claudeSdkProtocol.d.ts +1 -1
  10. package/dist/brain/claudeSdkProtocol.js +1 -1
  11. package/dist/brain/claudeSdkTools.d.ts +3 -0
  12. package/dist/brain/claudeSdkTools.js +4 -2
  13. package/dist/brain/hireActivationNotice.d.ts +19 -0
  14. package/dist/brain/hireActivationNotice.js +79 -0
  15. package/dist/brain/standingOfferNotice.d.ts +15 -0
  16. package/dist/brain/standingOfferNotice.js +89 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/ingress/normalizeIncoming.d.ts +3 -0
  20. package/dist/ingress/normalizeIncoming.js +8 -0
  21. package/dist/mcp/createMcpAgent.d.ts +1 -1
  22. package/dist/mcp/createMcpAgent.js +1 -1
  23. package/dist/memory/MemoryStore.d.ts +18 -4
  24. package/dist/memory/MemoryStore.js +52 -12
  25. package/dist/platform/ConnectionPool.d.ts +9 -4
  26. package/dist/platform/ConnectionPool.js +16 -6
  27. package/dist/platform/StateTimers.d.ts +12 -6
  28. package/dist/platform/StateTimers.js +26 -6
  29. package/dist/platform/ZiggsEffectHandler.js +5 -1
  30. package/dist/platform/inboxLoop.d.ts +5 -7
  31. package/dist/platform/inboxLoop.js +36 -80
  32. package/dist/platform/polls/declaredPollRunner.d.ts +7 -1
  33. package/dist/platform/polls/declaredPollRunner.js +7 -9
  34. package/dist/platform/tools/index.d.ts +1 -0
  35. package/dist/platform/tools/index.js +1 -0
  36. package/dist/platform/tools/tier2/contextTools.d.ts +2 -0
  37. package/dist/platform/tools/tier2/contextTools.js +3 -1
  38. package/dist/platform/tools/tier2/planTools.d.ts +20 -0
  39. package/dist/platform/tools/tier2/planTools.js +64 -0
  40. package/dist/pricing/fleetDefaults.js +4 -9
  41. package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
  42. package/dist/pricing/fleetTierByAgentId.gen.js +2 -752
  43. package/dist/runtime/AgentMachine.d.ts +3 -0
  44. package/dist/runtime/AgentMachine.js +6 -1
  45. package/dist/runtime/runTurn.d.ts +1 -0
  46. package/dist/runtime/runTurn.js +19 -15
  47. package/dist/runtime/wakeEffects.d.ts +35 -0
  48. package/dist/runtime/wakeEffects.js +121 -0
  49. package/package.json +2 -2
  50. package/dist/pricing/fleetFreeTierA.gen.d.ts +0 -6
  51. package/dist/pricing/fleetFreeTierA.gen.js +0 -11
@@ -105,6 +105,8 @@ export declare class AgentMachine {
105
105
  input: number;
106
106
  output: number;
107
107
  };
108
+ /** Last turn skipped the model; host records a no-op naming why. */
109
+ _skippedReason: string | undefined;
108
110
  _lastUserSenderId: string | null;
109
111
  _stateTimeoutUnhandled: boolean;
110
112
  _transitionTap: ((event: TransitionEvent) => void) | undefined;
@@ -117,6 +119,7 @@ export declare class AgentMachine {
117
119
  input: number;
118
120
  output: number;
119
121
  };
122
+ get skippedReason(): string | undefined;
120
123
  get stateTimeoutUnhandled(): boolean;
121
124
  constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput, writeKeys }: MachineOptions);
122
125
  getSnapshot(): MachineSnapshot;
@@ -41,6 +41,8 @@ export class AgentMachine {
41
41
  _draining = false;
42
42
  _llmCalls = 0;
43
43
  _tokens = { total: 0, input: 0, output: 0 };
44
+ /** Last turn skipped the model; host records a no-op naming why. */
45
+ _skippedReason;
44
46
  _lastUserSenderId = null;
45
47
  _stateTimeoutUnhandled = false;
46
48
  _transitionTap;
@@ -49,6 +51,7 @@ export class AgentMachine {
49
51
  _maxTurnsWithoutUserInput;
50
52
  get llmCalls() { return this._llmCalls; }
51
53
  get tokens() { return this._tokens; }
54
+ get skippedReason() { return this._skippedReason; }
52
55
  get stateTimeoutUnhandled() { return this._stateTimeoutUnhandled; }
53
56
  constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput, writeKeys }) {
54
57
  this.workflow = workflow;
@@ -316,7 +319,7 @@ export class AgentMachine {
316
319
  // Thinking: run a turn, produce an outcome, re-step.
317
320
  this._running = true;
318
321
  try {
319
- const { outcome: turnOutcome, toolResults, llmCalls, tokens } = await this.executionCore({
322
+ const { outcome: turnOutcome, toolResults, llmCalls, tokens, skippedReason } = await this.executionCore({
320
323
  stateId: this.state,
321
324
  statePrompt: sd.prompt,
322
325
  actions: sd.actions,
@@ -331,6 +334,8 @@ export class AgentMachine {
331
334
  });
332
335
  this.ctx.toolResults = toolResults;
333
336
  this._llmCalls += llmCalls;
337
+ if (skippedReason)
338
+ this._skippedReason = skippedReason;
334
339
  this._turnsSinceUserInput += 1;
335
340
  this._tokens.total += tokens.total;
336
341
  this._tokens.input += tokens.input;
@@ -44,6 +44,7 @@ interface RunTurnResult {
44
44
  input: number;
45
45
  output: number;
46
46
  };
47
+ skippedReason?: string;
47
48
  }
48
49
  /**
49
50
  * Run a single thinking-state turn.
@@ -112,12 +112,13 @@ export async function runTurn(input) {
112
112
  : [];
113
113
  const serverContext = await effWithTrace({ kind: 'read-context', sessionId, opts: {} });
114
114
  const ctxObj = normalizeContextSnapshot(serverContext);
115
- // A message wake whose chat no longer resolves (deleted, or never visible
116
- // to this agent) has no world to decide over — deciding anyway means
117
- // reasoning about a fabricated empty history plus whatever stale scraps
118
- // other fetches returned. Skip the LLM and park. Gated on message wakes
119
- // only. a bare agreement id as sessionId is a mis-keyed lane —
120
- // loud-log the empty snapshot rather than staying silent.
115
+ // A wake whose chat no longer resolves (deleted, or never visible to this
116
+ // agent) has no world to decide over — deciding anyway means reasoning
117
+ // about a fabricated empty history plus whatever stale scraps other
118
+ // fetches returned. Skip the LLM and park. Resource wakes used to slip
119
+ // past a message-only gate and burn a full turn. A bare
120
+ // agreement id as sessionId is a mis-keyed lane — loud-log the empty
121
+ // snapshot rather than staying silent.
121
122
  if (ctxObj.chatMissing) {
122
123
  const looksLikeBareAgreement = isAgreementId(sessionId);
123
124
  if (looksLikeBareAgreement) {
@@ -125,15 +126,18 @@ export async function runTurn(input) {
125
126
  `lane was keyed by agreementId instead of the agreement's origin/notification chat; ` +
126
127
  `wake continues with an empty snapshot`);
127
128
  }
128
- }
129
- if (ctxObj.chatMissing && ctx.lastOutcome?.kind === 'message-received') {
130
- runtimeLog.warn('runTurn', `chat ${sessionId} not found on backend — skipping message wake (state=${stateId})`);
131
- return {
132
- outcome: { kind: 'wait' },
133
- toolResults: [],
134
- llmCalls: 0,
135
- tokens: { total: 0, input: 0, output: 0 },
136
- };
129
+ else {
130
+ // Any wake in a room this agent cannot read — not only
131
+ // message wakes. Resource notices used to spend a full turn here.
132
+ runtimeLog.warn('runTurn', `chat ${sessionId} not readable — skipping wake (state=${stateId})`);
133
+ return {
134
+ outcome: { kind: 'wait' },
135
+ toolResults: [],
136
+ llmCalls: 0,
137
+ tokens: { total: 0, input: 0, output: 0 },
138
+ skippedReason: `chat_unreadable:${sessionId}`,
139
+ };
140
+ }
137
141
  }
138
142
  const prompt = promptBuilder.buildFromState({
139
143
  statePrompt, actions,
@@ -0,0 +1,35 @@
1
+ import type { Effect } from '../types.js';
2
+ export interface WorkScope {
3
+ agreementId?: string;
4
+ taskId?: string;
5
+ }
6
+ /** Never combine a task from one event with an agreement from another. */
7
+ export declare function uniqueWorkScope(scopes: WorkScope[]): WorkScope;
8
+ /** Only structured host/backend fields are authority; message text and timer reasons are not. */
9
+ export declare function workScopeFromWake(event: unknown, sessionId?: string): WorkScope;
10
+ /** Per-wake observation, including effects nested inside tools and cross-room sends. */
11
+ export declare class WakeEffects {
12
+ private readonly initialScope;
13
+ private readonly sessionId?;
14
+ private readonly inferScope;
15
+ sentMessage: boolean;
16
+ taskWrite: boolean;
17
+ readonly planVersions: Set<string>;
18
+ readonly sentMessageKeys: Set<string>;
19
+ /** Agreements the wake's OWN room reported, from its fenced snapshot. */
20
+ private roomAgreementIds;
21
+ /** Confirmed task writes: work this wake actually performed. */
22
+ private taskWrites;
23
+ constructor(initialScope: WorkScope, sessionId?: string | undefined, inferScope?: boolean);
24
+ static forWake(event: unknown, sessionId: string): WakeEffects;
25
+ /**
26
+ * Structured wake fields first. Otherwise the room's one agreement is the
27
+ * scope — spend follows who the agent acts for, and in a room that is the
28
+ * room's agreement — and a confirmed task write names its task only when
29
+ * the server tied the write to that same agreement. A plan written for
30
+ * customer A from customer B's room stays billed to B and names no task; a
31
+ * write whose agreement the server did not report adds nothing but the room.
32
+ */
33
+ get scope(): WorkScope;
34
+ observe(effect: Effect, response: unknown): void;
35
+ }
@@ -0,0 +1,121 @@
1
+ import { agreementIdFromLane } from '@ziggs-ai/api-client';
2
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
3
+ const id = (value) => typeof value === 'string' && value.trim() ? value : undefined;
4
+ /** Never combine a task from one event with an agreement from another. */
5
+ export function uniqueWorkScope(scopes) {
6
+ const agreements = new Set(scopes.map(s => s.agreementId).filter(Boolean));
7
+ const tasks = new Set(scopes.map(s => s.taskId).filter(Boolean));
8
+ if (agreements.size > 1 || tasks.size > 1)
9
+ return {};
10
+ if (agreements.size && tasks.size && scopes.some(s => s.taskId && !s.agreementId))
11
+ return {};
12
+ return {
13
+ ...(agreements.size ? { agreementId: [...agreements][0] } : {}),
14
+ ...(tasks.size ? { taskId: [...tasks][0] } : {}),
15
+ };
16
+ }
17
+ /** Only structured host/backend fields are authority; message text and timer reasons are not. */
18
+ export function workScopeFromWake(event, sessionId) {
19
+ const rec = record(event);
20
+ if (rec.type === 'batch' && Array.isArray(rec.events)) {
21
+ return uniqueWorkScope(rec.events.map(e => workScopeFromWake(e, sessionId)));
22
+ }
23
+ const resource = record(rec.resource);
24
+ const task = rec.type === 'task_result' || rec.type === 'notification' ? record(rec.result) : {};
25
+ const agreementId = id(rec.agreementId) ?? id(resource.agreementId) ?? id(task.agreementId)
26
+ ?? agreementIdFromLane(rec.chatId) ?? agreementIdFromLane(resource.chatId)
27
+ ?? agreementIdFromLane(sessionId) ?? undefined;
28
+ const taskId = id(rec.taskId) ?? id(resource.taskId) ?? id(task.taskId);
29
+ return { ...(agreementId ? { agreementId } : {}), ...(taskId ? { taskId } : {}) };
30
+ }
31
+ const TASK_WRITES = new Set(['task_create', 'task_set_result', 'task_update_plan_step', 'task_replace_plan']);
32
+ /** Per-wake observation, including effects nested inside tools and cross-room sends. */
33
+ export class WakeEffects {
34
+ initialScope;
35
+ sessionId;
36
+ inferScope;
37
+ sentMessage = false;
38
+ taskWrite = false;
39
+ planVersions = new Set();
40
+ sentMessageKeys = new Set();
41
+ /** Agreements the wake's OWN room reported, from its fenced snapshot. */
42
+ roomAgreementIds = [];
43
+ /** Confirmed task writes: work this wake actually performed. */
44
+ taskWrites = [];
45
+ constructor(initialScope, sessionId, inferScope = true) {
46
+ this.initialScope = initialScope;
47
+ this.sessionId = sessionId;
48
+ this.inferScope = inferScope;
49
+ }
50
+ static forWake(event, sessionId) {
51
+ const initialScope = workScopeFromWake(event, sessionId);
52
+ const frame = record(event);
53
+ const conflictingBatch = frame.type === 'batch' && Array.isArray(frame.events)
54
+ && !initialScope.agreementId && !initialScope.taskId
55
+ && frame.events.some(item => Object.keys(workScopeFromWake(item, sessionId)).length > 0);
56
+ return new WakeEffects(initialScope, sessionId, !conflictingBatch);
57
+ }
58
+ /**
59
+ * Structured wake fields first. Otherwise the room's one agreement is the
60
+ * scope — spend follows who the agent acts for, and in a room that is the
61
+ * room's agreement — and a confirmed task write names its task only when
62
+ * the server tied the write to that same agreement. A plan written for
63
+ * customer A from customer B's room stays billed to B and names no task; a
64
+ * write whose agreement the server did not report adds nothing but the room.
65
+ */
66
+ get scope() {
67
+ if (this.initialScope.agreementId || this.initialScope.taskId)
68
+ return this.initialScope;
69
+ if (!this.inferScope)
70
+ return {};
71
+ const room = new Set(this.roomAgreementIds);
72
+ if (room.size > 1)
73
+ return {};
74
+ const agreementId = [...room][0];
75
+ if (!agreementId)
76
+ return uniqueWorkScope(this.taskWrites);
77
+ const tasks = new Set(this.taskWrites.filter(w => w.agreementId === agreementId).map(w => w.taskId));
78
+ return { agreementId, ...(tasks.size === 1 ? { taskId: [...tasks][0] } : {}) };
79
+ }
80
+ observe(effect, response) {
81
+ const outer = record(response);
82
+ if (effect.kind === 'send-message' && outer.ok === true)
83
+ this.sentMessage = true;
84
+ // This is the fenced snapshot of the wake's own chat, not an arbitrary
85
+ // agreement consulted while answering. A room with multiple agreements
86
+ // remains ambiguous, and cross-room reads cannot change the wake's scope.
87
+ if (effect.kind === 'read-context' && effect.sessionId === this.sessionId
88
+ && outer.chatMissing !== true && Array.isArray(outer.agreements)) {
89
+ for (const row of outer.agreements) {
90
+ const agreementId = id(record(row).agreementId);
91
+ if (agreementId)
92
+ this.roomAgreementIds.push(agreementId);
93
+ }
94
+ }
95
+ if (effect.kind !== 'tool-call' || outer.ok !== true)
96
+ return;
97
+ const result = record(outer.result);
98
+ if (result.ok === false || result.success === false || result.error)
99
+ return;
100
+ const name = effect.name.replace(/^(functions|tools|function|tool)\./, '');
101
+ if (name === 'chat_send') {
102
+ this.sentMessage = true;
103
+ if (id(result.chatId) && id(result.messageId)) {
104
+ this.sentMessageKeys.add(JSON.stringify([result.chatId, result.messageId]));
105
+ }
106
+ }
107
+ if (TASK_WRITES.has(name))
108
+ this.taskWrite = true;
109
+ if ((name === 'task_replace_plan' || name === 'task_update_plan_step') && id(result.taskId)
110
+ && result.taskId === record(effect.args).taskId && id(result.updatedAt)) {
111
+ this.planVersions.add(JSON.stringify([result.taskId, result.updatedAt]));
112
+ }
113
+ // A confirmed task write identifies work actually performed. A broad
114
+ // task_list, arbitrary agreement_get or ids in message prose do not.
115
+ const agreementId = id(result.agreementId);
116
+ const taskId = id(result.taskId);
117
+ if (TASK_WRITES.has(name) && taskId) {
118
+ this.taskWrites.push({ taskId, ...(agreementId ? { agreementId } : {}) });
119
+ }
120
+ }
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.20.0",
3
+ "version": "0.21.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.19.0",
29
+ "@ziggs-ai/api-client": "0.20.0",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",
@@ -1,6 +0,0 @@
1
- /**
2
- * AUTO-GENERATED by scripts/build-fleet-tier-map.mjs — do not edit by hand.
3
- * Tier A trivial public lookups (free when price omitted).
4
- */
5
- export declare const FREE_TIER_A_AGENT_IDS: Set<string>;
6
- export declare const FREE_TIER_A_COUNT = 4;
@@ -1,11 +0,0 @@
1
- /**
2
- * AUTO-GENERATED by scripts/build-fleet-tier-map.mjs — do not edit by hand.
3
- * Tier A trivial public lookups (free when price omitted).
4
- */
5
- export const FREE_TIER_A_AGENT_IDS = new Set([
6
- "site-health-checker",
7
- "timezone-expert",
8
- "uv-index-advisor",
9
- "weather-forecaster"
10
- ]);
11
- export const FREE_TIER_A_COUNT = 4;