@yeaft/webchat-agent 1.0.201 → 1.0.202

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.
@@ -1 +1 @@
1
- {"version":"1.0.201"}
1
+ {"version":"1.0.202"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.201",
3
+ "version": "1.0.202",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,53 @@
1
+ function normalizeCriteria(value) {
2
+ if (!Array.isArray(value)) return null;
3
+ return value.map(item => String(item).trim()).filter(Boolean);
4
+ }
5
+
6
+ export function normalizeContractPatch(value) {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
8
+ const patch = {};
9
+ if (typeof value.goal === 'string' && value.goal.trim()) patch.goal = value.goal.trim();
10
+ if (Object.hasOwn(value, 'acceptanceCriteria')) {
11
+ const criteria = normalizeCriteria(value.acceptanceCriteria);
12
+ if (!criteria) throw new Error('contractPatch.acceptanceCriteria must be an array');
13
+ patch.acceptanceCriteria = criteria;
14
+ }
15
+ return Object.keys(patch).length > 0 ? patch : null;
16
+ }
17
+
18
+ function normalizeAcceptanceChecks(value, criteria) {
19
+ if (!Array.isArray(value) || value.length !== criteria.length) return null;
20
+ const checks = value.map((raw, index) => {
21
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
22
+ const criterion = typeof raw.criterion === 'string' ? raw.criterion.trim() : '';
23
+ const status = ['passed', 'deferred', 'not_applicable'].includes(raw.status) ? raw.status : '';
24
+ const evidence = typeof raw.evidence === 'string' ? raw.evidence.trim().slice(0, 1_000) : '';
25
+ if (criterion !== criteria[index] || !status || !evidence) return null;
26
+ return { criterion, status, evidence };
27
+ });
28
+ return checks.every(Boolean) ? checks : null;
29
+ }
30
+
31
+ export function validateCompletedResult(result, action, workItem) {
32
+ if (result.outcome !== 'completed') return;
33
+ if (result.evidence.length === 0) {
34
+ result.outcome = 'failed';
35
+ result.error = 'Completed Action requires at least one concrete evidence item';
36
+ return;
37
+ }
38
+ const criteria = result.contractPatch?.acceptanceCriteria
39
+ ?? (Array.isArray(workItem.acceptanceCriteria) ? workItem.acceptanceCriteria : []);
40
+ const checks = normalizeAcceptanceChecks(result.acceptanceChecks, criteria);
41
+ if (!checks) {
42
+ result.outcome = 'failed';
43
+ result.error = 'Completed Action requires one ordered acceptance check with evidence for every acceptance criterion';
44
+ return;
45
+ }
46
+ const mustVerify = action.type === 'test'
47
+ || action.type === 'deliver'
48
+ || (action.type === 'review' && result.reviewDecision === 'approved');
49
+ if (mustVerify && checks.some(check => check.status !== 'passed')) {
50
+ result.outcome = 'failed';
51
+ result.error = `${action.type} Action requires every acceptance check to pass`;
52
+ }
53
+ }
@@ -9,60 +9,7 @@ import {
9
9
  import { renderSessionContextSnapshot } from './session-context.js';
10
10
  import { normalizeEvidence } from './evidence.js';
11
11
  import { applyAdditivePlanProposal } from './plan-mutation.js';
12
-
13
- function normalizeCriteria(value) {
14
- if (!Array.isArray(value)) return null;
15
- return value.map(item => String(item).trim()).filter(Boolean);
16
- }
17
-
18
- function normalizeContractPatch(value) {
19
- if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
20
- const patch = {};
21
- if (typeof value.goal === 'string' && value.goal.trim()) patch.goal = value.goal.trim();
22
- if (Object.prototype.hasOwnProperty.call(value, 'acceptanceCriteria')) {
23
- const criteria = normalizeCriteria(value.acceptanceCriteria);
24
- if (!criteria) throw new Error('contractPatch.acceptanceCriteria must be an array');
25
- patch.acceptanceCriteria = criteria;
26
- }
27
- return Object.keys(patch).length > 0 ? patch : null;
28
- }
29
-
30
- function normalizeAcceptanceChecks(value, criteria) {
31
- if (!Array.isArray(value) || value.length !== criteria.length) return null;
32
- const checks = value.map((raw, index) => {
33
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
34
- const criterion = typeof raw.criterion === 'string' ? raw.criterion.trim() : '';
35
- const status = ['passed', 'deferred', 'not_applicable'].includes(raw.status) ? raw.status : '';
36
- const evidence = typeof raw.evidence === 'string' ? raw.evidence.trim().slice(0, 1_000) : '';
37
- if (criterion !== criteria[index] || !status || !evidence) return null;
38
- return { criterion, status, evidence };
39
- });
40
- return checks.every(Boolean) ? checks : null;
41
- }
42
-
43
- function validateCompletedResult(result, action, workItem) {
44
- if (result.outcome !== 'completed') return;
45
- if (result.evidence.length === 0) {
46
- result.outcome = 'failed';
47
- result.error = 'Completed Action requires at least one concrete evidence item';
48
- return;
49
- }
50
- const criteria = result.contractPatch?.acceptanceCriteria
51
- ?? (Array.isArray(workItem.acceptanceCriteria) ? workItem.acceptanceCriteria : []);
52
- const checks = normalizeAcceptanceChecks(result.acceptanceChecks, criteria);
53
- if (!checks) {
54
- result.outcome = 'failed';
55
- result.error = 'Completed Action requires one ordered acceptance check with evidence for every acceptance criterion';
56
- return;
57
- }
58
- const mustVerify = action.type === 'test'
59
- || action.type === 'deliver'
60
- || (action.type === 'review' && result.reviewDecision === 'approved');
61
- if (mustVerify && checks.some(check => check.status !== 'passed')) {
62
- result.outcome = 'failed';
63
- result.error = `${action.type} Action requires every acceptance check to pass`;
64
- }
65
- }
12
+ import { normalizeContractPatch, validateCompletedResult } from './completion-contract.js';
66
13
 
67
14
  function normalizeTerminalResult(result, action) {
68
15
  if (!result || !RUN_OUTCOMES.includes(result.outcome)) {
@@ -31,7 +31,9 @@ import { loadMCPConfig } from '../config.js';
31
31
  import { MCPManager } from '../mcp.js';
32
32
  import { buildMcpFlattenedTools } from '../tools/mcp-tools.js';
33
33
  import { recallWorkspaceSessionContext } from './workspace-context.js';
34
- import { BUILT_IN_ACTION_TYPES } from './workflow.js';
34
+ import { applyGeneratedPlan, BUILT_IN_ACTION_TYPES } from './workflow.js';
35
+ import { normalizeContractPatch, validateCompletedResult } from './completion-contract.js';
36
+ import { normalizeEvidence } from './evidence.js';
35
37
  import {
36
38
  MAINLINE_CONTEXT_HARD_LIMIT_BYTES,
37
39
  buildMainlineContextSnapshot,
@@ -300,14 +302,20 @@ export function planningVpCatalog(vps) {
300
302
  }));
301
303
  }
302
304
 
303
- export function createSubmitWorkItemPlanTool({ vps, collector, isRunActive }) {
305
+ export function createSubmitWorkItemPlanTool({
306
+ vps,
307
+ workItem,
308
+ collector,
309
+ isRunActive,
310
+ reservedStageIds = [],
311
+ }) {
304
312
  const vpCatalog = planningVpCatalog(vps);
305
313
  const vpIds = vpCatalog.map(vp => vp.id);
306
314
  const actionTypes = BUILT_IN_ACTION_TYPES.filter(type => type !== 'triage');
307
315
  const catalogDescription = `Action types: ${actionTypes.join(', ')}. Available VPs: ${vpCatalog.map(vp => `${vp.id} (${vp.role || vp.area || 'VP'}; ${vp.traits.join(', ') || 'no traits'})`).join('; ')}.`;
308
316
  return defineTool({
309
317
  name: 'SubmitWorkItemPlan',
310
- description: `Submit the complete initial WorkItem contract and executable Action DAG. Every Action must describe this WorkItem's concrete objective, repository-aware approach, and verifiable expected outcome; never copy generic Action-type text. The reference workflow catalog does not replace this Action list. This tool records a Run-local proposal only; Work Center validates and persists it in the current Run finalization transaction. ${catalogDescription}`,
318
+ description: `Submit the complete initial WorkItem contract and executable Action DAG. Every Action must describe this WorkItem's concrete objective, repository-aware approach, and verifiable expected outcome; never copy generic Action-type text. The reference workflow catalog does not replace this Action list. If any Action uses isolated-write workspace mode, the plan must contain exactly one integrate Action in integrate workspace mode; that Action must depend directly on every isolated-write Action, and all later Actions must consume those writes through it. This tool validates the proposal immediately so you can correct an invalid graph in the same triage loop; Work Center persists only a valid proposal in the current Run finalization transaction. ${catalogDescription}`,
311
319
  parameters: {
312
320
  type: 'object',
313
321
  additionalProperties: false,
@@ -331,6 +339,28 @@ export function createSubmitWorkItemPlanTool({ vps, collector, isRunActive }) {
331
339
  async execute(input, ctx = {}) {
332
340
  if (!isRunActive()) throw new Error('Work Center Run is no longer active');
333
341
  if (collector.value) throw new Error('WorkItem plan was already submitted for this Run');
342
+ const contractPatch = normalizeContractPatch(input.contractPatch);
343
+ const proposedResult = {
344
+ outcome: 'completed',
345
+ evidence: normalizeEvidence(input.evidence),
346
+ contractPatch,
347
+ acceptanceChecks: input.acceptanceChecks,
348
+ };
349
+ validateCompletedResult(proposedResult, { type: 'triage' }, workItem);
350
+ if (proposedResult.outcome !== 'completed') throw new Error(proposedResult.error);
351
+ const effectiveWorkItem = contractPatch ? {
352
+ ...workItem,
353
+ goal: contractPatch.goal ?? workItem.goal,
354
+ acceptanceCriteria: contractPatch.acceptanceCriteria ?? workItem.acceptanceCriteria,
355
+ } : workItem;
356
+ applyGeneratedPlan(effectiveWorkItem, {
357
+ workItemType: input.workItemType,
358
+ actions: input.actions,
359
+ }, {
360
+ availableVpIds: vpIds,
361
+ reservedStageIds,
362
+ });
363
+ if (!isRunActive()) throw new Error('Work Center Run is no longer active');
334
364
  collector.value = structuredClone(input);
335
365
  ctx.requestEndTurn?.({ kind: 'work_item_plan_submitted' });
336
366
  return JSON.stringify({ submitted: true, actionCount: input.actions.length });
@@ -860,7 +890,13 @@ export class WorkItemRunner {
860
890
  && workItem?.workflowSnapshot?.planningMode === 'ai';
861
891
  const runTools = [];
862
892
  if (planToolEnabled) runTools.push(createSubmitWorkItemPlanTool({
863
- vps: this.registry.listVps(), collector: planCollector, isRunActive,
893
+ vps: this.registry.listVps(),
894
+ workItem,
895
+ collector: planCollector,
896
+ isRunActive,
897
+ reservedStageIds: executionAction.stageId?.startsWith('replan-')
898
+ ? this.store.getWorkItemDetail(workItem.id).actions.map(item => item.stageId)
899
+ : [],
864
900
  }));
865
901
  if (!planToolEnabled && workItem?.workflowSnapshot?.executionMode === 'graph') {
866
902
  runTools.push(createProposeWorkItemActionsTool({
@@ -373,7 +373,7 @@ export function resolvePlanningWorkflowSnapshot(settings, requestedWorkItemType
373
373
  const typeInstruction = requestedType
374
374
  ? `The user explicitly selected workItemType "${requestedType}". Keep that exact type.`
375
375
  : 'Infer one specific workItemType from the contract.';
376
- const triageInstruction = `${normalized.actionInstructions.triage}\n\n${typeInstruction}\nReference workflow catalog:\n${catalog || '(none)'}\nUse the catalog only to understand established task categories and sequencing patterns. Always submit the smallest reliable graph of 1 to 8 task-specific Actions; never omit Actions or copy template brief text. Split independent work into separate Actions and declare dependsOnActionIds. Use workspaceMode read for analysis, isolated-write for independent Git changes, integrate for an integrate Action that combines isolated-write dependencies, and shared for serial side effects. Non-Git or dirty workspaces are serialized automatically. Every generated Action must state objective, approach, expectedOutcome, capability, dependencies, and workspaceMode. The objective, approach, and expectedOutcome must be specific to this WorkItem and that Action: describe the concrete work, the repository-aware execution method, and the verifiable result that will guide the executor. Generic Action-type boilerplate is invalid. Add only Actions required by this task. Do not copy a generic workflow.`;
376
+ const triageInstruction = `${normalized.actionInstructions.triage}\n\n${typeInstruction}\nReference workflow catalog:\n${catalog || '(none)'}\nUse the catalog only to understand established task categories and sequencing patterns. Always submit the smallest reliable graph of 1 to 8 task-specific Actions; never omit Actions or copy template brief text. Split independent work into separate Actions and declare dependsOnActionIds. Use workspaceMode read for analysis, isolated-write for independent Git changes, integrate for an integrate Action that combines isolated-write dependencies, and shared for serial side effects. If any Action uses isolated-write, add exactly one Action with type integrate and workspaceMode integrate; it must depend directly on every isolated-write Action, and all later Actions must depend on the integration result rather than an isolated-write Action. Non-Git or dirty workspaces are serialized automatically. Every generated Action must state objective, approach, expectedOutcome, capability, dependencies, and workspaceMode. The objective, approach, and expectedOutcome must be specific to this WorkItem and that Action: describe the concrete work, the repository-aware execution method, and the verifiable result that will guide the executor. Generic Action-type boilerplate is invalid. Add only Actions required by this task. Do not copy a generic workflow.`;
377
377
  return normalizeWorkflowDefinition({
378
378
  id: 'ai-planned',
379
379
  name: 'AI planned',