@ziggs-ai/agent-sdk 0.32.0 → 0.33.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 CHANGED
@@ -463,8 +463,10 @@ export class Agent {
463
463
  task = await this.taskService.getTask(taskId);
464
464
  }
465
465
  catch (error) {
466
+ // Unreadable is not a hold. The wake filter still drops a frame whose
467
+ // own task cannot be read; a tool that names one gets an error instead.
466
468
  if ([403, 404].includes(error.status ?? 0))
467
- return { blocked: true };
469
+ return { blocked: true, unread: true };
468
470
  throw error;
469
471
  }
470
472
  // A coordinator sees its children's task events too; those do not pause it.
@@ -1118,7 +1120,7 @@ export class Agent {
1118
1120
  release();
1119
1121
  taskRuns.clear();
1120
1122
  };
1121
- const checkpoint = async (effectTaskId) => {
1123
+ const checkpoint = async (namedTaskId) => {
1122
1124
  if (tickAbort.signal.aborted) {
1123
1125
  if (tickAbort.signal.reason?.code === 'TASK_CONTROL')
1124
1126
  heldTask = tickAbort.signal.reason;
@@ -1126,27 +1128,59 @@ export class Agent {
1126
1128
  }
1127
1129
  if (heldTask)
1128
1130
  throw new Error('Task execution held');
1129
- const taskId = effectTaskId ?? marks.scope.taskId ?? wakeTaskId;
1131
+ const wakeId = marks.scope.taskId ?? wakeTaskId;
1132
+ const taskId = namedTaskId ?? wakeId;
1130
1133
  if (!taskId)
1131
1134
  return;
1132
- registerTaskRun(taskId);
1135
+ // Only this wake's own task shares the tick abort. A tool that names
1136
+ // some other task must not be able to cancel the rest of the wake.
1137
+ const ownsWake = namedTaskId == null || namedTaskId === wakeId;
1138
+ if (ownsWake)
1139
+ registerTaskRun(taskId);
1133
1140
  const execution = await readControl(taskId);
1134
1141
  if (heldTask)
1135
1142
  throw new Error('Task execution held');
1136
- if (execution?.blocked) {
1137
- heldTask = { taskId, control: execution.control };
1138
- taskRuns.get(taskId).run.control = execution.control;
1139
- tickAbort.abort(Object.assign(new Error('Task execution held'), { code: 'TASK_CONTROL', taskId, control: execution.control }));
1140
- throw new Error('Task execution held');
1143
+ if (!execution?.blocked)
1144
+ return;
1145
+ if (!ownsWake || execution.unread) {
1146
+ const error = execution.unread
1147
+ ? `Task ${taskId} cannot be read. This call did not run.`
1148
+ : execution.closed
1149
+ ? `Task ${taskId} is cancelled. This call did not run.`
1150
+ : `Task ${taskId} is paused. This call did not run.`;
1151
+ return { error };
1141
1152
  }
1153
+ heldTask = { taskId, control: execution.control };
1154
+ taskRuns.get(taskId).run.control = execution.control;
1155
+ tickAbort.abort(Object.assign(new Error('Task execution held'), { code: 'TASK_CONTROL', taskId, control: execution.control }));
1156
+ throw new Error('Task execution held');
1157
+ };
1158
+ const taskIdNamedBy = (effect) => {
1159
+ if (effect.kind !== 'tool-call' || !effect.args || typeof effect.args !== 'object' || Array.isArray(effect.args))
1160
+ return undefined;
1161
+ const args = effect.args;
1162
+ if (typeof args.taskId === 'string')
1163
+ return args.taskId;
1164
+ const nested = args.args;
1165
+ if ((effect.name === 'tool_read' || effect.name === 'tool_write')
1166
+ && nested && typeof nested === 'object' && !Array.isArray(nested)) {
1167
+ const inner = nested.taskId;
1168
+ if (typeof inner === 'string')
1169
+ return inner;
1170
+ }
1171
+ return undefined;
1142
1172
  };
1143
1173
  const eff = async (effect) => {
1144
1174
  // Record-only telemetry can finish after interruption. Every action and
1145
1175
  // model call checks the current task before starting another operation.
1146
- if (effect.kind !== 'record-event') {
1147
- const args = effect.args;
1148
- const taskId = effect.kind === 'tool-call' && typeof args?.taskId === 'string' ? args.taskId : undefined;
1149
- await checkpoint(taskId);
1176
+ if (effect.kind === 'tool-call') {
1177
+ const refusal = await checkpoint(taskIdNamedBy(effect));
1178
+ // The handler is generic over every effect. This branch is only a tool call.
1179
+ if (refusal)
1180
+ return { ok: false, error: refusal.error };
1181
+ }
1182
+ else if (effect.kind !== 'record-event') {
1183
+ await checkpoint();
1150
1184
  }
1151
1185
  const agreementId = marks.scope.agreementId;
1152
1186
  const sameRoom = 'sessionId' in effect && effect.sessionId === session.sessionId;
@@ -395,14 +395,17 @@ export async function createMcpAgent(options) {
395
395
  : 'the external service';
396
396
  // Open a connection request for this user; returns its id so the caller can
397
397
  // deep-link the user straight to it on the Connections page.
398
- const triggerConnectionRequest = async (userId) => {
398
+ // The server holds the invoking user to the people this wake may ask, so
399
+ // the request carries the same lane as the call that missed: in the hire's
400
+ // room that user is its customer.
401
+ const triggerConnectionRequest = async (userId, laneId) => {
399
402
  if (!transportConfig.serverUrl)
400
403
  return null;
401
404
  const operatorKey = transportConfig.operatorKey || process.env.ZIGGS_OPERATOR_KEY;
402
405
  if (!operatorKey)
403
406
  return null;
404
407
  try {
405
- const body = await new ConnectionsClient(operatorKey, agentId, backendBase).requestMcpConnection({
408
+ const body = await new ConnectionsClient(operatorKey, agentId, backendBase, laneId).requestMcpConnection({
406
409
  serverUrl: transportConfig.serverUrl,
407
410
  tools: tools.map(t => t.name),
408
411
  onBehalfOfUserId: userId,
@@ -460,7 +463,7 @@ export async function createMcpAgent(options) {
460
463
  if (lastErr instanceof ZiggsGatewayError &&
461
464
  lastErr.code === 'NO_USER_CONNECTION' &&
462
465
  transportConfig.serverUrl) {
463
- const requestId = await triggerConnectionRequest(userId).catch(() => null);
466
+ const requestId = await triggerConnectionRequest(userId, laneId).catch(() => null);
464
467
  const link = connectionsSettingsAppUrl(appUrl, requestId);
465
468
  return {
466
469
  content: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.32.0",
3
+ "version": "0.33.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.284",
27
27
  "@anthropic-ai/sdk": "^0.110.0",
28
28
  "@modelcontextprotocol/sdk": "^1.29.0",
29
- "@ziggs-ai/api-client": "0.31.0",
29
+ "@ziggs-ai/api-client": "0.32.0",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",