@ziggs-ai/agent-sdk 0.42.2 → 0.43.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/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/dist/Agent.d.ts +21 -8
- package/dist/Agent.js +173 -97
- package/dist/brain/ClaudeBrain.d.ts +0 -1
- package/dist/brain/ClaudeBrain.js +18 -42
- package/dist/brain/claudeCognition.js +5 -6
- package/dist/brain/claudeSdkProtocol.js +9 -9
- package/dist/brain/claudeSdkTools.d.ts +3 -3
- package/dist/brain/claudeSdkTools.js +37 -133
- package/dist/brain/hostedToolSurface.js +1 -1
- package/dist/brain/standingOfferNotice.d.ts +0 -2
- package/dist/brain/standingOfferNotice.js +1 -21
- package/dist/brain/toolManagerToSdkMcp.js +1 -1
- package/dist/brain/wakeGate.d.ts +3 -1
- package/dist/brain/wakeGate.js +6 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/ingress/normalizeIncoming.d.ts +5 -3
- package/dist/ingress/normalizeIncoming.js +9 -8
- package/dist/mcp/createMcpAgent.d.ts +15 -0
- package/dist/mcp/createMcpAgent.js +47 -11
- package/dist/memory/MemoryStore.d.ts +1 -1
- package/dist/memory/MemoryStore.js +1 -6
- package/dist/platform/ConnectionPool.js +1 -1
- package/dist/platform/OutboxBuffer.d.ts +2 -2
- package/dist/platform/OutboxBuffer.js +17 -17
- package/dist/platform/StateTimers.d.ts +3 -1
- package/dist/platform/StateTimers.js +4 -3
- package/dist/platform/WakeLedger.d.ts +10 -6
- package/dist/platform/WakeLedger.js +16 -9
- package/dist/platform/ZiggsEffectHandler.d.ts +18 -4
- package/dist/platform/ZiggsEffectHandler.js +86 -55
- package/dist/platform/agreements/AgreementService.d.ts +1 -3
- package/dist/platform/agreements/AgreementService.js +1 -7
- package/dist/platform/inboxLoop.d.ts +3 -9
- package/dist/platform/inboxLoop.js +151 -158
- package/dist/platform/polls/PollKind.d.ts +13 -16
- package/dist/platform/polls/PollKind.js +4 -4
- package/dist/platform/polls/PollRunner.d.ts +3 -20
- package/dist/platform/polls/PollRunner.js +13 -15
- package/dist/platform/polls/declaredPollRunner.d.ts +1 -1
- package/dist/platform/polls/declaredPollRunner.js +1 -1
- package/dist/platform/tasks/TaskService.d.ts +4 -3
- package/dist/platform/tasks/TaskService.js +1 -4
- package/dist/platform/telemetryIngest.js +4 -4
- package/dist/platform/tools/index.d.ts +0 -1
- package/dist/platform/tools/index.js +0 -3
- package/dist/platform/tools/tier1/protocolRunner.d.ts +1 -1
- package/dist/platform/tools/tier1/protocolRunner.js +24 -17
- package/dist/platform/tools/tier1/protocolTools.js +2 -2
- package/dist/platform/tools/tier1/wakeTools.d.ts +1 -1
- package/dist/platform/tools/tier1/wakeTools.js +2 -1
- package/dist/platform/tools/tier2/mcpConnectionTools.js +1 -1
- package/dist/runtime/AgentMachine.d.ts +2 -2
- package/dist/runtime/AgentMachine.js +4 -5
- package/dist/runtime/PromptBuilder.d.ts +1 -1
- package/dist/runtime/PromptBuilder.js +5 -4
- package/dist/runtime/buildOutcome.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.js +1 -1
- package/dist/runtime/kickoff.d.ts +14 -43
- package/dist/runtime/kickoff.js +22 -178
- package/dist/runtime/messageCommand.d.ts +9 -0
- package/dist/runtime/messageCommand.js +23 -0
- package/dist/runtime/requiredContext.d.ts +6 -0
- package/dist/runtime/requiredContext.js +23 -0
- package/dist/runtime/runTurn.js +31 -106
- package/dist/runtime/taskIntake.js +8 -22
- package/dist/runtime/wakeEffects.d.ts +3 -12
- package/dist/runtime/wakeEffects.js +17 -48
- package/dist/runtime/wakeOutcome.js +1 -1
- package/dist/skills/createSkillAgent.js +12 -1
- package/dist/tools/capabilityAdapter.js +1 -1
- package/dist/tools/recordReport.d.ts +1 -1
- package/dist/tools/recordReport.js +3 -3
- package/dist/tools/toolCreds.d.ts +1 -1
- package/dist/tools/toolCreds.js +4 -2
- package/dist/types.d.ts +17 -27
- package/package.json +3 -3
- package/dist/platform/tools/tier2/chatTools.d.ts +0 -3
- package/dist/platform/tools/tier2/chatTools.js +0 -7
- package/dist/probeChat.d.ts +0 -10
- package/dist/probeChat.js +0 -12
package/dist/Agent.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { runWorkExecution } from '@ziggs-ai/api-client';
|
|
2
|
+
import { MessageRecordsClient } from '@ziggs-ai/api-client/http';
|
|
3
|
+
import { responseCommand } from './runtime/messageCommand.js';
|
|
1
4
|
import { validateWorkExecution, withWorkExecution, currentWorkExecution } from '@ziggs-ai/api-client';
|
|
2
5
|
import { SERVER_EVENTS } from '@ziggs-ai/contracts';
|
|
3
6
|
import { withAgreementContext } from './runtime/agreementContext.js';
|
|
4
|
-
import { agreementLaneId,
|
|
7
|
+
import { agreementLaneId, isContextAccessRequest } from '@ziggs-ai/api-client';
|
|
5
8
|
import { createDeclaredPollRunner } from './platform/polls/declaredPollRunner.js';
|
|
6
|
-
import { randomUUID } from 'node:crypto';
|
|
7
9
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
10
|
import { contextScopesFromWake, WakeEffects, workScopeFromWake } from './runtime/wakeEffects.js';
|
|
9
11
|
import { ExecutionController, asWorkTool, executionControllers } from './runtime/executionController.js';
|
|
@@ -16,7 +18,6 @@ import { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
|
16
18
|
import { resolveClaudeModel } from './brain/claudeCognition.js';
|
|
17
19
|
import { resolveFleetModel } from './shared/fleetModel.js';
|
|
18
20
|
export { DEFAULT_MODEL, resolveFleetModel } from './shared/fleetModel.js';
|
|
19
|
-
import { CHAT_TOOLS } from './platform/tools/tier2/chatTools.js';
|
|
20
21
|
import { contextReadTool, openTool, accessExplainTool } from './platform/tools/tier2/contextTools.js';
|
|
21
22
|
import { EventQueue } from './platform/EventQueue.js';
|
|
22
23
|
import { StateTimers } from './platform/StateTimers.js';
|
|
@@ -35,7 +36,7 @@ import { PROTOCOL_TOOLS } from './platform/tools/tier1/protocolTools.js';
|
|
|
35
36
|
import { marketplaceViewTool } from './platform/tools/tier2/marketplaceTools.js';
|
|
36
37
|
import { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
37
38
|
import { AnthropicAdapter } from './adapters/AnthropicAdapter.js';
|
|
38
|
-
import { ArtifactsClient, ConnectionsClient, ContextReadClient, TelemetryClient,
|
|
39
|
+
import { ArtifactsClient, ConnectionsClient, ContextReadClient, TelemetryClient, getAgreement, getTask, annotationForSdkTool, } from '@ziggs-ai/api-client';
|
|
39
40
|
import { isCheckoutNeed, normalizeNeeds, unmetNeeds, unmetNeedsMessage, } from './tools/needs.js';
|
|
40
41
|
import { openCheckout, closeCheckout } from './checkout/open.js';
|
|
41
42
|
import { resolveBackendUrl } from './shared/backendUrl.js';
|
|
@@ -56,20 +57,16 @@ async function loadConnectionPool() {
|
|
|
56
57
|
const mod = await import(/* @vite-ignore */ modPath);
|
|
57
58
|
return mod.ConnectionPool;
|
|
58
59
|
}
|
|
59
|
-
const DEFAULT_SESSION_RESOLVER = (normalized) => normalized.
|
|
60
|
+
const DEFAULT_SESSION_RESOLVER = (normalized) => normalized.addressId ?? (normalized.event.agreementId ? `agrn-${normalized.event.agreementId}` : normalized.event.messageId ? `msgn-${normalized.event.messageId}` : null);
|
|
60
61
|
/**
|
|
61
62
|
* one lane key per unit of work, derived the same way as inbox wakes.
|
|
62
63
|
*
|
|
63
|
-
* Inbox deliveries
|
|
64
|
+
* Inbox deliveries use their room when present. Bootstrap used to
|
|
64
65
|
* prefer bare `agreementId`, which opened a second lane for the same task and
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* notification chat (`agrn-<agreementId>`), which is always linked after.
|
|
66
|
+
* misdirected the snapshot read. Use the agreement's local processing key. An origin link is
|
|
67
|
+
* navigation, not a delivery destination. This key creates no chat or grant.
|
|
68
68
|
*/
|
|
69
69
|
export function laneSessionIdForTask(task) {
|
|
70
|
-
if (typeof task.originChatId === 'string' && task.originChatId) {
|
|
71
|
-
return task.originChatId;
|
|
72
|
-
}
|
|
73
70
|
if (typeof task.agreementId === 'string' && task.agreementId) {
|
|
74
71
|
return agreementLaneId(task.agreementId);
|
|
75
72
|
}
|
|
@@ -86,7 +83,7 @@ const CHECKOUT_TICK_TIMEOUT_MS = Number(process.env.CLAUDE_SDK_CHECKOUT_TICK_TIM
|
|
|
86
83
|
* Anything else is a custom tool, and under execution control a custom tool
|
|
87
84
|
* does work: it gets a `taskId` the model fills (see asWorkTool).
|
|
88
85
|
*/
|
|
89
|
-
const HAND_DEFINED_PLATFORM_TOOLS = new Set([...PROTOCOL_TOOLS, ...
|
|
86
|
+
const HAND_DEFINED_PLATFORM_TOOLS = new Set([...PROTOCOL_TOOLS, ...CLAUDE_SDK_TOOLS, ...WAKE_TOOLS, marketplaceViewTool, contextReadTool, openTool, accessExplainTool]
|
|
90
87
|
.map((tool) => toolName(tool)));
|
|
91
88
|
function customToolAsWork(tool) {
|
|
92
89
|
const name = toolName(tool);
|
|
@@ -111,9 +108,6 @@ function createToolManager(options) {
|
|
|
111
108
|
// Both directions: shedding claim (taskTools array without it) sheds browse.
|
|
112
109
|
const canClaim = taskTools.some((t) => t.schema.function.name === 'agreement_claim');
|
|
113
110
|
const browseTools = canClaim ? [marketplaceViewTool] : [];
|
|
114
|
-
// chat_open is always-on so claim/provider kickoff (FSM) and the
|
|
115
|
-
// Claude always-on set can open the buyer chat after a store claim. CHAT_TOOLS
|
|
116
|
-
// is also folded into CLAUDE_SDK_TOOLS; Map registration makes duplicates a no-op.
|
|
117
111
|
// wake_at stays registered for the FSM and for the hosted model, where it is
|
|
118
112
|
// deferred behind tool search; an agent that can be handed a task
|
|
119
113
|
// with a check-back rule but cannot arm a wake would have to busy-wait, and
|
|
@@ -126,7 +120,7 @@ function createToolManager(options) {
|
|
|
126
120
|
const everydayContext = isClaudeCognition(options.cognition)
|
|
127
121
|
? [contextReadTool, openTool, accessExplainTool].filter((t) => !ownNames.has(toolName(t)))
|
|
128
122
|
: [];
|
|
129
|
-
const platformTools = [...taskTools, ...
|
|
123
|
+
const platformTools = [...taskTools, ...browseTools, ...claudeSdkTools, ...everydayContext, ...WAKE_TOOLS];
|
|
130
124
|
warnOnShadowedPlatformTools(platformTools, ownTools, options.agentId);
|
|
131
125
|
const controlOn = options.executionControl ?? isClaudeCognition(options.cognition);
|
|
132
126
|
toolManager.registerAll([...platformTools, ...(controlOn ? ownTools.map(customToolAsWork) : ownTools)]);
|
|
@@ -241,6 +235,11 @@ export class Agent {
|
|
|
241
235
|
/** Tasks left active by the asking turn, failed right after it (per lane). */
|
|
242
236
|
unmetTasksPendingAsk = new Map();
|
|
243
237
|
wakeEffects = new AsyncLocalStorage();
|
|
238
|
+
/**
|
|
239
|
+
* The roomless lane a live wake runs on. A tool's send or read reaches the
|
|
240
|
+
* host without the wake's effect wrapper, so both look the lane up here.
|
|
241
|
+
*/
|
|
242
|
+
wakeDestination = new AsyncLocalStorage();
|
|
244
243
|
outboundMessages = new SeenMessages();
|
|
245
244
|
confirmedPlanVersions = new SeenMessages();
|
|
246
245
|
grantProviderCache = null;
|
|
@@ -312,7 +311,7 @@ export class Agent {
|
|
|
312
311
|
this.sessionStore = options.sessionStore ?? null;
|
|
313
312
|
this._validate();
|
|
314
313
|
this.pollRunner = createDeclaredPollRunner(this.options, {
|
|
315
|
-
|
|
314
|
+
wakeTask: (_agentId, taskId) => this.wakeTaskAssignment(taskId),
|
|
316
315
|
});
|
|
317
316
|
this._setup();
|
|
318
317
|
}
|
|
@@ -344,7 +343,7 @@ export class Agent {
|
|
|
344
343
|
this.toolManager = services.toolManager;
|
|
345
344
|
this.llm = services.llm;
|
|
346
345
|
// Outbound send is HTTP (delivery law, phase 4): the agent no longer holds a
|
|
347
|
-
// socket. `POST /
|
|
346
|
+
// socket. `POST /messaging/messages` persists the whole message — the same
|
|
348
347
|
// row the WS path persisted. Live-token streaming had no HTTP path, so
|
|
349
348
|
// `chunkSender` is intentionally dropped: partials degrade, the full reply
|
|
350
349
|
// is never lost (runTurn always follows a stream with a whole send-message).
|
|
@@ -352,32 +351,7 @@ export class Agent {
|
|
|
352
351
|
operatorKey: this.options.operatorKey,
|
|
353
352
|
agentId: this.options.agentId,
|
|
354
353
|
};
|
|
355
|
-
const messageSender =
|
|
356
|
-
const sentId = messageId ?? `sdk_${randomUUID()}`;
|
|
357
|
-
const result = await sendChatMessage({
|
|
358
|
-
chatId: sessionId,
|
|
359
|
-
// Omitted on purpose when the caller has no valid address for THIS
|
|
360
|
-
// room: the backend infers the recipient from the room's own
|
|
361
|
-
// participants.
|
|
362
|
-
receiverId,
|
|
363
|
-
text: message,
|
|
364
|
-
// messageId is the backend's (chatId, messageId) dedup key. Callers
|
|
365
|
-
// pass a wake-derived id for replay-stability; fall back to a fresh id
|
|
366
|
-
// only when absent (matches the old WS path letting the server assign).
|
|
367
|
-
messageId: sentId,
|
|
368
|
-
conversationAgreementId,
|
|
369
|
-
underAgreementId,
|
|
370
|
-
taskId,
|
|
371
|
-
...(sources ? { sources } : {}),
|
|
372
|
-
}, creds);
|
|
373
|
-
if (!result.success)
|
|
374
|
-
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.');
|
|
375
|
-
this.outboundMessages.mark(JSON.stringify([result.chatId, result.messageId]));
|
|
376
|
-
// Presented once, here, so every hosted send — FSM reply, brain tool,
|
|
377
|
-
// stream follow-up — reports the destination the same way the MCP
|
|
378
|
-
// surface does.
|
|
379
|
-
return { ...result, ...presentSendResult(result, { creds, surface: 'sdk' }) };
|
|
380
|
-
};
|
|
354
|
+
const messageSender = (command) => new MessageRecordsClient(creds.operatorKey, creds.agentId, resolveBackendUrl()).send(command);
|
|
381
355
|
this.taskService = services.taskService;
|
|
382
356
|
this.agreementService = services.agreementService;
|
|
383
357
|
this.telemetryClient = services.telemetryClient;
|
|
@@ -413,6 +387,10 @@ export class Agent {
|
|
|
413
387
|
taskService: services.taskService,
|
|
414
388
|
agreementService: services.agreementService,
|
|
415
389
|
messageSender,
|
|
390
|
+
conversationFor: (sessionId) => {
|
|
391
|
+
const lane = this.wakeDestination.getStore();
|
|
392
|
+
return lane?.sessionId === sessionId ? lane : undefined;
|
|
393
|
+
},
|
|
416
394
|
toolManager: services.toolManager,
|
|
417
395
|
operatorKey: this.options.operatorKey,
|
|
418
396
|
agentId: this.options.agentId,
|
|
@@ -467,17 +445,17 @@ export class Agent {
|
|
|
467
445
|
promptBuilder: services.promptBuilder,
|
|
468
446
|
});
|
|
469
447
|
this.eventQueue = new EventQueue((event, laneKey) => this._processEvent(event, laneKey));
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
448
|
+
// Timer dispatch is at-least-once. Only successful work checkpoints this
|
|
449
|
+
// exact wake; a crash/failure leaves it durable, and an older completion
|
|
450
|
+
// cannot consume a timer armed while that work was running.
|
|
473
451
|
this.stateTimers = new StateTimers((ev, laneKey) => {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
|
|
452
|
+
const work = this.eventQueue.enqueue(ev, laneKey).then(() => {
|
|
453
|
+
if (ev.type === 'wake-at')
|
|
454
|
+
this.wakeLedger.done(laneKey, ev.wakeId);
|
|
455
|
+
}).catch((err) => {
|
|
479
456
|
runtimeLog.warn('Agent', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
480
457
|
});
|
|
458
|
+
this.wakeLedger.track(work);
|
|
481
459
|
});
|
|
482
460
|
}
|
|
483
461
|
executionControlOn() {
|
|
@@ -618,15 +596,15 @@ export class Agent {
|
|
|
618
596
|
}
|
|
619
597
|
// Map the event to a candidate session key using the configured resolver
|
|
620
598
|
// on a synthetic normalized-event shape. Default resolver buckets by
|
|
621
|
-
//
|
|
599
|
+
// addressId; agents using a different sessionResolver (agreementId, taskId,
|
|
622
600
|
// counterpartyId) will get the right lane here as long as the wire
|
|
623
601
|
// event carries the corresponding id.
|
|
624
602
|
const fakeNormalized = {
|
|
625
|
-
|
|
603
|
+
addressId: event.addressId,
|
|
626
604
|
event: { type: 'message', timestamp: Date.now(), text: '' },
|
|
627
605
|
};
|
|
628
606
|
let sessionId = this.sessionResolver(fakeNormalized, ownAgentId);
|
|
629
|
-
// store claims deliver agreement wakes with no
|
|
607
|
+
// store claims deliver agreement wakes with no addressId. Do not
|
|
630
608
|
// drop them; lane by agreement the same way task bootstrap does
|
|
631
609
|
// (`agrn-<agreementId>`), never a bare agreement id.
|
|
632
610
|
if (!sessionId) {
|
|
@@ -654,10 +632,10 @@ export class Agent {
|
|
|
654
632
|
return this.eventQueue.enqueue({
|
|
655
633
|
type: SERVER_EVENTS.resourceChanged,
|
|
656
634
|
resource: event,
|
|
657
|
-
|
|
635
|
+
addressId: event.addressId,
|
|
658
636
|
taskId: event.taskId,
|
|
659
637
|
agreementId,
|
|
660
|
-
// Claimer/buyer —
|
|
638
|
+
// Claimer/buyer — the canonical reply command explicitly names the requester.
|
|
661
639
|
...(actorId ? { senderId: actorId } : {}),
|
|
662
640
|
}, laneKey);
|
|
663
641
|
}
|
|
@@ -671,15 +649,27 @@ export class Agent {
|
|
|
671
649
|
['proposal_approved', 'proposal_rejected'].includes(notice?.operation ?? '') &&
|
|
672
650
|
isContextAccessRequest(notice?.agreement?.grantRequest))
|
|
673
651
|
return;
|
|
674
|
-
const messageId = (metadata.
|
|
652
|
+
const messageId = (metadata.receiptId ?? metadata.messageId);
|
|
675
653
|
if (messageId && this.seenMessages.has(messageId)) {
|
|
676
654
|
runtimeLog.debug('Agent', `skip duplicate messageId=${messageId}`);
|
|
677
655
|
return;
|
|
678
656
|
}
|
|
679
657
|
if (messageId)
|
|
680
658
|
this.seenMessages.mark(messageId);
|
|
659
|
+
// The inbox marks a message that has no room. It has no other way back to
|
|
660
|
+
// the agent, so one that cannot be routed here stays pending, never dropped.
|
|
661
|
+
const roomless = metadata.roomless === true && !metadata.addressId;
|
|
662
|
+
const roomlessAgreementId = metadata.roomless === true && !metadata.addressId
|
|
663
|
+
&& typeof metadata.agreementId === 'string' && metadata.agreementId ? metadata.agreementId : undefined;
|
|
664
|
+
const unroutable = (why) => {
|
|
665
|
+
if (messageId)
|
|
666
|
+
this.seenMessages.unmark(messageId);
|
|
667
|
+
return new Error(`handleMessage: message ${messageId ?? '(no id)'} ${roomlessAgreementId ? `under agreement ${roomlessAgreementId}` : 'with an exact action'} has no address and ${why}. Left pending.`);
|
|
668
|
+
};
|
|
681
669
|
const ownAgentId = this._resolveOwnAgentId();
|
|
682
670
|
if (!ownAgentId) {
|
|
671
|
+
if (roomless)
|
|
672
|
+
throw unroutable('ownAgentId is missing');
|
|
683
673
|
runtimeLog.warn('Agent', 'handleMessage: ownAgentId missing — dropping event');
|
|
684
674
|
return;
|
|
685
675
|
}
|
|
@@ -700,7 +690,9 @@ export class Agent {
|
|
|
700
690
|
const normalized = normalizeIncomingEvent({ text: enrichedText, metadata, ownAgentId });
|
|
701
691
|
const sessionId = this.sessionResolver(normalized, ownAgentId);
|
|
702
692
|
if (!sessionId) {
|
|
703
|
-
|
|
693
|
+
if (roomless)
|
|
694
|
+
throw unroutable('the session resolver returned no lane for it');
|
|
695
|
+
runtimeLog.warn('Agent', `session resolver returned null ownAgentId=${ownAgentId} addressId=${normalized.addressId ?? 'null'}`);
|
|
704
696
|
return;
|
|
705
697
|
}
|
|
706
698
|
const executionContext = metadata.executionContext;
|
|
@@ -708,6 +700,8 @@ export class Agent {
|
|
|
708
700
|
if (!this.sessions.has(laneKey)) {
|
|
709
701
|
this.sessions.set(laneKey, { sessionId, ownAgentId, executionContext });
|
|
710
702
|
}
|
|
703
|
+
if (roomlessAgreementId)
|
|
704
|
+
this.sessions.get(laneKey).roomlessAgreementId = roomlessAgreementId;
|
|
711
705
|
// The inbox loop owns ack (act-before-ack, delivery law); the agent only
|
|
712
706
|
// routes the event onto its lane. No live-path watermark ack — there is no
|
|
713
707
|
// separate live/push channel to reconcile against anymore.
|
|
@@ -716,7 +710,7 @@ export class Agent {
|
|
|
716
710
|
// the same message being processed twice; leaving it set on a failed wake
|
|
717
711
|
// would make the redelivery this rejection buys us a no-op.
|
|
718
712
|
try {
|
|
719
|
-
await this.eventQueue.enqueue({ ...normalized.event,
|
|
713
|
+
await this.eventQueue.enqueue({ ...normalized.event, addressId: normalized.addressId }, laneKey);
|
|
720
714
|
}
|
|
721
715
|
catch (err) {
|
|
722
716
|
if (messageId)
|
|
@@ -810,8 +804,7 @@ export class Agent {
|
|
|
810
804
|
// Fail open: a lookup error must not drop a live hire's session.
|
|
811
805
|
}
|
|
812
806
|
}
|
|
813
|
-
async
|
|
814
|
-
const agreementId = agreementIdFromLane(sessionId);
|
|
807
|
+
async agreementIsTerminal(agreementId) {
|
|
815
808
|
if (!agreementId || !this.agreementService)
|
|
816
809
|
return false;
|
|
817
810
|
try {
|
|
@@ -835,8 +828,10 @@ export class Agent {
|
|
|
835
828
|
* Every rule here reads a row or the frame itself. None of them reads a lane,
|
|
836
829
|
* an agent kind, a name or an id shape.
|
|
837
830
|
*/
|
|
838
|
-
async wakeSuppression(event,
|
|
839
|
-
|
|
831
|
+
async wakeSuppression(event, _sessionId) {
|
|
832
|
+
const execution = currentWorkExecution();
|
|
833
|
+
const agreementId = execution ? execution.agreementId : workScopeFromWake(event).agreementId;
|
|
834
|
+
if (await this.agreementIsTerminal(agreementId)) {
|
|
840
835
|
return { reason: 'agreement_terminal', because: 'its agreement is terminal' };
|
|
841
836
|
}
|
|
842
837
|
if (this.isSettledTaskEcho(event)) {
|
|
@@ -877,7 +872,7 @@ export class Agent {
|
|
|
877
872
|
*
|
|
878
873
|
* A task frame that carries a terminal state AND a result is an echo of work
|
|
879
874
|
* this agent has already finished: there is no decision left in it. Dropping it
|
|
880
|
-
* is not the same suppression as {@link
|
|
875
|
+
* is not the same suppression as {@link agreementIsTerminal}, which waits
|
|
881
876
|
* for the agreement to end — this fires while the engagement is still live,
|
|
882
877
|
* which is where the cost actually was.
|
|
883
878
|
*
|
|
@@ -1046,17 +1041,74 @@ export class Agent {
|
|
|
1046
1041
|
const tools = this.options.tools ?? this.options.services?.tools ?? [];
|
|
1047
1042
|
return tools.some((t) => toolName(t) === 'connection_request');
|
|
1048
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* The work destination comes from the selected execution first, then the
|
|
1046
|
+
* structured wake. It does not depend on an earlier message being delivered
|
|
1047
|
+
* to this host, so task-first and restarted/scheduled runs work identically.
|
|
1048
|
+
* A reply defaults to the message that woke it, and to the wake's task.
|
|
1049
|
+
* A lane name is never an address: a conversation comes from a message.
|
|
1050
|
+
*/
|
|
1051
|
+
wakeDestinationFor(session, frame, marks) {
|
|
1052
|
+
const asFrame = (value) => value && typeof value === 'object' ? value : {};
|
|
1053
|
+
const text = (value) => typeof value === 'string' && value ? value : undefined;
|
|
1054
|
+
const execution = session.executionContext;
|
|
1055
|
+
const wake = asFrame(frame);
|
|
1056
|
+
const events = wake.type === 'batch' && Array.isArray(wake.events) ? wake.events.map(asFrame) : undefined;
|
|
1057
|
+
// The shared conversations this wake's own messages arrived in.
|
|
1058
|
+
const rooms = [...new Set((events ?? [wake]).filter(e => e.type === 'message').map(e => text(e.addressId)).filter((id) => !!id))];
|
|
1059
|
+
const room = rooms.length === 1 ? rooms[0] : undefined;
|
|
1060
|
+
// A direct execution exists to answer one exact message.
|
|
1061
|
+
const actionMessageId = execution && !execution.agreementId && execution.action?.kind === 'message' ? text(execution.action.id) : undefined;
|
|
1062
|
+
// Agreement work answers in its own conversation; only a lane without work
|
|
1063
|
+
// execution, or a direct execution, keeps the room its messages came from.
|
|
1064
|
+
const remembers = !execution || !!actionMessageId;
|
|
1065
|
+
if (remembers && rooms.length)
|
|
1066
|
+
session.addressId = room && (session.addressId === undefined || session.addressId === room) ? room : null;
|
|
1067
|
+
const remembered = remembers ? session.addressId ?? undefined : undefined;
|
|
1068
|
+
const explicitAgreement = text(wake.agreementId) ?? text(wake.resource?.agreementId) ?? text(wake.result?.agreementId)
|
|
1069
|
+
?? (events ? marks.scope.agreementId : undefined);
|
|
1070
|
+
const explicitChat = text(wake.addressId) ?? text(wake.resource?.addressId);
|
|
1071
|
+
// An exact incoming message supplies its reply address, and a batch of
|
|
1072
|
+
// messages from one room keeps that room; from several rooms the agent
|
|
1073
|
+
// names one. Work execution supplies authority, never a transport
|
|
1074
|
+
// default: under it, other events' navigation hints select no chat.
|
|
1075
|
+
const addressId = wake.type === 'message' ? explicitChat
|
|
1076
|
+
: events ? room ?? (rooms.length ? undefined : remembered)
|
|
1077
|
+
: execution ? remembered
|
|
1078
|
+
: explicitChat ?? remembered;
|
|
1079
|
+
// An unscoped batch cannot borrow a previous customer's agreement.
|
|
1080
|
+
const agreementId = execution ? execution.agreementId ?? undefined
|
|
1081
|
+
: explicitAgreement ?? (events ? undefined : session.roomlessAgreementId);
|
|
1082
|
+
const message = wake.type === 'message' && text(wake.messageId)
|
|
1083
|
+
? { messageId: wake.messageId, ...(text(wake.senderId) ? { senderId: wake.senderId } : {}), ...(text(wake.taskId) ? { taskId: wake.taskId } : {}) }
|
|
1084
|
+
: actionMessageId ? { messageId: actionMessageId } : undefined;
|
|
1085
|
+
const inputRooms = rooms.length > 1 ? rooms : undefined;
|
|
1086
|
+
if (!agreementId && !addressId && !message && !inputRooms)
|
|
1087
|
+
return undefined;
|
|
1088
|
+
const taskId = execution ? execution.taskId ?? undefined : marks.scope.taskId;
|
|
1089
|
+
return {
|
|
1090
|
+
sessionId: session.sessionId,
|
|
1091
|
+
agreementId,
|
|
1092
|
+
addressId,
|
|
1093
|
+
...(message ? { message } : {}),
|
|
1094
|
+
...(taskId ? { taskId } : {}),
|
|
1095
|
+
...(inputRooms ? { inputRooms } : {}),
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1049
1098
|
async _processEvent(event, laneKey) {
|
|
1050
|
-
|
|
1099
|
+
const context = this.sessions.get(laneKey)?.executionContext;
|
|
1100
|
+
if (!context)
|
|
1101
|
+
return this._processEventInContext(event, laneKey);
|
|
1102
|
+
return runWorkExecution(context, this.options.operatorKey, () => this._processEventInContext(event, laneKey), resolveBackendUrl());
|
|
1051
1103
|
}
|
|
1052
1104
|
async _processEventInContext(event, laneKey) {
|
|
1053
1105
|
const session = this.sessions.get(laneKey);
|
|
1054
1106
|
if (!session?.sessionId)
|
|
1055
1107
|
return;
|
|
1056
1108
|
if (session.executionContext)
|
|
1057
|
-
await validateWorkExecution(session.executionContext, this.options.operatorKey, resolveBackendUrl());
|
|
1109
|
+
await validateWorkExecution(currentWorkExecution() ?? session.executionContext, this.options.operatorKey, resolveBackendUrl());
|
|
1058
1110
|
// Presentation may call our sender an rpb-* room binding or the person we
|
|
1059
|
-
// stand in for. Only an exact
|
|
1111
|
+
// stand in for. Only an exact canonical messageId we successfully sent proves
|
|
1060
1112
|
// an echo; another message from the same binding still gets a full wake.
|
|
1061
1113
|
const incoming = event;
|
|
1062
1114
|
const events = incoming?.type === 'batch' && Array.isArray(incoming.events) ? incoming.events : [event];
|
|
@@ -1067,7 +1119,7 @@ export class Agent {
|
|
|
1067
1119
|
&& e?.source === 'active-task-snapshot' && (task?.state === 'active' || task?.state === 'in-progress')
|
|
1068
1120
|
&& task?.assigneeId === this.options.agentId && task.taskId && task.updatedAt && this.confirmedPlanVersions.has(JSON.stringify([laneKey, JSON.stringify([task.taskId, task.updatedAt])])))
|
|
1069
1121
|
return false;
|
|
1070
|
-
return e?.type !== 'message' || !e.messageId || !this.outboundMessages.has(
|
|
1122
|
+
return e?.type !== 'message' || !e.messageId || !this.outboundMessages.has(e.messageId);
|
|
1071
1123
|
});
|
|
1072
1124
|
if (remaining.length === 0) {
|
|
1073
1125
|
this.recordWakeOutcome(session, { kind: 'no-op', reason: events.some(e => e?.type === 'task_result') ? 'own_plan_snapshot' : 'own_message_echo' });
|
|
@@ -1192,7 +1244,9 @@ export class Agent {
|
|
|
1192
1244
|
// a zombie — burning tokens and racing the redelivered retry of the same
|
|
1193
1245
|
// event (duplicate sends, runaway per-wake input).
|
|
1194
1246
|
const tickAbort = new AbortController();
|
|
1195
|
-
const marks =
|
|
1247
|
+
const marks = session.executionContext
|
|
1248
|
+
? new WakeEffects({ agreementId: session.executionContext.agreementId ?? undefined, taskId: session.executionContext.taskId ?? undefined }, false)
|
|
1249
|
+
: WakeEffects.forWake(frame, session.sessionId);
|
|
1196
1250
|
const executionController = this.executionController(identity, tickAbort.signal, taskId => registerTaskRun(taskId));
|
|
1197
1251
|
let heldTask;
|
|
1198
1252
|
const pendingEffects = new Set();
|
|
@@ -1218,6 +1272,8 @@ export class Agent {
|
|
|
1218
1272
|
taskRuns.clear();
|
|
1219
1273
|
};
|
|
1220
1274
|
const checkpoint = async (namedTaskId) => {
|
|
1275
|
+
if (session.executionContext)
|
|
1276
|
+
await validateWorkExecution(currentWorkExecution() ?? session.executionContext, this.options.operatorKey, resolveBackendUrl());
|
|
1221
1277
|
if (tickAbort.signal.aborted) {
|
|
1222
1278
|
if (tickAbort.signal.reason?.code === 'TASK_CONTROL')
|
|
1223
1279
|
heldTask = tickAbort.signal.reason;
|
|
@@ -1276,11 +1332,9 @@ export class Agent {
|
|
|
1276
1332
|
const sameRoom = 'sessionId' in effect && effect.sessionId === session.sessionId;
|
|
1277
1333
|
const scopedEffect = sameRoom && effect.kind === 'read-context'
|
|
1278
1334
|
? { ...effect, opts: { ...effect.opts, workScopes: contextScopesFromWake(frame, session.sessionId) } }
|
|
1279
|
-
: agreementId && effect.kind === '
|
|
1280
|
-
? { ...effect,
|
|
1281
|
-
:
|
|
1282
|
-
? { ...effect, serviceAgreementId: effect.serviceAgreementId ?? agreementId }
|
|
1283
|
-
: effect;
|
|
1335
|
+
: agreementId && (effect.kind === 'tool-call' || effect.kind === 'record-event')
|
|
1336
|
+
? { ...effect, serviceAgreementId: effect.serviceAgreementId ?? agreementId }
|
|
1337
|
+
: effect;
|
|
1284
1338
|
const operation = this.eff(scopedEffect);
|
|
1285
1339
|
pendingEffects.add(operation);
|
|
1286
1340
|
const response = await operation.finally(() => { pendingEffects.delete(operation); });
|
|
@@ -1291,7 +1345,8 @@ export class Agent {
|
|
|
1291
1345
|
};
|
|
1292
1346
|
try {
|
|
1293
1347
|
await checkpoint();
|
|
1294
|
-
const
|
|
1348
|
+
const roomless = this.wakeDestinationFor(session, frame, marks);
|
|
1349
|
+
const tick = () => this.wakeDestination.run(roomless, () => this.wakeEffects.run(marks, () => this.brain.tick({ event: frame, ctx: session.ctx, state: session.state, identity, eff, memory, transitionTap: this.options.transitionTap, signal: tickAbort.signal, ...(checkout ? { checkout: checkout.session } : {}) })));
|
|
1295
1350
|
tickWork = executionController ? executionControllers.run(executionController, tick) : tick();
|
|
1296
1351
|
const result = await Promise.race([
|
|
1297
1352
|
tickWork,
|
|
@@ -1302,6 +1357,9 @@ export class Agent {
|
|
|
1302
1357
|
}, checkout ? Math.max(this.tickTimeoutMs, CHECKOUT_TICK_TIMEOUT_MS) : this.tickTimeoutMs);
|
|
1303
1358
|
}),
|
|
1304
1359
|
]);
|
|
1360
|
+
// Protected writes validate at their owner. A successful turn may have
|
|
1361
|
+
// completed its own task/answer, so post-turn admission would reject success.
|
|
1362
|
+
// Retained state is validated again before the next turn.
|
|
1305
1363
|
session.ctx = result.ctx;
|
|
1306
1364
|
this.carryExecutionState(session.ctx, executionController);
|
|
1307
1365
|
session.state = result.state;
|
|
@@ -1410,8 +1468,8 @@ export class Agent {
|
|
|
1410
1468
|
const ev = event;
|
|
1411
1469
|
const senderType = String(ev?.senderType ?? '').toUpperCase();
|
|
1412
1470
|
const senderId = ev?.senderId;
|
|
1413
|
-
if (senderId && senderType === 'USER') {
|
|
1414
|
-
this.eff
|
|
1471
|
+
if (senderId && senderType === 'USER' && err?.code !== 'REQUIRED_CONTEXT_UNAVAILABLE') {
|
|
1472
|
+
responseCommand(this.eff, session.sessionId, 'Something went wrong. Please try again.', { recipientId: senderId }).then(command => this.eff({ kind: 'send-message', sessionId: session.sessionId, command })).catch(() => { });
|
|
1415
1473
|
}
|
|
1416
1474
|
// and then say so. Everything above is recovery for the lane —
|
|
1417
1475
|
// it does not mean the event was handled, and the caller is about to
|
|
@@ -1508,13 +1566,15 @@ export class Agent {
|
|
|
1508
1566
|
* Borrowing that path would make a dev tool able to disturb a live one.
|
|
1509
1567
|
*/
|
|
1510
1568
|
async replayWake(record) {
|
|
1511
|
-
|
|
1569
|
+
if (!record.executionContext)
|
|
1570
|
+
return this.replayWakeInContext(record);
|
|
1571
|
+
return runWorkExecution(record.executionContext, this.options.operatorKey, () => this.replayWakeInContext(record), resolveBackendUrl());
|
|
1512
1572
|
}
|
|
1513
1573
|
async replayWakeInContext(record) {
|
|
1514
1574
|
if (!record?.sessionId)
|
|
1515
1575
|
throw new Error('replayWake: record.sessionId is required');
|
|
1516
1576
|
if (record.executionContext)
|
|
1517
|
-
await validateWorkExecution(record.executionContext, this.options.operatorKey, resolveBackendUrl());
|
|
1577
|
+
await validateWorkExecution(currentWorkExecution() ?? record.executionContext, this.options.operatorKey, resolveBackendUrl());
|
|
1518
1578
|
const identity = {
|
|
1519
1579
|
agentId: record.agentId || this.options.agentId,
|
|
1520
1580
|
sessionId: record.sessionId,
|
|
@@ -1535,11 +1595,14 @@ export class Agent {
|
|
|
1535
1595
|
// Same watchdog as a live wake, for the same reason: an unaborted Claude
|
|
1536
1596
|
// query outlives the promise that abandoned it and keeps spending.
|
|
1537
1597
|
const abort = new AbortController();
|
|
1538
|
-
const marks =
|
|
1598
|
+
const marks = record.executionContext
|
|
1599
|
+
? new WakeEffects({ agreementId: record.executionContext.agreementId ?? undefined, taskId: record.executionContext.taskId ?? undefined }, false)
|
|
1600
|
+
: WakeEffects.forWake(record.event, identity.sessionId);
|
|
1601
|
+
const destination = this.wakeDestinationFor({ sessionId: record.sessionId, ownAgentId: identity.agentId, executionContext: record.executionContext }, record.event, marks);
|
|
1539
1602
|
const controller = this.executionController(identity, abort.signal);
|
|
1540
1603
|
let watchdog;
|
|
1541
1604
|
try {
|
|
1542
|
-
const tick = () => this.wakeEffects.run(marks, () => this.brain.tick({
|
|
1605
|
+
const tick = () => this.wakeDestination.run(destination, () => this.wakeEffects.run(marks, () => this.brain.tick({
|
|
1543
1606
|
event: record.event,
|
|
1544
1607
|
ctx: record.ctx,
|
|
1545
1608
|
state: record.state,
|
|
@@ -1548,7 +1611,7 @@ export class Agent {
|
|
|
1548
1611
|
memory,
|
|
1549
1612
|
transitionTap: this.options.transitionTap,
|
|
1550
1613
|
signal: abort.signal,
|
|
1551
|
-
}));
|
|
1614
|
+
})));
|
|
1552
1615
|
const result = await Promise.race([
|
|
1553
1616
|
controller ? executionControllers.run(controller, tick) : tick(),
|
|
1554
1617
|
new Promise((_, reject) => {
|
|
@@ -1629,9 +1692,9 @@ export class Agent {
|
|
|
1629
1692
|
// work delivered by the inbox's task channel, not by a chat
|
|
1630
1693
|
// row — this is what reaches a task under a chat-less agreement or one
|
|
1631
1694
|
// the agent assigned to itself.
|
|
1632
|
-
onTask: async (task) => {
|
|
1695
|
+
onTask: async (task, inputs) => {
|
|
1633
1696
|
try {
|
|
1634
|
-
await this.wakeTaskAssignment(task.taskId, { fromInbox: true });
|
|
1697
|
+
await this.wakeTaskAssignment(task.taskId, { fromInbox: true, inputs });
|
|
1635
1698
|
}
|
|
1636
1699
|
catch (err) {
|
|
1637
1700
|
runtimeLog.warn(this.options.name || 'agent', `inbox task ${task.taskId} could not be woken: ${err.message}`);
|
|
@@ -1659,13 +1722,18 @@ export class Agent {
|
|
|
1659
1722
|
const laneKey = laneKeyFor(this.options.agentId, sessionId);
|
|
1660
1723
|
const scope = this.wakeEffects.getStore()?.scope;
|
|
1661
1724
|
const armed = this.stateTimers.armWakeAt(laneKey, at, reason, scope);
|
|
1725
|
+
// The conversation the lane's messages came from, so a restarted host
|
|
1726
|
+
// answers there too instead of reading the lane name as an address.
|
|
1727
|
+
const addressId = this.sessions.get(laneKey)?.addressId;
|
|
1662
1728
|
this.wakeLedger.arm({
|
|
1663
1729
|
laneKey,
|
|
1664
1730
|
sessionId,
|
|
1665
1731
|
firesAt: armed.firesAt,
|
|
1732
|
+
wakeId: armed.wakeId,
|
|
1666
1733
|
reason,
|
|
1667
1734
|
...(scope ? { scope } : {}),
|
|
1668
|
-
|
|
1735
|
+
...(addressId ? { addressId } : {}),
|
|
1736
|
+
executionContext: currentWorkExecution() ? { ...currentWorkExecution(), attemptId: undefined } : undefined,
|
|
1669
1737
|
});
|
|
1670
1738
|
return { ...armed, durable: this.wakeLedger.durable };
|
|
1671
1739
|
}
|
|
@@ -1693,10 +1761,10 @@ export class Agent {
|
|
|
1693
1761
|
if (this.stateTimers.peek(wake.laneKey))
|
|
1694
1762
|
continue;
|
|
1695
1763
|
if (!this.sessions.has(wake.laneKey)) {
|
|
1696
|
-
this.sessions.set(wake.laneKey, { sessionId: wake.sessionId, ownAgentId: agentId, executionContext: wake.executionContext });
|
|
1764
|
+
this.sessions.set(wake.laneKey, { sessionId: wake.sessionId, ownAgentId: agentId, executionContext: wake.executionContext, ...(wake.addressId ? { addressId: wake.addressId } : {}) });
|
|
1697
1765
|
}
|
|
1698
1766
|
try {
|
|
1699
|
-
this.stateTimers.armWakeAt(wake.laneKey, wake.firesAt, wake.reason, wake.scope);
|
|
1767
|
+
this.stateTimers.armWakeAt(wake.laneKey, wake.firesAt, wake.reason, wake.scope, wake.wakeId);
|
|
1700
1768
|
runtimeLog.info(this.options.name || 'agent', `re-armed a saved wake laneKey=${wake.laneKey} at=${new Date(wake.firesAt).toISOString()}`);
|
|
1701
1769
|
}
|
|
1702
1770
|
catch (err) {
|
|
@@ -1719,11 +1787,17 @@ export class Agent {
|
|
|
1719
1787
|
}
|
|
1720
1788
|
const needle = request.match.trim();
|
|
1721
1789
|
const title = request.title?.trim() || needle;
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
agreementId: request.agreementId
|
|
1726
|
-
}
|
|
1790
|
+
// The request is read by its agreement. It has no message and no
|
|
1791
|
+
// conversation, so the wake names neither.
|
|
1792
|
+
try {
|
|
1793
|
+
await this.handleMessage(`New marketplace request matching your tags (${needle}): ${title}. Claim with agreement_claim if you will take it.`, { agreementId: request.agreementId });
|
|
1794
|
+
}
|
|
1795
|
+
catch (err) {
|
|
1796
|
+
// An open request is an offer to look, not assigned work, and it stays
|
|
1797
|
+
// open in the marketplace. A wake that could not read it must not hold
|
|
1798
|
+
// every other delivery in the pass behind it.
|
|
1799
|
+
runtimeLog.warn(this.options.name || 'agent', `request ${request.agreementId}: wake failed, left open in the marketplace: ${err?.message || err}`);
|
|
1800
|
+
}
|
|
1727
1801
|
}
|
|
1728
1802
|
/** replay task-assigned for an existing active task (demo recovery). */
|
|
1729
1803
|
async wakeTaskAssignment(taskId, options) {
|
|
@@ -1736,21 +1810,23 @@ export class Agent {
|
|
|
1736
1810
|
if (state !== 'active' && state !== 'in-progress') {
|
|
1737
1811
|
return;
|
|
1738
1812
|
}
|
|
1813
|
+
const selected = currentWorkExecution();
|
|
1739
1814
|
const sessionId = laneSessionIdForTask(full);
|
|
1740
|
-
const laneKey = laneKeyFor(agentId, sessionId);
|
|
1815
|
+
const laneKey = laneKeyFor(agentId, sessionId, selected?.contextId);
|
|
1741
1816
|
if (!this.sessions.has(laneKey)) {
|
|
1742
1817
|
this.sessions.set(laneKey, { sessionId, ownAgentId: agentId, executionContext: currentWorkExecution() });
|
|
1743
1818
|
}
|
|
1744
1819
|
await this.eventQueue.enqueue({
|
|
1745
1820
|
type: 'task_result',
|
|
1746
|
-
|
|
1821
|
+
// New facts must not disappear behind an unchanged task snapshot version.
|
|
1822
|
+
...(options?.fromInbox && !options.inputs?.length ? { source: 'active-task-snapshot' } : {}),
|
|
1747
1823
|
result: {
|
|
1748
1824
|
...full,
|
|
1825
|
+
...(options?.inputs?.length ? { inputs: options.inputs } : {}),
|
|
1749
1826
|
taskId: full.taskId ?? taskId,
|
|
1750
1827
|
state,
|
|
1751
1828
|
providerIsYou: true,
|
|
1752
1829
|
},
|
|
1753
|
-
chatId: sessionId,
|
|
1754
1830
|
}, laneKey);
|
|
1755
1831
|
}
|
|
1756
1832
|
/**
|
|
@@ -5,7 +5,6 @@ import type { ToolManager } from '../tools/ToolManager.js';
|
|
|
5
5
|
import { type RawEvent } from '../runtime/buildOutcome.js';
|
|
6
6
|
import type { TickInput, TickOutput } from './ZiggsBrain.js';
|
|
7
7
|
import { type AskWakeGate } from './wakeGate.js';
|
|
8
|
-
export { isPublishProbeChat } from '../probeChat.js';
|
|
9
8
|
/**
|
|
10
9
|
* status notices must not spend a Claude turn answering
|
|
11
10
|
* nobody. Real work wakes arrive as chat `message`, synthetic `task_result`
|