@ziggs-ai/agent-sdk 0.10.0 → 0.10.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/CHANGELOG.md +23 -0
- package/README.md +9 -1
- package/dist/AgentHost.d.ts +50 -14
- package/dist/AgentHost.js +129 -7
- package/dist/agent/Agent.js +2 -2
- package/dist/agent/ClaudeSdkAgent.d.ts +23 -1
- package/dist/agent/ClaudeSdkAgent.js +156 -84
- package/dist/agent/claudeCognition.d.ts +25 -3
- package/dist/agent/claudeCognition.js +33 -1
- package/dist/agent/claudeSdkTools.d.ts +2 -2
- package/dist/agent/claudeSdkTools.js +1 -1
- package/dist/agent/toolManagerToSdkMcp.js +1 -1
- package/dist/context/applyEffects.js +4 -3
- package/dist/index.d.ts +6 -4
- package/dist/index.js +7 -3
- package/dist/ingress/normalizeIncoming.d.ts +2 -2
- package/dist/ingress/normalizeIncoming.js +1 -1
- package/dist/runtime/AgentMachine.d.ts +4 -4
- package/dist/runtime/AgentMachine.js +6 -6
- package/dist/runtime/bootstrapCtx.js +6 -8
- package/dist/runtime/buildOutcome.d.ts +9 -1
- package/dist/runtime/buildOutcome.js +29 -6
- package/dist/runtime/{wakeWriteKeys.d.ts → deliveryWriteKeys.d.ts} +2 -2
- package/dist/runtime/{wakeWriteKeys.js → deliveryWriteKeys.js} +15 -22
- package/dist/runtime/runMechanical.d.ts +2 -2
- package/dist/runtime/runMechanical.js +2 -2
- package/dist/runtime/runTurn.d.ts +2 -2
- package/dist/runtime/runTurn.js +5 -5
- package/dist/runtime/taskIntake.js +6 -4
- package/dist/server/ConnectionPool.d.ts +11 -53
- package/dist/server/ConnectionPool.js +29 -60
- package/dist/server/EventQueue.d.ts +6 -0
- package/dist/server/EventQueue.js +21 -6
- package/dist/server/SeenMessages.d.ts +7 -0
- package/dist/server/SeenMessages.js +15 -0
- package/dist/server/ZiggsEffectHandler.d.ts +1 -0
- package/dist/server/ZiggsEffectHandler.js +18 -11
- package/dist/server/createHealthServer.js +1 -1
- package/dist/server/inboxLoop.d.ts +17 -2
- package/dist/server/inboxLoop.js +92 -16
- package/dist/server/polls/PollKind.d.ts +72 -0
- package/dist/server/polls/PollKind.js +53 -0
- package/dist/server/polls/PollRunner.d.ts +62 -0
- package/dist/server/polls/PollRunner.js +138 -0
- package/dist/server/questMatch.d.ts +5 -0
- package/dist/server/questMatch.js +14 -0
- package/dist/server/runLauncher.d.ts +0 -6
- package/dist/server/runLauncher.js +54 -27
- package/dist/server/tools/tier1/protocolRunner.d.ts +4 -2
- package/dist/server/tools/tier1/protocolRunner.js +30 -34
- package/dist/server/tools/tier1/protocolTools.js +23 -13
- package/dist/server/tools/tier2/connectionTools.js +12 -1
- package/dist/tasks/protocolRegistry.d.ts +27 -0
- package/dist/tasks/protocolRegistry.js +36 -0
- package/dist/types.d.ts +4 -13
- package/package.json +2 -2
- package/dist/server/staleBacklogGate.d.ts +0 -57
- package/dist/server/staleBacklogGate.js +0 -100
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
-
import { outcomeFromEvent } from '../runtime/buildOutcome.js';
|
|
2
|
+
import { isMessageAddressedElsewhere, outcomeFromEvent } from '../runtime/buildOutcome.js';
|
|
3
3
|
import { getBatchEvents } from '../context/batch.js';
|
|
4
4
|
import { bootstrapCtx } from '../runtime/bootstrapCtx.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createDeliveryWriteKeys } from '../runtime/deliveryWriteKeys.js';
|
|
6
6
|
import { CLAUDE_SDK_PROTOCOL } from './claudeSdkProtocol.js';
|
|
7
7
|
import { buildZiggsSdkMcpServer } from './toolManagerToSdkMcp.js';
|
|
8
8
|
import { claudeQueryOptions, buildClaudeEventPrompt, summarizeContext, extractAssistantText, } from './claudeCognition.js';
|
|
@@ -20,12 +20,18 @@ import { runtimeLog } from '../shared/runtimeLog.js';
|
|
|
20
20
|
* `notification` only.
|
|
21
21
|
*
|
|
22
22
|
* Empty / missing batch → notice-only (do not burn a turn on a malformed wake).
|
|
23
|
+
*
|
|
24
|
+
* A message addressed to a DIFFERENT party is notice-only too (attention
|
|
25
|
+
* gate): the sender named who should act, and it wasn't this agent. The
|
|
26
|
+
* message still lands in chat history and arrives as context on the next
|
|
27
|
+
* real wake — skipped here is the LLM turn, not the information.
|
|
23
28
|
*/
|
|
24
|
-
export function isNoticeOnlyWake(event) {
|
|
29
|
+
export function isNoticeOnlyWake(event, ownAgentId) {
|
|
25
30
|
const events = getBatchEvents(event);
|
|
26
31
|
if (events.length === 0)
|
|
27
32
|
return true;
|
|
28
|
-
return events.every((ev) => ev?.type === 'notification'
|
|
33
|
+
return events.every((ev) => ev?.type === 'notification' ||
|
|
34
|
+
isMessageAddressedElsewhere(ev, ownAgentId));
|
|
29
35
|
}
|
|
30
36
|
const DEFAULT_STATE = 'ready';
|
|
31
37
|
/**
|
|
@@ -62,6 +68,20 @@ const MAX_WAKES_PER_WINDOW = Number(process.env.CLAUDE_SDK_MAX_WAKES_PER_WINDOW
|
|
|
62
68
|
* discard. Env-overridable. Kept below MAX_TASK_INPUT_TOKENS so a lane resets
|
|
63
69
|
* before it can approach the per-wake abort. */
|
|
64
70
|
const MAX_SESSION_RESUME_INPUT_TOKENS = Number(process.env.CLAUDE_SDK_MAX_SESSION_INPUT_TOKENS ?? 200_000);
|
|
71
|
+
/**
|
|
72
|
+
* ZIG-1191 — "the transcript you asked me to resume is not here".
|
|
73
|
+
*
|
|
74
|
+
* Claude Code answers a `resume` of an unknown id with this, and it is the only
|
|
75
|
+
* failure that a fresh session reliably fixes: the id is valid-looking but its
|
|
76
|
+
* transcript lived on a container that is gone. Matched on the message because
|
|
77
|
+
* that is all the CLI gives us — no code, no typed error — so keep it narrow
|
|
78
|
+
* enough that a different failure never claims the retry.
|
|
79
|
+
*/
|
|
80
|
+
export function isMissingSessionError(err) {
|
|
81
|
+
const message = err?.message;
|
|
82
|
+
return (typeof message === 'string' &&
|
|
83
|
+
/no conversation found with session id/i.test(message));
|
|
84
|
+
}
|
|
65
85
|
/** Total billable input for one API response (base + cache read + cache write). */
|
|
66
86
|
function billableInput(usage) {
|
|
67
87
|
if (!usage)
|
|
@@ -164,6 +184,13 @@ export class ClaudeSdkAgent {
|
|
|
164
184
|
maxTurns;
|
|
165
185
|
statelessPerWake;
|
|
166
186
|
queryFn;
|
|
187
|
+
/**
|
|
188
|
+
* ZIG-1120 #7 — tool set is fixed at host construction; rebuilding the
|
|
189
|
+
* in-process MCP server (+ zod schema tree) every wake was pure waste.
|
|
190
|
+
* A mutable holder supplies per-wake context without closing over `tick`.
|
|
191
|
+
*/
|
|
192
|
+
toolContextHolder = { current: null };
|
|
193
|
+
sdkMcpServer = null;
|
|
167
194
|
constructor(opts) {
|
|
168
195
|
if (!opts.workflow?.states)
|
|
169
196
|
throw new Error('ClaudeSdkAgent: workflow with states is required');
|
|
@@ -195,11 +222,11 @@ export class ClaudeSdkAgent {
|
|
|
195
222
|
const nextCtx = bootstrapCtx(identity, ctx);
|
|
196
223
|
nextCtx.lastOutcome = outcomeFromEvent(rawEvent ?? null, identity.agentId);
|
|
197
224
|
const now = Date.now();
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
runtimeLog.warn('ClaudeSdkAgent', `wake-rate breaker tripped: ${
|
|
225
|
+
const recentDeliveries = (nextCtx.recentDeliveries ?? []).filter((t) => now - t < WAKE_WINDOW_MS);
|
|
226
|
+
if (recentDeliveries.length >= MAX_WAKES_PER_WINDOW) {
|
|
227
|
+
runtimeLog.warn('ClaudeSdkAgent', `wake-rate breaker tripped: ${recentDeliveries.length} wakes in ${WAKE_WINDOW_MS / 60_000}min ` +
|
|
201
228
|
`session=${identity.sessionId} agent=${this.agentId} — skipping LLM for this wake`);
|
|
202
|
-
nextCtx.
|
|
229
|
+
nextCtx.recentDeliveries = recentDeliveries;
|
|
203
230
|
return {
|
|
204
231
|
ctx: nextCtx,
|
|
205
232
|
state: state ?? DEFAULT_STATE,
|
|
@@ -208,8 +235,8 @@ export class ClaudeSdkAgent {
|
|
|
208
235
|
metrics: { llmCalls: 0, tokens: { total: 0, input: 0, output: 0 } },
|
|
209
236
|
};
|
|
210
237
|
}
|
|
211
|
-
nextCtx.
|
|
212
|
-
if (isNoticeOnlyWake(rawEvent)) {
|
|
238
|
+
nextCtx.recentDeliveries = [...recentDeliveries, now];
|
|
239
|
+
if (isNoticeOnlyWake(rawEvent, identity.agentId)) {
|
|
213
240
|
runtimeLog.info('ClaudeSdkAgent', `notice-only wake — skipping LLM session=${identity.sessionId} agent=${this.agentId} ` +
|
|
214
241
|
`type=${rawEvent?.type ?? 'unknown'}`);
|
|
215
242
|
return {
|
|
@@ -222,9 +249,13 @@ export class ClaudeSdkAgent {
|
|
|
222
249
|
}
|
|
223
250
|
let contextSnap = null;
|
|
224
251
|
try {
|
|
252
|
+
// ZIG-1120 #5: pre-read is for wake gating + summarizeContext (a count) +
|
|
253
|
+
// onBehalf agreement resolution — same 12-message cap as the tool path.
|
|
254
|
+
// Uncapped pulls burned 35–47k input tokens/wake on long chats.
|
|
225
255
|
contextSnap = await eff({
|
|
226
256
|
kind: 'read-context',
|
|
227
257
|
sessionId: identity.sessionId,
|
|
258
|
+
opts: { maxMessages: 12 },
|
|
228
259
|
});
|
|
229
260
|
}
|
|
230
261
|
catch (err) {
|
|
@@ -265,12 +296,14 @@ export class ClaudeSdkAgent {
|
|
|
265
296
|
this.rolePrompt,
|
|
266
297
|
this.specialization ? `Specialization: ${this.specialization}` : '',
|
|
267
298
|
].filter(Boolean).join('\n');
|
|
268
|
-
|
|
299
|
+
// Two blocks, not one string: the shared protocol first so its cache prefix
|
|
300
|
+
// is reachable by every hosted agent, this agent's role second (ZIG-788).
|
|
301
|
+
const systemPrompt = [CLAUDE_SDK_PROTOCOL, roleBlock];
|
|
269
302
|
const runId = crypto.randomUUID();
|
|
270
303
|
// Replay-stable write keys for this wake (runId is random and can't serve):
|
|
271
304
|
// a crash-replay of the same event derives the same keys, so the backend
|
|
272
305
|
// drops the duplicate artifacts / no-ops the repeated task transitions.
|
|
273
|
-
const
|
|
306
|
+
const writeKeys = createDeliveryWriteKeys(identity.laneKey, rawEvent);
|
|
274
307
|
const toolContext = {
|
|
275
308
|
operatorKey: this.operatorKey,
|
|
276
309
|
agentId: this.agentId,
|
|
@@ -285,15 +318,24 @@ export class ClaudeSdkAgent {
|
|
|
285
318
|
// not just task_result events — see resolveOnBehalfAgreementId.
|
|
286
319
|
onBehalfOfAgreementId: resolveOnBehalfAgreementId(rawEvent, contextSnap, this.agentId, identity.sessionId),
|
|
287
320
|
runId,
|
|
288
|
-
|
|
321
|
+
writeKeys,
|
|
289
322
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
323
|
+
this.toolContextHolder.current = toolContext;
|
|
324
|
+
if (!this.sdkMcpServer) {
|
|
325
|
+
const availableTools = await this.toolManager.getAvailableTools();
|
|
326
|
+
this.sdkMcpServer = buildZiggsSdkMcpServer(availableTools, () => {
|
|
327
|
+
const ctx = this.toolContextHolder.current;
|
|
328
|
+
if (!ctx) {
|
|
329
|
+
throw new Error('ClaudeSdkAgent: tool context missing for in-process MCP call');
|
|
330
|
+
}
|
|
331
|
+
return ctx;
|
|
332
|
+
}, {
|
|
333
|
+
name: 'ziggs',
|
|
334
|
+
instructions: 'In-process Ziggs tools backed by api-client. No external MCP process.',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
295
337
|
const prompt = buildClaudeEventPrompt(rawEvent, summarizeContext(contextSnap));
|
|
296
|
-
const mcpServers = { ziggs:
|
|
338
|
+
const mcpServers = { ziggs: this.sdkMcpServer };
|
|
297
339
|
assertHostedAgentZiggsTools(mcpServers);
|
|
298
340
|
const queryOptions = claudeQueryOptions({
|
|
299
341
|
systemPrompt,
|
|
@@ -306,80 +348,110 @@ export class ClaudeSdkAgent {
|
|
|
306
348
|
if (line)
|
|
307
349
|
runtimeLog.warn('ClaudeSdkAgent', `stderr session=${identity.sessionId}: ${line}`);
|
|
308
350
|
},
|
|
309
|
-
// ZIG-860: stateless-per-wake never resumes — each wake is a fresh
|
|
310
|
-
// transcript, so per-wake input stays flat instead of replaying the
|
|
311
|
-
// whole lane history every turn.
|
|
312
|
-
resume: this.statelessPerWake ? undefined : nextCtx.claudeSessionId,
|
|
313
351
|
});
|
|
314
352
|
let metrics = { llmCalls: 0, tokens: { total: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
|
|
315
353
|
let sessionId = nextCtx.claudeSessionId;
|
|
316
354
|
let spentInput = 0;
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
355
|
+
// ZIG-860: stateless-per-wake never resumes — each wake is a fresh
|
|
356
|
+
// transcript, so per-wake input stays flat instead of replaying the whole
|
|
357
|
+
// lane history every turn.
|
|
358
|
+
const resumeId = this.statelessPerWake ? undefined : nextCtx.claudeSessionId;
|
|
359
|
+
const consumeQuery = async (resume) => {
|
|
360
|
+
// Host watchdog abort (see TickInput.signal): stop consuming AND interrupt
|
|
361
|
+
// the query, so a timed-out wake never lingers as a token-burning zombie
|
|
362
|
+
// racing its own redelivered retry. Breaking the for-await returns the
|
|
363
|
+
// generator; interrupt() additionally stops a query blocked mid-turn.
|
|
364
|
+
const query = this.queryFn({ prompt, options: { ...queryOptions, resume } });
|
|
365
|
+
const onAbort = () => {
|
|
366
|
+
void query.interrupt?.()?.catch(() => { });
|
|
367
|
+
};
|
|
368
|
+
if (signal?.aborted)
|
|
369
|
+
onAbort();
|
|
370
|
+
else
|
|
371
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
372
|
+
try {
|
|
373
|
+
for await (const message of query) {
|
|
374
|
+
if (signal?.aborted) {
|
|
375
|
+
runtimeLog.warn('ClaudeSdkAgent', `host watchdog aborted wake session=${identity.sessionId} agent=${this.agentId} — stopping query`);
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if ('session_id' in message && typeof message.session_id === 'string') {
|
|
379
|
+
sessionId = message.session_id;
|
|
380
|
+
}
|
|
381
|
+
// Surface the model's own prose so a hired Claude agent is as legible as
|
|
382
|
+
// an FSM agent (ZIG-787). Each completed assistant turn's text is recorded
|
|
383
|
+
// as a `thought` — the SAME agent-private channel the FSM uses for its
|
|
384
|
+
// reasoning (runTurn `emitThought` → record-event → recordThought), so it
|
|
385
|
+
// shows in the hired-agent view without cluttering the counterparty chat.
|
|
386
|
+
// Pure tool-use turns carry no text and record nothing.
|
|
387
|
+
//
|
|
388
|
+
// Fire-and-forget on purpose: this runs inside `for await (query())`, so
|
|
389
|
+
// awaiting an artifacts round-trip per turn would couple model-stream
|
|
390
|
+
// consumption to the artifacts backend — a slow/wedged backend would stall
|
|
391
|
+
// the whole wake, not just fail a breadcrumb. Recording is best-effort and
|
|
392
|
+
// thought ordering is not load-bearing, so we let it run in the background
|
|
393
|
+
// and swallow failures with a warn.
|
|
394
|
+
if (message.type === 'assistant') {
|
|
395
|
+
const text = extractAssistantText(message).trim();
|
|
396
|
+
if (text) {
|
|
397
|
+
// Ordinal drawn synchronously so the fire-and-forget below can't
|
|
398
|
+
// reorder key assignment against other writes of this wake.
|
|
399
|
+
const idempotencyKey = writeKeys.artifactKey({ kind: 'thought', text });
|
|
400
|
+
void eff({ kind: 'record-event', sessionId: identity.sessionId, entry: { kind: 'thought', text }, idempotencyKey })
|
|
401
|
+
.catch((err) => runtimeLog.warn('ClaudeSdkAgent', `record thought failed session=${identity.sessionId}: ${err.message}`));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const m = extractMetrics(message);
|
|
405
|
+
if (m)
|
|
406
|
+
metrics = m;
|
|
407
|
+
if (message.type === 'result' && message.subtype !== 'success') {
|
|
408
|
+
runtimeLog.warn('ClaudeSdkAgent', `query ended without success subtype=${message.subtype} session=${identity.sessionId}`);
|
|
409
|
+
}
|
|
410
|
+
// Runaway guard (ZIG-759): abort before an unbounded loop drains the
|
|
411
|
+
// shared fleet key. Breaking the for-await calls the generator's return(),
|
|
412
|
+
// which terminates the underlying query.
|
|
413
|
+
spentInput += billableInput(usageOf(message));
|
|
414
|
+
if (spentInput > MAX_TASK_INPUT_TOKENS) {
|
|
415
|
+
runtimeLog.warn('ClaudeSdkAgent', `input-token budget exceeded (~${spentInput} > ${MAX_TASK_INPUT_TOKENS}) session=${identity.sessionId} agent=${this.agentId} — aborting query to cap spend (ZIG-759)`);
|
|
416
|
+
break;
|
|
359
417
|
}
|
|
360
418
|
}
|
|
361
|
-
const m = extractMetrics(message);
|
|
362
|
-
if (m)
|
|
363
|
-
metrics = m;
|
|
364
|
-
if (message.type === 'result' && message.subtype !== 'success') {
|
|
365
|
-
runtimeLog.warn('ClaudeSdkAgent', `query ended without success subtype=${message.subtype} session=${identity.sessionId}`);
|
|
366
|
-
}
|
|
367
|
-
// Runaway guard (ZIG-759): abort before an unbounded loop drains the
|
|
368
|
-
// shared fleet key. Breaking the for-await calls the generator's return(),
|
|
369
|
-
// which terminates the underlying query.
|
|
370
|
-
spentInput += billableInput(usageOf(message));
|
|
371
|
-
if (spentInput > MAX_TASK_INPUT_TOKENS) {
|
|
372
|
-
runtimeLog.warn('ClaudeSdkAgent', `input-token budget exceeded (~${spentInput} > ${MAX_TASK_INPUT_TOKENS}) session=${identity.sessionId} agent=${this.agentId} — aborting query to cap spend (ZIG-759)`);
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
419
|
}
|
|
420
|
+
finally {
|
|
421
|
+
signal?.removeEventListener('abort', onAbort);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
try {
|
|
425
|
+
await consumeQuery(resumeId);
|
|
376
426
|
}
|
|
377
427
|
catch (err) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
428
|
+
// ZIG-1191: the resumed transcript is gone. Claude Code stores transcripts
|
|
429
|
+
// on the container's own disk while the session id outlives it — persisted
|
|
430
|
+
// per lane in the session store with a 24h TTL — so a redeployed agents
|
|
431
|
+
// task inherits an id pointing at a file that no longer exists. Every wake
|
|
432
|
+
// on that lane then failed identically for as long as the TTL held, and the
|
|
433
|
+
// human saw one "Something went wrong. Please try again." per attempt.
|
|
434
|
+
//
|
|
435
|
+
// Retrying here is safe precisely because this failure happens BEFORE any
|
|
436
|
+
// turn runs: nothing was emitted, no tool ran, no idempotency ordinal was
|
|
437
|
+
// spent. That is why the retry is scoped to this one error and to one
|
|
438
|
+
// attempt — a mid-wake failure must keep propagating, since its partial
|
|
439
|
+
// writes are already out.
|
|
440
|
+
if (!isMissingSessionError(err) || !resumeId) {
|
|
441
|
+
runtimeLog.warn('ClaudeSdkAgent', `query failed session=${identity.sessionId}: ${err.message}`);
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
runtimeLog.info('ClaudeSdkAgent', `resumed Claude session ${resumeId} no longer exists (container replaced?) ` +
|
|
445
|
+
`session=${identity.sessionId} agent=${this.agentId} — retrying once on a fresh session (ZIG-1191)`);
|
|
446
|
+
nextCtx.claudeSessionId = undefined;
|
|
447
|
+
sessionId = undefined;
|
|
448
|
+
try {
|
|
449
|
+
await consumeQuery(undefined);
|
|
450
|
+
}
|
|
451
|
+
catch (retryErr) {
|
|
452
|
+
runtimeLog.warn('ClaudeSdkAgent', `query failed session=${identity.sessionId}: ${retryErr.message}`);
|
|
453
|
+
throw retryErr;
|
|
454
|
+
}
|
|
383
455
|
}
|
|
384
456
|
// Stateless-per-wake keeps no session id — clear any carried over from the
|
|
385
457
|
// prior ctx so the next wake starts fresh too (ZIG-860). Otherwise remember
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
* it MUST be applied on every Claude-brain wake. Centralizing it here means a
|
|
12
12
|
* caller cannot silently drift from it.
|
|
13
13
|
*/
|
|
14
|
-
import type
|
|
14
|
+
import { type Options, type SDKAssistantMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import type { RawEvent } from '../runtime/buildOutcome.js';
|
|
16
16
|
export interface ClaudeQueryOptionsParams {
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The system prompt in cache-ordered blocks: the shared protocol first, then
|
|
19
|
+
* this agent's role. Both are stable across wakes — see
|
|
20
|
+
* {@link claudeQueryOptions} for why that matters.
|
|
21
|
+
*/
|
|
22
|
+
systemPrompt: string[];
|
|
19
23
|
/** Claude model id for the brain. */
|
|
20
24
|
model: string;
|
|
21
25
|
/** In-process MCP servers exposing the agent's tools (e.g. `{ ziggs }`). */
|
|
@@ -47,6 +51,24 @@ export declare function resolveClaudeModel(preferred?: string, servicesModel?: s
|
|
|
47
51
|
* Build the `query()` options for a Claude-brain wake. The lockdown fields are
|
|
48
52
|
* fixed here; callers only supply the per-wake variables (prompt, model, tools,
|
|
49
53
|
* turn budget, resume). Returns `Options` so it drops straight into `query()`.
|
|
54
|
+
*
|
|
55
|
+
* ZIG-788 — prompt caching. The system prompt is handed over as blocks with a
|
|
56
|
+
* trailing `SYSTEM_PROMPT_DYNAMIC_BOUNDARY`: everything before the marker is
|
|
57
|
+
* eligible for cross-session caching, and there is nothing after it because
|
|
58
|
+
* nothing in this prompt varies per wake. Every per-wake input — the event, the
|
|
59
|
+
* context digest — is in the user prompt (`buildClaudeEventPrompt`), which sits
|
|
60
|
+
* after the cached prefix and cannot invalidate it.
|
|
61
|
+
*
|
|
62
|
+
* Cross-SESSION is the point, not cross-turn: `statelessPerWake` starts a fresh
|
|
63
|
+
* transcript on every wake, so without this the identical prefix was re-read at
|
|
64
|
+
* full price on each one.
|
|
65
|
+
*
|
|
66
|
+
* Caching is a prefix match, so block ORDER decides how far the hit reaches:
|
|
67
|
+
* the protocol block is byte-identical for every hosted agent and goes first,
|
|
68
|
+
* the role block is per-agent and goes second. An agent whose combined prompt
|
|
69
|
+
* lands under the model's minimum cacheable prefix (~1k tokens on Sonnet, more
|
|
70
|
+
* on Haiku) simply writes no entry — that is a silent no-op, not an error, so
|
|
71
|
+
* read `cacheRead`/`cacheCreation` in the wake metrics rather than assuming.
|
|
50
72
|
*/
|
|
51
73
|
export declare function claudeQueryOptions(params: ClaudeQueryOptionsParams): Options;
|
|
52
74
|
/**
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Claude-Agent-SDK cognition pieces.
|
|
3
|
+
*
|
|
4
|
+
* Everything a Claude-brain agent needs to turn a Ziggs wake into a `query()`
|
|
5
|
+
* call, EXCEPT the loop itself. `ClaudeSdkAgent` owns the loop (drive `query()`,
|
|
6
|
+
* collect metrics); this module owns the periphery so it lives in one place and
|
|
7
|
+
* is unit-testable without spinning up the SDK.
|
|
8
|
+
*
|
|
9
|
+
* The lockdown option set (`tools: []`, `strictMcpConfig`, `settingSources: []`,
|
|
10
|
+
* bypass permissions, no local settings) is a security boundary, not a default —
|
|
11
|
+
* it MUST be applied on every Claude-brain wake. Centralizing it here means a
|
|
12
|
+
* caller cannot silently drift from it.
|
|
13
|
+
*/
|
|
14
|
+
import { SYSTEM_PROMPT_DYNAMIC_BOUNDARY, } from '@anthropic-ai/claude-agent-sdk';
|
|
1
15
|
import { getBatchEvents } from '../context/batch.js';
|
|
2
16
|
const DEFAULT_CLAUDE_MODEL = 'claude-sonnet-4-6';
|
|
3
17
|
/**
|
|
@@ -17,10 +31,28 @@ export function resolveClaudeModel(preferred, servicesModel) {
|
|
|
17
31
|
* Build the `query()` options for a Claude-brain wake. The lockdown fields are
|
|
18
32
|
* fixed here; callers only supply the per-wake variables (prompt, model, tools,
|
|
19
33
|
* turn budget, resume). Returns `Options` so it drops straight into `query()`.
|
|
34
|
+
*
|
|
35
|
+
* ZIG-788 — prompt caching. The system prompt is handed over as blocks with a
|
|
36
|
+
* trailing `SYSTEM_PROMPT_DYNAMIC_BOUNDARY`: everything before the marker is
|
|
37
|
+
* eligible for cross-session caching, and there is nothing after it because
|
|
38
|
+
* nothing in this prompt varies per wake. Every per-wake input — the event, the
|
|
39
|
+
* context digest — is in the user prompt (`buildClaudeEventPrompt`), which sits
|
|
40
|
+
* after the cached prefix and cannot invalidate it.
|
|
41
|
+
*
|
|
42
|
+
* Cross-SESSION is the point, not cross-turn: `statelessPerWake` starts a fresh
|
|
43
|
+
* transcript on every wake, so without this the identical prefix was re-read at
|
|
44
|
+
* full price on each one.
|
|
45
|
+
*
|
|
46
|
+
* Caching is a prefix match, so block ORDER decides how far the hit reaches:
|
|
47
|
+
* the protocol block is byte-identical for every hosted agent and goes first,
|
|
48
|
+
* the role block is per-agent and goes second. An agent whose combined prompt
|
|
49
|
+
* lands under the model's minimum cacheable prefix (~1k tokens on Sonnet, more
|
|
50
|
+
* on Haiku) simply writes no entry — that is a silent no-op, not an error, so
|
|
51
|
+
* read `cacheRead`/`cacheCreation` in the wake metrics rather than assuming.
|
|
20
52
|
*/
|
|
21
53
|
export function claudeQueryOptions(params) {
|
|
22
54
|
return {
|
|
23
|
-
systemPrompt: params.systemPrompt,
|
|
55
|
+
systemPrompt: [...params.systemPrompt, SYSTEM_PROMPT_DYNAMIC_BOUNDARY],
|
|
24
56
|
model: params.model,
|
|
25
57
|
mcpServers: params.mcpServers,
|
|
26
58
|
// The agent's ONLY capabilities are its MCP tools — no built-in file/bash/web
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ToolDefinition } from '../tools/defineTool.js';
|
|
2
|
-
import type { EffectHandler,
|
|
2
|
+
import type { EffectHandler, DeliveryWriteKeys } from '../types.js';
|
|
3
3
|
export interface ClaudeSdkToolContext extends Record<string, unknown> {
|
|
4
4
|
operatorKey: string;
|
|
5
5
|
agentId: string;
|
|
@@ -14,7 +14,7 @@ export interface ClaudeSdkToolContext extends Record<string, unknown> {
|
|
|
14
14
|
/** Per-wake id grouping this tick's tool calls in telemetry (ZIG-775). Random — never use for idempotency. */
|
|
15
15
|
runId?: string;
|
|
16
16
|
/** Replay-stable write keys for this wake — write tools derive idempotency keys from here, never from the LLM. */
|
|
17
|
-
|
|
17
|
+
writeKeys?: DeliveryWriteKeys;
|
|
18
18
|
}
|
|
19
19
|
export declare const contextSnapshotTool: ToolDefinition;
|
|
20
20
|
export declare const chatSendTool: ToolDefinition;
|
|
@@ -77,7 +77,7 @@ export const chatSendTool = defineTool('chat_send', {
|
|
|
77
77
|
const text = String(args.text);
|
|
78
78
|
const messageId = args.idempotencyKey
|
|
79
79
|
? `sdk_idem_${String(args.idempotencyKey)}`
|
|
80
|
-
: c.
|
|
80
|
+
: c.writeKeys?.messageKey({ text, sessionId, receiverId }) ?? `sdk_${randomUUID()}`;
|
|
81
81
|
return c.eff({
|
|
82
82
|
kind: 'send-message',
|
|
83
83
|
sessionId,
|
|
@@ -100,7 +100,7 @@ export function buildSdkToolDefs(toolDefs, getContext) {
|
|
|
100
100
|
senderType: ctx.senderType,
|
|
101
101
|
onBehalfOfAgreementId: ctx.onBehalfOfAgreementId,
|
|
102
102
|
runId: ctx.runId,
|
|
103
|
-
|
|
103
|
+
writeKeys: ctx.writeKeys,
|
|
104
104
|
});
|
|
105
105
|
if (r.ok)
|
|
106
106
|
return toCallToolResult(r.result);
|
|
@@ -21,10 +21,11 @@ function applyTaskOperationResultToContext(updates, operation, result) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
if (operation === 'task-update') {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// `state` only — ZIG-1057 cut `status` from the wire (ZIG-1128).
|
|
25
|
+
const state = (task?.['state'] || r['state']);
|
|
26
|
+
if (state === 'completed')
|
|
26
27
|
updates['taskCompleted'] = true;
|
|
27
|
-
if (
|
|
28
|
+
if (state === 'failed')
|
|
28
29
|
updates['taskFailed'] = true;
|
|
29
30
|
}
|
|
30
31
|
if (operation === 'agreement-respond')
|
package/dist/index.d.ts
CHANGED
|
@@ -35,13 +35,12 @@ export { validateWorkflow, WorkflowValidationError } from './runtime/validateWor
|
|
|
35
35
|
export { deriveRespondsToProposals, statesRespondToProposals, } from './runtime/deriveRespondsToProposals.js';
|
|
36
36
|
export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
|
|
37
37
|
export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
|
|
38
|
-
export {
|
|
39
|
-
export type { Outcome, OutcomeKind, Ctx, Identity, HistoryEntry, ToolResultEntry, PendingSubtask, CollectedSubtaskResult, TaskBookkeeping, Workflow, State, ParkedState, ThinkingState, MechanicalState, Action, Transition, PromptDef, ActionPrompt, Produces, ProducesFn, AgreementRef, TaskRef, ProposalRef, AgentConfig, DefineAgentInput, ServicesConfig, Effect, EffectKind, EffectResult, EffectResultMap, EffectHandler, LlmMessage, LlmToolCall, LlmToolSchema, LlmResponse, ContextSnapshot, ReadContextOpts, ToolCallResult, RecordedEntry,
|
|
38
|
+
export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
|
|
39
|
+
export type { Outcome, OutcomeKind, Ctx, Identity, HistoryEntry, ToolResultEntry, PendingSubtask, CollectedSubtaskResult, TaskBookkeeping, Workflow, State, ParkedState, ThinkingState, MechanicalState, Action, Transition, PromptDef, ActionPrompt, Produces, ProducesFn, AgreementRef, TaskRef, ProposalRef, AgentConfig, DefineAgentInput, ServicesConfig, Effect, EffectKind, EffectResult, EffectResultMap, EffectHandler, LlmMessage, LlmToolCall, LlmToolSchema, LlmResponse, ContextSnapshot, ReadContextOpts, ToolCallResult, RecordedEntry, DeliveryWriteKeys, } from './types.js';
|
|
40
40
|
export { default } from './AgentHost.js';
|
|
41
41
|
export { validateContext, ContextWarning } from './cognition/validateContext.js';
|
|
42
42
|
export { runLauncher } from './server/runLauncher.js';
|
|
43
43
|
export { ConnectionPool } from './server/ConnectionPool.js';
|
|
44
|
-
export { createStaleBacklogGate, resolveReplayPolicy, newestActionableAt, parseReplayMaxAge, } from './server/staleBacklogGate.js';
|
|
45
44
|
export { decodeOperatorKeyClaims, boundKeyFleetWarning } from './shared/operatorKeyClaims.js';
|
|
46
45
|
export { createHealthServer } from './server/createHealthServer.js';
|
|
47
46
|
export { TaskService } from './server/tasks/TaskService.js';
|
|
@@ -55,6 +54,8 @@ export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
|
55
54
|
export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
|
|
56
55
|
export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormatter } from './formatters/index.js';
|
|
57
56
|
export { ProactiveTrigger } from './server/proactive/ProactiveTrigger.js';
|
|
57
|
+
export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './server/polls/PollKind.js';
|
|
58
|
+
export { PollRunner, type PollRunnerOptions, type WakeTarget } from './server/polls/PollRunner.js';
|
|
58
59
|
export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, paymentTransferTool, paymentHoldTool, paymentReleaseTool, paymentResolveWalletTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, LINK_TOOLS, linkCreateInviteTool, linkListTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './server/tools/index.js';
|
|
59
60
|
export { CLAUDE_SDK_TOOLS } from './agent/claudeSdkTools.js';
|
|
60
61
|
export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
|
|
@@ -65,7 +66,8 @@ export { classifyIncomingEvent } from './context/classifyEnvelope.js';
|
|
|
65
66
|
export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
|
|
66
67
|
export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
|
|
67
68
|
export { classifyWorkflowEvent, findTaskResult, unwrapBatchEvent, findIncomingTaskResult } from './context/routingLabels.js';
|
|
68
|
-
export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl,
|
|
69
|
+
export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl, configureApiClient, } from '@ziggs-ai/api-client';
|
|
70
|
+
export type { ApiClientConfig } from '@ziggs-ai/api-client';
|
|
69
71
|
export { SeenMessages } from './server/SeenMessages.js';
|
|
70
72
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
71
73
|
export type { RuntimeLogLevel } from './shared/runtimeLog.js';
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,12 @@ export { validateWorkflow, WorkflowValidationError } from './runtime/validateWor
|
|
|
29
29
|
export { deriveRespondsToProposals, statesRespondToProposals, } from './runtime/deriveRespondsToProposals.js';
|
|
30
30
|
export { outcomeFromEvent, outcomeFromActionResult, applyOutcomeToCtx } from './runtime/buildOutcome.js';
|
|
31
31
|
export { LANE_BOOK_KEY, allCollectedResults, allDelegatedAgreementIds, allPendingSubtasks, } from './runtime/taskBookkeeping.js';
|
|
32
|
-
export {
|
|
32
|
+
export { createDeliveryWriteKeys } from './runtime/deliveryWriteKeys.js';
|
|
33
33
|
export { default } from './AgentHost.js';
|
|
34
34
|
// ── Supporting modules ──────────────────────────────────────────────────────
|
|
35
35
|
export { validateContext, ContextWarning } from './cognition/validateContext.js';
|
|
36
36
|
export { runLauncher } from './server/runLauncher.js';
|
|
37
37
|
export { ConnectionPool } from './server/ConnectionPool.js';
|
|
38
|
-
export { createStaleBacklogGate, resolveReplayPolicy, newestActionableAt, parseReplayMaxAge, } from './server/staleBacklogGate.js';
|
|
39
38
|
export { decodeOperatorKeyClaims, boundKeyFleetWarning } from './shared/operatorKeyClaims.js';
|
|
40
39
|
export { createHealthServer } from './server/createHealthServer.js';
|
|
41
40
|
export { TaskService } from './server/tasks/TaskService.js';
|
|
@@ -48,6 +47,8 @@ export { OpenAIAdapter } from './adapters/OpenAIAdapter.js';
|
|
|
48
47
|
export { extractJSON, safeParseJSON } from './utils/jsonExtractor.js';
|
|
49
48
|
export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormatter } from './formatters/index.js';
|
|
50
49
|
export { ProactiveTrigger } from './server/proactive/ProactiveTrigger.js';
|
|
50
|
+
export { PollRegistry, declaredPolls, } from './server/polls/PollKind.js';
|
|
51
|
+
export { PollRunner } from './server/polls/PollRunner.js';
|
|
51
52
|
// ZIG-894 — the hand-rolled ZiggsPayClient / ZiggsConnectClient were deleted;
|
|
52
53
|
// payment and connection tools now use @ziggs-ai/api-client's PaymentsClient /
|
|
53
54
|
// ConnectionsClient, the same clients ziggs-mcp uses.
|
|
@@ -70,6 +71,9 @@ export { classifyIncomingEvent } from './context/classifyEnvelope.js';
|
|
|
70
71
|
export { buildContextUpdates, CONTEXT_RESET } from './context/applyEffects.js';
|
|
71
72
|
export { getBatchEvents, isTaskResultRelevantToAgent } from './context/batch.js';
|
|
72
73
|
export { classifyWorkflowEvent, findTaskResult, unwrapBatchEvent, findIncomingTaskResult } from './context/routingLabels.js';
|
|
73
|
-
export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl,
|
|
74
|
+
export { MessagesClient, ArtifactsClient, AgentSearchClient, getBackendUrl,
|
|
75
|
+
// ZIG-1204 — self-hosted / non-default backend: consumers of agent-sdk alone
|
|
76
|
+
// must be able to point the shared HTTP client without a direct api-client dep.
|
|
77
|
+
configureApiClient, } from '@ziggs-ai/api-client';
|
|
74
78
|
export { SeenMessages } from './server/SeenMessages.js';
|
|
75
79
|
export { runtimeLog, resetRuntimeLogLevelCache } from './shared/runtimeLog.js';
|
|
@@ -7,7 +7,7 @@ export interface NormalizedEvent {
|
|
|
7
7
|
/**
|
|
8
8
|
* Backend per-delivery id of the chat row this event came from. The one
|
|
9
9
|
* field of the event that is identical on live push and on catch-up
|
|
10
|
-
* redelivery, so it anchors the wake's idempotency keys (
|
|
10
|
+
* redelivery, so it anchors the wake's idempotency keys (deliveryWriteKeys).
|
|
11
11
|
*/
|
|
12
12
|
messageId?: string;
|
|
13
13
|
timestamp: number;
|
|
@@ -92,7 +92,7 @@ interface NormalizeArgs {
|
|
|
92
92
|
* what is fenced to this agent, so there is no client-side "is this for me"
|
|
93
93
|
* relevance gate — this function decides SHAPE (message vs task_result vs
|
|
94
94
|
* agreement_lifecycle vs notification) and the fields the FSM / SessionResolver /
|
|
95
|
-
*
|
|
95
|
+
* deliveryWriteKeys depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
96
96
|
* compatibility but no longer gates anything.
|
|
97
97
|
*/
|
|
98
98
|
export declare function normalizeIncomingEvent({ text, metadata }: NormalizeArgs): NormalizedEvent;
|
|
@@ -86,7 +86,7 @@ function isTerminalTaskState(state) {
|
|
|
86
86
|
* what is fenced to this agent, so there is no client-side "is this for me"
|
|
87
87
|
* relevance gate — this function decides SHAPE (message vs task_result vs
|
|
88
88
|
* agreement_lifecycle vs notification) and the fields the FSM / SessionResolver /
|
|
89
|
-
*
|
|
89
|
+
* deliveryWriteKeys depend on, nothing more. `ownAgentId` is accepted for call-site
|
|
90
90
|
* compatibility but no longer gates anything.
|
|
91
91
|
*/
|
|
92
92
|
export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Ctx, Outcome, Workflow, Identity, EffectHandler,
|
|
1
|
+
import type { Ctx, Outcome, Workflow, Identity, EffectHandler, DeliveryWriteKeys } from '../types.js';
|
|
2
2
|
import { type RawEvent } from './buildOutcome.js';
|
|
3
3
|
import type { runTurn as RunTurnFn } from './runTurn.js';
|
|
4
4
|
import { PromptBuilder } from './PromptBuilder.js';
|
|
@@ -60,7 +60,7 @@ interface MachineOptions {
|
|
|
60
60
|
/** Runaway-loop backstop: max thinking turns without human input before parking. Default 100. */
|
|
61
61
|
maxTurnsWithoutUserInput?: number;
|
|
62
62
|
/** This wake's replay-stable write keys — one stream shared by every turn of the tick. */
|
|
63
|
-
|
|
63
|
+
writeKeys?: DeliveryWriteKeys;
|
|
64
64
|
}
|
|
65
65
|
interface IncomingFrame {
|
|
66
66
|
event?: RawEvent;
|
|
@@ -86,7 +86,7 @@ export declare class AgentMachine {
|
|
|
86
86
|
toolManager: ToolManager;
|
|
87
87
|
promptBuilder: PromptBuilder;
|
|
88
88
|
memory?: MemoryStore;
|
|
89
|
-
|
|
89
|
+
writeKeys?: DeliveryWriteKeys;
|
|
90
90
|
state: string;
|
|
91
91
|
status: 'active' | 'stopped';
|
|
92
92
|
ctx: Ctx;
|
|
@@ -114,7 +114,7 @@ export declare class AgentMachine {
|
|
|
114
114
|
output: number;
|
|
115
115
|
};
|
|
116
116
|
get stateTimeoutUnhandled(): boolean;
|
|
117
|
-
constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput,
|
|
117
|
+
constructor({ workflow, executionCore, eff, toolManager, promptBuilder, memory, input, transitionTap, maxTurnsWithoutUserInput, writeKeys }: MachineOptions);
|
|
118
118
|
getSnapshot(): MachineSnapshot;
|
|
119
119
|
/** Parked = currently in a parked state and not executing a turn. */
|
|
120
120
|
get isParked(): boolean;
|