@yeaft/webchat-agent 1.0.266 → 1.0.267
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 +5 -6
- 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/work-center/coordinator.js +117 -48
- package/yeaft/work-center/plan-mutation.js +17 -3
- package/yeaft/work-center/runner.js +15 -3
- package/yeaft/work-center/service.js +1 -56
- package/yeaft/work-center/workflow.js +12 -2
|
Binary file
|
package/package.json
CHANGED
|
@@ -24,7 +24,16 @@ const COORDINATOR_MAX_WORK_ITEM_BYTES = 14 * 1024;
|
|
|
24
24
|
const COORDINATOR_MAX_ACTIONS_BYTES = 34 * 1024;
|
|
25
25
|
const COORDINATOR_MAX_CONVERSATION_BYTES = 10 * 1024;
|
|
26
26
|
const COORDINATOR_MAX_STAGE_ID_BYTES = 256;
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
function coordinatorLanguage(value) {
|
|
29
|
+
return String(value || '').toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function coordinatorTemporaryError(language) {
|
|
33
|
+
return coordinatorLanguage(language) === 'zh'
|
|
34
|
+
? 'Work Center Coordinator 暂时不可用;系统会自动重试恢复'
|
|
35
|
+
: 'Work Center Coordinator is temporarily unavailable; automatic recovery will retry';
|
|
36
|
+
}
|
|
28
37
|
|
|
29
38
|
function truncateUtf8(value, maxBytes) {
|
|
30
39
|
const bytes = Buffer.from(String(value || ''), 'utf8');
|
|
@@ -172,6 +181,15 @@ Decision rules:
|
|
|
172
181
|
- Stage ids in the snapshot may be bounded aliases. Echo them exactly; the runtime resolves them to durable identities.
|
|
173
182
|
- Never return destructive cancellation. Tell the user to use the explicit cancel control instead.`;
|
|
174
183
|
|
|
184
|
+
function coordinatorSystemPrompt(language) {
|
|
185
|
+
const userLanguage = coordinatorLanguage(language) === 'zh'
|
|
186
|
+
? 'Simplified Chinese (zh-CN)'
|
|
187
|
+
: 'English';
|
|
188
|
+
return `${COORDINATOR_SYSTEM_PROMPT}
|
|
189
|
+
|
|
190
|
+
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.`;
|
|
191
|
+
}
|
|
192
|
+
|
|
175
193
|
function parseJsonObject(value) {
|
|
176
194
|
const source = String(value || '').trim();
|
|
177
195
|
if (!source) throw new Error('Work Center Coordinator returned an empty response');
|
|
@@ -196,25 +214,40 @@ function cleanText(value, limit, name) {
|
|
|
196
214
|
return text;
|
|
197
215
|
}
|
|
198
216
|
|
|
199
|
-
function permanentCoordinatorDiagnostic(cause, phase) {
|
|
217
|
+
function permanentCoordinatorDiagnostic(cause, phase, language) {
|
|
218
|
+
const zh = coordinatorLanguage(language) === 'zh';
|
|
200
219
|
if (cause instanceof LLMAuthError) {
|
|
201
|
-
return
|
|
220
|
+
return zh
|
|
221
|
+
? 'Work Center Coordinator 认证失败。请更新 Provider 凭据后再重试。'
|
|
222
|
+
: 'Work Center Coordinator authentication failed. Update the configured provider credentials before retrying this Action.';
|
|
202
223
|
}
|
|
203
224
|
if (cause instanceof LLMContextError) {
|
|
204
|
-
return
|
|
225
|
+
return zh
|
|
226
|
+
? 'Work Center Coordinator 超过模型上下文限制。请减少 WorkItem 上下文,或改用上下文窗口更大的模型后重试。'
|
|
227
|
+
: 'Work Center Coordinator exceeded the model context limit. Reduce the WorkItem context or select a model with a larger context window before retrying this Action.';
|
|
205
228
|
}
|
|
206
229
|
const detail = sanitizeDiagnosticText(cause?.message || String(cause || ''), 2_000);
|
|
207
|
-
const label =
|
|
208
|
-
?
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
230
|
+
const label = zh
|
|
231
|
+
? (phase === 'runtime'
|
|
232
|
+
? '无法加载运行时'
|
|
233
|
+
: phase === 'policy'
|
|
234
|
+
? '无法加载设置'
|
|
235
|
+
: phase === 'selection'
|
|
236
|
+
? '执行者或模型选择失败'
|
|
237
|
+
: 'Provider 请求失败')
|
|
238
|
+
: (phase === 'runtime'
|
|
239
|
+
? 'runtime could not be loaded'
|
|
240
|
+
: phase === 'policy'
|
|
241
|
+
? 'settings could not be loaded'
|
|
242
|
+
: phase === 'selection'
|
|
243
|
+
? 'executor or model selection failed'
|
|
244
|
+
: 'provider request failed');
|
|
245
|
+
return zh
|
|
246
|
+
? `Work Center Coordinator ${label}${detail ? `:${detail}` : '。'}`
|
|
247
|
+
: `Work Center Coordinator ${label}${detail ? `: ${detail}` : '.'}`;
|
|
215
248
|
}
|
|
216
249
|
|
|
217
|
-
function coordinatorExecutionError(cause, phase) {
|
|
250
|
+
function coordinatorExecutionError(cause, phase, language) {
|
|
218
251
|
if (cause?.coordinatorClassified === true) return cause;
|
|
219
252
|
const explicitlyPermanent = cause?.retryable === false;
|
|
220
253
|
const retryable = !explicitlyPermanent && (
|
|
@@ -223,8 +256,8 @@ function coordinatorExecutionError(cause, phase) {
|
|
|
223
256
|
|| (['runtime', 'policy'].includes(phase) && cause?.retryable === true)
|
|
224
257
|
);
|
|
225
258
|
const error = new Error(retryable
|
|
226
|
-
?
|
|
227
|
-
: permanentCoordinatorDiagnostic(cause, phase));
|
|
259
|
+
? coordinatorTemporaryError(language)
|
|
260
|
+
: permanentCoordinatorDiagnostic(cause, phase, language));
|
|
228
261
|
error.coordinatorClassified = true;
|
|
229
262
|
error.coordinatorRetryable = retryable;
|
|
230
263
|
error.coordinatorPhase = phase;
|
|
@@ -232,19 +265,34 @@ function coordinatorExecutionError(cause, phase) {
|
|
|
232
265
|
return error;
|
|
233
266
|
}
|
|
234
267
|
|
|
235
|
-
function permanentRecoveryDecision(error) {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
? '
|
|
239
|
-
:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
268
|
+
function permanentRecoveryDecision(error, language) {
|
|
269
|
+
const zh = coordinatorLanguage(language) === 'zh';
|
|
270
|
+
const diagnostic = String(error?.message || (zh
|
|
271
|
+
? 'Work Center Coordinator 无法自动恢复这个 Action'
|
|
272
|
+
: 'Work Center Coordinator cannot recover this Action automatically'));
|
|
273
|
+
const resolution = zh
|
|
274
|
+
? (error?.cause instanceof LLMAuthError
|
|
275
|
+
? '请更新 Provider 凭据,然后让 Yeaft 重试或重新规划失败的 Action。'
|
|
276
|
+
: error?.cause instanceof LLMContextError
|
|
277
|
+
? '请减少 WorkItem 上下文,或选择上下文窗口更大的模型,然后让 Yeaft 重试或重新规划失败的 Action。'
|
|
278
|
+
: error?.coordinatorPhase === 'selection'
|
|
279
|
+
? '请配置可用的 VP 和模型,然后让 Yeaft 重试或重新规划失败的 Action。'
|
|
280
|
+
: error?.coordinatorPhase === 'policy'
|
|
281
|
+
? '请修正 Work Center 设置,然后让 Yeaft 重试或重新规划失败的 Action。'
|
|
282
|
+
: '请修正 Coordinator 运行时或 Provider 配置,然后让 Yeaft 重试或重新规划失败的 Action。')
|
|
283
|
+
: (error?.cause instanceof LLMAuthError
|
|
284
|
+
? 'Update the provider credentials, then tell Yeaft to retry or replan the failed Action.'
|
|
285
|
+
: error?.cause instanceof LLMContextError
|
|
286
|
+
? 'Reduce the WorkItem context or choose a model with a larger context window, then tell Yeaft to retry or replan the failed Action.'
|
|
287
|
+
: error?.coordinatorPhase === 'selection'
|
|
288
|
+
? 'Configure an available VP and model, then tell Yeaft to retry or replan the failed Action.'
|
|
289
|
+
: error?.coordinatorPhase === 'policy'
|
|
290
|
+
? 'Correct the Work Center settings, then tell Yeaft to retry or replan the failed Action.'
|
|
291
|
+
: 'Correct the Coordinator runtime or provider configuration, then tell Yeaft to retry or replan the failed Action.');
|
|
246
292
|
return {
|
|
247
|
-
reply:
|
|
293
|
+
reply: zh
|
|
294
|
+
? `${diagnostic} 为避免重复尝试,自动恢复已停止。`
|
|
295
|
+
: `${diagnostic} Automatic recovery stopped to avoid repeated attempts.`,
|
|
248
296
|
decision: {
|
|
249
297
|
kind: 'request_human',
|
|
250
298
|
reason: `Automatic recovery stopped after a non-retryable ${error?.coordinatorPhase || 'Coordinator'} error`,
|
|
@@ -256,6 +304,18 @@ function permanentRecoveryDecision(error) {
|
|
|
256
304
|
};
|
|
257
305
|
}
|
|
258
306
|
|
|
307
|
+
function coordinatorDecisionError(error, language) {
|
|
308
|
+
const diagnostic = sanitizeDiagnosticText(error?.message || String(error || ''), 2_000);
|
|
309
|
+
const wrapped = new Error(coordinatorLanguage(language) === 'zh'
|
|
310
|
+
? 'Work Center Coordinator 未能生成有效回复。你的消息已经保留,请重试。'
|
|
311
|
+
: 'Work Center Coordinator could not produce a valid response. Your message was preserved; try again.');
|
|
312
|
+
wrapped.coordinatorClassified = true;
|
|
313
|
+
wrapped.coordinatorRetryable = false;
|
|
314
|
+
wrapped.coordinatorPhase = 'decision';
|
|
315
|
+
wrapped.cause = diagnostic ? new Error(diagnostic) : error;
|
|
316
|
+
return wrapped;
|
|
317
|
+
}
|
|
318
|
+
|
|
259
319
|
function normalizeGuidance(value, detail) {
|
|
260
320
|
if (!Array.isArray(value) || value.length < 1 || value.length > 8) {
|
|
261
321
|
throw new Error('Work Center Coordinator guidance requires between 1 and 8 targets');
|
|
@@ -303,9 +363,7 @@ export function normalizeCoordinatorResponse(value, detail, options = {}) {
|
|
|
303
363
|
: {};
|
|
304
364
|
const allowedKinds = options.recovery === true
|
|
305
365
|
? ['guide_actions', 'replan', 'request_human']
|
|
306
|
-
:
|
|
307
|
-
? ['guide_actions', 'replan']
|
|
308
|
-
: ['answer', 'guide_actions', 'replan'];
|
|
366
|
+
: ['answer', 'guide_actions', 'replan'];
|
|
309
367
|
const kind = allowedKinds.includes(source.kind) ? source.kind : '';
|
|
310
368
|
if (!kind) throw new Error('Work Center Coordinator decision kind is invalid');
|
|
311
369
|
const reason = cleanText(source.reason, 2_000, 'decision reason');
|
|
@@ -474,6 +532,9 @@ export class WorkItemCoordinator {
|
|
|
474
532
|
this.policyProvider = typeof options.policyProvider === 'function' ? options.policyProvider : async () => ({});
|
|
475
533
|
this.registry = options.registry;
|
|
476
534
|
this.attachmentRoot = options.attachmentRoot || null;
|
|
535
|
+
this.languageProvider = typeof options.languageProvider === 'function'
|
|
536
|
+
? options.languageProvider
|
|
537
|
+
: runtime => runtime?.config?.language || 'en';
|
|
477
538
|
this.activeTurns = new Map();
|
|
478
539
|
this.activeTasks = new Map();
|
|
479
540
|
this.shuttingDown = false;
|
|
@@ -497,15 +558,12 @@ export class WorkItemCoordinator {
|
|
|
497
558
|
}, {
|
|
498
559
|
attachments: input.attachments,
|
|
499
560
|
addedAttachments,
|
|
500
|
-
recovery: input.recovery,
|
|
501
|
-
requireWaitingRecovery: input.controlRequired === true,
|
|
502
561
|
});
|
|
503
562
|
if (!started) throw new Error(`WorkItem not found: ${id}`);
|
|
504
563
|
options.onUpdate?.('coordinator.turn_started', started.detail);
|
|
505
564
|
return this.#scheduleTurn(started, {
|
|
506
565
|
text: promptText,
|
|
507
566
|
recovery: false,
|
|
508
|
-
controlRequired: input.controlRequired === true,
|
|
509
567
|
addedAttachments,
|
|
510
568
|
options,
|
|
511
569
|
});
|
|
@@ -541,17 +599,17 @@ export class WorkItemCoordinator {
|
|
|
541
599
|
const text = `Action stage "${action.stageId}" failed. Decide the next safe control transition. `
|
|
542
600
|
+ 'Failure is not a terminal WorkItem state: guide or replan executable work whenever possible. '
|
|
543
601
|
+ 'Request human input only when the snapshot lacks information required for a safe decision.';
|
|
544
|
-
return this.#scheduleTurn(started, { text, recovery: true,
|
|
602
|
+
return this.#scheduleTurn(started, { text, recovery: true, options });
|
|
545
603
|
}
|
|
546
604
|
|
|
547
605
|
#scheduleTurn(started, {
|
|
548
|
-
text, recovery,
|
|
606
|
+
text, recovery, addedAttachments = [], options,
|
|
549
607
|
}) {
|
|
550
608
|
const abortController = new AbortController();
|
|
551
609
|
this.activeTurns.set(started.turnId, abortController);
|
|
552
610
|
const task = new Promise(resolve => setTimeout(resolve, 0))
|
|
553
611
|
.then(() => this.#executeTurn(started, {
|
|
554
|
-
text, recovery,
|
|
612
|
+
text, recovery, addedAttachments, options, abortController,
|
|
555
613
|
}))
|
|
556
614
|
.finally(() => {
|
|
557
615
|
this.activeTurns.delete(started.turnId);
|
|
@@ -562,7 +620,7 @@ export class WorkItemCoordinator {
|
|
|
562
620
|
}
|
|
563
621
|
|
|
564
622
|
async #executeTurn(started, {
|
|
565
|
-
text, recovery,
|
|
623
|
+
text, recovery, addedAttachments, options, abortController,
|
|
566
624
|
}) {
|
|
567
625
|
try {
|
|
568
626
|
let normalized = null;
|
|
@@ -570,6 +628,7 @@ export class WorkItemCoordinator {
|
|
|
570
628
|
let attemptCount = 0;
|
|
571
629
|
let lastError = null;
|
|
572
630
|
const snapshotText = coordinatorSnapshotText(started.detail);
|
|
631
|
+
let language = 'en';
|
|
573
632
|
const attachmentContext = !recovery && this.attachmentRoot
|
|
574
633
|
? buildWorkItemAttachmentContext({ ...started.detail, attachments: addedAttachments }, {
|
|
575
634
|
root: this.attachmentRoot,
|
|
@@ -581,13 +640,14 @@ export class WorkItemCoordinator {
|
|
|
581
640
|
let settings;
|
|
582
641
|
try {
|
|
583
642
|
runtime = await this.runtimeProvider();
|
|
643
|
+
language = coordinatorLanguage(await this.languageProvider(runtime));
|
|
584
644
|
} catch (error) {
|
|
585
|
-
throw coordinatorExecutionError(error, 'runtime');
|
|
645
|
+
throw coordinatorExecutionError(error, 'runtime', language);
|
|
586
646
|
}
|
|
587
647
|
try {
|
|
588
648
|
settings = await this.policyProvider();
|
|
589
649
|
} catch (error) {
|
|
590
|
-
throw coordinatorExecutionError(error, 'policy');
|
|
650
|
+
throw coordinatorExecutionError(error, 'policy', language);
|
|
591
651
|
}
|
|
592
652
|
if (this.shuttingDown) throw new Error('Work Center Coordinator is shutting down');
|
|
593
653
|
let vps;
|
|
@@ -611,7 +671,7 @@ export class WorkItemCoordinator {
|
|
|
611
671
|
};
|
|
612
672
|
resolved = resolveWorkItemModel(runtime.config, assignment.vp, coordinatorPolicy);
|
|
613
673
|
} catch (error) {
|
|
614
|
-
throw coordinatorExecutionError(error, 'selection');
|
|
674
|
+
throw coordinatorExecutionError(error, 'selection', language);
|
|
615
675
|
}
|
|
616
676
|
const maxAttempts = recovery
|
|
617
677
|
? COORDINATOR_RECOVERY_DECISION_ATTEMPTS
|
|
@@ -632,7 +692,7 @@ export class WorkItemCoordinator {
|
|
|
632
692
|
result = await Promise.race([
|
|
633
693
|
runtime.adapter.call({
|
|
634
694
|
model: resolved.model,
|
|
635
|
-
system:
|
|
695
|
+
system: coordinatorSystemPrompt(language),
|
|
636
696
|
messages: [{ role: 'user', content }],
|
|
637
697
|
maxTokens: Math.min(
|
|
638
698
|
resolveMaxOutputTokens(resolved.model, runtime.config),
|
|
@@ -651,11 +711,10 @@ export class WorkItemCoordinator {
|
|
|
651
711
|
]);
|
|
652
712
|
} catch (error) {
|
|
653
713
|
if (abortController.signal.aborted || this.shuttingDown) throw error;
|
|
654
|
-
throw coordinatorExecutionError(error, 'provider');
|
|
714
|
+
throw coordinatorExecutionError(error, 'provider', language);
|
|
655
715
|
}
|
|
656
716
|
normalized = normalizeCoordinatorResponse(result?.text, started.detail, {
|
|
657
717
|
recovery,
|
|
658
|
-
controlRequired,
|
|
659
718
|
recoveryActionId: started.fence.recovery?.actionId || null,
|
|
660
719
|
});
|
|
661
720
|
if (normalized.decision.kind === 'replan') {
|
|
@@ -692,17 +751,27 @@ export class WorkItemCoordinator {
|
|
|
692
751
|
throw lastError || new Error('Work Center Coordinator was interrupted');
|
|
693
752
|
}
|
|
694
753
|
if (!normalized) {
|
|
695
|
-
if (!recovery
|
|
696
|
-
throw lastError
|
|
754
|
+
if (!recovery) {
|
|
755
|
+
throw lastError?.coordinatorClassified
|
|
756
|
+
? lastError
|
|
757
|
+
: coordinatorDecisionError(
|
|
758
|
+
lastError || new Error('Work Center Coordinator did not produce a decision'),
|
|
759
|
+
language,
|
|
760
|
+
);
|
|
697
761
|
}
|
|
762
|
+
if (lastError?.coordinatorRetryable) throw lastError;
|
|
698
763
|
normalized = lastError?.coordinatorClassified
|
|
699
|
-
? permanentRecoveryDecision(lastError)
|
|
764
|
+
? permanentRecoveryDecision(lastError, language)
|
|
700
765
|
: {
|
|
701
|
-
reply:
|
|
766
|
+
reply: language === 'zh'
|
|
767
|
+
? '自动恢复无法确定安全、可执行的下一步,需要你补充信息。'
|
|
768
|
+
: 'Automatic recovery could not choose a safe executable next step. Human input is required.',
|
|
702
769
|
decision: {
|
|
703
770
|
kind: 'request_human',
|
|
704
771
|
reason: 'Automatic recovery exhausted its bounded decision attempts',
|
|
705
|
-
question:
|
|
772
|
+
question: language === 'zh'
|
|
773
|
+
? '请查看失败的 Action,并补充安全重试或重新规划所需的决定或约束。'
|
|
774
|
+
: 'Review the failed Action and provide the missing decision or constraint needed to retry or replan it safely.',
|
|
706
775
|
contractPatch: null,
|
|
707
776
|
guidance: [],
|
|
708
777
|
actions: [],
|
|
@@ -4,9 +4,12 @@ import {
|
|
|
4
4
|
canonicalActionId,
|
|
5
5
|
canonicalExplicitActionId,
|
|
6
6
|
canonicalExplicitActionIds,
|
|
7
|
+
MAX_WORK_ITEM_ACTIONS,
|
|
7
8
|
validateGeneratedCompletionGate,
|
|
8
9
|
} from './workflow.js';
|
|
9
10
|
|
|
11
|
+
export const MAX_REPLAN_ADDED_ACTIONS = 8;
|
|
12
|
+
|
|
10
13
|
function cleanProposalId(value) {
|
|
11
14
|
const id = typeof value === 'string' ? value.trim().slice(0, 128) : '';
|
|
12
15
|
if (!id) throw new Error('Work Center plan proposal requires proposalId');
|
|
@@ -185,7 +188,10 @@ export function applyAdditivePlanProposal({ workItem, actions, proposal, availab
|
|
|
185
188
|
workItemType: workItem.workflowSnapshot.workItemType,
|
|
186
189
|
actions: orderedActions,
|
|
187
190
|
};
|
|
188
|
-
const workflowSnapshot = applyGeneratedPlan(synthetic, rawPlan, {
|
|
191
|
+
const workflowSnapshot = applyGeneratedPlan(synthetic, rawPlan, {
|
|
192
|
+
availableVpIds,
|
|
193
|
+
maxActions: MAX_WORK_ITEM_ACTIONS,
|
|
194
|
+
});
|
|
189
195
|
const addedStages = workflowSnapshot.stages.filter(stage => addedIds.has(stage.id));
|
|
190
196
|
return {
|
|
191
197
|
proposalId,
|
|
@@ -269,7 +275,7 @@ export function applyCoordinatorReplan({ workItem, actions, proposal, availableV
|
|
|
269
275
|
const workflowSnapshot = applyGeneratedPlan(synthetic, {
|
|
270
276
|
workItemType: workItem.workflowSnapshot.workItemType,
|
|
271
277
|
actions: [...completedInputs, ...normalizedFuture],
|
|
272
|
-
}, { availableVpIds });
|
|
278
|
+
}, { availableVpIds, maxActions: MAX_WORK_ITEM_ACTIONS });
|
|
273
279
|
const stageById = new Map(workflowSnapshot.stages.map(stage => [stage.id, stage]));
|
|
274
280
|
|
|
275
281
|
return {
|
|
@@ -305,6 +311,14 @@ export function applyReplanMutation({ workItem, action, actions, proposal, avail
|
|
|
305
311
|
throw new Error('Work Center replan Action is missing its frozen candidate set');
|
|
306
312
|
}
|
|
307
313
|
const candidateIds = barrier.candidateActionIds;
|
|
314
|
+
for (const field of ['retain', 'replace', 'remove', 'add']) {
|
|
315
|
+
if (!Array.isArray(proposal[field])) {
|
|
316
|
+
throw new Error(`Work Center replan mutation requires ${field} to be an array`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (proposal.add.length > MAX_REPLAN_ADDED_ACTIONS) {
|
|
320
|
+
throw new Error(`Work Center replan mutation can add at most ${MAX_REPLAN_ADDED_ACTIONS} new Actions`);
|
|
321
|
+
}
|
|
308
322
|
const actionById = new Map(actions.map(candidate => [candidate.id, candidate]));
|
|
309
323
|
const candidates = new Map(candidateIds.map(id => [id, actionById.get(id)]));
|
|
310
324
|
for (const [id, candidate] of candidates) {
|
|
@@ -377,7 +391,7 @@ export function applyReplanMutation({ workItem, action, actions, proposal, avail
|
|
|
377
391
|
const workflowSnapshot = applyGeneratedPlan(synthetic, {
|
|
378
392
|
workItemType: workItem.workflowSnapshot.workItemType,
|
|
379
393
|
actions: stableTopologicalActions([...completedInputs, ...retainedInputs, ...replacementInputs, ...addedInputs]),
|
|
380
|
-
}, { availableVpIds });
|
|
394
|
+
}, { availableVpIds, maxActions: MAX_WORK_ITEM_ACTIONS });
|
|
381
395
|
const stageById = new Map(workflowSnapshot.stages.map(stage => [stage.id, stage]));
|
|
382
396
|
const context = (Array.isArray(action.context) ? action.context : [])
|
|
383
397
|
.filter(entry => entry?.type !== 'replan-barrier' && entry?.type !== 'input');
|
|
@@ -32,7 +32,11 @@ import { MCPManager } from '../mcp.js';
|
|
|
32
32
|
import { buildMcpFlattenedTools } from '../tools/mcp-tools.js';
|
|
33
33
|
import { recallWorkspaceSessionContext } from './workspace-context.js';
|
|
34
34
|
import { applyGeneratedPlan, BUILT_IN_ACTION_TYPES } from './workflow.js';
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
applyAdditivePlanProposal,
|
|
37
|
+
applyReplanMutation,
|
|
38
|
+
MAX_REPLAN_ADDED_ACTIONS,
|
|
39
|
+
} from './plan-mutation.js';
|
|
36
40
|
import { normalizeContractPatch, validateCompletedResult } from './completion-contract.js';
|
|
37
41
|
import { normalizeEvidence } from './evidence.js';
|
|
38
42
|
import {
|
|
@@ -468,7 +472,7 @@ export function createSubmitWorkItemReplanTool({ vps, workItem, action, actions,
|
|
|
468
472
|
const candidateIdSchema = candidateIds.length > 0
|
|
469
473
|
? { type: 'string', enum: candidateIds }
|
|
470
474
|
: { type: 'string' };
|
|
471
|
-
const candidateLimit =
|
|
475
|
+
const candidateLimit = candidateIds.length;
|
|
472
476
|
const classification = { type: 'object', additionalProperties: false,
|
|
473
477
|
required: ['actionId', 'action'], properties: {
|
|
474
478
|
actionId: candidateIdSchema,
|
|
@@ -486,11 +490,19 @@ export function createSubmitWorkItemReplanTool({ vps, workItem, action, actions,
|
|
|
486
490
|
retain: { type: 'array', maxItems: candidateLimit, items: classification },
|
|
487
491
|
replace: { type: 'array', maxItems: candidateLimit, items: classification },
|
|
488
492
|
remove: { type: 'array', maxItems: candidateLimit, uniqueItems: true, items: candidateIdSchema },
|
|
489
|
-
add: { type: 'array', maxItems:
|
|
493
|
+
add: { type: 'array', maxItems: MAX_REPLAN_ADDED_ACTIONS, items: plannedActionSchema(vpIds) },
|
|
490
494
|
} },
|
|
491
495
|
async execute(input, ctx = {}) {
|
|
492
496
|
if (!isRunActive()) throw new Error('Work Center Run is no longer active');
|
|
493
497
|
if (collector.value) throw new Error('A WorkItem plan was already submitted for this Run');
|
|
498
|
+
applyReplanMutation({
|
|
499
|
+
workItem,
|
|
500
|
+
action,
|
|
501
|
+
actions,
|
|
502
|
+
proposal: input,
|
|
503
|
+
availableVpIds: vpIds,
|
|
504
|
+
});
|
|
505
|
+
if (!isRunActive()) throw new Error('Work Center Run is no longer active');
|
|
494
506
|
collector.value = structuredClone(input);
|
|
495
507
|
ctx.requestEndTurn?.({ kind: 'work_item_replan_submitted', proposalId: input.proposalId });
|
|
496
508
|
return JSON.stringify({ submitted: true, proposalId: input.proposalId });
|
|
@@ -60,19 +60,6 @@ function parseBoardCursor(value) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function coordinatorHumanRequest(detail, action, generation) {
|
|
64
|
-
if (detail?.status !== 'waiting'
|
|
65
|
-
|| action?.status !== 'waiting'
|
|
66
|
-
|| Number(action.generation) !== Number(generation)) return null;
|
|
67
|
-
return [...(Array.isArray(detail.messages) ? detail.messages : [])].reverse().find(message => (
|
|
68
|
-
message?.role === 'assistant'
|
|
69
|
-
&& message.status === 'completed'
|
|
70
|
-
&& message.decision?.kind === 'request_human'
|
|
71
|
-
&& message.recovery?.actionId === action.id
|
|
72
|
-
&& message.recovery?.actionGeneration === action.generation
|
|
73
|
-
)) || null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
63
|
function listBoardItems(store, payload) {
|
|
77
64
|
const limit = Math.min(Math.max(Number(payload.limit) || 100, 1), 200);
|
|
78
65
|
const cursor = parseBoardCursor(payload.cursor);
|
|
@@ -387,49 +374,7 @@ export class WorkCenterService {
|
|
|
387
374
|
throw new Error('generation must be a positive integer');
|
|
388
375
|
}
|
|
389
376
|
const workItem = this.#requiredItem(id);
|
|
390
|
-
|
|
391
|
-
const humanRequest = coordinatorHumanRequest(workItem, targetAction, generation);
|
|
392
|
-
if (humanRequest) {
|
|
393
|
-
let addedAttachments = [];
|
|
394
|
-
let turn;
|
|
395
|
-
try {
|
|
396
|
-
addedAttachments = appendWorkItemAttachments(workItem.attachments, payload.files, {
|
|
397
|
-
root: this.attachmentRoot,
|
|
398
|
-
workItemId: id,
|
|
399
|
-
});
|
|
400
|
-
turn = this.coordinator.message(id, {
|
|
401
|
-
text: typeof payload.text === 'string' ? payload.text : '',
|
|
402
|
-
revision: payload.revision,
|
|
403
|
-
planRevision: workItem.planRevision,
|
|
404
|
-
ledgerRevision: workItem.ledgerRevision,
|
|
405
|
-
coordinatorRevision: workItem.coordinatorRevision,
|
|
406
|
-
controlRequired: true,
|
|
407
|
-
recovery: { ...humanRequest.recovery },
|
|
408
|
-
addedAttachments,
|
|
409
|
-
attachments: [...(workItem.attachments || []), ...addedAttachments],
|
|
410
|
-
}, {
|
|
411
|
-
onUpdate: (type, nextWorkItem) => {
|
|
412
|
-
this.watcher.abortInvalidWorkItemRuns(id);
|
|
413
|
-
this.#emit({ type, workItem: nextWorkItem });
|
|
414
|
-
},
|
|
415
|
-
});
|
|
416
|
-
} catch (error) {
|
|
417
|
-
try {
|
|
418
|
-
if ((workItem.attachments || []).length === 0 && addedAttachments.length > 0) {
|
|
419
|
-
removeWorkItemAttachments(this.attachmentRoot, id);
|
|
420
|
-
} else {
|
|
421
|
-
removeWorkItemAttachmentFiles(this.attachmentRoot, id, addedAttachments);
|
|
422
|
-
}
|
|
423
|
-
} catch {}
|
|
424
|
-
throw error;
|
|
425
|
-
}
|
|
426
|
-
turn.task.catch(() => {});
|
|
427
|
-
return {
|
|
428
|
-
accepted: true,
|
|
429
|
-
routedTo: 'coordinator',
|
|
430
|
-
turnId: turn.detail.messages?.at(-1)?.turnId || null,
|
|
431
|
-
};
|
|
432
|
-
}
|
|
377
|
+
this.#requiredAction(workItem, payload.actionId);
|
|
433
378
|
let addedAttachments = [];
|
|
434
379
|
let detail;
|
|
435
380
|
try {
|
|
@@ -464,6 +464,9 @@ export function validateGeneratedCompletionGate(stages) {
|
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
export const MAX_INITIAL_PLAN_ACTIONS = 8;
|
|
468
|
+
export const MAX_WORK_ITEM_ACTIONS = 64;
|
|
469
|
+
|
|
467
470
|
export function applyGeneratedPlan(workItem, rawPlan, options = {}) {
|
|
468
471
|
const source = workflowFrom(workItem);
|
|
469
472
|
const forceGraph = options.forceGraph !== false;
|
|
@@ -481,8 +484,15 @@ export function applyGeneratedPlan(workItem, rawPlan, options = {}) {
|
|
|
481
484
|
}
|
|
482
485
|
const reservedStageIds = new Set((options.reservedStageIds || [])
|
|
483
486
|
.map(id => String(id || '').trim()).filter(Boolean));
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
const maxActions = Math.min(
|
|
488
|
+
Math.max(Number(options.maxActions) || MAX_INITIAL_PLAN_ACTIONS, 1),
|
|
489
|
+
MAX_WORK_ITEM_ACTIONS,
|
|
490
|
+
);
|
|
491
|
+
if (!Array.isArray(rawPlan.actions) || rawPlan.actions.length < 1
|
|
492
|
+
|| rawPlan.actions.length > maxActions) {
|
|
493
|
+
throw new Error(maxActions === MAX_INITIAL_PLAN_ACTIONS
|
|
494
|
+
? 'AI-planned triage requires between 1 and 8 task-specific Actions'
|
|
495
|
+
: `AI-planned graph requires between 1 and ${maxActions} task-specific Actions`);
|
|
486
496
|
}
|
|
487
497
|
const availableVpIds = Array.isArray(options.availableVpIds)
|
|
488
498
|
? new Set(options.availableVpIds.map(id => String(id || '').trim()).filter(Boolean))
|