@wowyuarm/dsh-agent-team 0.1.1 → 0.1.3
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/README.md +1 -0
- package/README.zh.md +2 -0
- package/package.json +42 -25
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/lib/attachments.js +11 -12
- package/packages/agent-team/lib/auto-compaction.js +163 -0
- package/packages/agent-team/lib/index.js +262 -189
- package/packages/agent-team/lib/ledger.js +140 -109
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/spec.js +10 -4
- package/packages/agent-team/lib/typert.host.js +68 -71
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +41 -56
- package/packages/agent-team/lib/types/attachments.d.ts +2 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +49 -24
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +24 -11
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +15 -16
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +20 -12
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +8 -3
- package/packages/client-agent-team/README.md +3 -2
- package/packages/client-agent-team/README.zh.md +3 -2
- package/packages/client-agent-team/lib/client.js +1507 -525
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -182
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +27 -5
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +19 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +22 -2
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +170 -18
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +4 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +70 -13
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +72 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +8 -2
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/requests.js +22 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +35 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +25 -0
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/task-refs.js +77 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +54 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +83 -2
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
- package/packages/tool-agent-team/lib/index.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
|
@@ -19,6 +19,10 @@ function emptyProjection() {
|
|
|
19
19
|
orderedFacts: [], factsByThread: new Map(), topLevelMessages: [], mentionsByMessage: new Map(), messageCountByThread: new Map(),
|
|
20
20
|
attentionByThread: new Map() };
|
|
21
21
|
}
|
|
22
|
+
/** Exhaustiveness guard for the closed operation union: adding a kind must update every dispatch. */
|
|
23
|
+
function assertUnhandledKind(operation) {
|
|
24
|
+
throw new Error(`agent-team ledger does not handle operation kind '${operation.kind}'`);
|
|
25
|
+
}
|
|
22
26
|
/** Replay and append logic behind the Agent Team service interface. */
|
|
23
27
|
export class AgentTeamLedger {
|
|
24
28
|
table;
|
|
@@ -191,31 +195,6 @@ export class AgentTeamLedger {
|
|
|
191
195
|
resumeMember(request) {
|
|
192
196
|
return this.setMemberState(request, 'enabled');
|
|
193
197
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Audit record of one in-place Member session restart. The operation changes
|
|
196
|
-
* no projection state — identity, transcript, and memory are untouched — so
|
|
197
|
-
* replay treats it as a marker while the Host re-mints the live handle.
|
|
198
|
-
*/
|
|
199
|
-
restartMember(request) {
|
|
200
|
-
return this.enqueue(async () => {
|
|
201
|
-
const existing = this.state.byRequest.get(request.requestId);
|
|
202
|
-
if (existing !== undefined) {
|
|
203
|
-
this.assertSameMemberRestart(existing, request);
|
|
204
|
-
return this.resolved(this.memberResult(existing));
|
|
205
|
-
}
|
|
206
|
-
this.assertHumanActor(request.actor);
|
|
207
|
-
const member = this.requireMember(request.memberId);
|
|
208
|
-
if (member.state !== 'enabled')
|
|
209
|
-
throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
|
|
210
|
-
const operation = Object.freeze({
|
|
211
|
-
...this.operationBase(request, this.nextSequence()), kind: 'team/member-session-restarted',
|
|
212
|
-
data: Object.freeze({ member }),
|
|
213
|
-
});
|
|
214
|
-
await this.table.put(operation.operationId, operation);
|
|
215
|
-
this.apply(operation);
|
|
216
|
-
return this.committed(this.memberResult(operation));
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
198
|
getMember(memberId) {
|
|
220
199
|
return this.state.members.get(memberId);
|
|
221
200
|
}
|
|
@@ -335,19 +314,14 @@ export class AgentTeamLedger {
|
|
|
335
314
|
return this.resolved(this.replyResult(existing));
|
|
336
315
|
}
|
|
337
316
|
const actor = this.assertActorForWorkspace(request.actor, request.workspaceId);
|
|
338
|
-
const task = this.
|
|
339
|
-
const thread = this.requireThread(task.threadRef);
|
|
340
|
-
if (actor.kind === 'member')
|
|
341
|
-
this.requireMemberChannel(this.requireMember(actor.memberId), task.channelRef);
|
|
317
|
+
const { task, thread } = this.threadForActor(actor, request.workspaceId, request.taskRef);
|
|
342
318
|
const body = request.body.trim();
|
|
343
319
|
if (body === '')
|
|
344
320
|
throw new Error('message body must not be empty');
|
|
345
321
|
this.assertMentionTargets(this.requireChannel(request.workspaceId, task.channelRef), recipients);
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
-
return this.resolved(
|
|
349
|
-
if (request.baseRevision !== thread.revision)
|
|
350
|
-
return this.resolved(this.staleRevision(task, thread, request.baseRevision));
|
|
322
|
+
const deferred = this.deferredThreadWrite(actor.memberId, task, thread, request.baseRevision);
|
|
323
|
+
if (deferred !== undefined)
|
|
324
|
+
return this.resolved(deferred);
|
|
351
325
|
if (task.resolution === 'closed')
|
|
352
326
|
throw new Error(`Task '${task.taskRef}' is closed; reopen it before replying`);
|
|
353
327
|
const unfollowedAgents = recipients.filter(memberId => this.state.members.has(memberId) && !this.isFollowing(thread.threadRef, memberId));
|
|
@@ -389,15 +363,10 @@ export class AgentTeamLedger {
|
|
|
389
363
|
return this.resolved(this.claimResult(existing));
|
|
390
364
|
}
|
|
391
365
|
const actor = this.assertActorForWorkspace(request.actor, request.workspaceId);
|
|
392
|
-
const task = this.
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
395
|
-
this.
|
|
396
|
-
const unread = this.unreadFor(actor.memberId, thread.threadRef);
|
|
397
|
-
if (unread.length > 0)
|
|
398
|
-
return this.resolved(this.unreadRequired(task, thread, unread));
|
|
399
|
-
if (request.baseRevision !== thread.revision)
|
|
400
|
-
return this.resolved(this.staleRevision(task, thread, request.baseRevision));
|
|
366
|
+
const { task, thread } = this.threadForActor(actor, request.workspaceId, request.taskRef);
|
|
367
|
+
const deferred = this.deferredThreadWrite(actor.memberId, task, thread, request.baseRevision);
|
|
368
|
+
if (deferred !== undefined)
|
|
369
|
+
return this.resolved(deferred);
|
|
401
370
|
if (task.resolution !== 'open')
|
|
402
371
|
throw new Error(`Task '${task.taskRef}' is ${task.status}; reopen it before changing Claims`);
|
|
403
372
|
if (actor.kind !== 'member')
|
|
@@ -466,24 +435,32 @@ export class AgentTeamLedger {
|
|
|
466
435
|
return this.resolved(this.taskResult(existing));
|
|
467
436
|
}
|
|
468
437
|
this.assertHumanActor(request.actor);
|
|
469
|
-
const task = this.
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return this.resolved(this.unreadRequired(task, thread, unread));
|
|
474
|
-
if (request.baseRevision !== thread.revision)
|
|
475
|
-
return this.resolved(this.staleRevision(task, thread, request.baseRevision));
|
|
438
|
+
const { task, thread } = this.threadForActor(request.actor, request.workspaceId, request.taskRef);
|
|
439
|
+
const deferred = this.deferredThreadWrite(request.actor.memberId, task, thread, request.baseRevision);
|
|
440
|
+
if (deferred !== undefined)
|
|
441
|
+
return this.resolved(deferred);
|
|
476
442
|
if (request.action === 'accept' && task.resolution !== 'open')
|
|
477
443
|
throw new Error(`Task '${task.taskRef}' is already ${task.resolution}`);
|
|
478
444
|
if (request.action === 'close' && task.resolution === 'closed')
|
|
479
445
|
throw new Error(`Task '${task.taskRef}' is already closed`);
|
|
480
|
-
|
|
481
|
-
|
|
446
|
+
// Acceptance normally waits for every Claim to finish (in_review);
|
|
447
|
+
// a Human may also accept early while work is in progress, which then
|
|
448
|
+
// completes the still-active Claims inside the same atomic operation.
|
|
449
|
+
if (request.action === 'accept' && task.status !== 'in_review' && task.status !== 'in_progress') {
|
|
450
|
+
throw new Error(`Task '${task.taskRef}' must be in_review or in_progress before acceptance`);
|
|
451
|
+
}
|
|
452
|
+
const priorActiveClaims = [...this.state.claims.values()].filter(claim => claim.taskRef === task.taskRef && claim.state === 'active');
|
|
453
|
+
if (request.action === 'accept' && task.status === 'in_progress' && priorActiveClaims.length === 0) {
|
|
454
|
+
throw new Error(`Task '${task.taskRef}' has no active Claims to complete for early acceptance`);
|
|
455
|
+
}
|
|
482
456
|
if (request.action === 'reopen' && task.resolution === 'open')
|
|
483
457
|
throw new Error(`Task '${task.taskRef}' is already open`);
|
|
484
458
|
const sequence = this.nextSequence();
|
|
485
|
-
const claims = [...this.state.claims.values()].filter(claim => claim.taskRef === task.taskRef).map(claim => request.action === 'close' && claim.state === 'active' ? Object.freeze({ ...claim, state: 'released' })
|
|
459
|
+
const claims = [...this.state.claims.values()].filter(claim => claim.taskRef === task.taskRef).map(claim => request.action === 'close' && claim.state === 'active' ? Object.freeze({ ...claim, state: 'released' })
|
|
460
|
+
: request.action === 'accept' && claim.state === 'active' ? Object.freeze({ ...claim, state: 'done' })
|
|
461
|
+
: claim);
|
|
486
462
|
const releasedClaims = claims.filter(claim => claim.state === 'released' && this.state.claims.get(claim.claimRef)?.state === 'active');
|
|
463
|
+
const completedClaims = claims.filter(claim => claim.state === 'done' && this.state.claims.get(claim.claimRef)?.state === 'active');
|
|
487
464
|
const resolution = request.action === 'reopen' ? 'open' : request.action === 'accept' ? 'accepted' : 'closed';
|
|
488
465
|
const status = resolution === 'accepted' ? 'done' : resolution === 'closed' ? 'closed'
|
|
489
466
|
: this.deriveTaskStatus(task.taskRef, claims);
|
|
@@ -491,12 +468,13 @@ export class AgentTeamLedger {
|
|
|
491
468
|
const nextThread = Object.freeze({ ...thread, revision: sequence });
|
|
492
469
|
const activity = Object.freeze({ activityRef: this.ref('activity'), kind: request.action,
|
|
493
470
|
taskRef: task.taskRef, threadRef: task.threadRef, actor: request.actor.memberId, sequence,
|
|
494
|
-
...(releasedClaims.length === 0 ? {} : { releasedClaimRefs: Object.freeze(releasedClaims.map(claim => claim.claimRef)) })
|
|
471
|
+
...(releasedClaims.length === 0 ? {} : { releasedClaimRefs: Object.freeze(releasedClaims.map(claim => claim.claimRef)) }),
|
|
472
|
+
...(completedClaims.length === 0 ? {} : { completedClaimRefs: Object.freeze(completedClaims.map(claim => claim.claimRef)) }) });
|
|
495
473
|
const inbox = request.action === 'close'
|
|
496
474
|
? this.closeThreadInbox(activity, thread.threadRef)
|
|
497
475
|
: request.action === 'reopen'
|
|
498
476
|
? this.reopenThreadInbox(activity, thread.threadRef)
|
|
499
|
-
: this.
|
|
477
|
+
: this.acceptThreadInbox(activity, thread.threadRef, completedClaims.map(claim => claim.owner));
|
|
500
478
|
const operation = Object.freeze({
|
|
501
479
|
...this.operationBase(request, sequence), kind: 'team/task-changed',
|
|
502
480
|
data: Object.freeze({ workspaceId: request.workspaceId, baseRevision: request.baseRevision,
|
|
@@ -548,10 +526,7 @@ export class AgentTeamLedger {
|
|
|
548
526
|
return this.resolved(this.attentionResult(existing));
|
|
549
527
|
}
|
|
550
528
|
const actor = this.assertActorForWorkspace(request.actor, request.workspaceId);
|
|
551
|
-
const task = this.
|
|
552
|
-
const thread = this.requireThread(task.threadRef);
|
|
553
|
-
if (actor.kind === 'member')
|
|
554
|
-
this.requireMemberChannel(this.requireMember(actor.memberId), task.channelRef);
|
|
529
|
+
const { task, thread } = this.threadForActor(actor, request.workspaceId, request.taskRef);
|
|
555
530
|
const current = this.attentionFor(actor.memberId, thread.threadRef);
|
|
556
531
|
if (request.action === 'follow') {
|
|
557
532
|
if (task.resolution === 'closed')
|
|
@@ -584,10 +559,7 @@ export class AgentTeamLedger {
|
|
|
584
559
|
}
|
|
585
560
|
attentionStatus(actor, request) {
|
|
586
561
|
const authorized = this.assertActorForWorkspace(actor, request.workspaceId);
|
|
587
|
-
const task = this.
|
|
588
|
-
if (authorized.kind === 'member')
|
|
589
|
-
this.requireMemberChannel(this.requireMember(authorized.memberId), task.channelRef);
|
|
590
|
-
const thread = this.requireThread(task.threadRef);
|
|
562
|
+
const { task, thread } = this.threadForActor(authorized, request.workspaceId, request.taskRef);
|
|
591
563
|
const attention = this.attentionFor(authorized.memberId, thread.threadRef);
|
|
592
564
|
return attention === undefined
|
|
593
565
|
? Object.freeze({ task, thread })
|
|
@@ -688,22 +660,9 @@ export class AgentTeamLedger {
|
|
|
688
660
|
}
|
|
689
661
|
return Object.freeze({ items: Object.freeze(observations.slice(-limit)) });
|
|
690
662
|
}
|
|
663
|
+
/** Every operation carrying an inbox delta drives the Inbox projection; the payload shape decides, not a per-kind list. */
|
|
691
664
|
attentionDelta(operation) {
|
|
692
|
-
|
|
693
|
-
case 'team/channel-member-removed':
|
|
694
|
-
case 'team/message-sent':
|
|
695
|
-
case 'team/thread-replied':
|
|
696
|
-
case 'team/claim-created':
|
|
697
|
-
case 'team/claim-done':
|
|
698
|
-
case 'team/claim-released':
|
|
699
|
-
case 'team/task-changed':
|
|
700
|
-
case 'team/thread-attention-changed':
|
|
701
|
-
case 'team/thread-read':
|
|
702
|
-
case 'team/member-removed':
|
|
703
|
-
return operation.data.inbox;
|
|
704
|
-
default:
|
|
705
|
-
return undefined;
|
|
706
|
-
}
|
|
665
|
+
return 'inbox' in operation.data ? operation.data.inbox : undefined;
|
|
707
666
|
}
|
|
708
667
|
/** Attachment ids referenced by any stored Message — the GC's keep-set oracle. */
|
|
709
668
|
referencedAttachmentIds() {
|
|
@@ -718,10 +677,7 @@ export class AgentTeamLedger {
|
|
|
718
677
|
}
|
|
719
678
|
threadHistory(actor, request) {
|
|
720
679
|
const authorized = this.assertActorForWorkspace(actor, request.workspaceId);
|
|
721
|
-
const task = this.
|
|
722
|
-
if (authorized.kind === 'member')
|
|
723
|
-
this.requireMemberChannel(this.requireMember(authorized.memberId), task.channelRef);
|
|
724
|
-
const thread = this.requireThread(task.threadRef);
|
|
680
|
+
const { task, thread } = this.threadForActor(authorized, request.workspaceId, request.taskRef);
|
|
725
681
|
const limit = request.limit ?? 20;
|
|
726
682
|
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
727
683
|
throw new Error('history limit must be an integer between 1 and 100');
|
|
@@ -737,14 +693,27 @@ export class AgentTeamLedger {
|
|
|
737
693
|
}
|
|
738
694
|
listClaims(actor, request) {
|
|
739
695
|
const authorized = this.assertActorForWorkspace(actor, request.workspaceId);
|
|
740
|
-
const task = this.
|
|
741
|
-
|
|
742
|
-
this.requireMemberChannel(this.requireMember(authorized.memberId), task.channelRef);
|
|
743
|
-
return Object.freeze({ task, thread: this.requireThread(task.threadRef), claims: this.claimsForTask(task.taskRef) });
|
|
696
|
+
const { task, thread } = this.threadForActor(authorized, request.workspaceId, request.taskRef);
|
|
697
|
+
return Object.freeze({ task, thread, claims: this.claimsForTask(task.taskRef) });
|
|
744
698
|
}
|
|
745
699
|
getTask(taskRef) {
|
|
746
700
|
return this.state.tasks.get(taskRef);
|
|
747
701
|
}
|
|
702
|
+
getClaim(claimRef) {
|
|
703
|
+
return this.state.claims.get(claimRef);
|
|
704
|
+
}
|
|
705
|
+
/** Navigation facts for message-body Task refs; unknown refs are omitted. */
|
|
706
|
+
resolveTaskRefs(workspaceId, taskRefs) {
|
|
707
|
+
const numbers = this.taskNumbers(workspaceId);
|
|
708
|
+
const resolved = [];
|
|
709
|
+
for (const taskRef of taskRefs) {
|
|
710
|
+
const task = this.state.tasks.get(taskRef);
|
|
711
|
+
if (task === undefined || this.state.channels.get(task.channelRef)?.workspaceId !== workspaceId)
|
|
712
|
+
continue;
|
|
713
|
+
resolved.push(Object.freeze({ taskRef: task.taskRef, channelRef: task.channelRef, threadRef: task.threadRef, taskNumber: numbers.get(task.taskRef) ?? 0 }));
|
|
714
|
+
}
|
|
715
|
+
return resolved;
|
|
716
|
+
}
|
|
748
717
|
view(request, memberId) {
|
|
749
718
|
const limit = request.limit ?? 20;
|
|
750
719
|
const direction = request.direction ?? 'after';
|
|
@@ -794,7 +763,7 @@ export class AgentTeamLedger {
|
|
|
794
763
|
const visibleTasks = [...this.state.tasks.values()].filter(task => channelRefs.has(task.channelRef)
|
|
795
764
|
&& (request.channelRef === undefined || task.channelRef === request.channelRef)
|
|
796
765
|
&& (request.threadRef === undefined || task.threadRef === request.threadRef));
|
|
797
|
-
const taskNumbers = this.taskNumbers(request.workspaceId
|
|
766
|
+
const taskNumbers = this.taskNumbers(request.workspaceId);
|
|
798
767
|
const items = selected.filter((fact) => fact.kind === 'message').map(fact => {
|
|
799
768
|
const message = fact.message;
|
|
800
769
|
const task = this.requireTask(request.workspaceId, message.taskRef);
|
|
@@ -881,6 +850,8 @@ export class AgentTeamLedger {
|
|
|
881
850
|
// A read advances only the reader's private watermark; no projection
|
|
882
851
|
// visible to other participants changes, so nobody is woken.
|
|
883
852
|
return [];
|
|
853
|
+
default:
|
|
854
|
+
return assertUnhandledKind(operation);
|
|
884
855
|
}
|
|
885
856
|
}
|
|
886
857
|
/** Members whose Inbox projection may have changed; the Host notifies only live ones. */
|
|
@@ -923,8 +894,19 @@ export class AgentTeamLedger {
|
|
|
923
894
|
case 'team/channel-member-removed':
|
|
924
895
|
case 'team/member-removed':
|
|
925
896
|
return operation.data.activities.map(activity => activity.threadRef);
|
|
926
|
-
|
|
897
|
+
case 'team/initialized':
|
|
898
|
+
case 'team/channel-created':
|
|
899
|
+
case 'team/channel-updated':
|
|
900
|
+
case 'team/member-added':
|
|
901
|
+
case 'team/member-suspended':
|
|
902
|
+
case 'team/member-resumed':
|
|
903
|
+
case 'team/member-session-restarted':
|
|
904
|
+
case 'team/member-updated':
|
|
905
|
+
case 'team/channel-member-added':
|
|
906
|
+
case 'team/thread-read':
|
|
927
907
|
return [];
|
|
908
|
+
default:
|
|
909
|
+
return assertUnhandledKind(operation);
|
|
928
910
|
}
|
|
929
911
|
}
|
|
930
912
|
replay() {
|
|
@@ -1001,7 +983,7 @@ export class AgentTeamLedger {
|
|
|
1001
983
|
if (operation.kind === 'team/member-added') {
|
|
1002
984
|
assertHuman();
|
|
1003
985
|
const { member, channelRefs } = operation.data;
|
|
1004
|
-
if (projection.members.has(member.memberId) ||
|
|
986
|
+
if (projection.members.has(member.memberId) || new Set(channelRefs).size !== channelRefs.length)
|
|
1005
987
|
throw new Error('invalid Member creation');
|
|
1006
988
|
this.addRef(refs, member.memberId);
|
|
1007
989
|
for (const channelRef of channelRefs) {
|
|
@@ -1202,12 +1184,18 @@ export class AgentTeamLedger {
|
|
|
1202
1184
|
const expectedResolution = activity.kind === 'accept' ? 'accepted' : activity.kind === 'close' ? 'closed' : 'open';
|
|
1203
1185
|
const expectedStatus = expectedResolution === 'accepted' ? 'done' : expectedResolution === 'closed' ? 'closed'
|
|
1204
1186
|
: this.deriveTaskStatus(task.taskRef, operation.data.claims);
|
|
1187
|
+
const expectedCompletedClaimRefs = operation.data.claims
|
|
1188
|
+
.filter(claim => projection.claims.get(claim.claimRef)?.state === 'active' && claim.state === 'done')
|
|
1189
|
+
.map(claim => claim.claimRef);
|
|
1205
1190
|
if (task.resolution !== expectedResolution || task.status !== expectedStatus
|
|
1206
1191
|
|| (activity.kind !== 'close' && activity.releasedClaimRefs !== undefined)
|
|
1192
|
+
|| (activity.kind !== 'accept' && activity.completedClaimRefs !== undefined)
|
|
1207
1193
|
|| (activity.kind === 'close' && !this.sameList(activity.releasedClaimRefs ?? [], operation.data.claims
|
|
1208
1194
|
.filter(claim => projection.claims.get(claim.claimRef)?.state === 'active' && claim.state === 'released')
|
|
1209
|
-
.map(claim => claim.claimRef)))
|
|
1195
|
+
.map(claim => claim.claimRef)))
|
|
1196
|
+
|| !this.sameList(activity.completedClaimRefs ?? [], expectedCompletedClaimRefs)) {
|
|
1210
1197
|
throw new Error('invalid Task state transition');
|
|
1198
|
+
}
|
|
1211
1199
|
const priorClaims = [...projection.claims.values()].filter(claim => claim.taskRef === task.taskRef).sort((left, right) => left.claimRef.localeCompare(right.claimRef));
|
|
1212
1200
|
const operationClaims = [...operation.data.claims].sort((left, right) => left.claimRef.localeCompare(right.claimRef));
|
|
1213
1201
|
if (priorClaims.length !== operationClaims.length || priorClaims.some((claim, index) => claim.claimRef !== operationClaims[index]?.claimRef))
|
|
@@ -1218,7 +1206,9 @@ export class AgentTeamLedger {
|
|
|
1218
1206
|
throw new Error('invalid Task Claim identity');
|
|
1219
1207
|
const legal = activity.kind === 'close'
|
|
1220
1208
|
? (previousClaim.state === 'active' ? claim.state === 'released' : claim.state === previousClaim.state)
|
|
1221
|
-
:
|
|
1209
|
+
: activity.kind === 'accept'
|
|
1210
|
+
? (previousClaim.state === 'active' ? claim.state === 'done' : claim.state === previousClaim.state)
|
|
1211
|
+
: claim.state === previousClaim.state;
|
|
1222
1212
|
if (!legal)
|
|
1223
1213
|
throw new Error('invalid Task Claim transition');
|
|
1224
1214
|
}
|
|
@@ -1227,12 +1217,13 @@ export class AgentTeamLedger {
|
|
|
1227
1217
|
? this.closeThreadInboxFrom(projection, activity, thread.threadRef)
|
|
1228
1218
|
: activity.kind === 'reopen'
|
|
1229
1219
|
? this.reopenThreadInboxFrom(projection, activity, thread.threadRef)
|
|
1230
|
-
: this.
|
|
1220
|
+
: this.acceptThreadInboxFrom(projection, activity, thread.threadRef, expectedCompletedClaimRefs);
|
|
1231
1221
|
if (!isDeepStrictEqual(operation.data.inbox, expectedInbox))
|
|
1232
1222
|
throw new Error('invalid Task inbox projection');
|
|
1233
1223
|
this.validateInboxDelta(operation.data.inbox, projection, refs, [], [], [activity]);
|
|
1234
1224
|
return;
|
|
1235
1225
|
}
|
|
1226
|
+
assertUnhandledKind(operation);
|
|
1236
1227
|
}
|
|
1237
1228
|
validateInboxDelta(delta, projection, _refs, additionalThreadRefs = [], additionalMessages = [], additionalActivities = []) {
|
|
1238
1229
|
const attentionKeys = new Set();
|
|
@@ -1482,7 +1473,9 @@ export class AgentTeamLedger {
|
|
|
1482
1473
|
}
|
|
1483
1474
|
if (operation.kind === 'team/thread-attention-changed' || operation.kind === 'team/thread-read') {
|
|
1484
1475
|
this.applyInboxDelta(target, operation.data.inbox);
|
|
1476
|
+
return;
|
|
1485
1477
|
}
|
|
1478
|
+
assertUnhandledKind(operation);
|
|
1486
1479
|
}
|
|
1487
1480
|
/** Facts arrive in ledger sequence order, so global and per-thread lists stay sorted by append only. */
|
|
1488
1481
|
appendMessageFact(target, message, mentions) {
|
|
@@ -1648,6 +1641,25 @@ export class AgentTeamLedger {
|
|
|
1648
1641
|
activityRef: activity.activityRef, sequence: activity.sequence }));
|
|
1649
1642
|
return this.inboxDelta([], removed, [], [...projection.directMarkers.values()].filter(marker => marker.threadRef === threadRef), activityMarkers, [...projection.activityMarkers.values()].filter(marker => marker.threadRef === threadRef));
|
|
1650
1643
|
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Early acceptance completes open Claims inside the accept operation; their
|
|
1646
|
+
* owners learn about it through activity markers regardless of whether they
|
|
1647
|
+
* still follow the Thread. Plain accepts (no completed Claims) stay silent.
|
|
1648
|
+
*/
|
|
1649
|
+
acceptThreadInbox(activity, threadRef, completedOwners) {
|
|
1650
|
+
if (completedOwners.length === 0)
|
|
1651
|
+
return this.inboxDelta();
|
|
1652
|
+
const recipients = [...new Set(completedOwners)].filter(memberId => memberId !== activity.actor);
|
|
1653
|
+
const markers = recipients.map(memberId => Object.freeze({ memberId, threadRef,
|
|
1654
|
+
activityRef: activity.activityRef, sequence: activity.sequence }));
|
|
1655
|
+
return this.inboxDelta([], [], [], [], markers);
|
|
1656
|
+
}
|
|
1657
|
+
acceptThreadInboxFrom(projection, activity, threadRef, completedClaimRefs) {
|
|
1658
|
+
if (completedClaimRefs.length === 0)
|
|
1659
|
+
return this.inboxDelta();
|
|
1660
|
+
const owners = completedClaimRefs.map(claimRef => projection.claims.get(claimRef)?.owner).filter(owner => owner !== undefined);
|
|
1661
|
+
return this.acceptThreadInbox(activity, threadRef, owners);
|
|
1662
|
+
}
|
|
1651
1663
|
reopenThreadInbox(activity, threadRef) {
|
|
1652
1664
|
return this.reopenThreadInboxFrom(this.state, activity, threadRef);
|
|
1653
1665
|
}
|
|
@@ -1787,6 +1799,29 @@ export class AgentTeamLedger {
|
|
|
1787
1799
|
return Object.freeze({ ...task, status: this.deriveResolvedTaskStatus(task, projected.values()) });
|
|
1788
1800
|
}));
|
|
1789
1801
|
}
|
|
1802
|
+
/** Resolve one Task's Thread for a workspace-authorized actor; Member actors must belong to the Task's Channel. */
|
|
1803
|
+
threadForActor(actor, workspaceId, taskRef) {
|
|
1804
|
+
const task = this.requireTask(workspaceId, taskRef);
|
|
1805
|
+
const thread = this.requireThread(task.threadRef);
|
|
1806
|
+
if (actor.kind === 'member')
|
|
1807
|
+
this.requireMemberChannel(this.requireMember(actor.memberId), task.channelRef);
|
|
1808
|
+
return { task, thread };
|
|
1809
|
+
}
|
|
1810
|
+
/** Business outcomes that defer a thread write: unread work first, then a stale revision. */
|
|
1811
|
+
deferredThreadWrite(actorMemberId, task, thread, baseRevision) {
|
|
1812
|
+
const unread = this.unreadFor(actorMemberId, thread.threadRef);
|
|
1813
|
+
if (unread.length > 0)
|
|
1814
|
+
return this.unreadRequired(task, thread, unread);
|
|
1815
|
+
if (baseRevision !== thread.revision)
|
|
1816
|
+
return this.staleRevision(task, thread, baseRevision);
|
|
1817
|
+
return undefined;
|
|
1818
|
+
}
|
|
1819
|
+
/** Commit projections shared by the Message-sent and Thread-replied results. */
|
|
1820
|
+
committedMessageResult(operation) {
|
|
1821
|
+
return { kind: 'committed', receipt: this.receipt(operation), message: operation.data.message,
|
|
1822
|
+
task: operation.data.task, thread: operation.data.thread, attention: operation.data.inbox.attention.set,
|
|
1823
|
+
directMarkers: operation.data.inbox.directMarkers.added };
|
|
1824
|
+
}
|
|
1790
1825
|
threadAnchor(task) {
|
|
1791
1826
|
return this.threadAnchorFrom(this.state, task);
|
|
1792
1827
|
}
|
|
@@ -1799,16 +1834,21 @@ export class AgentTeamLedger {
|
|
|
1799
1834
|
threadFactKey(fact) {
|
|
1800
1835
|
return fact.kind === 'message' ? `message:${fact.message.messageRef}` : `activity:${fact.activity.activityRef}`;
|
|
1801
1836
|
}
|
|
1802
|
-
|
|
1837
|
+
/**
|
|
1838
|
+
* Display numbers for Tasks: one counter per home Channel, in creation
|
|
1839
|
+
* order. This is the single numbering authority — Channel cards, Thread
|
|
1840
|
+
* headings, cross-channel ref resolution, and inbox renders all show the
|
|
1841
|
+
* ordinal the Task holds inside its own Channel.
|
|
1842
|
+
*/
|
|
1843
|
+
taskNumbers(workspaceId) {
|
|
1803
1844
|
const numbers = new Map();
|
|
1804
|
-
|
|
1845
|
+
const next = new Map();
|
|
1805
1846
|
for (const message of this.state.topLevelMessages) {
|
|
1806
1847
|
if (this.state.channels.get(message.channelRef)?.workspaceId !== workspaceId)
|
|
1807
1848
|
continue;
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
numbers.set(message.taskRef,
|
|
1811
|
-
next += 1;
|
|
1849
|
+
const ordinal = (next.get(message.channelRef) ?? 0) + 1;
|
|
1850
|
+
next.set(message.channelRef, ordinal);
|
|
1851
|
+
numbers.set(message.taskRef, ordinal);
|
|
1812
1852
|
}
|
|
1813
1853
|
return numbers;
|
|
1814
1854
|
}
|
|
@@ -1987,11 +2027,6 @@ export class AgentTeamLedger {
|
|
|
1987
2027
|
|| !this.sameList(operation.data.channelRefs, this.normalizeUnique(request.channelRefs, 'initial Member Channels')))
|
|
1988
2028
|
this.throwRequestCollision(request.requestId);
|
|
1989
2029
|
}
|
|
1990
|
-
assertSameMemberRestart(operation, request) {
|
|
1991
|
-
if (operation.kind !== 'team/member-session-restarted' || !this.sameActor(operation.actor, request.actor)
|
|
1992
|
-
|| operation.data.member.memberId !== request.memberId)
|
|
1993
|
-
this.throwRequestCollision(request.requestId);
|
|
1994
|
-
}
|
|
1995
2030
|
assertSameMemberState(operation, request, state) {
|
|
1996
2031
|
const kind = state === 'suspended' ? 'team/member-suspended' : 'team/member-resumed';
|
|
1997
2032
|
if (operation.kind !== kind || !this.sameActor(operation.actor, request.actor) || operation.data.member.memberId !== request.memberId)
|
|
@@ -2129,14 +2164,10 @@ export class AgentTeamLedger {
|
|
|
2129
2164
|
releasedClaims: operation.data.claims, removedAttention: operation.data.inbox.attention.removed });
|
|
2130
2165
|
}
|
|
2131
2166
|
messageResult(operation) {
|
|
2132
|
-
return Object.freeze(
|
|
2133
|
-
task: operation.data.task, thread: operation.data.thread, attention: operation.data.inbox.attention.set,
|
|
2134
|
-
directMarkers: operation.data.inbox.directMarkers.added });
|
|
2167
|
+
return Object.freeze(this.committedMessageResult(operation));
|
|
2135
2168
|
}
|
|
2136
2169
|
replyResult(operation) {
|
|
2137
|
-
return Object.freeze(
|
|
2138
|
-
task: operation.data.task, thread: operation.data.thread, attention: operation.data.inbox.attention.set,
|
|
2139
|
-
directMarkers: operation.data.inbox.directMarkers.added });
|
|
2170
|
+
return Object.freeze(this.committedMessageResult(operation));
|
|
2140
2171
|
}
|
|
2141
2172
|
claimResult(operation) {
|
|
2142
2173
|
const attention = operation.data.inbox.attention.set.find(candidate => candidate.memberId === operation.data.claim.owner);
|
|
@@ -8,59 +8,55 @@ export function classifyRecoverableError(message) {
|
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
10
10
|
export const RECOVERY_DELAY_MS = 120_000;
|
|
11
|
-
export const
|
|
11
|
+
export const RECOVERY_MAX_CONSECUTIVE_ERRORS = 3;
|
|
12
12
|
/**
|
|
13
|
-
* Per-member automatic recovery episodes.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the coordinator stands down and leaves the Member in error for the operator.
|
|
13
|
+
* Per-member automatic recovery episodes. An episode is every recoverable
|
|
14
|
+
* `agent/error` occurrence until a clean turn end, regardless of error text
|
|
15
|
+
* or family. Each of the first two occurrences schedules its own wakeup.
|
|
17
16
|
*/
|
|
18
17
|
export class RecoveryCoordinator {
|
|
19
18
|
episodes = new Map();
|
|
20
|
-
|
|
19
|
+
wake;
|
|
21
20
|
onStandDown;
|
|
22
21
|
delayMs;
|
|
23
|
-
|
|
22
|
+
maxConsecutiveErrors;
|
|
24
23
|
constructor(options) {
|
|
25
|
-
this.
|
|
24
|
+
this.wake = options.wake;
|
|
26
25
|
this.onStandDown = options.onStandDown;
|
|
27
26
|
this.delayMs = options.delayMs ?? RECOVERY_DELAY_MS;
|
|
28
|
-
this.
|
|
27
|
+
this.maxConsecutiveErrors = options.maxConsecutiveErrors ?? RECOVERY_MAX_CONSECUTIVE_ERRORS;
|
|
29
28
|
}
|
|
30
|
-
/** Observe one error occurrence for a Member. */
|
|
29
|
+
/** Observe one `agent/error` occurrence for a Member. */
|
|
31
30
|
onError(memberId, errorMessage) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// A non-retryable failure cancels anything pending: retrying cannot help.
|
|
31
|
+
if (classifyRecoverableError(errorMessage) === undefined) {
|
|
32
|
+
// A non-recoverable failure cancels anything pending: retrying cannot help.
|
|
35
33
|
this.stopTracking(memberId);
|
|
36
34
|
return;
|
|
37
35
|
}
|
|
38
36
|
let episode = this.episodes.get(memberId);
|
|
39
|
-
if (episode === undefined
|
|
40
|
-
|
|
41
|
-
episode = { kind, lastError: errorMessage, attempts: 0 };
|
|
37
|
+
if (episode === undefined) {
|
|
38
|
+
episode = { consecutiveFailures: 0, timers: new Set() };
|
|
42
39
|
this.episodes.set(memberId, episode);
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
// The same error survived every allowed injection — stand down once.
|
|
46
|
-
if (episode.stoodDown !== true) {
|
|
47
|
-
episode.stoodDown = true;
|
|
48
|
-
this.onStandDown?.(memberId, episode.attempts);
|
|
49
|
-
}
|
|
41
|
+
if (episode.stoodDown === true)
|
|
50
42
|
return;
|
|
51
|
-
|
|
52
|
-
if (episode.
|
|
43
|
+
episode.consecutiveFailures += 1;
|
|
44
|
+
if (episode.consecutiveFailures >= this.maxConsecutiveErrors) {
|
|
45
|
+
this.cancelTimers(episode);
|
|
46
|
+
episode.stoodDown = true;
|
|
47
|
+
this.onStandDown?.(memberId, episode.consecutiveFailures);
|
|
53
48
|
return;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
episode.
|
|
49
|
+
}
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
episode.timers.delete(timer);
|
|
57
52
|
try {
|
|
58
|
-
this.
|
|
53
|
+
this.wake(memberId);
|
|
59
54
|
}
|
|
60
55
|
catch {
|
|
61
56
|
this.stopTracking(memberId);
|
|
62
57
|
}
|
|
63
58
|
}, this.delayMs);
|
|
59
|
+
episode.timers.add(timer);
|
|
64
60
|
}
|
|
65
61
|
/** A turn ended cleanly (running→idle without an error): the episode is over. */
|
|
66
62
|
onCleanTurnEnd(memberId) {
|
|
@@ -70,18 +66,17 @@ export class RecoveryCoordinator {
|
|
|
70
66
|
const episode = this.episodes.get(memberId);
|
|
71
67
|
if (episode === undefined)
|
|
72
68
|
return;
|
|
73
|
-
this.
|
|
69
|
+
this.cancelTimers(episode);
|
|
74
70
|
this.episodes.delete(memberId);
|
|
75
71
|
}
|
|
76
72
|
dispose() {
|
|
77
73
|
for (const episode of this.episodes.values())
|
|
78
|
-
this.
|
|
74
|
+
this.cancelTimers(episode);
|
|
79
75
|
this.episodes.clear();
|
|
80
76
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
episode.timer = undefined;
|
|
77
|
+
cancelTimers(episode) {
|
|
78
|
+
for (const timer of episode.timers)
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
episode.timers.clear();
|
|
86
81
|
}
|
|
87
82
|
}
|
|
@@ -26,13 +26,18 @@ const operationBase = {
|
|
|
26
26
|
const modelSelectionSchema = z.object({
|
|
27
27
|
provider: z.string().min(1),
|
|
28
28
|
model: z.string().min(1),
|
|
29
|
-
|
|
29
|
+
reasoningEffort: z.string().min(1).transform(value => value).optional(),
|
|
30
|
+
}).strict().transform(({ provider, model, reasoningEffort }) => ({
|
|
31
|
+
provider,
|
|
32
|
+
model,
|
|
33
|
+
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
34
|
+
}));
|
|
30
35
|
const memberSchema = z.object({
|
|
31
36
|
memberId: memberIdSchema,
|
|
32
37
|
sessionId: sessionIdSchema,
|
|
33
38
|
workspaceId: workspaceIdSchema,
|
|
34
39
|
handle: z.string().min(1),
|
|
35
|
-
description: z.string()
|
|
40
|
+
description: z.string(),
|
|
36
41
|
presetId: z.string().min(1),
|
|
37
42
|
// Ledgers written before per-Member model selection existed omit the field.
|
|
38
43
|
model: modelSelectionSchema.optional(),
|
|
@@ -43,7 +48,7 @@ const channelSchema = z.object({
|
|
|
43
48
|
channelRef: channelRefSchema,
|
|
44
49
|
workspaceId: workspaceIdSchema,
|
|
45
50
|
name: z.string().min(1),
|
|
46
|
-
description: z.string()
|
|
51
|
+
description: z.string(),
|
|
47
52
|
createdAtSequence: z.number().int().positive(),
|
|
48
53
|
}).strict();
|
|
49
54
|
// Ledgers written before message occurredAt or attachments existed store bare
|
|
@@ -155,6 +160,7 @@ const taskActivitySchema = z.object({
|
|
|
155
160
|
...activityBase,
|
|
156
161
|
kind: z.union([z.literal('accept'), z.literal('close'), z.literal('reopen')]),
|
|
157
162
|
releasedClaimRefs: z.array(claimRefSchema).min(1).optional(),
|
|
163
|
+
completedClaimRefs: z.array(claimRefSchema).min(1).optional(),
|
|
158
164
|
}).strict();
|
|
159
165
|
const claimsReleasedActivitySchema = z.object({
|
|
160
166
|
...activityBase,
|
|
@@ -208,7 +214,7 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
|
|
|
208
214
|
...operationBase,
|
|
209
215
|
previousOperationId: operationIdSchema.nullable(),
|
|
210
216
|
kind: z.literal('team/member-added'),
|
|
211
|
-
data: z.object({ member: memberSchema, channelRefs: z.array(channelRefSchema)
|
|
217
|
+
data: z.object({ member: memberSchema, channelRefs: z.array(channelRefSchema) }).strict(),
|
|
212
218
|
}).strict(),
|
|
213
219
|
z.object({
|
|
214
220
|
...operationBase,
|