@yeaft/webchat-agent 1.0.530 → 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/engine.js +1 -0
- package/yeaft/sub-agent/runner.js +4 -1
- package/yeaft/tools/activation.js +5 -0
- package/yeaft/tools/git-read.js +121 -24
- 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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { assertCoordinatorContractAuthority, normalizeContractPatch } from './completion-contract.js';
|
|
3
|
+
import { deriveGoalProgress } from './goal-state.js';
|
|
2
4
|
import {
|
|
3
5
|
BUILT_IN_ACTION_TYPES,
|
|
4
6
|
MAX_WORK_ITEM_ACTIONS,
|
|
@@ -110,7 +112,9 @@ export function prepareDynamicActionMutation({
|
|
|
110
112
|
actions,
|
|
111
113
|
decision,
|
|
112
114
|
availableVpIds = null,
|
|
115
|
+
automatic = false,
|
|
113
116
|
}) {
|
|
117
|
+
assertCoordinatorContractAuthority(decision?.contractPatch, !automatic);
|
|
114
118
|
if (!isDynamicWorkItem(workItem)) {
|
|
115
119
|
throw new Error('Work Center dynamic Action creation requires a Coordinator-driven WorkItem');
|
|
116
120
|
}
|
|
@@ -131,10 +135,20 @@ export function prepareDynamicActionMutation({
|
|
|
131
135
|
if (supersedeActionIds.some(actionId => closeActionIds.has(actionId))) {
|
|
132
136
|
throw new Error('Work Center cannot both close and supersede the same Action');
|
|
133
137
|
}
|
|
138
|
+
const contractPatch = normalizeContractPatch(decision.contractPatch);
|
|
134
139
|
const effectiveWorkItem = {
|
|
135
140
|
...workItem,
|
|
136
|
-
...(
|
|
141
|
+
...(contractPatch || {}),
|
|
137
142
|
};
|
|
143
|
+
if (contractPatch && ['title', 'goal', 'acceptanceCriteria', 'deliveryTarget']
|
|
144
|
+
.some(key => JSON.stringify(effectiveWorkItem[key]) !== JSON.stringify(workItem[key]))) {
|
|
145
|
+
effectiveWorkItem.revision = (Number(workItem.revision) || 1) + 1;
|
|
146
|
+
}
|
|
147
|
+
const progress = deriveGoalProgress({ ...effectiveWorkItem, actions });
|
|
148
|
+
if (progress.totalCriteriaCount > 0 && !progress.remainingCriteria.length
|
|
149
|
+
&& progress.delivery.status === 'passed' && !progress.blockers.length) {
|
|
150
|
+
throw new Error('Action creation requires an unmet goal condition or blocker; current evidence already supports completion');
|
|
151
|
+
}
|
|
138
152
|
const createdActions = requested.map((raw, index) => {
|
|
139
153
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
140
154
|
throw new Error('Work Center Coordinator Action specification must be an object');
|
|
@@ -187,6 +201,18 @@ export function prepareDynamicActionMutation({
|
|
|
187
201
|
throw new Error(`Work Center integrate Action source is not a completed isolated write: ${invalid}`);
|
|
188
202
|
}
|
|
189
203
|
}
|
|
204
|
+
// Optional refs are persisted inside the existing brief envelope, keeping
|
|
205
|
+
// historical Actions readable without a schema migration.
|
|
206
|
+
const goalRefs = normalizeDynamicGoalRefs(raw.goalRefs, effectiveWorkItem, actions);
|
|
207
|
+
const repeated = actions.some(previous => !['closed', 'superseded', 'cancelled'].includes(previous.status)
|
|
208
|
+
&& previous.type === type && previous.brief?.objective === brief.objective)
|
|
209
|
+
|| requested.slice(0, index).some(previous => previous.type === type && previous.objective?.trim() === brief.objective);
|
|
210
|
+
const rationale = raw.rationale || (goalRefs ? raw.goalRefs.rationale : '');
|
|
211
|
+
if (repeated && (!goalRefs || typeof rationale !== 'string' || rationale.trim().length < 20)) {
|
|
212
|
+
throw new Error('Repeated Action requires goalRefs and a concrete rationale explaining the remaining gap or changed evidence');
|
|
213
|
+
}
|
|
214
|
+
if (goalRefs) brief.goalRefs = goalRefs;
|
|
215
|
+
if (typeof rationale === 'string' && rationale.trim()) brief.rationale = rationale.trim().slice(0, 2_000);
|
|
190
216
|
const action = {
|
|
191
217
|
id,
|
|
192
218
|
type,
|
|
@@ -233,11 +259,34 @@ export function prepareDynamicActionMutation({
|
|
|
233
259
|
createdActions,
|
|
234
260
|
closeActions,
|
|
235
261
|
supersedeActionIds,
|
|
236
|
-
contractPatch
|
|
262
|
+
contractPatch,
|
|
237
263
|
workItemType,
|
|
238
264
|
};
|
|
239
265
|
}
|
|
240
266
|
|
|
267
|
+
/** Optional explicit connection to an unmet criterion, delivery boundary, or blocker. */
|
|
268
|
+
export function normalizeDynamicGoalRefs(value, workItem, actions = []) {
|
|
269
|
+
if (value == null) return null;
|
|
270
|
+
if (typeof value !== 'object' || Array.isArray(value)) throw new Error('Action goalRefs must be an object');
|
|
271
|
+
const criteria = uniqueStrings(value.criteria);
|
|
272
|
+
const blockerActionIds = uniqueStrings(value.blockerActionIds);
|
|
273
|
+
const delivery = value.delivery === true;
|
|
274
|
+
const progress = deriveGoalProgress({ ...workItem, actions });
|
|
275
|
+
if (!criteria.length && !blockerActionIds.length && !delivery) {
|
|
276
|
+
throw new Error('Action goalRefs must address an unmet criterion, delivery condition, or blocker');
|
|
277
|
+
}
|
|
278
|
+
if (criteria.some(criterion => !progress.remainingCriteria.includes(criterion))) {
|
|
279
|
+
throw new Error('Action goalRefs references an unknown or already satisfied criterion');
|
|
280
|
+
}
|
|
281
|
+
if (blockerActionIds.some(id => !progress.blockers.some(blocker => blocker.actionId === id))) {
|
|
282
|
+
throw new Error('Action goalRefs references an unknown or resolved blocker');
|
|
283
|
+
}
|
|
284
|
+
if (delivery && progress.delivery.status === 'passed') {
|
|
285
|
+
throw new Error('Action goalRefs references an already satisfied delivery condition');
|
|
286
|
+
}
|
|
287
|
+
return { criteria, blockerActionIds, delivery };
|
|
288
|
+
}
|
|
289
|
+
|
|
241
290
|
function normalizeEvidenceRunIds(value) {
|
|
242
291
|
return uniqueStrings(value);
|
|
243
292
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { normalizeEvidence, normalizeOutputs } from './evidence.js';
|
|
2
|
+
import { runMatchesActionIdentity } from './action-identity.js';
|
|
3
|
+
|
|
4
|
+
const CHECK_STATUSES = new Set(['passed', 'failed', 'deferred', 'not_applicable']);
|
|
5
|
+
const INACTIVE = new Set(['closed', 'superseded', 'cancelled']);
|
|
6
|
+
const NEGATIVE = new Set(['failed', 'error', 'pending']);
|
|
7
|
+
const revision = value => Math.max(1, Number(value) || 1);
|
|
8
|
+
|
|
9
|
+
export function validGoalChecks(run, criteria) {
|
|
10
|
+
return Array.isArray(run?.acceptanceChecks) && run.acceptanceChecks.length === criteria.length
|
|
11
|
+
&& run.acceptanceChecks.every((check, index) => (
|
|
12
|
+
check?.criterion === criteria[index] && CHECK_STATUSES.has(check.status)
|
|
13
|
+
&& typeof check.evidence === 'string' && check.evidence.trim()
|
|
14
|
+
));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasContradictoryEvidence(run) {
|
|
18
|
+
return !!run?.error || run?.reviewDecision === 'changes_requested'
|
|
19
|
+
|| [...normalizeEvidence(run?.evidence), ...normalizeOutputs(run?.outputs)]
|
|
20
|
+
.some(item => NEGATIVE.has(item.status));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read only durable identities, never a model's progress estimate. Older records
|
|
25
|
+
* use Action.contractRevision when the optional execution manifest is absent.
|
|
26
|
+
* A completed Run needs its canonical pointer; failed/waiting Actions use their
|
|
27
|
+
* latest terminal attempt only to report blockers/contradictions, never proof.
|
|
28
|
+
*/
|
|
29
|
+
export function currentGoalRuns(detail) {
|
|
30
|
+
const runs = Array.isArray(detail?.runs) ? detail.runs : [];
|
|
31
|
+
const result = [];
|
|
32
|
+
for (const action of detail?.actions || []) {
|
|
33
|
+
if (INACTIVE.has(action.status) || action.status === 'running'
|
|
34
|
+
|| revision(action.contractRevision) !== revision(detail.revision)) continue;
|
|
35
|
+
const owned = runs.filter(run => run.actionId === action.id
|
|
36
|
+
&& (!run.workItemId || run.workItemId === detail.id)
|
|
37
|
+
&& (!action.workItemId || action.workItemId === detail.id)
|
|
38
|
+
&& runMatchesActionIdentity(run, action)
|
|
39
|
+
&& (run.executionManifest?.actionGeneration == null || revision(run.executionManifest.actionGeneration) === revision(action.generation))
|
|
40
|
+
&& (!run.executionManifest?.actionSpecHash || run.executionManifest.actionSpecHash === action.specHash)
|
|
41
|
+
&& revision(run.executionManifest?.contractRevision ?? action.contractRevision) === revision(detail.revision)
|
|
42
|
+
&& revision(run.contextSnapshot?.contract?.revision ?? action.contractRevision) === revision(detail.revision)
|
|
43
|
+
&& ['completed', 'failed', 'waiting'].includes(run.status));
|
|
44
|
+
const run = action.status === 'completed'
|
|
45
|
+
? owned.find(candidate => candidate.id === action.resultRunId && candidate.status === 'completed')
|
|
46
|
+
: ['failed', 'waiting'].includes(action.status)
|
|
47
|
+
? owned.sort((a, b) => Number(b.endedAt || b.startedAt) - Number(a.endedAt || a.startedAt))[0]
|
|
48
|
+
: null;
|
|
49
|
+
if (run) result.push(run);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Derived, restart-safe projection; no new database state or migrations. */
|
|
55
|
+
export function deriveGoalProgress(detail) {
|
|
56
|
+
const contract = Array.isArray(detail?.acceptanceCriteria) ? detail.acceptanceCriteria : [];
|
|
57
|
+
const runs = currentGoalRuns(detail);
|
|
58
|
+
const proof = runs.filter(run => run.status === 'completed' && normalizeEvidence(run.evidence).length > 0
|
|
59
|
+
&& !hasContradictoryEvidence(run) && validGoalChecks(run, contract));
|
|
60
|
+
const byId = new Map((detail?.actions || []).map(action => [action.id, action]));
|
|
61
|
+
// Retrying, guiding, or closing an Action is not a correction. Keep historical
|
|
62
|
+
// negative observations until a newer current canonical Run disproves them.
|
|
63
|
+
// These attempts can invalidate proof but can never establish it.
|
|
64
|
+
const observations = (detail?.runs || []).filter(run => {
|
|
65
|
+
const action = byId.get(run.actionId);
|
|
66
|
+
return action && (!run.workItemId || run.workItemId === detail.id)
|
|
67
|
+
&& (!action.workItemId || action.workItemId === detail.id)
|
|
68
|
+
&& ['completed', 'failed', 'waiting', 'retryable'].includes(run.status)
|
|
69
|
+
&& revision(run.executionManifest?.contractRevision ?? action.contractRevision) === revision(detail.revision)
|
|
70
|
+
&& revision(run.contextSnapshot?.contract?.revision ?? action.contractRevision) === revision(detail.revision);
|
|
71
|
+
});
|
|
72
|
+
const observedAt = run => Number(run.endedAt || run.startedAt) || 0;
|
|
73
|
+
// Subsequent writes can invalidate earlier tests. A later read-only observation
|
|
74
|
+
// does not invalidate unrelated proof; after a write, re-check the affected
|
|
75
|
+
// contract rather than completing from a pre-change observation.
|
|
76
|
+
// Retiring a writer is not rollback. Include historical attempts, including
|
|
77
|
+
// failed/closed/superseded generations, in the freshness watermark.
|
|
78
|
+
const writes = (detail?.runs || []).filter(run => {
|
|
79
|
+
const action = byId.get(run.actionId);
|
|
80
|
+
return action && (!run.workItemId || run.workItemId === detail.id)
|
|
81
|
+
&& action.workspaceMode && action.workspaceMode !== 'read'
|
|
82
|
+
&& revision(run.executionManifest?.contractRevision ?? action.contractRevision) === revision(detail.revision);
|
|
83
|
+
});
|
|
84
|
+
// Ending after another writer is insufficient: tests may have run before its
|
|
85
|
+
// mutation. Without a durable ordering at equal timestamps, require a new
|
|
86
|
+
// observation. The writer's own end-to-end proof remains usable.
|
|
87
|
+
const freshProof = proof.filter(run => writes.every(writer => writer.id === run.id
|
|
88
|
+
|| (Number(run.startedAt) || observedAt(run)) > observedAt(writer)));
|
|
89
|
+
const contradictions = contract.map((criterion, index) => observations.filter(run => (
|
|
90
|
+
run.acceptanceChecks?.[index]?.criterion === criterion
|
|
91
|
+
&& (run.acceptanceChecks[index].status === 'failed'
|
|
92
|
+
|| (run.acceptanceChecks[index].status !== 'not_applicable' && hasContradictoryEvidence(run)))
|
|
93
|
+
)));
|
|
94
|
+
const latestContradictionAt = contradictions.map(runs => Math.max(-Infinity, ...runs.map(observedAt)));
|
|
95
|
+
const criteria = contract.map((criterion, index) => {
|
|
96
|
+
// A correction establishes new proof; it never revives an older disproved
|
|
97
|
+
// Run. Overlapping/tied observations cannot establish corrective ordering.
|
|
98
|
+
const passing = freshProof.filter(run => run.acceptanceChecks[index].status === 'passed'
|
|
99
|
+
&& (Number(run.startedAt) || observedAt(run)) > latestContradictionAt[index]);
|
|
100
|
+
const conflicts = passing.length ? [] : contradictions[index];
|
|
101
|
+
const evidenceRunIds = passing.map(run => run.id);
|
|
102
|
+
return {
|
|
103
|
+
criterion,
|
|
104
|
+
status: conflicts.length ? 'failed' : evidenceRunIds.length ? 'passed' : 'unmet',
|
|
105
|
+
evidenceRunIds: conflicts.length ? [] : evidenceRunIds,
|
|
106
|
+
...(conflicts.length ? { conflictingRunIds: conflicts.map(run => run.id) } : {}),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
const target = detail?.deliveryTarget || null;
|
|
110
|
+
const outputKind = { workspace_files: 'file', pull_request: 'pr', merge: 'commit' }[target];
|
|
111
|
+
const deliveryRunIds = freshProof.filter(run => target === 'response'
|
|
112
|
+
? typeof run.summary === 'string' && run.summary.trim()
|
|
113
|
+
// Summaries are indivisible: do not deliver one whose applicable claims
|
|
114
|
+
// predate a contradiction, even after a different Run corrects it.
|
|
115
|
+
&& run.acceptanceChecks.every((check, index) => check.status !== 'failed'
|
|
116
|
+
&& (check.status === 'not_applicable'
|
|
117
|
+
|| (Number(run.startedAt) || observedAt(run)) > latestContradictionAt[index]))
|
|
118
|
+
: outputKind && normalizeOutputs(run.outputs).some(output => output.kind === outputKind && !NEGATIVE.has(output.status)))
|
|
119
|
+
.map(run => run.id);
|
|
120
|
+
const blockers = (detail?.actions || []).filter(action => ['failed', 'waiting'].includes(action.status))
|
|
121
|
+
.map(action => {
|
|
122
|
+
const run = runs.find(candidate => candidate.actionId === action.id);
|
|
123
|
+
return { actionId: action.id, status: action.status, reason: run?.waitingReason || run?.error || action.brief?.objective || '' };
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
contractRevision: revision(detail?.revision),
|
|
127
|
+
criteria,
|
|
128
|
+
remainingCriteria: criteria.filter(item => item.status !== 'passed').map(item => item.criterion),
|
|
129
|
+
completedCriteriaCount: criteria.filter(item => item.status === 'passed').length,
|
|
130
|
+
totalCriteriaCount: criteria.length,
|
|
131
|
+
evidenceRunIds: [...new Set(criteria.flatMap(item => item.evidenceRunIds))],
|
|
132
|
+
blockers,
|
|
133
|
+
delivery: { target, status: deliveryRunIds.length ? 'passed' : 'unmet', evidenceRunIds: deliveryRunIds },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -162,6 +162,15 @@ function sumExecutionStats(values) {
|
|
|
162
162
|
}, emptyExecutionStats());
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
function combinedExecutionStats(detail) {
|
|
166
|
+
const stats = Array.isArray(detail.runs) ? sumExecutionStats(detail.runs) : executionStats(detail.executionStats);
|
|
167
|
+
if (detail.executionControl?.usage) {
|
|
168
|
+
const usage = executionStats(detail.executionControl.usage);
|
|
169
|
+
for (const key of Object.keys(usage)) if (!['loopCount', 'toolCount'].includes(key)) stats[key] = usage[key];
|
|
170
|
+
}
|
|
171
|
+
return stats;
|
|
172
|
+
}
|
|
173
|
+
|
|
165
174
|
function actionGeneration(value) {
|
|
166
175
|
return Math.max(1, count(value) || 1);
|
|
167
176
|
}
|
|
@@ -765,6 +774,7 @@ function enforceWorkItemBrowserDtoBudget(value, options = {}) {
|
|
|
765
774
|
status: truncateUtf8(workItem.status, 256),
|
|
766
775
|
currentActionId: truncateUtf8(workItem.currentActionId, 4 * 1024) || null,
|
|
767
776
|
executionStats: workItem.executionStats,
|
|
777
|
+
executionControl: workItem.executionControl,
|
|
768
778
|
actionCount: count(workItem.actionCount),
|
|
769
779
|
actions: Array.isArray(workItem.actions) ? [] : undefined,
|
|
770
780
|
actionStats: Array.isArray(workItem.actionStats) ? [] : undefined,
|
|
@@ -896,6 +906,39 @@ function workItemFailureReason(detail) {
|
|
|
896
906
|
* Authenticated browser detail DTO. Raw execution records stay Agent-local;
|
|
897
907
|
* the browser receives only aggregate execution stats plus the explicit user-facing response.
|
|
898
908
|
*/
|
|
909
|
+
function projectGoalProgress(progress) {
|
|
910
|
+
if (!progress || !Array.isArray(progress.criteria)) return null;
|
|
911
|
+
const ids = value => Array.isArray(value) ? value.slice(0, 64).map(id => truncateUtf8(String(id), 256)) : [];
|
|
912
|
+
const prioritized = [...progress.criteria.filter(item => item.status !== 'passed'),
|
|
913
|
+
...progress.criteria.filter(item => item.status === 'passed')];
|
|
914
|
+
const criteria = prioritized.slice(0, 100).map(item => ({
|
|
915
|
+
criterion: truncateUtf8(item.criterion || '', 1024),
|
|
916
|
+
status: ['passed', 'failed'].includes(item.status) ? item.status : 'unmet',
|
|
917
|
+
evidenceRunIds: ids(item.evidenceRunIds),
|
|
918
|
+
...(item.conflictingRunIds ? { conflictingRunIds: ids(item.conflictingRunIds) } : {}),
|
|
919
|
+
}));
|
|
920
|
+
return {
|
|
921
|
+
contractRevision: count(progress.contractRevision), criteria,
|
|
922
|
+
completedCriteriaCount: count(progress.completedCriteriaCount),
|
|
923
|
+
totalCriteriaCount: count(progress.totalCriteriaCount),
|
|
924
|
+
remainingCriteriaCount: Math.max(0, count(progress.totalCriteriaCount) - count(progress.completedCriteriaCount)),
|
|
925
|
+
omittedCriteriaCount: Math.max(0, progress.criteria.length - criteria.length),
|
|
926
|
+
remainingCriteria: criteria.filter(item => item.status !== 'passed').map(item => item.criterion),
|
|
927
|
+
evidenceRunIds: ids(progress.evidenceRunIds),
|
|
928
|
+
blockers: (progress.blockers || []).slice(0, 64).map(blocker => ({
|
|
929
|
+
actionId: truncateUtf8(blocker.actionId || '', 256),
|
|
930
|
+
status: blocker.status === 'failed' ? 'failed' : 'waiting',
|
|
931
|
+
reason: sanitizeDiagnosticText(blocker.reason || '', MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
932
|
+
})),
|
|
933
|
+
delivery: {
|
|
934
|
+
target: ['response', 'workspace_files', 'pull_request', 'merge'].includes(progress.delivery?.target)
|
|
935
|
+
? progress.delivery.target : null,
|
|
936
|
+
status: progress.delivery?.status === 'passed' ? 'passed' : 'unmet',
|
|
937
|
+
evidenceRunIds: ids(progress.delivery?.evidenceRunIds),
|
|
938
|
+
},
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
899
942
|
export function projectWorkItemDetail(detail, options = {}) {
|
|
900
943
|
if (!detail) return null;
|
|
901
944
|
const liveActionId = bodyActionId(detail);
|
|
@@ -949,6 +992,12 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
949
992
|
runId: truncateUtf8(rawOutput?.runId || '', 256) || null,
|
|
950
993
|
};
|
|
951
994
|
}).filter(output => output?.kind && output.label && output.ref) : [],
|
|
995
|
+
responses: Array.isArray(detail.finalResult.responses)
|
|
996
|
+
? detail.finalResult.responses.slice(0, 24).map(response => ({
|
|
997
|
+
runId: truncateUtf8(response?.runId || '', 256),
|
|
998
|
+
summary: truncateUtf8(response?.summary || '', 8 * 1024),
|
|
999
|
+
evidence: projectCanonicalEvidence(response?.evidence),
|
|
1000
|
+
})) : [],
|
|
952
1001
|
residualRisks: Array.isArray(detail.finalResult.residualRisks)
|
|
953
1002
|
? detail.finalResult.residualRisks
|
|
954
1003
|
.map(risk => truncateUtf8(risk, MAX_ACTION_MESSAGE_CHARS)).slice(0, 24) : [],
|
|
@@ -956,6 +1005,7 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
956
1005
|
title: detail.title,
|
|
957
1006
|
goal: detail.goal,
|
|
958
1007
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
1008
|
+
goalProgress: projectGoalProgress(detail.goalProgress),
|
|
959
1009
|
workflowTemplate: detail.workflowTemplate,
|
|
960
1010
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
961
1011
|
planningMode: detail.workflowSnapshot?.planningMode || detail.planningMode || 'static',
|
|
@@ -967,11 +1017,10 @@ export function projectWorkItemDetail(detail, options = {}) {
|
|
|
967
1017
|
attentionActionIds: Array.isArray(detail.attentionActionIds) ? detail.attentionActionIds : undefined,
|
|
968
1018
|
mainline,
|
|
969
1019
|
currentActionId: detail.currentActionId || null,
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
: executionStats(detail.executionStats),
|
|
1020
|
+
executionControl: detail.executionControl,
|
|
1021
|
+
executionStats: combinedExecutionStats(detail),
|
|
973
1022
|
reuseMemory: detail.reuseMemory !== false,
|
|
974
|
-
deliveryTarget: ['workspace_files', 'pull_request', 'merge'].includes(detail.deliveryTarget)
|
|
1023
|
+
deliveryTarget: ['response', 'workspace_files', 'pull_request', 'merge'].includes(detail.deliveryTarget)
|
|
975
1024
|
? detail.deliveryTarget : null,
|
|
976
1025
|
waitingReason: sanitizeDiagnosticText(waitingReason(detail), MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
977
1026
|
failureReason: workItemFailureReason(detail),
|
|
@@ -1059,7 +1108,8 @@ export function projectWorkItemSummary(detail) {
|
|
|
1059
1108
|
? detail.actionStats.map(action => ({ ...action })) : [],
|
|
1060
1109
|
actionCount: count(detail.actionCount),
|
|
1061
1110
|
completedActionCount: count(detail.completedActionCount),
|
|
1062
|
-
executionStats:
|
|
1111
|
+
executionStats: combinedExecutionStats(detail),
|
|
1112
|
+
executionControl: detail.executionControl,
|
|
1063
1113
|
origin: detail.origin?.sessionId ? { sessionId: detail.origin.sessionId } : null,
|
|
1064
1114
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
1065
1115
|
attachmentCount: Array.isArray(detail.attachments) ? detail.attachments.length : 0,
|
|
@@ -1094,9 +1144,8 @@ export function projectWorkItemSummary(detail) {
|
|
|
1094
1144
|
currentActionId: detail.currentActionId || null,
|
|
1095
1145
|
actionCount: detail.actions.filter(item => !['superseded', 'cancelled'].includes(item?.status)).length,
|
|
1096
1146
|
completedActionCount: detail.actions.filter(item => item?.status === 'completed').length,
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
: executionStats(detail.executionStats),
|
|
1147
|
+
executionControl: detail.executionControl,
|
|
1148
|
+
executionStats: combinedExecutionStats(detail),
|
|
1100
1149
|
failureReason: workItemFailureReason(detail),
|
|
1101
1150
|
|
|
1102
1151
|
currentAction: projectCurrentActionSummary(action, projectedAction),
|