@yeaft/webchat-agent 1.0.366 → 1.0.368
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 +51 -51
- 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
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
BUILT_IN_ACTION_TYPES,
|
|
4
|
+
MAX_WORK_ITEM_ACTIONS,
|
|
5
|
+
canonicalActionId,
|
|
6
|
+
canonicalActionInstruction,
|
|
7
|
+
normalizeActionBrief,
|
|
8
|
+
normalizeWorkCenterSettings,
|
|
9
|
+
taskSpecificActionBrief,
|
|
10
|
+
} from './workflow.js';
|
|
11
|
+
import {
|
|
12
|
+
DYNAMIC_COORDINATION_MODE,
|
|
13
|
+
DYNAMIC_EXECUTION_SCHEMA_VERSION,
|
|
14
|
+
isDynamicWorkItem,
|
|
15
|
+
} from './execution-mode.js';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
DYNAMIC_COORDINATION_MODE,
|
|
19
|
+
DYNAMIC_EXECUTION_SCHEMA_VERSION,
|
|
20
|
+
isDynamicWorkItem,
|
|
21
|
+
};
|
|
22
|
+
const DYNAMIC_ACTION_LIMIT = 8;
|
|
23
|
+
const DYNAMIC_WORKSPACE_MODES = new Set(['read', 'shared', 'isolated-write', 'integrate']);
|
|
24
|
+
|
|
25
|
+
function uniqueStrings(value) {
|
|
26
|
+
if (!Array.isArray(value)) return [];
|
|
27
|
+
return [...new Set(value.map(item => String(item || '').trim()).filter(Boolean))];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function requiredText(value, name, limit = 2_000) {
|
|
31
|
+
const text = typeof value === 'string' ? value.trim().slice(0, limit) : '';
|
|
32
|
+
if (!text) throw new Error(`Work Center dynamic Action ${name} is required`);
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build the frozen policy catalog for a Coordinator-driven WorkItem. The legacy
|
|
38
|
+
* workflow_snapshot column remains the on-disk compatibility envelope, but new
|
|
39
|
+
* entries contain no stages, dependencies, or precomputed successors.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveDynamicActionPolicySnapshot(settings, requestedWorkItemType = null) {
|
|
42
|
+
const normalized = normalizeWorkCenterSettings(settings);
|
|
43
|
+
const requested = typeof requestedWorkItemType === 'string'
|
|
44
|
+
&& requestedWorkItemType.trim()
|
|
45
|
+
&& requestedWorkItemType.trim().toLowerCase() !== 'auto'
|
|
46
|
+
? canonicalActionId(requestedWorkItemType, '').slice(0, 64)
|
|
47
|
+
: '';
|
|
48
|
+
return {
|
|
49
|
+
version: 2,
|
|
50
|
+
id: 'coordinator-driven',
|
|
51
|
+
name: 'Coordinator driven',
|
|
52
|
+
planningMode: 'coordinator',
|
|
53
|
+
executionMode: 'dynamic',
|
|
54
|
+
workItemType: requested || null,
|
|
55
|
+
globalInstructions: normalized.globalInstructions,
|
|
56
|
+
modelPolicy: normalized.modelPolicy,
|
|
57
|
+
coordinatorModelPolicy: normalized.coordinatorModelPolicy,
|
|
58
|
+
actionModelPolicies: normalized.actionModelPolicies,
|
|
59
|
+
actionInstructions: normalized.actionInstructions,
|
|
60
|
+
actionTemplates: BUILT_IN_ACTION_TYPES.filter(type => type !== 'triage').map(type => ({ type })),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeSourceActionIds(value, actions) {
|
|
65
|
+
const ids = uniqueStrings(value);
|
|
66
|
+
const available = new Set(actions.map(action => action.id));
|
|
67
|
+
for (const id of ids) {
|
|
68
|
+
if (!available.has(id)) throw new Error(`Work Center dynamic Action references an unknown source Action: ${id}`);
|
|
69
|
+
}
|
|
70
|
+
return ids;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeSupersededActionIds(value, actions) {
|
|
74
|
+
const ids = uniqueStrings(value);
|
|
75
|
+
const byId = new Map(actions.map(action => [action.id, action]));
|
|
76
|
+
for (const id of ids) {
|
|
77
|
+
const action = byId.get(id);
|
|
78
|
+
if (!action || !['ready', 'waiting', 'failed'].includes(action.status)) {
|
|
79
|
+
throw new Error(`Work Center can supersede only a non-running unfinished Action: ${id}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return ids;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Normalize one just-in-time Coordinator decision into concrete durable Action
|
|
87
|
+
* records. sourceActionIds are audit/context links only and never gate dispatch.
|
|
88
|
+
*/
|
|
89
|
+
export function prepareDynamicActionMutation({
|
|
90
|
+
workItem,
|
|
91
|
+
actions,
|
|
92
|
+
decision,
|
|
93
|
+
availableVpIds = null,
|
|
94
|
+
}) {
|
|
95
|
+
if (!isDynamicWorkItem(workItem)) {
|
|
96
|
+
throw new Error('Work Center dynamic Action creation requires a Coordinator-driven WorkItem');
|
|
97
|
+
}
|
|
98
|
+
const requested = Array.isArray(decision?.actions) ? decision.actions : [];
|
|
99
|
+
if (requested.length < 1 || requested.length > DYNAMIC_ACTION_LIMIT) {
|
|
100
|
+
throw new Error('Work Center Coordinator must create between 1 and 8 currently justified Actions');
|
|
101
|
+
}
|
|
102
|
+
const historicalCount = Array.isArray(actions) ? actions.length : 0;
|
|
103
|
+
if (historicalCount + requested.length > MAX_WORK_ITEM_ACTIONS) {
|
|
104
|
+
throw new Error(`Work Center cannot exceed ${MAX_WORK_ITEM_ACTIONS} Actions`);
|
|
105
|
+
}
|
|
106
|
+
const knownVpIds = Array.isArray(availableVpIds)
|
|
107
|
+
? new Set(availableVpIds.map(value => String(value || '').trim()).filter(Boolean))
|
|
108
|
+
: null;
|
|
109
|
+
const supersedeActionIds = normalizeSupersededActionIds(decision.supersedeActionIds, actions);
|
|
110
|
+
const effectiveWorkItem = {
|
|
111
|
+
...workItem,
|
|
112
|
+
...(decision.contractPatch || {}),
|
|
113
|
+
};
|
|
114
|
+
const createdActions = requested.map((raw, index) => {
|
|
115
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
116
|
+
throw new Error('Work Center Coordinator Action specification must be an object');
|
|
117
|
+
}
|
|
118
|
+
if (Object.hasOwn(raw, 'dependsOnActionIds') || Object.hasOwn(raw, 'dependsOnStageIds')) {
|
|
119
|
+
throw new Error('Coordinator-driven Actions cannot contain dependency fields');
|
|
120
|
+
}
|
|
121
|
+
const type = canonicalActionId(raw.type, 'custom').slice(0, 64);
|
|
122
|
+
if (type === 'triage') throw new Error('Coordinator-driven WorkItems do not create triage Actions');
|
|
123
|
+
const brief = normalizeActionBrief({
|
|
124
|
+
objective: requiredText(raw.objective, 'objective'),
|
|
125
|
+
approach: requiredText(raw.approach, 'approach'),
|
|
126
|
+
expectedOutcome: requiredText(raw.expectedOutcome, 'expectedOutcome'),
|
|
127
|
+
}, type);
|
|
128
|
+
if (!taskSpecificActionBrief(brief, type)) {
|
|
129
|
+
throw new Error(`Work Center dynamic Action ${index + 1} must use a task-specific brief`);
|
|
130
|
+
}
|
|
131
|
+
const candidateVpIds = uniqueStrings(raw.candidateVpIds);
|
|
132
|
+
if (knownVpIds) {
|
|
133
|
+
const unavailable = candidateVpIds.find(vpId => !knownVpIds.has(vpId));
|
|
134
|
+
if (unavailable) throw new Error(`Work Center dynamic Action references unavailable VP "${unavailable}"`);
|
|
135
|
+
}
|
|
136
|
+
const assignmentReason = candidateVpIds.length > 0
|
|
137
|
+
? requiredText(raw.assignmentReason, 'assignmentReason', 1_000)
|
|
138
|
+
: '';
|
|
139
|
+
const id = randomUUID();
|
|
140
|
+
const workspaceMode = DYNAMIC_WORKSPACE_MODES.has(raw.workspaceMode)
|
|
141
|
+
? raw.workspaceMode
|
|
142
|
+
: 'shared';
|
|
143
|
+
const sourceActionIds = normalizeSourceActionIds(raw.sourceActionIds, actions);
|
|
144
|
+
if (workspaceMode === 'integrate' && sourceActionIds.length === 0) {
|
|
145
|
+
throw new Error('Work Center integrate Action requires sourceActionIds');
|
|
146
|
+
}
|
|
147
|
+
if (workspaceMode === 'integrate') {
|
|
148
|
+
const byId = new Map(actions.map(candidate => [candidate.id, candidate]));
|
|
149
|
+
const invalid = sourceActionIds.find(sourceId => (
|
|
150
|
+
byId.get(sourceId)?.workspaceMode !== 'isolated-write'
|
|
151
|
+
|| byId.get(sourceId)?.status !== 'completed'
|
|
152
|
+
));
|
|
153
|
+
if (invalid) {
|
|
154
|
+
throw new Error(`Work Center integrate Action source is not a completed isolated write: ${invalid}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const action = {
|
|
158
|
+
id,
|
|
159
|
+
type,
|
|
160
|
+
// stage_id is retained only as a legacy storage alias. Dynamic dispatch
|
|
161
|
+
// and Coordinator references use the durable Action id above.
|
|
162
|
+
stageId: id,
|
|
163
|
+
assignmentPolicy: candidateVpIds.length > 0 ? {
|
|
164
|
+
mode: 'planned',
|
|
165
|
+
capability: canonicalActionId(raw.capability, type),
|
|
166
|
+
candidateVpIds,
|
|
167
|
+
fixedVpId: null,
|
|
168
|
+
assignmentReason,
|
|
169
|
+
separateFromStageTypes: uniqueStrings(raw.separateFromActionTypes),
|
|
170
|
+
} : {
|
|
171
|
+
mode: 'auto',
|
|
172
|
+
capability: canonicalActionId(raw.capability, type),
|
|
173
|
+
candidateVpIds: [],
|
|
174
|
+
fixedVpId: null,
|
|
175
|
+
assignmentReason: '',
|
|
176
|
+
separateFromStageTypes: uniqueStrings(raw.separateFromActionTypes),
|
|
177
|
+
},
|
|
178
|
+
modelPolicy: workItem.workflowSnapshot?.actionModelPolicies?.[type]
|
|
179
|
+
|| workItem.workflowSnapshot?.actionModelPolicies?.custom
|
|
180
|
+
|| workItem.workflowSnapshot?.modelPolicy
|
|
181
|
+
|| null,
|
|
182
|
+
dependsOnStageIds: [],
|
|
183
|
+
sourceActionIds,
|
|
184
|
+
workspaceMode,
|
|
185
|
+
changesRequestedStageId: null,
|
|
186
|
+
requiredRole: '',
|
|
187
|
+
brief,
|
|
188
|
+
context: [],
|
|
189
|
+
maxAttempts: Math.min(Math.max(Number(raw.maxAttempts) || 2, 1), 5),
|
|
190
|
+
};
|
|
191
|
+
action.instruction = canonicalActionInstruction(effectiveWorkItem, action, []);
|
|
192
|
+
return action;
|
|
193
|
+
});
|
|
194
|
+
const workItemType = canonicalActionId(
|
|
195
|
+
decision.workItemType || workItem.workflowSnapshot?.workItemType,
|
|
196
|
+
'',
|
|
197
|
+
).slice(0, 64);
|
|
198
|
+
if (!workItemType) throw new Error('Work Center Coordinator must choose a specific WorkItem type');
|
|
199
|
+
return {
|
|
200
|
+
createdActions,
|
|
201
|
+
supersedeActionIds,
|
|
202
|
+
contractPatch: decision.contractPatch || null,
|
|
203
|
+
workItemType,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeEvidenceRunIds(value) {
|
|
208
|
+
return uniqueStrings(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function normalizeDynamicCompletion(value, acceptanceCriteria) {
|
|
212
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
213
|
+
throw new Error('Work Center Coordinator completion is required');
|
|
214
|
+
}
|
|
215
|
+
const criteria = Array.isArray(acceptanceCriteria) ? acceptanceCriteria : [];
|
|
216
|
+
if (criteria.length === 0) throw new Error('Work Center completion requires acceptance criteria');
|
|
217
|
+
if (!Array.isArray(value.acceptanceResults) || value.acceptanceResults.length !== criteria.length) {
|
|
218
|
+
throw new Error('Work Center completion requires one ordered result for every acceptance criterion');
|
|
219
|
+
}
|
|
220
|
+
const acceptanceResults = value.acceptanceResults.map((raw, index) => {
|
|
221
|
+
const criterion = typeof raw?.criterion === 'string' ? raw.criterion.trim() : '';
|
|
222
|
+
const status = raw?.status === 'passed' ? 'passed' : '';
|
|
223
|
+
const evidenceRunIds = normalizeEvidenceRunIds(raw?.evidenceRunIds);
|
|
224
|
+
if (criterion !== criteria[index] || !status || evidenceRunIds.length === 0) {
|
|
225
|
+
throw new Error('Work Center completion requires every criterion to pass with owned Run evidence');
|
|
226
|
+
}
|
|
227
|
+
return { criterion, status, evidenceRunIds };
|
|
228
|
+
});
|
|
229
|
+
const evidenceRunIds = normalizeEvidenceRunIds(value.evidenceRunIds);
|
|
230
|
+
if (evidenceRunIds.length === 0) {
|
|
231
|
+
throw new Error('Work Center completion requires at least one evidence Run');
|
|
232
|
+
}
|
|
233
|
+
const acceptanceEvidence = new Set(acceptanceResults.flatMap(result => result.evidenceRunIds));
|
|
234
|
+
if ([...acceptanceEvidence].some(runId => !evidenceRunIds.includes(runId))) {
|
|
235
|
+
throw new Error('Work Center completion evidence must include every acceptance evidence Run');
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
summary: requiredText(value.summary, 'completion summary', 8_000),
|
|
239
|
+
acceptanceResults,
|
|
240
|
+
evidenceRunIds,
|
|
241
|
+
residualRisks: uniqueStrings(value.residualRisks).slice(0, 24),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const LEGACY_COORDINATION_MODE = 'legacy';
|
|
2
|
+
export const DYNAMIC_COORDINATION_MODE = 'dynamic';
|
|
3
|
+
export const DYNAMIC_EXECUTION_SCHEMA_VERSION = 3;
|
|
4
|
+
|
|
5
|
+
export function isDynamicWorkItem(workItem) {
|
|
6
|
+
return workItem?.coordinationMode === DYNAMIC_COORDINATION_MODE;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function usesMainlineContext(workItem) {
|
|
10
|
+
return Number(workItem?.executionSchemaVersion) >= 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function usesLegacyGraph(workItem) {
|
|
14
|
+
return !isDynamicWorkItem(workItem)
|
|
15
|
+
&& workItem?.workflowSnapshot?.executionMode === 'graph';
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isDynamicWorkItem } from './execution-mode.js';
|
|
2
3
|
import {
|
|
3
4
|
currentActionInputEventIds,
|
|
4
5
|
eventMatchesActionGeneration,
|
|
@@ -281,6 +282,7 @@ export function buildMainlineProjection(detail) {
|
|
|
281
282
|
const actions = (Array.isArray(detail.actions) ? detail.actions : []).slice().sort(stableActionOrder);
|
|
282
283
|
const runs = Array.isArray(detail.runs) ? detail.runs : [];
|
|
283
284
|
const activeActions = actions.filter(action => action.status !== 'superseded');
|
|
285
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
284
286
|
const completedStageIds = new Set(activeActions
|
|
285
287
|
.filter(action => action.status === 'completed')
|
|
286
288
|
.map(action => action.stageId));
|
|
@@ -292,10 +294,11 @@ export function buildMainlineProjection(detail) {
|
|
|
292
294
|
generation: Math.max(1, count(action.generation) || 1),
|
|
293
295
|
specHash: action.specHash || '',
|
|
294
296
|
status: action.status,
|
|
295
|
-
dependsOnStageIds: [...new Set(action.dependsOnStageIds || [])].sort(),
|
|
297
|
+
dependsOnStageIds: dynamic ? [] : [...new Set(action.dependsOnStageIds || [])].sort(),
|
|
298
|
+
sourceActionIds: dynamic ? [...new Set(action.sourceActionIds || [])].sort() : [],
|
|
296
299
|
}));
|
|
297
300
|
const frontier = nodes.filter(node => !CLOSED_ACTION_STATUSES.has(node.status)
|
|
298
|
-
&& node.dependsOnStageIds.every(stageId => completedStageIds.has(stageId)))
|
|
301
|
+
&& (dynamic || node.dependsOnStageIds.every(stageId => completedStageIds.has(stageId))))
|
|
299
302
|
.map(node => node.id);
|
|
300
303
|
const canonicalActionResults = {};
|
|
301
304
|
for (const action of activeActions) {
|
|
@@ -321,7 +324,9 @@ export function buildMainlineProjection(detail) {
|
|
|
321
324
|
goal: detail.goal || '',
|
|
322
325
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
323
326
|
},
|
|
324
|
-
|
|
327
|
+
...(dynamic
|
|
328
|
+
? { actionJournal: { revision: count(detail.planRevision), entries: nodes, runnableActionIds: frontier } }
|
|
329
|
+
: { graph: { planRevision: count(detail.planRevision), nodes, frontier } }),
|
|
325
330
|
canonicalActionResults,
|
|
326
331
|
planConflicts: (Array.isArray(detail.planConflicts) ? detail.planConflicts : [])
|
|
327
332
|
.slice()
|
|
@@ -343,14 +348,17 @@ export function buildMainlineContextSnapshot(detail, action, budgetInput = {}) {
|
|
|
343
348
|
throw mainlineContextBlocked(`Mainline fixed prompt content exceeds 64 KiB (${reservedBytes} rendered UTF-8 bytes)`);
|
|
344
349
|
}
|
|
345
350
|
const projection = buildMainlineProjection(detail);
|
|
346
|
-
const
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
351
|
+
const dynamic = isDynamicWorkItem(detail);
|
|
352
|
+
const dependencyIds = new Set(dynamic ? action.sourceActionIds || [] : action.dependsOnStageIds || []);
|
|
353
|
+
const actionByReference = new Map((detail.actions || []).filter(candidate => candidate.status !== 'superseded')
|
|
354
|
+
.map(candidate => [dynamic ? candidate.id : candidate.stageId, candidate]));
|
|
355
|
+
const dependencies = [...dependencyIds].sort().map(reference => {
|
|
356
|
+
const dependency = actionByReference.get(reference);
|
|
357
|
+
if (!dependency) return dynamic
|
|
358
|
+
? { sourceActionId: reference, actionId: null, result: null }
|
|
359
|
+
: { stageId: reference, actionId: null, result: null };
|
|
352
360
|
return {
|
|
353
|
-
stageId,
|
|
361
|
+
...(dynamic ? { sourceActionId: reference } : { stageId: reference }),
|
|
354
362
|
actionId: dependency.id,
|
|
355
363
|
generation: dependency.generation || 1,
|
|
356
364
|
specHash: dependency.specHash || '',
|
|
@@ -377,14 +385,18 @@ export function buildMainlineContextSnapshot(detail, action, budgetInput = {}) {
|
|
|
377
385
|
policyInstruction: detail.workflowSnapshot?.actionInstructions?.[action.type]
|
|
378
386
|
|| detail.workflowSnapshot?.actionInstructions?.custom
|
|
379
387
|
|| '',
|
|
380
|
-
|
|
388
|
+
...(dynamic
|
|
389
|
+
? { sourceActionIds: [...dependencyIds].sort() }
|
|
390
|
+
: { dependsOnStageIds: [...dependencyIds].sort() }),
|
|
381
391
|
workspaceMode: action.workspaceMode || 'shared',
|
|
382
392
|
changesRequestedStageId: action.changesRequestedStageId || null,
|
|
383
393
|
},
|
|
384
394
|
},
|
|
385
|
-
|
|
395
|
+
...(dynamic
|
|
396
|
+
? { actionJournal: projection.actionJournal }
|
|
397
|
+
: { graph: projection.graph }),
|
|
386
398
|
canonicalCompletedResultsIndex: resultIndex,
|
|
387
|
-
directDependencies: dependencies,
|
|
399
|
+
...(dynamic ? { sourceResults: dependencies } : { directDependencies: dependencies }),
|
|
388
400
|
userContext: {
|
|
389
401
|
sessionContext: [],
|
|
390
402
|
workItemMessages: [],
|
|
@@ -605,6 +605,7 @@ function projectAction(action, runs, events, includeBody = true) {
|
|
|
605
605
|
? (action.assignmentPolicy || null)
|
|
606
606
|
: projectAssignmentPolicy(action.assignmentPolicy),
|
|
607
607
|
dependsOnStageIds: Array.isArray(action.dependsOnStageIds) ? action.dependsOnStageIds : [],
|
|
608
|
+
sourceActionIds: Array.isArray(action.sourceActionIds) ? action.sourceActionIds : [],
|
|
608
609
|
workspaceMode: action.workspaceMode || 'shared',
|
|
609
610
|
requiredRole: action.requiredRole || '',
|
|
610
611
|
generation: Math.max(1, count(action.generation) || 1),
|
|
@@ -797,17 +798,20 @@ function projectCanonicalEvidence(value) {
|
|
|
797
798
|
}
|
|
798
799
|
|
|
799
800
|
function projectMainlineBrowser(detail) {
|
|
800
|
-
if (!detail?.id || detail.executionSchemaVersion
|
|
801
|
+
if (!detail?.id || Number(detail.executionSchemaVersion) < 2) return null;
|
|
801
802
|
const mainline = buildMainlineProjection(detail);
|
|
803
|
+
const actionSet = mainline.actionJournal || mainline.graph;
|
|
804
|
+
const nodes = actionSet.entries || actionSet.nodes || [];
|
|
805
|
+
const frontier = actionSet.runnableActionIds || actionSet.frontier || [];
|
|
802
806
|
const actionById = new Map((detail.actions || []).map(action => [action.id, action]));
|
|
803
807
|
const activeActionIds = Array.isArray(detail.activeActionIds)
|
|
804
808
|
? detail.activeActionIds
|
|
805
|
-
:
|
|
809
|
+
: nodes.filter(node => ['ready', 'running'].includes(node.status)).map(node => node.id);
|
|
806
810
|
const attentionActionIds = Array.isArray(detail.attentionActionIds)
|
|
807
811
|
? detail.attentionActionIds
|
|
808
|
-
:
|
|
812
|
+
: nodes.filter(node => ['waiting', 'failed'].includes(node.status)).map(node => node.id);
|
|
809
813
|
const counts = Object.fromEntries(['completed', 'running', 'ready', 'waiting', 'failed']
|
|
810
|
-
.map(status => [status,
|
|
814
|
+
.map(status => [status, nodes.filter(node => node.status === status).length]));
|
|
811
815
|
return {
|
|
812
816
|
contract: {
|
|
813
817
|
title: truncateUtf8(mainline.contract.title, 8_000),
|
|
@@ -816,15 +820,15 @@ function projectMainlineBrowser(detail) {
|
|
|
816
820
|
.map(criterion => truncateUtf8(criterion, 4_000)),
|
|
817
821
|
},
|
|
818
822
|
progress: {
|
|
819
|
-
lifecycle: detail.lifecycle || (counts.completed ===
|
|
823
|
+
lifecycle: detail.lifecycle || (counts.completed === nodes.length ? 'done' : 'active'),
|
|
820
824
|
attentionState: detail.attentionState || (counts.waiting && counts.failed ? 'mixed'
|
|
821
825
|
: counts.waiting ? 'waiting' : counts.failed ? 'failed' : 'none'),
|
|
822
826
|
activeActionIds: [...activeActionIds],
|
|
823
827
|
attentionActionIds: [...attentionActionIds],
|
|
824
|
-
frontierActionIds: [...
|
|
828
|
+
frontierActionIds: [...frontier],
|
|
825
829
|
counts,
|
|
826
830
|
},
|
|
827
|
-
actions:
|
|
831
|
+
actions: nodes.map(node => {
|
|
828
832
|
const action = actionById.get(node.id) || {};
|
|
829
833
|
const result = mainline.canonicalActionResults[node.id];
|
|
830
834
|
return {
|
|
@@ -839,7 +843,7 @@ function projectMainlineBrowser(detail) {
|
|
|
839
843
|
truncateUtf8(value, MAX_CURRENT_BRIEF_BYTES),
|
|
840
844
|
]))
|
|
841
845
|
: null,
|
|
842
|
-
dependencies: [...node.dependsOnStageIds],
|
|
846
|
+
dependencies: [...(node.sourceActionIds?.length ? node.sourceActionIds : node.dependsOnStageIds || [])],
|
|
843
847
|
canonicalResult: result ? {
|
|
844
848
|
status: result.status,
|
|
845
849
|
summary: sanitizeMainlineDiagnostic(result.summary, MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
@@ -895,6 +899,22 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
895
899
|
planRevision: count(detail.planRevision),
|
|
896
900
|
ledgerRevision: count(detail.ledgerRevision),
|
|
897
901
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
902
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
903
|
+
finalResult: detail.finalResult && typeof detail.finalResult === 'object' ? {
|
|
904
|
+
summary: truncateUtf8(detail.finalResult.summary || '', MAX_ACTION_MESSAGE_CHARS),
|
|
905
|
+
acceptanceResults: Array.isArray(detail.finalResult.acceptanceResults)
|
|
906
|
+
? detail.finalResult.acceptanceResults.slice(0, 24).map(result => ({
|
|
907
|
+
criterion: truncateUtf8(result?.criterion || '', MAX_ACTION_MESSAGE_CHARS),
|
|
908
|
+
status: result?.status === 'passed' ? 'passed' : null,
|
|
909
|
+
evidenceRunIds: Array.isArray(result?.evidenceRunIds)
|
|
910
|
+
? result.evidenceRunIds.map(String).slice(0, 24) : [],
|
|
911
|
+
})) : [],
|
|
912
|
+
evidenceRunIds: Array.isArray(detail.finalResult.evidenceRunIds)
|
|
913
|
+
? detail.finalResult.evidenceRunIds.map(String).slice(0, 64) : [],
|
|
914
|
+
residualRisks: Array.isArray(detail.finalResult.residualRisks)
|
|
915
|
+
? detail.finalResult.residualRisks
|
|
916
|
+
.map(risk => truncateUtf8(risk, MAX_ACTION_MESSAGE_CHARS)).slice(0, 24) : [],
|
|
917
|
+
} : null,
|
|
898
918
|
title: detail.title,
|
|
899
919
|
goal: detail.goal,
|
|
900
920
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
@@ -931,7 +951,8 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
931
951
|
status: ['thinking', 'completed', 'failed'].includes(message.status) ? message.status : 'completed',
|
|
932
952
|
error: truncateUtf8(message.error || '', MAX_ACTION_DIAGNOSTIC_CHARS) || null,
|
|
933
953
|
decision: message.decision && typeof message.decision === 'object' ? {
|
|
934
|
-
kind: ['answer', 'guide_actions', 'replan', 'request_human']
|
|
954
|
+
kind: ['answer', 'create_actions', 'guide_actions', 'replan', 'request_human', 'complete']
|
|
955
|
+
.includes(message.decision.kind)
|
|
935
956
|
? message.decision.kind : null,
|
|
936
957
|
reason: truncateUtf8(message.decision.reason || '', MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
937
958
|
changedContract: message.decision.changedContract === true,
|
|
@@ -981,6 +1002,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
981
1002
|
planRevision: count(detail.planRevision),
|
|
982
1003
|
ledgerRevision: count(detail.ledgerRevision),
|
|
983
1004
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
1005
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
984
1006
|
title: detail.title,
|
|
985
1007
|
goal: detail.goal,
|
|
986
1008
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -1017,6 +1039,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
1017
1039
|
planRevision: count(detail.planRevision),
|
|
1018
1040
|
ledgerRevision: count(detail.ledgerRevision),
|
|
1019
1041
|
coordinatorRevision: count(detail.coordinatorRevision),
|
|
1042
|
+
coordinationMode: detail.coordinationMode || 'legacy',
|
|
1020
1043
|
title: detail.title,
|
|
1021
1044
|
goal: detail.goal,
|
|
1022
1045
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -34,6 +34,7 @@ import { MCPManager } from '../mcp.js';
|
|
|
34
34
|
import { buildMcpFlattenedTools } from '../tools/mcp-tools.js';
|
|
35
35
|
import { recallWorkspaceSessionContext } from './workspace-context.js';
|
|
36
36
|
import { applyGeneratedPlan, BUILT_IN_ACTION_TYPES } from './workflow.js';
|
|
37
|
+
import { isDynamicWorkItem, usesMainlineContext } from './execution-mode.js';
|
|
37
38
|
import {
|
|
38
39
|
applyAdditivePlanProposal,
|
|
39
40
|
applyReplanMutation,
|
|
@@ -946,7 +947,9 @@ export class WorkItemRunner {
|
|
|
946
947
|
action: finalizeOwnedIntegration(this.store, action, run, ownerBootId),
|
|
947
948
|
};
|
|
948
949
|
}
|
|
949
|
-
const dependencies =
|
|
950
|
+
const dependencies = isDynamicWorkItem(workItem)
|
|
951
|
+
? this.store.listActionSources(workItem.id, action.sourceActionIds || [])
|
|
952
|
+
: this.store.listActionDependencies(workItem.id, action.dependsOnStageIds || []);
|
|
950
953
|
if (dependencies.length > 0 && dependencies.every(dependency => (
|
|
951
954
|
dependency.workspaceMode === 'shared' && !dependency.workspace?.isolated
|
|
952
955
|
))) {
|
|
@@ -1021,9 +1024,8 @@ export class WorkItemRunner {
|
|
|
1021
1024
|
|
|
1022
1025
|
async run({ workItem, action, run, signal, ownerBootId, onProgress, registerProgressReader, registerInputWake }) {
|
|
1023
1026
|
const runtime = await this.runtimeProvider();
|
|
1024
|
-
const currentSettings = workItem?.workflowSnapshot?.planningMode
|
|
1025
|
-
? await this.policyProvider()
|
|
1026
|
-
: null;
|
|
1027
|
+
const currentSettings = ['ai', 'coordinator'].includes(workItem?.workflowSnapshot?.planningMode)
|
|
1028
|
+
&& this.policyProvider ? await this.policyProvider() : null;
|
|
1027
1029
|
const currentModelPolicy = currentSettings?.actionModelPolicies?.[action.type]
|
|
1028
1030
|
|| currentSettings?.actionModelPolicies?.custom
|
|
1029
1031
|
|| currentSettings?.modelPolicy
|
|
@@ -1036,15 +1038,16 @@ export class WorkItemRunner {
|
|
|
1036
1038
|
? resolveWorkItemWorkDir({ workspaceKey: action.workspace.path }, runtime.defaultWorkDir)
|
|
1037
1039
|
: workspaceDir;
|
|
1038
1040
|
const priorRuns = this.store.listCompletedRuns(workItem.id);
|
|
1039
|
-
const
|
|
1040
|
-
const dependencyContext =
|
|
1041
|
-
? []
|
|
1042
|
-
:
|
|
1041
|
+
const mainlineExecution = usesMainlineContext(workItem);
|
|
1042
|
+
const dependencyContext = isDynamicWorkItem(workItem)
|
|
1043
|
+
? this.store.listActionSources?.(workItem.id, action.sourceActionIds || []) || []
|
|
1044
|
+
: mainlineExecution ? []
|
|
1045
|
+
: this.store.listActionDependencies?.(workItem.id, action.dependsOnStageIds || []) || [];
|
|
1043
1046
|
const dependencyBlock = dependencyContext.length === 0 ? '' : `\n\nCompleted dependency results:\n${dependencyContext.map(dependency => {
|
|
1044
1047
|
const evidence = dependency.evidence?.length
|
|
1045
1048
|
? `\nEvidence: ${dependency.evidence.map(item => item.label).join('; ')}`
|
|
1046
1049
|
: '';
|
|
1047
|
-
return `### ${dependency.stageId} (${dependency.vpId || 'unknown VP'})\n${dependency.summary || '(no summary)'}${evidence}`;
|
|
1050
|
+
return `### ${isDynamicWorkItem(workItem) ? dependency.id : dependency.stageId} (${dependency.vpId || 'unknown VP'})\n${dependency.summary || '(no summary)'}${evidence}`;
|
|
1048
1051
|
}).join('\n\n')}`;
|
|
1049
1052
|
const resumeBlock = renderActionResumeBlock(this.store.getActionResumeContext?.(action.id, run.id));
|
|
1050
1053
|
const assignment = executionAction.assignmentPolicy
|
|
@@ -1084,7 +1087,7 @@ export class WorkItemRunner {
|
|
|
1084
1087
|
const attachmentFileById = new Map(attachmentContext.files.map(file => [file.id, file]));
|
|
1085
1088
|
const fixedPromptSuffix = `${resumeBlock}${attachmentContext.promptBlock}${completionContract(executionAction, workItem)}`;
|
|
1086
1089
|
const reservedPromptBytes = Buffer.byteLength(fixedPromptSuffix, 'utf8');
|
|
1087
|
-
const mainline =
|
|
1090
|
+
const mainline = mainlineExecution
|
|
1088
1091
|
? buildMainlineContextSnapshot(
|
|
1089
1092
|
this.store.getWorkItemDetail(workItem.id),
|
|
1090
1093
|
executionAction,
|
|
@@ -1212,7 +1215,9 @@ export class WorkItemRunner {
|
|
|
1212
1215
|
executionManifest: mainline ? {
|
|
1213
1216
|
schemaVersion: 2,
|
|
1214
1217
|
ledgerRevision: mainline.contextSnapshot.ledgerRevision,
|
|
1215
|
-
planRevision:
|
|
1218
|
+
planRevision: isDynamicWorkItem(workItem)
|
|
1219
|
+
? mainline.contextSnapshot.actionJournal.revision
|
|
1220
|
+
: mainline.contextSnapshot.graph.planRevision,
|
|
1216
1221
|
contractRevision: mainline.contextSnapshot.contract.revision,
|
|
1217
1222
|
actionGeneration: mainline.contextSnapshot.action.generation,
|
|
1218
1223
|
actionSpecHash: mainline.contextSnapshot.action.specHash,
|
|
@@ -1336,11 +1341,11 @@ export class WorkItemRunner {
|
|
|
1336
1341
|
}
|
|
1337
1342
|
};
|
|
1338
1343
|
try {
|
|
1339
|
-
const prompt =
|
|
1344
|
+
const prompt = mainlineExecution
|
|
1340
1345
|
? `${renderMainlineContextSnapshot(mainline.contextSnapshot)}${fixedPromptSuffix}`
|
|
1341
1346
|
: `${executionAction.instruction}${dependencyBlock}${resumeBlock}${attachmentContext.promptBlock}${workspaceSessionBlock}${memoryBlock}${completionContract(executionAction, workItem)}`;
|
|
1342
1347
|
const promptBytes = Buffer.byteLength(prompt, 'utf8');
|
|
1343
|
-
if (
|
|
1348
|
+
if (mainlineExecution && promptBytes > MAINLINE_CONTEXT_HARD_LIMIT_BYTES) {
|
|
1344
1349
|
throw new Error(`Work Center Mainline prompt exceeds 64 KiB (${promptBytes} rendered UTF-8 bytes)`);
|
|
1345
1350
|
}
|
|
1346
1351
|
const promptParts = attachmentContext.promptParts.length > 0
|