@yeaft/webchat-agent 1.0.531 → 1.0.532
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 +217 -104
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/work-center/bridge.js +3 -2
- package/yeaft/work-center/capabilities.js +44 -0
- package/yeaft/work-center/completion-contract.js +11 -3
- package/yeaft/work-center/controller.js +8 -1
- package/yeaft/work-center/coordinator.js +42 -21
- package/yeaft/work-center/dynamic-coordination.js +51 -2
- package/yeaft/work-center/goal-state.js +135 -0
- package/yeaft/work-center/projection.js +57 -8
- package/yeaft/work-center/resource-control.js +460 -0
- package/yeaft/work-center/runner.js +10 -20
- package/yeaft/work-center/service.js +28 -7
- package/yeaft/work-center/store.js +161 -61
- package/yeaft/work-center/workflow.js +7 -3
|
@@ -17,6 +17,7 @@ import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import { sessionMessageQuotePrompt } from '../session-message-quote.js';
|
|
19
19
|
import { buildWorkItemAttachmentContext } from './attachments.js';
|
|
20
|
+
import { WorkCenterResourceAdapter } from './resource-control.js';
|
|
20
21
|
import { withUsageAccounting } from '../llm/usage-accounting.js';
|
|
21
22
|
import {
|
|
22
23
|
commitActionWorktree,
|
|
@@ -51,20 +52,7 @@ import {
|
|
|
51
52
|
renderMainlineContextSnapshot,
|
|
52
53
|
} from './mainline-projection.js';
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
'FileRead',
|
|
56
|
-
'FileWrite',
|
|
57
|
-
'FileEdit',
|
|
58
|
-
'ApplyPatch',
|
|
59
|
-
'Glob',
|
|
60
|
-
'Grep',
|
|
61
|
-
'ListDir',
|
|
62
|
-
'Bash',
|
|
63
|
-
'WebSearch',
|
|
64
|
-
'WebFetch',
|
|
65
|
-
'ViewImage',
|
|
66
|
-
'Skill',
|
|
67
|
-
]);
|
|
55
|
+
import { WORK_ITEM_TOOL_NAMES, workItemBuiltinToolNames } from './capabilities.js';
|
|
68
56
|
const WORK_ITEM_TOOL_ALLOWLIST = new Set(WORK_ITEM_TOOL_NAMES);
|
|
69
57
|
const DEFAULT_PROGRESS_INTERVAL_MS = 200;
|
|
70
58
|
const ACTION_INPUT_QUOTE_MAX_BYTES = 8 * 1024;
|
|
@@ -272,7 +260,7 @@ function assertToolInput(toolName, input, workDir, attachmentFiles) {
|
|
|
272
260
|
|
|
273
261
|
export function workItemToolPolicySnapshot(workDir, attachmentRefs = [], extraToolNames = []) {
|
|
274
262
|
const hasAttachments = attachmentRefs.length > 0;
|
|
275
|
-
const builtInTools =
|
|
263
|
+
const builtInTools = workItemBuiltinToolNames(hasAttachments);
|
|
276
264
|
return {
|
|
277
265
|
policyVersion: 1,
|
|
278
266
|
allowedToolNames: [...builtInTools, ...extraToolNames],
|
|
@@ -427,7 +415,7 @@ export function createSubmitWorkItemPlanTool({
|
|
|
427
415
|
properties: {
|
|
428
416
|
summary: { type: 'string', minLength: 1, maxLength: 2_000 },
|
|
429
417
|
evidence: { type: 'array', minItems: 1, maxItems: 20, items: { type: 'string', minLength: 1, maxLength: 1_000 } },
|
|
430
|
-
acceptanceChecks: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['criterion', 'status', 'evidence'], properties: { criterion: { type: 'string' }, status: { type: 'string', enum: ['passed', 'deferred', 'not_applicable'] }, evidence: { type: 'string', minLength: 1, maxLength: 1_000 } } } },
|
|
418
|
+
acceptanceChecks: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['criterion', 'status', 'evidence'], properties: { criterion: { type: 'string' }, status: { type: 'string', enum: ['passed', 'failed', 'deferred', 'not_applicable'] }, evidence: { type: 'string', minLength: 1, maxLength: 1_000 } } } },
|
|
431
419
|
contractPatch: { type: 'object', additionalProperties: false, required: ['title', 'goal', 'acceptanceCriteria'], properties: { title: { type: 'string', minLength: 1, maxLength: 200 }, goal: { type: 'string', minLength: 1, maxLength: 8_000 }, acceptanceCriteria: { type: 'array', minItems: 1, items: { type: 'string', minLength: 1, maxLength: 2_000 } } } },
|
|
432
420
|
workItemType: { type: 'string', minLength: 1, maxLength: 64 },
|
|
433
421
|
actions: { type: 'array', minItems: 1, maxItems: 8, items: { type: 'object', additionalProperties: false, required: ['id', 'name', 'type', 'objective', 'approach', 'expectedOutcome', 'candidateVpIds', 'assignmentReason', 'dependsOnActionIds', 'workspaceMode'], properties: {
|
|
@@ -483,7 +471,7 @@ function terminalPlanningFields(options = {}) {
|
|
|
483
471
|
return {
|
|
484
472
|
summary: { type: 'string', minLength: 1, maxLength: 2_000 },
|
|
485
473
|
evidence: { type: 'array', minItems: 1, maxItems: 20, items: { type: 'string', minLength: 1, maxLength: 1_000 } },
|
|
486
|
-
acceptanceChecks: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['criterion', 'status', 'evidence'], properties: { criterion: { type: 'string' }, status: { type: 'string', enum: ['passed', 'deferred', 'not_applicable'] }, evidence: { type: 'string', minLength: 1, maxLength: 1_000 } } } },
|
|
474
|
+
acceptanceChecks: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['criterion', 'status', 'evidence'], properties: { criterion: { type: 'string' }, status: { type: 'string', enum: ['passed', 'failed', 'deferred', 'not_applicable'] }, evidence: { type: 'string', minLength: 1, maxLength: 1_000 } } } },
|
|
487
475
|
...(options.review === true ? {
|
|
488
476
|
reviewDecision: { type: 'string', const: 'changes_requested' },
|
|
489
477
|
} : {}),
|
|
@@ -748,7 +736,7 @@ function completionContract(action, workItem) {
|
|
|
748
736
|
: '';
|
|
749
737
|
const acceptanceChecks = (workItem?.acceptanceCriteria || []).map(criterion => ({
|
|
750
738
|
criterion,
|
|
751
|
-
status: 'passed|deferred|not_applicable',
|
|
739
|
+
status: 'passed|failed|deferred|not_applicable',
|
|
752
740
|
evidence: 'specific evidence reference',
|
|
753
741
|
}));
|
|
754
742
|
return `${toolSubmission}\n\nYou are executing one Work Center Action. Before the terminal JSON, write a concise user-facing response describing what you did and the result. Do not include raw tool output or secrets. End your response with exactly one JSON object, preferably in a json code fence:\n{
|
|
@@ -759,7 +747,7 @@ function completionContract(action, workItem) {
|
|
|
759
747
|
"acceptanceChecks": ${JSON.stringify(acceptanceChecks)},
|
|
760
748
|
"waitingReason": null,
|
|
761
749
|
"error": null${reviewField}${triageField}${planField}
|
|
762
|
-
}\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, deferred, or not_applicable and a non-empty evidence reference. Report every user-consumable file, URL, PR, or commit in outputs; evidence proves work, while outputs tell the user where the deliverable is. Triage must use its proposed criteria when submitting a contractPatch. An intermediate Action may defer criteria outside its task-specific expected result; the final deliver Action, and an approved review with no downstream work, require every criterion to pass. If a criterion is no longer applicable,
|
|
750
|
+
}\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, failed, deferred, or not_applicable and a non-empty evidence reference. Report every user-consumable file, URL, PR, or commit in outputs; evidence proves work, while outputs tell the user where the deliverable is. Triage must use its proposed criteria when submitting a contractPatch. An intermediate Action may defer criteria outside its task-specific expected result; the final deliver Action, and an approved review with no downstream work, require every criterion to pass. If a criterion is no longer applicable, request user confirmation of any contract change instead of pretending it passed. For response delivery, provide the user-facing conclusion in summary with concrete evidence; no artificial file or PR is required. This is a deterministic submission gate, not independent proof: later verification and delivery Actions must verify the claims. A model turn ending is not completion. Use waiting when user or external input is required. Use retryable only for a transient failure. Do not start background jobs or delegate this Action.`;
|
|
763
751
|
}
|
|
764
752
|
|
|
765
753
|
function safeCheckpointUrl(value) {
|
|
@@ -1163,6 +1151,7 @@ export class WorkItemRunner {
|
|
|
1163
1151
|
)
|
|
1164
1152
|
: null;
|
|
1165
1153
|
const isRunActive = () => !signal.aborted
|
|
1154
|
+
&& !this.store.isExecutionStopped?.(workItem.id)
|
|
1166
1155
|
&& this.store.isActiveRun(run.id, ownerBootId, run.leaseEpoch);
|
|
1167
1156
|
const workspaceRuntime = await this.#workspaceRuntime(workspaceDir, workDir, isRunActive);
|
|
1168
1157
|
const mcpToolNames = workspaceRuntime.mcpTools.map(tool => tool.name);
|
|
@@ -1337,7 +1326,8 @@ export class WorkItemRunner {
|
|
|
1337
1326
|
return onProgress(currentProgress());
|
|
1338
1327
|
};
|
|
1339
1328
|
if (typeof registerProgressReader === 'function') registerProgressReader(currentProgress);
|
|
1340
|
-
const adapter = withUsageAccounting(
|
|
1329
|
+
const adapter = withUsageAccounting(
|
|
1330
|
+
new WorkCenterResourceAdapter(runtime.adapter, this.store, workItem.id, run.id), usage => {
|
|
1341
1331
|
usageStats.inputTokens += usage.inputTokens;
|
|
1342
1332
|
usageStats.outputTokens += usage.outputTokens;
|
|
1343
1333
|
usageStats.cacheReadTokens += usage.cacheReadTokens;
|
|
@@ -247,13 +247,16 @@ export class WorkCenterService {
|
|
|
247
247
|
workItemId,
|
|
248
248
|
});
|
|
249
249
|
const shouldStart = payload.start === undefined ? settings.startImmediately : payload.start !== false;
|
|
250
|
+
const goal = requiredString(payload.goal, 'goal');
|
|
251
|
+
const requestedCriteria = Array.isArray(payload.acceptanceCriteria)
|
|
252
|
+
? payload.acceptanceCriteria.map(value => String(value).trim()).filter(Boolean) : [];
|
|
250
253
|
this.controller.create({
|
|
251
254
|
id: workItemId,
|
|
252
255
|
title: requiredString(payload.title, 'title'),
|
|
253
|
-
goal
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
goal,
|
|
257
|
+
// With no separate criteria, the user's goal itself is the minimum
|
|
258
|
+
// contract. Do not force a follow-up or invent broader requirements.
|
|
259
|
+
acceptanceCriteria: requestedCriteria.length ? requestedCriteria : [goal],
|
|
257
260
|
workflowTemplate,
|
|
258
261
|
workflowSnapshot,
|
|
259
262
|
coordinationMode: DYNAMIC_COORDINATION_MODE,
|
|
@@ -263,7 +266,7 @@ export class WorkCenterService {
|
|
|
263
266
|
// browser/user request. Trusted model producers may provide
|
|
264
267
|
// Session provenance, but cannot grant themselves delivery rights.
|
|
265
268
|
deliveryTarget: requestContext.userOriginated === true
|
|
266
|
-
&& ['workspace_files', 'pull_request', 'merge'].includes(payload.deliveryTarget)
|
|
269
|
+
&& ['response', 'workspace_files', 'pull_request', 'merge'].includes(payload.deliveryTarget)
|
|
267
270
|
? payload.deliveryTarget : null,
|
|
268
271
|
reuseMemory: payload.reuseMemory !== false,
|
|
269
272
|
origin: payload.origin && typeof payload.origin === 'object'
|
|
@@ -320,9 +323,21 @@ export class WorkCenterService {
|
|
|
320
323
|
this.#emit({ type: 'work_item.cancelled', workItem: detail });
|
|
321
324
|
return detail;
|
|
322
325
|
}
|
|
326
|
+
case 'extend_budget': {
|
|
327
|
+
if (requestContext.userOriginated !== true) throw new Error('Only explicit user requests can extend execution budget');
|
|
328
|
+
const id = requiredString(payload.id, 'id');
|
|
329
|
+
const detail = this.controller.extendBudget(id, payload);
|
|
330
|
+
this.#emit({ type: 'work_item.execution_budget_extended', workItem: detail });
|
|
331
|
+
return detail;
|
|
332
|
+
}
|
|
323
333
|
case 'resume': {
|
|
334
|
+
if (requestContext.userOriginated !== true) throw new Error('Only explicit user requests can resume execution');
|
|
324
335
|
const id = requiredString(payload.id, 'id');
|
|
325
|
-
|
|
336
|
+
if (!Number.isSafeInteger(payload.executionControlRevision)) {
|
|
337
|
+
throw new Error('executionControlRevision is required to resume a WorkItem');
|
|
338
|
+
}
|
|
339
|
+
const detail = this.controller.resume(id, { revision: payload.revision,
|
|
340
|
+
executionControlRevision: payload.executionControlRevision });
|
|
326
341
|
this.watcher.abortInvalidWorkItemRuns(id);
|
|
327
342
|
if (detail.coordinationMode === DYNAMIC_COORDINATION_MODE) {
|
|
328
343
|
this.#queueDynamicCoordinatorWake(id);
|
|
@@ -633,7 +648,8 @@ export class WorkCenterService {
|
|
|
633
648
|
const entry = entries.find(candidate => candidate.payload?.turnId) || entries[0];
|
|
634
649
|
if (!entry) return null;
|
|
635
650
|
const detail = this.store.getWorkItemDetail(workItemId);
|
|
636
|
-
if (
|
|
651
|
+
if (!this.store.canAutomaticallyCoordinate(workItemId)
|
|
652
|
+
|| detail?.actions?.some(action => action.status === 'running')) return null;
|
|
637
653
|
let turn;
|
|
638
654
|
try {
|
|
639
655
|
turn = this.coordinator.advance(entry.id, {
|
|
@@ -673,6 +689,7 @@ export class WorkCenterService {
|
|
|
673
689
|
this.store.recoverCoordinatorProviderTurns();
|
|
674
690
|
this.store.recoverCoordinatorMailbox();
|
|
675
691
|
for (const recoverable of this.store.getRecoverableCoordinatorTurns?.() || []) {
|
|
692
|
+
if (!this.store.canAutomaticallyCoordinate(recoverable.workItemId)) continue;
|
|
676
693
|
const claim = this.store.claimCoordinatorTurn(
|
|
677
694
|
recoverable.workItemId, recoverable.turnId, this.ownerBootId,
|
|
678
695
|
);
|
|
@@ -717,6 +734,10 @@ export class WorkCenterService {
|
|
|
717
734
|
let next = null;
|
|
718
735
|
for (const [key, entry] of this.recoveryQueue) {
|
|
719
736
|
const detail = this.store.getWorkItemDetail(entry.workItemId);
|
|
737
|
+
if (!this.store.canAutomaticallyCoordinate(entry.workItemId)) {
|
|
738
|
+
this.recoveryQueue.delete(key);
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
720
741
|
const action = detail?.actions?.find(candidate => candidate.id === entry.actionId);
|
|
721
742
|
if (!detail || ['done', 'cancelled'].includes(detail.status)
|
|
722
743
|
|| action?.status !== 'failed'
|