@yeaft/webchat-agent 1.0.366 → 1.0.367
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +50 -50
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/tools/create-work-item.js +8 -8
- package/yeaft/work-center/controller.js +12 -2
- package/yeaft/work-center/coordinator.js +156 -26
- package/yeaft/work-center/durable-model.js +51 -1
- package/yeaft/work-center/dynamic-coordination.js +243 -0
- package/yeaft/work-center/execution-mode.js +16 -0
- package/yeaft/work-center/mainline-projection.js +25 -13
- package/yeaft/work-center/projection.js +32 -9
- package/yeaft/work-center/runner.js +18 -13
- package/yeaft/work-center/service.js +86 -13
- package/yeaft/work-center/store.js +535 -60
|
Binary file
|
package/package.json
CHANGED
|
@@ -11,10 +11,10 @@ export default defineTool({
|
|
|
11
11
|
description: {
|
|
12
12
|
en: `Create a persistent Agent-level Work Center item from the current Session.
|
|
13
13
|
|
|
14
|
-
Use this when work must continue beyond the current turn, needs role handoffs, review, waiting, retry, or durable tracking. This creates only the goal contract; Work Center
|
|
14
|
+
Use this when work must continue beyond the current turn, needs role handoffs, review, waiting, retry, or durable tracking. This creates only the goal contract; the Work Center Coordinator dynamically chooses the next Actions and executors until the acceptance criteria are verified. The current Session is always stamped as the origin and cannot be overridden by model input.`,
|
|
15
15
|
zh: `从当前 Session 创建一个持久化的 Agent 级工作项。
|
|
16
16
|
|
|
17
|
-
当工作需要跨 turn
|
|
17
|
+
当工作需要跨 turn 继续、需要角色接力、评审、等待、重试或长期跟踪时使用。该工具只创建目标契约;Work Center Coordinator 会动态选择下一批 Action 和执行者,直到验收条件得到验证。来源 Session 由运行时强制写入,模型输入不能覆盖。`,
|
|
18
18
|
},
|
|
19
19
|
parameters: {
|
|
20
20
|
type: 'object',
|
|
@@ -42,15 +42,15 @@ Use this when work must continue beyond the current turn, needs role handoffs, r
|
|
|
42
42
|
},
|
|
43
43
|
start: {
|
|
44
44
|
type: 'boolean',
|
|
45
|
-
description: { en: 'Start
|
|
45
|
+
description: { en: 'Start Coordinator execution immediately (default true)', zh: '是否立即启动 Coordinator 执行(默认 true)' },
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
required: ['title', 'goal'],
|
|
49
49
|
},
|
|
50
50
|
isConcurrencySafe: () => false,
|
|
51
51
|
isReadOnly: () => false,
|
|
52
|
-
// The
|
|
53
|
-
//
|
|
52
|
+
// The persistent Coordinator can create a writable Action after creation
|
|
53
|
+
// returns. A paused item has no Coordinator- or watcher-owned execution.
|
|
54
54
|
mayMutateWorkspaceAfterReturn: input => input?.start !== false,
|
|
55
55
|
async execute(input, ctx = {}) {
|
|
56
56
|
const sessionId = typeof ctx.sessionId === 'string' ? ctx.sessionId.trim() : '';
|
|
@@ -69,9 +69,9 @@ Use this when work must continue beyond the current turn, needs role handoffs, r
|
|
|
69
69
|
acceptanceCriteria: cleanCriteria(input.acceptanceCriteria),
|
|
70
70
|
workItemType: typeof input.workItemType === 'string' ? input.workItemType.trim() : 'auto',
|
|
71
71
|
workDir: typeof input.workDir === 'string' ? input.workDir.trim() : (ctx.cwd || ''),
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
72
|
+
// Agent-local Work Center settings freeze the Action-template and model
|
|
73
|
+
// policy snapshot. Tool callers create the contract; they cannot smuggle
|
|
74
|
+
// a different dispatch policy into it.
|
|
75
75
|
origin: {
|
|
76
76
|
sessionId,
|
|
77
77
|
messageId: ctx.inboundEnvelope?.msgId || null,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { renderSessionContextSnapshot } from './session-context.js';
|
|
13
13
|
import { normalizeSessionMessageQuote } from '../session-message-quote.js';
|
|
14
14
|
import { normalizeEvidence } from './evidence.js';
|
|
15
|
+
import { isDynamicWorkItem } from './execution-mode.js';
|
|
15
16
|
import { applyAdditivePlanProposal, applyReplanMutation } from './plan-mutation.js';
|
|
16
17
|
import { normalizeContractPatch, validateCompletedResult } from './completion-contract.js';
|
|
17
18
|
|
|
@@ -129,7 +130,7 @@ export class WorkflowController {
|
|
|
129
130
|
acceptanceCriteria: Array.isArray(input.acceptanceCriteria) ? input.acceptanceCriteria : [],
|
|
130
131
|
attachments: Array.isArray(input.attachments) ? input.attachments : [],
|
|
131
132
|
};
|
|
132
|
-
let firstAction = input.start !== false ? initialActionFor(draft) : null;
|
|
133
|
+
let firstAction = input.start !== false && !isDynamicWorkItem(draft) ? initialActionFor(draft) : null;
|
|
133
134
|
if (firstAction) {
|
|
134
135
|
firstAction = {
|
|
135
136
|
...firstAction,
|
|
@@ -301,7 +302,7 @@ export class WorkflowController {
|
|
|
301
302
|
quote: input.inputEvent?.quote || null,
|
|
302
303
|
});
|
|
303
304
|
}
|
|
304
|
-
if (Number(workItem.executionSchemaVersion)
|
|
305
|
+
if (Number(workItem.executionSchemaVersion) >= 2 && input.inputEvent?.inputId) {
|
|
305
306
|
context.push({
|
|
306
307
|
type: 'input',
|
|
307
308
|
role: 'user',
|
|
@@ -337,6 +338,15 @@ export class WorkflowController {
|
|
|
337
338
|
throw new Error('Run has unconsumed Action input and cannot finish yet');
|
|
338
339
|
}
|
|
339
340
|
const result = normalizeTerminalResult(rawResult, activeAction);
|
|
341
|
+
if (isDynamicWorkItem(activeWorkItem)) {
|
|
342
|
+
result.contractPatch = null;
|
|
343
|
+
result.plan = null;
|
|
344
|
+
result.planProposal = null;
|
|
345
|
+
result.replanRequest = null;
|
|
346
|
+
result.replanMutation = null;
|
|
347
|
+
result.nextActions = [];
|
|
348
|
+
result.expandPlan = null;
|
|
349
|
+
}
|
|
340
350
|
if (result.outcome === 'completed'
|
|
341
351
|
&& activeAction.stageId?.startsWith('replan-')
|
|
342
352
|
&& !result.replanMutation) {
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from '../llm/adapter.js';
|
|
10
10
|
import { resolveWorkItemModel, selectWorkItemVp } from './assignment.js';
|
|
11
11
|
import { normalizeContractPatch } from './completion-contract.js';
|
|
12
|
+
import { prepareDynamicActionMutation } from './dynamic-coordination.js';
|
|
13
|
+
import { isDynamicWorkItem } from './execution-mode.js';
|
|
12
14
|
import { applyCoordinatorReplan } from './plan-mutation.js';
|
|
13
15
|
import { buildWorkItemAttachmentContext } from './attachments.js';
|
|
14
16
|
import { sanitizeDiagnosticText } from './debug-projection.js';
|
|
@@ -124,17 +126,25 @@ function coordinatorStageReferences(detail) {
|
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
function boundedAction(action, result, stageReferences, compact = false) {
|
|
129
|
+
function boundedAction(action, result, stageReferences, compact = false, dynamic = false) {
|
|
128
130
|
const brief = action?.brief && typeof action.brief === 'object' ? action.brief : null;
|
|
129
131
|
return {
|
|
130
|
-
|
|
132
|
+
...(dynamic
|
|
133
|
+
? {
|
|
134
|
+
actionId: truncateUtf8(action?.id, 256),
|
|
135
|
+
sourceActionIds: (Array.isArray(action?.sourceActionIds) ? action.sourceActionIds : [])
|
|
136
|
+
.slice(0, 8).map(value => truncateUtf8(value, 256)).filter(Boolean),
|
|
137
|
+
}
|
|
138
|
+
: { stageId: stageReferences.project(action?.stageId) }),
|
|
131
139
|
type: truncateUtf8(action?.type, 64),
|
|
132
140
|
status: truncateUtf8(action?.status, 64),
|
|
133
141
|
generation: Math.max(1, Number(action?.generation) || 1),
|
|
134
|
-
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
...(dynamic ? {} : {
|
|
143
|
+
dependencies: (Array.isArray(action?.dependsOnStageIds) ? action.dependsOnStageIds : [])
|
|
144
|
+
.slice(0, 8)
|
|
145
|
+
.map(value => stageReferences.project(value))
|
|
146
|
+
.filter(Boolean),
|
|
147
|
+
}),
|
|
138
148
|
workspaceMode: truncateUtf8(action?.workspaceMode, 64),
|
|
139
149
|
...(!compact && brief ? {
|
|
140
150
|
brief: {
|
|
@@ -146,7 +156,15 @@ function boundedAction(action, result, stageReferences, compact = false) {
|
|
|
146
156
|
result: result ? {
|
|
147
157
|
status: truncateUtf8(result.status, 64),
|
|
148
158
|
summary: truncateUtf8(result.summary, compact ? 256 : 768),
|
|
149
|
-
...(!compact ? {
|
|
159
|
+
...(!compact ? {
|
|
160
|
+
evidence: boundedEvidence(result.evidence),
|
|
161
|
+
acceptanceChecks: (Array.isArray(result.acceptanceChecks) ? result.acceptanceChecks : [])
|
|
162
|
+
.slice(0, 24).map(check => ({
|
|
163
|
+
criterion: truncateUtf8(check?.criterion, 512),
|
|
164
|
+
status: truncateUtf8(check?.status, 64),
|
|
165
|
+
evidence: truncateUtf8(check?.evidence, 1_000),
|
|
166
|
+
})),
|
|
167
|
+
} : {}),
|
|
150
168
|
waitingReason: truncateUtf8(result.waitingReason, 384) || null,
|
|
151
169
|
error: truncateUtf8(result.error, 384) || null,
|
|
152
170
|
reviewDecision: truncateUtf8(result.reviewDecision, 64) || null,
|
|
@@ -188,11 +206,42 @@ Decision rules:
|
|
|
188
206
|
- Stage ids in the snapshot may be bounded aliases. Echo them exactly; the runtime resolves them to durable identities.
|
|
189
207
|
- Never return destructive cancellation. Tell the user to use the explicit cancel control instead.`;
|
|
190
208
|
|
|
191
|
-
|
|
209
|
+
const DYNAMIC_COORDINATOR_SYSTEM_PROMPT = `You are the persistent Work Center Coordinator. You own progress toward one durable WorkItem.
|
|
210
|
+
|
|
211
|
+
Observe the current contract, Action journal, canonical Run evidence, and conversation. Decide only the next justified step; never predict or emit a complete workflow graph.
|
|
212
|
+
|
|
213
|
+
Return exactly one JSON object and no surrounding prose:
|
|
214
|
+
{
|
|
215
|
+
"reply": "natural user-facing response",
|
|
216
|
+
"decision": {
|
|
217
|
+
"kind": "answer|create_actions|guide_actions|request_human|complete",
|
|
218
|
+
"reason": "short audit reason",
|
|
219
|
+
"question": null,
|
|
220
|
+
"workItemType": null,
|
|
221
|
+
"contractPatch": null,
|
|
222
|
+
"supersedeActionIds": [],
|
|
223
|
+
"guidance": [],
|
|
224
|
+
"actions": [],
|
|
225
|
+
"completion": null
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
Rules:
|
|
230
|
+
- answer: explain state only. Never use it for an automatic advance trigger.
|
|
231
|
+
- create_actions: create 1..8 currently runnable Actions. Every Action needs type, objective, approach, expectedOutcome, capability, candidateVpIds, assignmentReason, sourceActionIds, workspaceMode, and optional maxAttempts/separateFromActionTypes. sourceActionIds are context/audit references, never scheduling dependencies. Do not include dependsOnActionIds, dependsOnStageIds, stages, or a graph.
|
|
232
|
+
- guide_actions: target 1..8 unfinished non-running Actions by durable actionId.
|
|
233
|
+
- request_human: only when external information or a user decision is genuinely required.
|
|
234
|
+
- complete: only when every acceptance criterion has canonical completed Run evidence and there are no unfinished Actions. Include summary, ordered acceptanceResults with evidenceRunIds, evidenceRunIds, and residualRisks.
|
|
235
|
+
- Preserve completed Action history. Never claim tests, review, merge, release, or external effects without canonical Run evidence.
|
|
236
|
+
- Action templates are reusable capabilities, not a prescribed workflow. Create the smallest useful Action boundary, not tool-call-sized work.
|
|
237
|
+
- Never return destructive cancellation. The user owns the explicit cancel control.`;
|
|
238
|
+
|
|
239
|
+
function coordinatorSystemPrompt(language, detail) {
|
|
192
240
|
const userLanguage = coordinatorLanguage(language) === 'zh'
|
|
193
241
|
? 'Simplified Chinese (zh-CN)'
|
|
194
242
|
: 'English';
|
|
195
|
-
|
|
243
|
+
const base = isDynamicWorkItem(detail) ? DYNAMIC_COORDINATOR_SYSTEM_PROMPT : COORDINATOR_SYSTEM_PROMPT;
|
|
244
|
+
return `${base}
|
|
196
245
|
|
|
197
246
|
User-facing language: ${userLanguage}. Write reply and question in that language. Keep JSON property names and decision enum values in English. Never expose deterministic validator errors as the user-facing reply; explain the underlying issue plainly.`;
|
|
198
247
|
}
|
|
@@ -327,19 +376,22 @@ function normalizeGuidance(value, detail) {
|
|
|
327
376
|
if (!Array.isArray(value) || value.length < 1 || value.length > 8) {
|
|
328
377
|
throw new Error('Work Center Coordinator guidance requires between 1 and 8 targets');
|
|
329
378
|
}
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
.
|
|
379
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
380
|
+
const active = (detail.actions || [])
|
|
381
|
+
.filter(action => !['completed', 'superseded', 'cancelled'].includes(action.status));
|
|
382
|
+
const activeByReference = new Map(active.map(action => [dynamic ? action.id : action.stageId, action]));
|
|
333
383
|
const stageReferences = coordinatorStageReferences(detail);
|
|
334
384
|
const seen = new Set();
|
|
335
385
|
return value.map(entry => {
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
386
|
+
const reference = dynamic
|
|
387
|
+
? (typeof entry?.actionId === 'string' ? entry.actionId.trim() : '')
|
|
388
|
+
: stageReferences.resolve(entry?.stageId);
|
|
389
|
+
if (!reference || seen.has(reference) || !activeByReference.has(reference)) {
|
|
390
|
+
throw new Error(`Work Center Coordinator guidance references an invalid unfinished Action: ${reference || '(missing)'}`);
|
|
339
391
|
}
|
|
340
|
-
seen.add(
|
|
392
|
+
seen.add(reference);
|
|
341
393
|
return {
|
|
342
|
-
stageId,
|
|
394
|
+
...(dynamic ? { actionId: reference } : { stageId: reference }),
|
|
343
395
|
instruction: cleanText(entry?.instruction, COORDINATOR_MAX_INSTRUCTION_CHARS, 'guidance instruction'),
|
|
344
396
|
};
|
|
345
397
|
});
|
|
@@ -368,9 +420,14 @@ export function normalizeCoordinatorResponse(value, detail, options = {}) {
|
|
|
368
420
|
const source = parsed?.decision && typeof parsed.decision === 'object' && !Array.isArray(parsed.decision)
|
|
369
421
|
? parsed.decision
|
|
370
422
|
: {};
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
423
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
424
|
+
const allowedKinds = dynamic
|
|
425
|
+
? (options.automatic === true
|
|
426
|
+
? ['create_actions', 'guide_actions', 'request_human', 'complete']
|
|
427
|
+
: ['answer', 'create_actions', 'guide_actions', 'request_human', 'complete'])
|
|
428
|
+
: (options.recovery === true
|
|
429
|
+
? ['guide_actions', 'replan', 'request_human']
|
|
430
|
+
: ['answer', 'guide_actions', 'replan']);
|
|
374
431
|
const kind = allowedKinds.includes(source.kind) ? source.kind : '';
|
|
375
432
|
if (!kind) throw new Error('Work Center Coordinator decision kind is invalid');
|
|
376
433
|
const reason = cleanText(source.reason, 2_000, 'decision reason');
|
|
@@ -411,6 +468,41 @@ export function normalizeCoordinatorResponse(value, detail, options = {}) {
|
|
|
411
468
|
},
|
|
412
469
|
};
|
|
413
470
|
}
|
|
471
|
+
if (dynamic && kind === 'complete') {
|
|
472
|
+
return {
|
|
473
|
+
reply,
|
|
474
|
+
decision: {
|
|
475
|
+
kind,
|
|
476
|
+
reason,
|
|
477
|
+
completion: source.completion,
|
|
478
|
+
contractPatch: null,
|
|
479
|
+
guidance: [],
|
|
480
|
+
actions: [],
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (dynamic && kind === 'create_actions') {
|
|
485
|
+
const contractPatch = normalizeContractPatch(source.contractPatch);
|
|
486
|
+
const decision = {
|
|
487
|
+
kind,
|
|
488
|
+
reason,
|
|
489
|
+
workItemType: source.workItemType,
|
|
490
|
+
contractPatch,
|
|
491
|
+
supersedeActionIds: source.supersedeActionIds,
|
|
492
|
+
guidance: [],
|
|
493
|
+
actions: source.actions,
|
|
494
|
+
};
|
|
495
|
+
return {
|
|
496
|
+
reply,
|
|
497
|
+
decision,
|
|
498
|
+
mutation: prepareDynamicActionMutation({
|
|
499
|
+
workItem: detail,
|
|
500
|
+
actions: detail.actions || [],
|
|
501
|
+
decision,
|
|
502
|
+
availableVpIds: options.availableVpIds,
|
|
503
|
+
}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
414
506
|
const contractPatch = normalizeContractPatch(source.contractPatch);
|
|
415
507
|
if (!Array.isArray(source.actions)) {
|
|
416
508
|
throw new Error('Work Center Coordinator replan requires the complete unfinished Action graph');
|
|
@@ -496,6 +588,7 @@ function coordinatorSnapshot(detail) {
|
|
|
496
588
|
throw new Error('WorkItem contract cannot be represented within the Coordinator snapshot budget');
|
|
497
589
|
}
|
|
498
590
|
|
|
591
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
499
592
|
const currentActions = (Array.isArray(detail.actions) ? detail.actions : [])
|
|
500
593
|
.filter(action => !['superseded', 'cancelled'].includes(action.status));
|
|
501
594
|
const stageReferences = coordinatorStageReferences(detail);
|
|
@@ -510,20 +603,24 @@ function coordinatorSnapshot(detail) {
|
|
|
510
603
|
canonicalRunByAction.get(action.id),
|
|
511
604
|
stageReferences,
|
|
512
605
|
action.status === 'completed',
|
|
606
|
+
dynamic,
|
|
513
607
|
));
|
|
514
608
|
let actions = boundedJsonArray(projectedActions, COORDINATOR_MAX_ACTIONS_BYTES);
|
|
515
|
-
|
|
516
|
-
|
|
609
|
+
const identity = action => `${dynamic ? action.actionId : action.stageId}:${action.generation}`;
|
|
610
|
+
const expectedIdentity = action => `${dynamic ? action.id : stageReferences.project(action.stageId)}:${action.generation}`;
|
|
611
|
+
let includedActionIdentities = new Set(actions.map(identity));
|
|
612
|
+
if (unfinished.some(action => !includedActionIdentities.has(expectedIdentity(action)))) {
|
|
517
613
|
projectedActions = selected.map(action => boundedAction(
|
|
518
614
|
action,
|
|
519
615
|
canonicalRunByAction.get(action.id),
|
|
520
616
|
stageReferences,
|
|
521
617
|
true,
|
|
618
|
+
dynamic,
|
|
522
619
|
));
|
|
523
620
|
actions = boundedJsonArray(projectedActions, COORDINATOR_MAX_ACTIONS_BYTES);
|
|
524
|
-
includedActionIdentities = new Set(actions.map(
|
|
621
|
+
includedActionIdentities = new Set(actions.map(identity));
|
|
525
622
|
}
|
|
526
|
-
if (unfinished.some(action => !includedActionIdentities.has(
|
|
623
|
+
if (unfinished.some(action => !includedActionIdentities.has(expectedIdentity(action)))) {
|
|
527
624
|
throw new Error('Active Actions cannot be represented within the Coordinator snapshot budget');
|
|
528
625
|
}
|
|
529
626
|
|
|
@@ -604,6 +701,34 @@ export class WorkItemCoordinator {
|
|
|
604
701
|
});
|
|
605
702
|
}
|
|
606
703
|
|
|
704
|
+
advance(mailboxId, options = {}) {
|
|
705
|
+
if (this.shuttingDown) throw new Error('Work Center Coordinator is shutting down');
|
|
706
|
+
const claim = this.store.claimCoordinatorMailbox(options.workItemId, this.ownerBootId, this.claimLeaseMs);
|
|
707
|
+
if (!claim) return null;
|
|
708
|
+
if (claim.id !== mailboxId) {
|
|
709
|
+
this.store.releaseCoordinatorMailboxClaim(
|
|
710
|
+
claim.id, this.ownerBootId, Number(claim.claim_epoch),
|
|
711
|
+
);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
const started = this.store.beginDynamicCoordinatorTurn(mailboxId, {
|
|
715
|
+
ownerBootId: this.ownerBootId,
|
|
716
|
+
claimEpoch: Number(claim.claim_epoch),
|
|
717
|
+
});
|
|
718
|
+
if (!started) {
|
|
719
|
+
this.store.releaseCoordinatorMailboxClaim(
|
|
720
|
+
claim.id, this.ownerBootId, Number(claim.claim_epoch),
|
|
721
|
+
);
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
options.onUpdate?.('coordinator.advance_started', started.detail);
|
|
725
|
+
const trigger = started.detail.messages?.find(message => message.turnId === started.turnId)?.trigger;
|
|
726
|
+
const text = `Automatic WorkItem advance trigger: ${JSON.stringify(trigger || { kind: claim.kind })}. `
|
|
727
|
+
+ 'Observe the current durable state and choose the next justified Actions, a genuine human request, '
|
|
728
|
+
+ 'or evidence-backed completion. Do not stop merely because the previous Action ended.';
|
|
729
|
+
return this.#scheduleTurn(started, { text, recovery: false, options });
|
|
730
|
+
}
|
|
731
|
+
|
|
607
732
|
recover(id, options = {}) {
|
|
608
733
|
if (this.shuttingDown) throw new Error('Work Center Coordinator is shutting down');
|
|
609
734
|
const detail = this.store.getWorkItemDetail(id);
|
|
@@ -746,7 +871,7 @@ export class WorkItemCoordinator {
|
|
|
746
871
|
: latestMessage;
|
|
747
872
|
const requestBody = {
|
|
748
873
|
model: resolved.model,
|
|
749
|
-
system: coordinatorSystemPrompt(language),
|
|
874
|
+
system: coordinatorSystemPrompt(language, started.detail),
|
|
750
875
|
messages: [{ role: 'user', content }],
|
|
751
876
|
maxTokens: Math.min(
|
|
752
877
|
resolveMaxOutputTokens(resolved.model, runtime.config),
|
|
@@ -799,8 +924,11 @@ export class WorkItemCoordinator {
|
|
|
799
924
|
}
|
|
800
925
|
normalized = normalizeCoordinatorResponse(result?.text, started.detail, {
|
|
801
926
|
recovery,
|
|
927
|
+
automatic: started.fence.automatic === true,
|
|
802
928
|
recoveryActionId: started.fence.recovery?.actionId || null,
|
|
929
|
+
availableVpIds: vps.map(vp => vp.id),
|
|
803
930
|
});
|
|
931
|
+
mutation = normalized.mutation || null;
|
|
804
932
|
if (normalized.decision.kind === 'replan') {
|
|
805
933
|
finalizedCriteria(started.detail, normalized.decision.contractPatch);
|
|
806
934
|
mutation = applyCoordinatorReplan({
|
|
@@ -873,7 +1001,9 @@ export class WorkItemCoordinator {
|
|
|
873
1001
|
attemptCount,
|
|
874
1002
|
}, started.fence);
|
|
875
1003
|
if (!detail) return this.store.getWorkItemDetail(started.detail.id);
|
|
876
|
-
options.onUpdate?.(
|
|
1004
|
+
options.onUpdate?.(started.fence.automatic === true
|
|
1005
|
+
? 'coordinator.advance_completed'
|
|
1006
|
+
: recovery ? 'coordinator.recovery_completed' : 'coordinator.turn_completed', detail);
|
|
877
1007
|
return detail;
|
|
878
1008
|
} catch (error) {
|
|
879
1009
|
if (providerTurn?.status === 'responded') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
export const WORK_CENTER_SCHEMA_VERSION =
|
|
3
|
+
export const WORK_CENTER_SCHEMA_VERSION = 37;
|
|
4
4
|
|
|
5
5
|
const MIGRATIONS = [
|
|
6
6
|
['23-conversation-stream', migrateConversationStream],
|
|
@@ -16,6 +16,8 @@ const MIGRATIONS = [
|
|
|
16
16
|
['33-coordinator-provider-turns', migrateCoordinatorProviderTurns],
|
|
17
17
|
['34-engine-turn-status-repair', repairEngineTurnStatusContract],
|
|
18
18
|
['35-coordinator-provider-claims', migrateCoordinatorProviderClaims],
|
|
19
|
+
['36-dynamic-coordination', migrateDynamicCoordination],
|
|
20
|
+
['37-run-acceptance-checks', migrateRunAcceptanceChecks],
|
|
19
21
|
];
|
|
20
22
|
|
|
21
23
|
const MIGRATION_ALIASES = new Map([
|
|
@@ -478,6 +480,54 @@ function migrateCoordinatorProviderClaims(db) {
|
|
|
478
480
|
`);
|
|
479
481
|
}
|
|
480
482
|
|
|
483
|
+
function migrateDynamicCoordination(db) {
|
|
484
|
+
if (!hasColumn(db, 'work_items', 'coordination_mode')) {
|
|
485
|
+
db.exec("ALTER TABLE work_items ADD COLUMN coordination_mode TEXT NOT NULL DEFAULT 'legacy'");
|
|
486
|
+
}
|
|
487
|
+
if (!hasColumn(db, 'work_items', 'final_result')) {
|
|
488
|
+
db.exec('ALTER TABLE work_items ADD COLUMN final_result TEXT');
|
|
489
|
+
}
|
|
490
|
+
if (!hasColumn(db, 'actions', 'source_action_ids')) {
|
|
491
|
+
db.exec("ALTER TABLE actions ADD COLUMN source_action_ids TEXT NOT NULL DEFAULT '[]'");
|
|
492
|
+
}
|
|
493
|
+
db.exec(`
|
|
494
|
+
CREATE INDEX IF NOT EXISTS idx_work_items_dynamic_status
|
|
495
|
+
ON work_items(coordination_mode, status, updated_at);
|
|
496
|
+
CREATE TRIGGER IF NOT EXISTS trg_work_item_final_result_immutable
|
|
497
|
+
BEFORE UPDATE OF final_result ON work_items
|
|
498
|
+
WHEN OLD.final_result IS NOT NULL AND NEW.final_result IS NOT OLD.final_result
|
|
499
|
+
BEGIN
|
|
500
|
+
SELECT RAISE(ABORT, 'WorkItem final result is immutable');
|
|
501
|
+
END;
|
|
502
|
+
`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function migrateRunAcceptanceChecks(db) {
|
|
506
|
+
if (!hasColumn(db, 'runs', 'acceptance_checks')) {
|
|
507
|
+
db.exec("ALTER TABLE runs ADD COLUMN acceptance_checks TEXT NOT NULL DEFAULT '[]'");
|
|
508
|
+
}
|
|
509
|
+
db.exec(`
|
|
510
|
+
DROP TRIGGER IF EXISTS trg_runs_terminal_identity_immutable;
|
|
511
|
+
CREATE TRIGGER IF NOT EXISTS trg_runs_terminal_identity_immutable
|
|
512
|
+
BEFORE UPDATE ON runs
|
|
513
|
+
WHEN OLD.terminal_status IS NOT NULL AND (
|
|
514
|
+
NEW.action_id IS NOT OLD.action_id OR NEW.work_item_id IS NOT OLD.work_item_id OR
|
|
515
|
+
NEW.owner_boot_id IS NOT OLD.owner_boot_id OR NEW.lease_epoch IS NOT OLD.lease_epoch OR
|
|
516
|
+
NEW.ordinal IS NOT OLD.ordinal OR NEW.started_at IS NOT OLD.started_at OR
|
|
517
|
+
NEW.status IS NOT OLD.status OR NEW.ended_at IS NOT OLD.ended_at OR
|
|
518
|
+
NEW.terminal_status IS NOT OLD.terminal_status OR NEW.terminal_at IS NOT OLD.terminal_at OR
|
|
519
|
+
NEW.response IS NOT OLD.response OR NEW.summary IS NOT OLD.summary OR
|
|
520
|
+
NEW.evidence IS NOT OLD.evidence OR NEW.acceptance_checks IS NOT OLD.acceptance_checks OR
|
|
521
|
+
NEW.waiting_reason IS NOT OLD.waiting_reason OR NEW.error IS NOT OLD.error OR
|
|
522
|
+
NEW.failure_kind IS NOT OLD.failure_kind OR NEW.failure_code IS NOT OLD.failure_code OR
|
|
523
|
+
NEW.review_decision IS NOT OLD.review_decision OR NEW.contract_patch IS NOT OLD.contract_patch OR
|
|
524
|
+
NEW.checkpoint IS NOT OLD.checkpoint)
|
|
525
|
+
BEGIN
|
|
526
|
+
SELECT RAISE(ABORT, 'terminal Run result is immutable');
|
|
527
|
+
END;
|
|
528
|
+
`);
|
|
529
|
+
}
|
|
530
|
+
|
|
481
531
|
function migrateReliabilityGuards(db) {
|
|
482
532
|
for (const [column, definition] of [
|
|
483
533
|
['dispatch_capability', "TEXT NOT NULL DEFAULT 'unknown'"],
|