@yeaft/webchat-agent 1.0.531 → 1.0.533
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/connection/message-router.js +85 -1
- package/index.js +21 -11
- package/local-runtime/server/handlers/agent-sync.js +8 -2
- package/local-runtime/server/handlers/client-misc.js +40 -0
- package/local-runtime/server/ws-agent.js +2 -0
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +229 -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/config-api.js +30 -0
- package/yeaft/config.js +1 -0
- package/yeaft/session.js +3 -2
- package/yeaft/work-center/bridge.js +14 -3
- 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/feature.js +24 -7
- 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
|
@@ -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),
|