@taskforcehq/taskforce 0.3.330 → 0.3.333

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +7 -2
  2. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
  3. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
  4. package/dist/components/features/planning/PlanningModule.js +15 -3
  5. package/dist/components/features/planning/planningMetadata.d.ts +10 -0
  6. package/dist/components/features/planning/planningMetadata.js +8 -0
  7. package/dist/components/views/StandaloneLayout.js +35 -1
  8. package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
  9. package/dist/components/views/panels/PlanningDrawer.js +12 -8
  10. package/dist/config/envSchema.js +7 -0
  11. package/dist/core/McpTokenService.d.ts +2 -0
  12. package/dist/core/McpTokenService.js +13 -5
  13. package/dist/core/Taskforce.d.ts +11 -3
  14. package/dist/core/Taskforce.js +33 -3
  15. package/dist/core/types.d.ts +1 -0
  16. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
  17. package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
  18. package/dist/mcp/httpProtocolRouting.d.ts +1 -0
  19. package/dist/mcp/httpProtocolRouting.js +8 -5
  20. package/dist/mcp/runtime.js +24 -5
  21. package/dist/mcp/taskPlanningRegistrar.js +2 -2
  22. package/dist/mcp/toolProfiles.js +1 -0
  23. package/dist/mcp/toolRegistry.js +8 -8
  24. package/dist/server/buildInfo.d.ts +1 -0
  25. package/dist/server/buildInfo.js +5 -1
  26. package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
  27. package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
  28. package/dist/server/localWorkspaceSyncServerRuntime.js +13 -5
  29. package/dist/server/routes/admin.js +14 -2
  30. package/dist/server/routes/localService.js +1 -0
  31. package/dist/server/routes/shared.d.ts +3 -0
  32. package/dist/server/routes/shared.js +280 -240
  33. package/dist/server/routes/syncPushRoutes.d.ts +3 -0
  34. package/dist/server/routes/syncPushRoutes.js +8 -2
  35. package/dist/service/localServiceCli.d.ts +2 -2
  36. package/dist/service/localServiceCli.js +4 -3
  37. package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
  38. package/dist/services/executorPhaseAIssuerCore.js +107 -0
  39. package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
  40. package/dist/services/taskforceAgentMcpBridge.js +35 -5
  41. package/dist/shared/executorPhaseASchemaValidation.js +1 -1
  42. package/dist/storage/postgresAdapter.js +3 -1
  43. package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
  44. package/dist/storage/postgresRequestDiagnostics.js +15 -2
  45. package/dist/storage/postgresWorker.js +13 -3
  46. package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
  47. package/dist/storage/workflowStore.d.ts +2 -0
  48. package/dist/storage/workflowStore.js +39 -2
  49. package/dist/sync/cloudSyncApi.js +4 -2
  50. package/dist/sync/collaborationSyncState.js +5 -0
  51. package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
  52. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
  53. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +1 -0
  54. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +7 -6
  55. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +2 -2
  56. package/dist/sync/engine/workspaceSyncEngineLocalApply.d.ts +1 -0
  57. package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +6 -0
  58. package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +34 -1
  59. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +2 -2
  60. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +4 -1
  61. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
  62. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +55 -10
  63. package/dist/sync/syncApplyHandlers.d.ts +1 -0
  64. package/dist/sync/syncApplyHandlers.js +72 -15
  65. package/dist/sync/syncService.d.ts +1 -0
  66. package/dist/sync/syncService.js +5 -2
  67. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
  68. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
  69. package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
  70. package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
  71. package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
  72. package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
  73. package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
  74. package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
  75. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
  76. package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
  77. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
  78. package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
  79. package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
  80. package/dist/sync/v3/syncDurabilityStore.js +34 -6
  81. package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
  82. package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
  83. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
  84. package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
  85. package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
  86. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
  87. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
  88. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
  89. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
  90. package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-Cfvb9Nuv.js} +1 -1
  91. package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-0oR70eFG.js} +1 -1
  92. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-C33iH_r_.js} +1 -1
  93. package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-BAqxUtke.js} +1 -1
  94. package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-BudIbOR9.js} +2 -2
  95. package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-A8dFQyId.js} +1 -1
  96. package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-g7vtdsx1.js} +1 -1
  97. package/dist/ui/assets/PlanningModule-D-DeQs4F.js +1 -0
  98. package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-BWmfGi0Z.js} +1 -1
  99. package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-BA_lQmS9.js} +1 -1
  100. package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-DAlH4YIP.js} +1 -1
  101. package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-DmeCf6pJ.js} +2 -2
  102. package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-p08eDOaa.js} +1 -1
  103. package/dist/ui/assets/index-GHJgcW3j.js +7 -0
  104. package/dist/ui/index.html +1 -1
  105. package/package.json +3 -1
  106. package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
  107. package/dist/ui/assets/index-CTsDBaef.js +0 -7
@@ -2,7 +2,6 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { applyLocalServiceCloudTarget, localServiceCloudTargetsEqual, parseLocalServiceCloudTarget, resolveLocalServiceCloudTarget, } from '../config/localServiceCloudTarget.js';
4
4
  import { localServiceRuntimeContractsEqual, parseLocalServiceRuntimeContract, resolveLocalServiceRuntimeContract, } from '../config/localServiceRuntimeContract.js';
5
- import { createStandaloneServer } from '../server/index.js';
6
5
  import { inspectLocalServiceDescriptor, redactLocalServiceDescriptor, } from '../server/localServiceLease.js';
7
6
  import { acquireLocalServiceOwnershipForRuntime, } from '../server/localServiceOwnershipBootstrap.js';
8
7
  import { discoverOrStartLocalService, LOCAL_SERVICE_API_BASE_PATH, LOCAL_SERVICE_PROTOCOL_VERSION, resolveLocalServiceLeaseInput, spawnDetachedLocalService, waitForLocalServiceDescriptor, } from './localServiceClientLifecycle.js';
@@ -126,7 +125,9 @@ async function runForeground(command, options) {
126
125
  });
127
126
  };
128
127
  try {
129
- server = options.createServer({
128
+ const createServer = options.createServer
129
+ || (await import('../server/index.js')).createStandaloneServer;
130
+ server = createServer({
130
131
  projectRoot: command.projectRoot,
131
132
  port: 0,
132
133
  localSyncProcessLock: ownership.syncProcessLock || undefined,
@@ -370,7 +371,7 @@ export async function runLocalServiceCli(argv, input) {
370
371
  executablePath: input.executablePath,
371
372
  cliEntryPath: input.cliEntryPath,
372
373
  execArgv: input.execArgv || [],
373
- createServer: input.createServer || createStandaloneServer,
374
+ createServer: input.createServer,
374
375
  acquireOwnership: input.acquireOwnership || acquireLocalServiceOwnershipForRuntime,
375
376
  inspectDescriptor: input.inspectDescriptor || inspectLocalServiceDescriptor,
376
377
  spawnDetached: input.spawnDetached || spawnDetachedLocalService,
@@ -17,6 +17,17 @@ export interface ExecutorPhaseAOidcKey {
17
17
  lastIssuedAt: string | null;
18
18
  publishUntil: string | null;
19
19
  }
20
+ export interface ExecutorPhaseAOidcVerificationKey {
21
+ keyId: string;
22
+ publicKey: KeyObject;
23
+ status: 'active' | 'retiring' | 'disabled';
24
+ validFrom: string;
25
+ validUntil: string;
26
+ }
27
+ declare const verifiedWorkloadJwtClaimsBrand: unique symbol;
28
+ export type VerifiedExecutorPhaseAWorkloadJwtClaims = ExecutorPhaseAWorkloadJwtClaims & {
29
+ readonly [verifiedWorkloadJwtClaimsBrand]: true;
30
+ };
20
31
  export interface ExecutorPhaseAWorkloadJwtClaims {
21
32
  iss: string;
22
33
  aud: string;
@@ -103,6 +114,8 @@ export declare function buildExecutorPhaseAOidcDiscovery(issuer: string): {
103
114
  };
104
115
  export declare function buildExecutorPhaseAWorkloadJwtClaims(bundle: VerifiedExecutorPhaseAWorkloadGrantBundle, issuer: string, audience: string, tokenId: string, now: Date): ExecutorPhaseAWorkloadJwtClaims;
105
116
  export declare function signExecutorPhaseAWorkloadJwt(claims: ExecutorPhaseAWorkloadJwtClaims, signer: ExecutorPhaseARs256Signer): Promise<string>;
117
+ export declare function verifyExecutorPhaseAWorkloadJwt(jwt: string, bundle: VerifiedExecutorPhaseAWorkloadGrantBundle, keys: readonly ExecutorPhaseAOidcVerificationKey[], now?: Date): VerifiedExecutorPhaseAWorkloadJwtClaims;
118
+ export declare function assertVerifiedExecutorPhaseAWorkloadJwtClaims(claims: VerifiedExecutorPhaseAWorkloadJwtClaims): void;
106
119
  export declare class ExecutorPhaseAIssuerCore {
107
120
  private readonly options;
108
121
  constructor(options: {
@@ -14,6 +14,8 @@ const TRANSITIVE_TAG_KEYS = [
14
14
  'TaskforceWorkspaceHash',
15
15
  'TaskforceSource',
16
16
  ];
17
+ const verifiedWorkloadJwtClaimsBrand = Symbol('verifiedExecutorPhaseAWorkloadJwtClaims');
18
+ const verifiedWorkloadJwtClaims = new WeakSet();
17
19
  function timestamp(value, field) {
18
20
  const parsed = Date.parse(value);
19
21
  if (!Number.isFinite(parsed))
@@ -155,6 +157,111 @@ export async function signExecutorPhaseAWorkloadJwt(claims, signer) {
155
157
  throw new TypeError('The OIDC signer returned a non-RSA-2048 signature.');
156
158
  return `${signingInput}.${signature.toString('base64url')}`;
157
159
  }
160
+ function decodeCanonicalJwtSegment(segment, field) {
161
+ if (!/^[A-Za-z0-9_-]+$/.test(segment))
162
+ throw new TypeError(`The workload JWT ${field} is not base64url.`);
163
+ const bytes = Buffer.from(segment, 'base64url');
164
+ if (bytes.toString('base64url') !== segment)
165
+ throw new TypeError(`The workload JWT ${field} is not canonical base64url.`);
166
+ let value;
167
+ try {
168
+ value = JSON.parse(bytes.toString('utf8'));
169
+ }
170
+ catch {
171
+ throw new TypeError(`The workload JWT ${field} is not valid JSON.`);
172
+ }
173
+ if (canonicalizeExecutorPhaseAJson(value).toString('base64url') !== segment) {
174
+ throw new TypeError(`The workload JWT ${field} is not canonical JSON.`);
175
+ }
176
+ return value;
177
+ }
178
+ export function verifyExecutorPhaseAWorkloadJwt(jwt, bundle, keys, now = new Date()) {
179
+ if (jwt.length > 16_384)
180
+ throw new TypeError('The workload JWT exceeds the maximum size.');
181
+ const segments = jwt.split('.');
182
+ if (segments.length !== 3)
183
+ throw new TypeError('The workload JWT must contain exactly three segments.');
184
+ const [headerSegment, payloadSegment, signatureSegment] = segments;
185
+ const header = decodeCanonicalJwtSegment(headerSegment, 'header');
186
+ if (!header || typeof header !== 'object' || Array.isArray(header)
187
+ || Object.keys(header).sort().join(',') !== 'alg,kid,typ'
188
+ || header.alg !== 'RS256'
189
+ || header.typ !== 'JWT'
190
+ || typeof header.kid !== 'string') {
191
+ throw new TypeError('The workload JWT header is invalid.');
192
+ }
193
+ const key = keys.find((candidate) => candidate.keyId === header.kid);
194
+ const signature = Buffer.from(signatureSegment, 'base64url');
195
+ const nowMs = now.getTime();
196
+ if (new Set(keys.map((candidate) => candidate.keyId)).size !== keys.length
197
+ || !key || (key.status !== 'active' && key.status !== 'retiring')
198
+ || nowMs < timestamp(key.validFrom, 'key.validFrom')
199
+ || nowMs >= timestamp(key.validUntil, 'key.validUntil') || key.publicKey.asymmetricKeyType !== 'rsa'
200
+ || key.publicKey.asymmetricKeyDetails?.modulusLength !== 2048
201
+ || !/^[A-Za-z0-9_-]+$/.test(signatureSegment)
202
+ || signature.length !== 256 || signature.toString('base64url') !== signatureSegment
203
+ || !verify('RSA-SHA256', Buffer.from(`${headerSegment}.${payloadSegment}`, 'ascii'), key.publicKey, signature)) {
204
+ throw new TypeError('The workload JWT signature or signing key is invalid.');
205
+ }
206
+ const claims = decodeCanonicalJwtSegment(payloadSegment, 'payload');
207
+ assertExecutorPhaseASchema('workloadJwtClaims', claims);
208
+ const capability = bundle.capability.payload;
209
+ const grant = bundle.grant.payload;
210
+ const nowSeconds = Math.floor(now.getTime() / 1000);
211
+ const skewSeconds = EXECUTOR_PHASE_A_CROSS_SERVICE_CLOCK_SKEW_MS / 1000;
212
+ const grantIssuedAt = Math.floor(timestamp(grant.issuedAt, 'grant.issuedAt') / 1000);
213
+ const grantExpiresAt = Math.floor(timestamp(grant.expiresAt, 'grant.expiresAt') / 1000);
214
+ const phaseDeadline = Math.floor(timestamp(grant.phase === 'cleanup' ? capability.cleanupDeadline : capability.mutationDeadline, 'phaseDeadline') / 1000);
215
+ const runDigest = createHash('sha256').update(capability.runId, 'utf8').digest('base64url');
216
+ const tags = claims['https://aws.amazon.com/tags'];
217
+ const expectedTags = grant.sessionTags;
218
+ if (claims.iss !== capability.oidcIssuer || claims.aud !== capability.oidcAudience
219
+ || claims.sub !== `tfr:${runDigest}:${grant.phase}`
220
+ || claims['https://aws.amazon.com/source_identity'] !== executorPhaseASourceIdentity(capability.runId, grant.phase)
221
+ || claims.iat < grantIssuedAt - skewSeconds || claims.iat > nowSeconds + skewSeconds
222
+ || claims.nbf !== claims.iat
223
+ || nowSeconds >= claims.exp + skewSeconds || claims.exp <= claims.iat || claims.exp - claims.iat > JWT_TTL_SECONDS
224
+ || claims.exp > grantExpiresAt || claims.exp > phaseDeadline
225
+ || tags.principal_tags.TaskforceRun[0] !== expectedTags.TaskforceRun
226
+ || tags.principal_tags.TaskforceReceipt[0] !== expectedTags.TaskforceReceipt
227
+ || tags.principal_tags.TaskforcePhase[0] !== expectedTags.TaskforcePhase
228
+ || tags.principal_tags.TaskforceWorkspaceHash[0] !== expectedTags.TaskforceWorkspaceHash
229
+ || tags.principal_tags.TaskforceSource[0] !== expectedTags.TaskforceSource
230
+ || claims.taskforce.receiptId !== capability.receiptId
231
+ || claims.taskforce.receiptSha256 !== capability.receiptSha256
232
+ || claims.taskforce.workspaceId !== capability.workspaceId
233
+ || claims.taskforce.taskId !== capability.taskId
234
+ || claims.taskforce.runId !== capability.runId
235
+ || claims.taskforce.sourceCommit !== capability.sourceCommit
236
+ || claims.taskforce.accountId !== capability.accountId
237
+ || claims.taskforce.region !== capability.region
238
+ || claims.taskforce.availabilityZone !== capability.availabilityZone
239
+ || claims.taskforce.logicalEnvelopeSha256 !== capability.logicalEnvelopeSha256
240
+ || claims.taskforce.budgetsSha256 !== capability.budgetsSha256
241
+ || claims.taskforce.roleArn !== grant.roleArn
242
+ || claims.taskforce.brokerKeyFingerprint !== capability.brokerKeyFingerprint
243
+ || claims.taskforce.grantId !== grant.grantId
244
+ || claims.taskforce.grantSha256 !== bundle.grantSha256
245
+ || claims.taskforce.senderProofSha256 !== bundle.senderProofSha256) {
246
+ throw new TypeError('The workload JWT claims differ from the verified grant.');
247
+ }
248
+ const verified = structuredClone(claims);
249
+ const freeze = (value) => {
250
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
251
+ return;
252
+ for (const child of Object.values(value))
253
+ freeze(child);
254
+ Object.freeze(value);
255
+ };
256
+ freeze(verified);
257
+ verifiedWorkloadJwtClaims.add(verified);
258
+ return verified;
259
+ }
260
+ export function assertVerifiedExecutorPhaseAWorkloadJwtClaims(claims) {
261
+ if (!claims || typeof claims !== 'object' || !verifiedWorkloadJwtClaims.has(claims)) {
262
+ throw new TypeError('The broker requires runtime-verified workload JWT claims.');
263
+ }
264
+ }
158
265
  function verifyRedemptionResponse(response, request, cloudKeys, now) {
159
266
  assertExecutorPhaseASchema('issuerRedemptionResponse', response);
160
267
  const trustedKey = cloudKeys.find((key) => key.keyId === response.envelope.keyId && key.status !== 'disabled');
@@ -59,7 +59,7 @@ export declare function taskforceAgentMcpRequestId(call: TaskforceAgentMcpToolCa
59
59
  declare const MANAGED_AGENT_CAPABILITY_TOOLS: Readonly<{
60
60
  readonly tasks: readonly ["add_comment", "add_task_checklist_item", "complete_workflow_step", "get_schedule_board", "get_stats", "get_task", "get_task_attachments", "get_task_checklist", "get_task_workflow", "list_task_relationships", "list_tasks", "replace_task_checklist", "search_tasks", "update_task_checklist_item"];
61
61
  readonly planning: readonly ["add_initiative_comment", "add_workstream_comment", "get_initiative", "get_workstream", "list_initiatives", "list_workstreams"];
62
- readonly documents: readonly ["get_document", "get_document_reviews", "search_documents"];
62
+ readonly documents: readonly ["get_document", "get_document_reviews", "review_document", "search_documents"];
63
63
  readonly images: readonly ["get_image", "get_image_annotations", "search_images"];
64
64
  readonly workspace: readonly ["get_config", "get_current_profile", "list_assignees"];
65
65
  }>;
@@ -75,6 +75,7 @@ const MANAGED_AGENT_CAPABILITY_TOOLS = Object.freeze({
75
75
  documents: [
76
76
  'get_document',
77
77
  'get_document_reviews',
78
+ 'review_document',
78
79
  'search_documents',
79
80
  ],
80
81
  images: [
@@ -187,21 +188,50 @@ function buildToolInstructions(baseSystem, tools, allowedMutationTaskId, readOnl
187
188
  const toolLines = tools.map((tool) => (`- ${tool.name}: ${compactText(tool.description, 180)} Args: ${describeToolSchema(tool.inputSchema)}`));
188
189
  const taskScope = String(allowedMutationTaskId || '').trim();
189
190
  const canCompleteWorkflow = tools.some((tool) => tool.name === 'complete_workflow_step');
191
+ const canReviewDocuments = tools.some((tool) => tool.name === 'review_document');
192
+ const canAddComments = tools.some((tool) => [
193
+ 'add_comment',
194
+ 'add_initiative_comment',
195
+ 'add_workstream_comment',
196
+ ].includes(tool.name));
197
+ const canChangeChecklists = tools.some((tool) => [
198
+ 'add_task_checklist_item',
199
+ 'replace_task_checklist',
200
+ 'update_task_checklist_item',
201
+ ].includes(tool.name));
202
+ const allowedWrites = [
203
+ ...(canAddComments ? ['comments'] : []),
204
+ ...(canChangeChecklists ? ['checklist changes'] : []),
205
+ ...(canReviewDocuments ? ['document review feedback'] : []),
206
+ ...(canCompleteWorkflow ? ['completion of the current owned workflow step'] : []),
207
+ ];
208
+ const allowedWritesSentence = allowedWrites.length > 0
209
+ ? `Allowed writes: ${allowedWrites.join(', ')}.${canCompleteWorkflow ? ' Use complete_workflow_step only after finishing that step.' : ''}`
210
+ : 'No Taskforce writes are enabled for this run.';
211
+ const availableToolNames = new Set(tools.map((tool) => tool.name));
212
+ const selectedToolCallExample = [
213
+ { name: 'get_task', arguments: { id: 'T-123' } },
214
+ { name: 'get_document', arguments: { documentId: 'D-123' } },
215
+ { name: 'get_initiative', arguments: { id: 'IN-123' } },
216
+ { name: 'get_workstream', arguments: { id: 'WS-123' } },
217
+ { name: 'get_config', arguments: {} },
218
+ ].find((example) => availableToolNames.has(example.name));
219
+ const toolCallExample = selectedToolCallExample
220
+ ? { tool: selectedToolCallExample.name, arguments: selectedToolCallExample.arguments }
221
+ : { tool: tools[0]?.name, arguments: {} };
190
222
  return [
191
223
  baseSystem,
192
224
  '',
193
225
  'Taskforce project tools are attached invisibly by the server. Use them when the user asks about current Taskforce tasks, initiatives, workstreams, documents, images, annotations, config, stats, or specific reference labels such as T-123, IN-123, WS-123, D-123, or I-123.',
194
226
  readOnly
195
227
  ? 'This run is read-only. Use Taskforce tools only to inspect data; no Taskforce writes are available.'
196
- : canCompleteWorkflow
197
- ? 'Allowed writes: task comments, checklist changes, and completion of the current owned workflow step. Use complete_workflow_step only after finishing that step.'
198
- : 'Allowed writes: task comments and checklist changes.',
228
+ : allowedWritesSentence,
199
229
  ...(canCompleteWorkflow && !readOnly ? [
200
230
  'For workflow-step work, call get_task_workflow before acting. Reuse its completionCall arguments exactly; never invent or reuse workflow IDs. If requiresCommentEvidence is true, call add_comment and add its returned comment.id as evidenceCommentId. After all required work succeeds, call complete_workflow_step before answering. A comment or other task mutation does not complete the workflow step.',
201
231
  ] : []),
202
- ...(taskScope ? [`This run is task-scoped to ${taskScope}. Mutating Taskforce tools must target only this task.`] : []),
232
+ ...(taskScope ? [`This run is task-scoped to ${taskScope}. Task-targeted mutations must target only this task.`] : []),
203
233
  'To use a Taskforce tool, respond with only one JSON object and no prose:',
204
- '{"tool":"get_task","arguments":{"id":"T-123"}}',
234
+ JSON.stringify(toolCallExample),
205
235
  'After the server returns a tool result, answer the user normally unless another Taskforce tool is required; if another tool is required, respond with only one more tool-call JSON object.',
206
236
  'Do not mention MCP setup or credentials.',
207
237
  'Available Taskforce tools:',
@@ -1,4 +1,4 @@
1
- import Ajv2020 from 'ajv/dist/2020.js';
1
+ import { Ajv2020 } from 'ajv/dist/2020.js';
2
2
  import approvalReceiptSchema from './executorPhaseASchemas/approval-receipt.schema.json' with { type: 'json' };
3
3
  import driftAttestationSchema from './executorPhaseASchemas/drift-attestation.schema.json' with { type: 'json' };
4
4
  import driftRequestSchema from './executorPhaseASchemas/drift-request.schema.json' with { type: 'json' };
@@ -179,8 +179,9 @@ export class PostgresAdapter {
179
179
  }
180
180
  const waitStartedAt = performance.now();
181
181
  const waitResult = Atomics.wait(status, RESPONSE_STATUS_INDEX, RESPONSE_INITIAL, this.deadlines.responseTimeoutMs);
182
- recordPostgresWorkerWait(payload.op, performance.now() - waitStartedAt);
182
+ const workerWaitMs = performance.now() - waitStartedAt;
183
183
  if (waitResult === 'timed-out') {
184
+ recordPostgresWorkerWait(payload.op, workerWaitMs);
184
185
  this.quarantineWorker(worker);
185
186
  throw new PostgresWorkerOutcomeUnknownError(payload.op);
186
187
  }
@@ -190,6 +191,7 @@ export class PostgresAdapter {
190
191
  }
191
192
  const body = this.decoder.decode(responseBytes.subarray(0, responseLength));
192
193
  const parsed = JSON.parse(body);
194
+ recordPostgresWorkerWait(payload.op, workerWaitMs, parsed.pool);
193
195
  this.consecutiveUnexpectedExits = 0;
194
196
  if (!parsed.ok && parsed.errorCode === 'RESPONSE_BUFFER_TOO_SMALL') {
195
197
  const requiredBytes = Number(parsed.requiredBytes);
@@ -2,7 +2,15 @@ export type RequestPerformanceDiagnostics = {
2
2
  postgresQueryCount: number;
3
3
  postgresWorkerCallCount: number;
4
4
  postgresWorkerWaitMs: number;
5
+ postgresWorkerWaitMaxMs: number;
6
+ postgresPoolWaitingMax: number;
7
+ postgresPoolTotalMax: number;
8
+ postgresPoolIdleMin: number | null;
5
9
  };
6
10
  export declare function runWithRequestPerformanceDiagnostics<T>(enabled: boolean, callback: () => T): T;
7
- export declare function recordPostgresWorkerWait(operation: string, durationMs: number): void;
11
+ export declare function recordPostgresWorkerWait(operation: string, durationMs: number, pool?: {
12
+ totalCount: number;
13
+ idleCount: number;
14
+ waitingCount: number;
15
+ }): void;
8
16
  export declare function getRequestPerformanceDiagnostics(): RequestPerformanceDiagnostics | null;
@@ -7,16 +7,29 @@ export function runWithRequestPerformanceDiagnostics(enabled, callback) {
7
7
  postgresQueryCount: 0,
8
8
  postgresWorkerCallCount: 0,
9
9
  postgresWorkerWaitMs: 0,
10
+ postgresWorkerWaitMaxMs: 0,
11
+ postgresPoolWaitingMax: 0,
12
+ postgresPoolTotalMax: 0,
13
+ postgresPoolIdleMin: null,
10
14
  }, callback);
11
15
  }
12
- export function recordPostgresWorkerWait(operation, durationMs) {
16
+ export function recordPostgresWorkerWait(operation, durationMs, pool) {
13
17
  const diagnostics = requestPerformanceStorage.getStore();
14
18
  if (!diagnostics)
15
19
  return;
16
20
  diagnostics.postgresWorkerCallCount += 1;
17
21
  if (operation === 'query')
18
22
  diagnostics.postgresQueryCount += 1;
19
- diagnostics.postgresWorkerWaitMs += Math.max(0, durationMs);
23
+ const boundedDurationMs = Math.max(0, durationMs);
24
+ diagnostics.postgresWorkerWaitMs += boundedDurationMs;
25
+ diagnostics.postgresWorkerWaitMaxMs = Math.max(diagnostics.postgresWorkerWaitMaxMs, boundedDurationMs);
26
+ if (pool) {
27
+ diagnostics.postgresPoolWaitingMax = Math.max(diagnostics.postgresPoolWaitingMax, pool.waitingCount);
28
+ diagnostics.postgresPoolTotalMax = Math.max(diagnostics.postgresPoolTotalMax, pool.totalCount);
29
+ diagnostics.postgresPoolIdleMin = diagnostics.postgresPoolIdleMin === null
30
+ ? pool.idleCount
31
+ : Math.min(diagnostics.postgresPoolIdleMin, pool.idleCount);
32
+ }
20
33
  }
21
34
  export function getRequestPerformanceDiagnostics() {
22
35
  const diagnostics = requestPerformanceStorage.getStore();
@@ -111,6 +111,16 @@ if (!runtimeOptions.deadlines) {
111
111
  }
112
112
  const pool = createPool(connectionString, runtimeOptions.deadlines);
113
113
  const txClients = new Map();
114
+ function withPoolSnapshot(payload) {
115
+ return {
116
+ ...payload,
117
+ pool: {
118
+ totalCount: Math.max(0, Number(pool.totalCount) || 0),
119
+ idleCount: Math.max(0, Number(pool.idleCount) || 0),
120
+ waitingCount: Math.max(0, Number(pool.waitingCount) || 0),
121
+ },
122
+ };
123
+ }
114
124
  async function handleMessage(message) {
115
125
  const { payload } = message;
116
126
  const txId = payload.txId ? String(payload.txId) : null;
@@ -174,7 +184,7 @@ parentPort.on('message', async (message) => {
174
184
  return;
175
185
  }
176
186
  const response = await handleMessage(message);
177
- writeResponse(message.statusBuffer, message.responseBuffer, response);
187
+ writeResponse(message.statusBuffer, message.responseBuffer, withPoolSnapshot(response));
178
188
  }
179
189
  catch (error) {
180
190
  const messageText = error instanceof Error ? error.message : String(error);
@@ -182,10 +192,10 @@ parentPort.on('message', async (message) => {
182
192
  && typeof error.code === 'string'
183
193
  ? String(error.code)
184
194
  : undefined;
185
- writeResponse(message.statusBuffer, message.responseBuffer, {
195
+ writeResponse(message.statusBuffer, message.responseBuffer, withPoolSnapshot({
186
196
  ok: false,
187
197
  error: messageText,
188
198
  ...(databaseErrorCode ? { databaseErrorCode } : {}),
189
- });
199
+ }));
190
200
  }
191
201
  });
@@ -10,6 +10,11 @@ export type PostgresWorkerQueryResult = {
10
10
  rows: Record<string, unknown>[];
11
11
  rowCount?: number;
12
12
  };
13
+ export type PostgresWorkerPoolSnapshot = {
14
+ totalCount: number;
15
+ idleCount: number;
16
+ waitingCount: number;
17
+ };
13
18
  export type PostgresWorkerResponsePayload = {
14
19
  ok: boolean;
15
20
  result?: PostgresWorkerQueryResult;
@@ -18,6 +23,7 @@ export type PostgresWorkerResponsePayload = {
18
23
  databaseErrorCode?: string;
19
24
  requiredBytes?: number;
20
25
  responseToken?: string;
26
+ pool?: PostgresWorkerPoolSnapshot;
21
27
  };
22
28
  export type PostgresWorkerDeadlines = {
23
29
  responseTimeoutMs: number;
@@ -200,7 +200,9 @@ export declare class WorkflowStore {
200
200
  installAssignmentForSync(workspaceIdRaw: string, snapshot: WorkflowAssignmentSyncRecord, options: {
201
201
  authority: 'pending-intent' | 'cloud-execution';
202
202
  expectedTaskId: string;
203
+ replaceAuthoritativeHistory?: boolean;
203
204
  }): WorkflowExecutionStateRecord;
205
+ private runAssignmentSyncTransaction;
204
206
  appendExecutionTransition(input: {
205
207
  workspaceId: string;
206
208
  executionId: string;
@@ -759,7 +759,7 @@ export class WorkflowStore {
759
759
  || snapshot.execution.updatedAt !== snapshotProjection.updatedAt) {
760
760
  throw new WorkflowAssignmentSyncConflictError('Workflow assignment execution does not match its event history.');
761
761
  }
762
- return this.db.transaction(() => {
762
+ return this.runAssignmentSyncTransaction(Boolean(options.authority === 'cloud-execution' && options.replaceAuthoritativeHistory), () => {
763
763
  const existingForTask = this.getActiveExecutionForTask(workspaceId, snapshot.execution.taskId);
764
764
  if (existingForTask && existingForTask.id !== snapshot.execution.id) {
765
765
  throw new WorkflowAssignmentSyncConflictError('Task already has a different active workflow execution.');
@@ -852,7 +852,14 @@ export class WorkflowStore {
852
852
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
853
853
  `).run(snapshot.execution.id, this.tenantId, workspaceId, snapshot.execution.taskId, snapshot.template.id, snapshot.version.id, snapshot.execution.status, snapshot.execution.currentStepId || null, snapshot.execution.createdByActorId || null, snapshot.execution.updatedByActorId || null, snapshot.execution.createdAt, snapshot.execution.updatedAt, snapshot.execution.startedAt || null, snapshot.execution.completedAt || null, snapshot.execution.endedAt || null, snapshot.execution.deletedAt || null);
854
854
  }
855
- const canonicalEvents = this.listExecutionEvents(workspaceId, snapshot.execution.id);
855
+ let canonicalEvents = this.listExecutionEvents(workspaceId, snapshot.execution.id);
856
+ if (options.authority === 'cloud-execution' && options.replaceAuthoritativeHistory) {
857
+ this.db.prepare(`
858
+ DELETE FROM workflow_execution_events
859
+ WHERE tenant_id = ? AND workspace_id = ? AND workflow_execution_id = ?
860
+ `).run(this.tenantId, workspaceId, snapshot.execution.id);
861
+ canonicalEvents = [];
862
+ }
856
863
  const authoritativeHistoryIsExactPrefix = options.authority === 'cloud-execution'
857
864
  && snapshot.events.length < canonicalEvents.length
858
865
  && snapshot.events.every((event, index) => canonicalJson(event) === canonicalJson(canonicalEvents[index]));
@@ -908,6 +915,36 @@ export class WorkflowStore {
908
915
  `).run(snapshotProjection.status, snapshotProjection.currentStepId || null, snapshot.execution.updatedByActorId || null, snapshotProjection.updatedAt, snapshotProjection.startedAt || null, snapshotProjection.completedAt || null, snapshotProjection.endedAt || null, snapshot.execution.deletedAt || null, this.tenantId, workspaceId, snapshot.execution.id);
909
916
  }
910
917
  return this.getExecutionStateSnapshot(workspaceId, snapshot.execution.id);
918
+ });
919
+ }
920
+ runAssignmentSyncTransaction(replaceAuthoritativeHistory, apply) {
921
+ return this.db.transaction(() => {
922
+ if (!replaceAuthoritativeHistory)
923
+ return apply();
924
+ if (this.db.provider === 'postgres') {
925
+ throw new WorkflowAssignmentSyncConflictError('Authoritative workflow history replacement is local-runtime only.');
926
+ }
927
+ this.db.exec(`
928
+ DROP TRIGGER IF EXISTS trg_workflow_execution_events_no_update;
929
+ DROP TRIGGER IF EXISTS trg_workflow_execution_events_no_delete;
930
+ `);
931
+ try {
932
+ return apply();
933
+ }
934
+ finally {
935
+ this.db.exec(`
936
+ CREATE TRIGGER IF NOT EXISTS trg_workflow_execution_events_no_update
937
+ BEFORE UPDATE ON workflow_execution_events
938
+ BEGIN
939
+ SELECT RAISE(ABORT, 'Workflow execution events are append-only');
940
+ END;
941
+ CREATE TRIGGER IF NOT EXISTS trg_workflow_execution_events_no_delete
942
+ BEFORE DELETE ON workflow_execution_events
943
+ BEGIN
944
+ SELECT RAISE(ABORT, 'Workflow execution events are append-only');
945
+ END;
946
+ `);
947
+ }
911
948
  })();
912
949
  }
913
950
  appendExecutionTransition(input) {
@@ -558,8 +558,10 @@ export async function prepareWorkspacePushChanges(input, dependencies = {}) {
558
558
  .filter((taskId) => taskUpsertIdSet.has(taskId) && !activeV3TaskCreateIds.has(taskId));
559
559
  }
560
560
  const ownershipFilteredChanges = input.changes.filter((change) => {
561
- if (change.op === 'upsert')
562
- return !activeV3TaskCreateIds.has(String(change.task?.id || '').trim());
561
+ if (change.op === 'upsert') {
562
+ const taskId = String(change.task?.id || '').trim();
563
+ return !activeV3TaskCreateIds.has(taskId) && !activeV3TaskDeleteIds.has(taskId);
564
+ }
563
565
  if (change.op === 'delete') {
564
566
  return !activeV3TaskDeleteIds.has(String(change.taskId || '').trim());
565
567
  }
@@ -32,6 +32,11 @@ export function listWorkspaceSyncDocumentReviewCommentsSnapshot(deps, workspaceI
32
32
  authorActorId: comment.authorActorId,
33
33
  updatedByActorId: comment.updatedByActorId,
34
34
  deletedByActorId: comment.deletedByActorId,
35
+ parentCommentId: comment.parentCommentId,
36
+ threadStatus: comment.threadStatus,
37
+ threadStatusUpdatedAt: comment.threadStatusUpdatedAt,
38
+ resolvedAt: comment.resolvedAt,
39
+ resolvedByActorId: comment.resolvedByActorId,
35
40
  createdAt: comment.createdAt,
36
41
  updatedAt: comment.updatedAt,
37
42
  deletedAt: comment.deletedAt,
@@ -1,6 +1,6 @@
1
1
  import type { LocalSyncCoordinatorCommandRecord } from './localSyncCoordinatorTypes.js';
2
2
  import type { LocalSyncCoordinatorStore } from './localSyncCoordinatorStore.js';
3
- import { type LocalWorkspaceSyncRunnerCheckpoint, type LocalWorkspaceSyncRunnerState, type LocalWorkspaceSyncRunnerStateStore } from './localWorkspaceSyncRunnerState.js';
3
+ import { type LocalWorkspaceSyncRunnerCheckpoint, type LocalWorkspaceSyncRunnerRetryKind, type LocalWorkspaceSyncRunnerState, type LocalWorkspaceSyncRunnerStateStore } from './localWorkspaceSyncRunnerState.js';
4
4
  import type { WorkspaceSyncCredentialBroker } from './workspaceSyncCredentialBroker.js';
5
5
  export interface LocalWorkspaceSyncRunnerOperationInput {
6
6
  command: LocalSyncCoordinatorCommandRecord | null;
@@ -55,6 +55,7 @@ export declare class LocalWorkspaceSyncRunnerError extends Error {
55
55
  readonly code: 'SYNC_RUNNER_NOT_STARTED' | 'SYNC_RUNNER_ALREADY_STARTED' | 'SYNC_RUNNER_COORDINATOR_FENCE_INVALID' | 'SYNC_RUNNER_COMMAND_INVALID' | 'SYNC_RUNNER_COMMAND_DISABLED' | 'SYNC_RUNNER_DRAIN_TIMEOUT';
56
56
  constructor(message: string, code: 'SYNC_RUNNER_NOT_STARTED' | 'SYNC_RUNNER_ALREADY_STARTED' | 'SYNC_RUNNER_COORDINATOR_FENCE_INVALID' | 'SYNC_RUNNER_COMMAND_INVALID' | 'SYNC_RUNNER_COMMAND_DISABLED' | 'SYNC_RUNNER_DRAIN_TIMEOUT');
57
57
  }
58
+ export declare function canCoordinatorRecoveryPreemptRetry(kind: LocalWorkspaceSyncRunnerRetryKind): boolean;
58
59
  export declare class LocalWorkspaceSyncRunner {
59
60
  private readonly options;
60
61
  private context;
@@ -87,6 +88,7 @@ export declare class LocalWorkspaceSyncRunner {
87
88
  activate(): void;
88
89
  executeCommand(commandId: string): Promise<LocalSyncCoordinatorCommandRecord | null>;
89
90
  inspect(): Promise<void>;
91
+ recoverCoordinatorObligationsDue(isRecoveryStillRequired: () => boolean): Promise<void>;
90
92
  pullDue(): Promise<void>;
91
93
  retryDue(): Promise<void>;
92
94
  pushDue(): Promise<void>;
@@ -21,6 +21,14 @@ const CONTENT_PREREQUISITE_BYPASSED_RETRY_KINDS = new Set([
21
21
  'pull',
22
22
  'repair',
23
23
  ]);
24
+ const COORDINATOR_RECOVERY_BYPASSED_RETRY_KINDS = new Set([
25
+ 'pull',
26
+ 'push',
27
+ 'v3-dispatch',
28
+ ]);
29
+ export function canCoordinatorRecoveryPreemptRetry(kind) {
30
+ return COORDINATOR_RECOVERY_BYPASSED_RETRY_KINDS.has(kind);
31
+ }
24
32
  function cloneCheckpoint(checkpoint) {
25
33
  return JSON.parse(canonicalWorkspaceSyncJson(checkpoint));
26
34
  }
@@ -140,6 +148,16 @@ export class LocalWorkspaceSyncRunner {
140
148
  }
141
149
  return this.enqueue(async () => this.runInspection());
142
150
  }
151
+ recoverCoordinatorObligationsDue(isRecoveryStillRequired) {
152
+ if (!this.context || this.terminated || !this.acceptingWork) {
153
+ return Promise.resolve();
154
+ }
155
+ return this.enqueue(async () => {
156
+ if (!isRecoveryStillRequired())
157
+ return;
158
+ await this.runInspection(this.options.operations.sync, { bypassRetryKinds: COORDINATOR_RECOVERY_BYPASSED_RETRY_KINDS });
159
+ });
160
+ }
143
161
  pullDue() {
144
162
  if (!this.context || this.terminated || !this.acceptingWork) {
145
163
  return Promise.resolve();
@@ -82,6 +82,7 @@ export interface LocalWorkspaceSyncSettledV2PushBatch {
82
82
  task: WorkspaceSyncTaskSnapshot;
83
83
  durableRevision?: WorkspaceSyncDurableTaskRevision;
84
84
  }>;
85
+ settledTaskDeleteIds?: string[];
85
86
  }
86
87
  export interface LocalWorkspaceSyncPendingV2PushBatchProgress {
87
88
  totalBatches: number;
@@ -528,11 +528,7 @@ function isPendingV2Push(value) {
528
528
  && settledBatchOperationIdentities.every((identity) => /^[a-f0-9]{64}$/.test(identity))
529
529
  && settledBatchContributions !== null
530
530
  && settledBatchContributions.every((entry) => (isRecord(entry)
531
- && hasExactKeys(entry, [
532
- 'operationIdentity',
533
- 'settlementIdentityFingerprint',
534
- 'appliedTaskUpserts',
535
- ])
531
+ && hasRequiredAndAllowedKeys(entry, ['operationIdentity', 'settlementIdentityFingerprint', 'appliedTaskUpserts'], ['settledTaskDeleteIds'])
536
532
  && typeof entry.operationIdentity === 'string'
537
533
  && /^[a-f0-9]{64}$/.test(entry.operationIdentity)
538
534
  && typeof entry.settlementIdentityFingerprint === 'string'
@@ -545,7 +541,11 @@ function isPendingV2Push(value) {
545
541
  && isNonEmptyText(upsert.task.id)
546
542
  && isJson(upsert.task)
547
543
  && (upsert.durableRevision === undefined
548
- || (isRecord(upsert.durableRevision) && isJson(upsert.durableRevision)))))))
544
+ || (isRecord(upsert.durableRevision) && isJson(upsert.durableRevision)))))
545
+ && (entry.settledTaskDeleteIds === undefined
546
+ || (isStringArray(entry.settledTaskDeleteIds)
547
+ && new Set(entry.settledTaskDeleteIds).size
548
+ === entry.settledTaskDeleteIds.length))))
549
549
  && new Set(settledBatchContributions.map((entry) => entry.operationIdentity)).size === settledBatchContributions.length
550
550
  && settledBatchContributions.every((entry) => (settledBatchOperationIdentities.includes(entry.operationIdentity))));
551
551
  const settlementContributionsMatchPlan = hasLegacyShape || (preparedPlan !== null
@@ -563,6 +563,7 @@ function isPendingV2Push(value) {
563
563
  operationIdentity: entry.operationIdentity,
564
564
  changes,
565
565
  appliedTaskUpserts: entry.appliedTaskUpserts,
566
+ settledTaskDeleteIds: entry.settledTaskDeleteIds,
566
567
  excludedProjections: preparedPlan.ownership.combinedExcludedProjections,
567
568
  })) === entry.settlementIdentityFingerprint;
568
569
  }
@@ -2,7 +2,7 @@ import { workspaceSyncGatewayOperationClassFor, workspaceSyncGatewayTimeoutMs, }
2
2
  import { canonicalWorkspaceSyncJson } from '../v3/canonicalWorkspaceSyncJson.js';
3
3
  import { WORKSPACE_SYNC_V3_COMBINED_COVERAGE_SELECTOR, resolveWorkspaceSyncV3CombinedCoverageForExclusions, } from '../v3/workspaceSyncV3CombinedWireContract.js';
4
4
  import { WORKSPACE_SYNC_ENGINE_TRANSFER_COMPLETION_VERSION, } from './workspaceSyncEngineTransferCompletion.js';
5
- import { parseWorkspaceSyncEngineAppliedTaskUpserts, parseWorkspaceSyncEngineV2PushBoundary, workspaceSyncEngineExpectedTaskEchoIds, } from './workspaceSyncEnginePushCompletion.js';
5
+ import { parseWorkspaceSyncEnginePushTaskSettlement, parseWorkspaceSyncEngineV2PushBoundary, workspaceSyncEngineExpectedTaskEchoIds, } from './workspaceSyncEnginePushCompletion.js';
6
6
  const PERMIT_DEADLINE_SLACK_MS = 5_000;
7
7
  function isPlainObject(value) {
8
8
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -388,7 +388,7 @@ export function createBrowserGatewayWorkspaceSyncEngineRemoteServices(options) {
388
388
  requireAcceptedJsonObject: true,
389
389
  validateAcceptedData: (value) => {
390
390
  try {
391
- parseWorkspaceSyncEngineAppliedTaskUpserts(value, expectedTaskEchoIds);
391
+ parseWorkspaceSyncEnginePushTaskSettlement(value, expectedTaskEchoIds);
392
392
  return true;
393
393
  }
394
394
  catch {
@@ -11,6 +11,7 @@ export interface WorkspaceSyncEngineLocalApplyResult {
11
11
  result: unknown;
12
12
  deleteCount: number;
13
13
  emittedEventIds: string[];
14
+ settledTaskDeleteIds?: string[];
14
15
  }
15
16
  export interface WorkspaceSyncEngineLocalContentReader {
16
17
  loadDocument(workspaceId: string, path: string): Promise<{