@yeaft/webchat-agent 1.0.218 → 1.0.220
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/server/api.js +18 -15
- package/local-runtime/server/auth/request-auth.js +82 -0
- package/local-runtime/server/index.js +1 -1
- package/local-runtime/server/routes/auth-routes.js +30 -16
- package/local-runtime/server/ws-client.js +12 -11
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +105 -91
- 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/projection.js +107 -20
- package/yeaft/work-center/service.js +3 -6
- package/yeaft/work-center/store.js +63 -14
|
Binary file
|
package/package.json
CHANGED
|
@@ -148,6 +148,39 @@ function sumExecutionStats(values) {
|
|
|
148
148
|
}, emptyExecutionStats());
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
function actionGeneration(value) {
|
|
152
|
+
return Math.max(1, count(value) || 1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function runGeneration(run) {
|
|
156
|
+
return actionGeneration(run?.actionGeneration ?? run?.executionManifest?.actionGeneration);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function runSpecHash(run) {
|
|
160
|
+
return typeof run?.actionSpecHash === 'string' && run.actionSpecHash
|
|
161
|
+
? run.actionSpecHash
|
|
162
|
+
: typeof run?.executionManifest?.actionSpecHash === 'string'
|
|
163
|
+
? run.executionManifest.actionSpecHash
|
|
164
|
+
: '';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function threadRuns(action, runs) {
|
|
168
|
+
const source = (Array.isArray(runs) ? runs : []).filter(run => run?.actionId === action?.id);
|
|
169
|
+
const selectedSpecByGeneration = new Map((Array.isArray(action?.identityHistory) ? action.identityHistory : [])
|
|
170
|
+
.filter(identity => typeof identity?.specHash === 'string' && identity.specHash)
|
|
171
|
+
.map(identity => [actionGeneration(identity.generation), identity.specHash]));
|
|
172
|
+
const currentGeneration = actionGeneration(action?.generation);
|
|
173
|
+
if (typeof action?.specHash === 'string' && action.specHash) {
|
|
174
|
+
selectedSpecByGeneration.set(currentGeneration, action.specHash);
|
|
175
|
+
}
|
|
176
|
+
return source.filter(run => {
|
|
177
|
+
const generation = runGeneration(run);
|
|
178
|
+
const selectedSpec = selectedSpecByGeneration.get(generation) || '';
|
|
179
|
+
const spec = runSpecHash(run);
|
|
180
|
+
return selectedSpec ? spec === selectedSpec : generation === 1 && !spec;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
151
184
|
function normalizeProjectedMessage(message) {
|
|
152
185
|
if (!message || typeof message !== 'object') return null;
|
|
153
186
|
const text = typeof message.text === 'string'
|
|
@@ -167,13 +200,14 @@ function normalizeProjectedMessage(message) {
|
|
|
167
200
|
...(message.progressRevision == null ? {} : { progressRevision: count(message.progressRevision) }),
|
|
168
201
|
...(message.generation == null ? {} : { generation: Math.max(1, count(message.generation) || 1) }),
|
|
169
202
|
...(message.attempt == null ? {} : { attempt: Math.max(1, count(message.attempt) || 1) }),
|
|
203
|
+
...(message.runId == null ? {} : { runId: String(message.runId) }),
|
|
170
204
|
};
|
|
171
205
|
}
|
|
172
206
|
|
|
173
|
-
function actionInputMessages(action, events) {
|
|
207
|
+
function actionInputMessages(action, events, generation = actionGeneration(action?.generation), includeThreadIdentity = false) {
|
|
174
208
|
return (Array.isArray(events) ? events : [])
|
|
175
209
|
.filter(event => event?.actionId === action?.id
|
|
176
|
-
&&
|
|
210
|
+
&& actionGeneration(event.actionGeneration) === generation
|
|
177
211
|
&& ['action.guidance_added', 'action.input_added'].includes(event.type))
|
|
178
212
|
.map(event => normalizeProjectedMessage({
|
|
179
213
|
id: `event:${event.id}`,
|
|
@@ -183,11 +217,12 @@ function actionInputMessages(action, events) {
|
|
|
183
217
|
text: event.data?.text || event.data?.guidance || '',
|
|
184
218
|
attachments: event.data?.attachments,
|
|
185
219
|
createdAt: event.createdAt,
|
|
220
|
+
...(includeThreadIdentity ? { generation } : {}),
|
|
186
221
|
}))
|
|
187
222
|
.filter(Boolean);
|
|
188
223
|
}
|
|
189
224
|
|
|
190
|
-
function runResponseMessage(run) {
|
|
225
|
+
function runResponseMessage(run, includeThreadIdentity = false) {
|
|
191
226
|
return normalizeProjectedMessage({
|
|
192
227
|
id: `run:${run.id}`,
|
|
193
228
|
role: 'assistant',
|
|
@@ -197,15 +232,18 @@ function runResponseMessage(run) {
|
|
|
197
232
|
createdAt: count(run.startedAt),
|
|
198
233
|
updatedAt: count(run.endedAt || run.startedAt),
|
|
199
234
|
progressRevision: count(run.progressRevision),
|
|
200
|
-
|
|
201
|
-
|
|
235
|
+
...(includeThreadIdentity ? {
|
|
236
|
+
generation: runGeneration(run),
|
|
237
|
+
attempt: run.actionAttempt,
|
|
238
|
+
runId: run.id,
|
|
239
|
+
} : {}),
|
|
202
240
|
});
|
|
203
241
|
}
|
|
204
242
|
|
|
205
|
-
function loopOutputMessages(action, events, matchingRunIds) {
|
|
243
|
+
function loopOutputMessages(action, events, matchingRunIds, generation = actionGeneration(action?.generation), includeThreadIdentity = false) {
|
|
206
244
|
return (Array.isArray(events) ? events : [])
|
|
207
245
|
.filter(event => event?.actionId === action?.id
|
|
208
|
-
&&
|
|
246
|
+
&& actionGeneration(event.actionGeneration ?? event.data?.actionGeneration) === generation
|
|
209
247
|
&& matchingRunIds.has(event.runId)
|
|
210
248
|
&& event.type === 'run.loop_output')
|
|
211
249
|
.map(event => normalizeProjectedMessage({
|
|
@@ -215,33 +253,73 @@ function loopOutputMessages(action, events, matchingRunIds) {
|
|
|
215
253
|
status: 'completed',
|
|
216
254
|
text: event.data?.response || '',
|
|
217
255
|
createdAt: event.createdAt,
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
...(includeThreadIdentity ? {
|
|
257
|
+
generation: event.actionGeneration ?? event.data?.actionGeneration,
|
|
258
|
+
attempt: event.data?.actionAttempt,
|
|
259
|
+
runId: event.runId,
|
|
260
|
+
} : {}),
|
|
220
261
|
}))
|
|
221
262
|
.filter(Boolean);
|
|
222
263
|
}
|
|
223
264
|
|
|
224
|
-
function
|
|
225
|
-
const matchingRuns =
|
|
226
|
-
? runs.filter(run => run?.actionId === action?.id && runMatchesActionIdentity(run, action))
|
|
227
|
-
: [];
|
|
265
|
+
function messagesForGeneration(action, runs, events, generation, includeThreadIdentity = false) {
|
|
266
|
+
const matchingRuns = threadRuns(action, runs).filter(run => runGeneration(run) === generation);
|
|
228
267
|
const matchingRunIds = new Set(matchingRuns.map(run => run.id));
|
|
229
268
|
const runsWithLoopOutput = new Set((Array.isArray(events) ? events : [])
|
|
230
269
|
.filter(event => event?.actionId === action?.id
|
|
231
|
-
&&
|
|
270
|
+
&& actionGeneration(event.actionGeneration ?? event.data?.actionGeneration) === generation
|
|
232
271
|
&& matchingRunIds.has(event.runId)
|
|
233
272
|
&& event.type === 'run.loop_output')
|
|
234
273
|
.map(event => event.runId));
|
|
235
|
-
return [
|
|
274
|
+
return [
|
|
275
|
+
...actionInputMessages(action, events, generation, includeThreadIdentity),
|
|
276
|
+
...loopOutputMessages(action, events, matchingRunIds, generation, includeThreadIdentity),
|
|
277
|
+
...matchingRuns
|
|
236
278
|
.sort((left, right) => count(left.startedAt) - count(right.startedAt))
|
|
237
279
|
.filter(run => !runsWithLoopOutput.has(run.id))
|
|
238
|
-
.map(run => runResponseMessage(run))
|
|
280
|
+
.map(run => runResponseMessage(run, includeThreadIdentity))
|
|
239
281
|
.filter(Boolean)]
|
|
240
282
|
.sort((left, right) => left.createdAt - right.createdAt
|
|
241
283
|
|| (left.role === 'user' ? -1 : 1)
|
|
242
284
|
|| left.id.localeCompare(right.id));
|
|
243
285
|
}
|
|
244
286
|
|
|
287
|
+
function actionMessages(action, runs, events) {
|
|
288
|
+
return messagesForGeneration(action, runs, events, actionGeneration(action?.generation));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function projectActionThread(action, runs, events) {
|
|
292
|
+
const allRuns = threadRuns(action, runs);
|
|
293
|
+
const generations = new Set([
|
|
294
|
+
actionGeneration(action?.generation),
|
|
295
|
+
...allRuns.map(runGeneration),
|
|
296
|
+
...(Array.isArray(events) ? events : [])
|
|
297
|
+
.filter(event => event?.actionId === action?.id
|
|
298
|
+
&& ['action.guidance_added', 'action.input_added', 'run.loop_output'].includes(event.type))
|
|
299
|
+
.map(event => actionGeneration(event.actionGeneration ?? event.data?.actionGeneration)),
|
|
300
|
+
]);
|
|
301
|
+
return [...generations].sort((left, right) => left - right).map(generation => {
|
|
302
|
+
const canonical = generation === actionGeneration(action?.generation);
|
|
303
|
+
return {
|
|
304
|
+
generation,
|
|
305
|
+
canonical,
|
|
306
|
+
messages: canonical ? [] : messagesForGeneration(action, allRuns, events, generation, true).slice(-MAX_ACTION_MESSAGES),
|
|
307
|
+
runs: allRuns.filter(run => runGeneration(run) === generation)
|
|
308
|
+
.sort((left, right) => count(left.startedAt) - count(right.startedAt) || String(left.id).localeCompare(String(right.id)))
|
|
309
|
+
.map(run => ({
|
|
310
|
+
id: run.id,
|
|
311
|
+
attempt: Math.max(1, count(run.actionAttempt) || 1),
|
|
312
|
+
status: run.status || 'running',
|
|
313
|
+
startedAt: count(run.startedAt),
|
|
314
|
+
endedAt: count(run.endedAt),
|
|
315
|
+
progressRevision: count(run.progressRevision),
|
|
316
|
+
loopCount: count(run.loopCount),
|
|
317
|
+
toolCount: count(run.toolCount),
|
|
318
|
+
})),
|
|
319
|
+
};
|
|
320
|
+
}).filter(entry => entry.messages.length > 0 || entry.runs.length > 0 || entry.canonical);
|
|
321
|
+
}
|
|
322
|
+
|
|
245
323
|
|
|
246
324
|
|
|
247
325
|
const MAX_FAILURE_REASON_LENGTH = 2_000;
|
|
@@ -457,6 +535,7 @@ function projectAction(action, runs, events, includeBody = true) {
|
|
|
457
535
|
response: execution.response,
|
|
458
536
|
failure: execution.failure,
|
|
459
537
|
messages: execution.messages,
|
|
538
|
+
thread: Array.isArray(runs) ? projectActionThread(action, runs, events) : (action.thread || []),
|
|
460
539
|
liveMessage: execution.liveMessage,
|
|
461
540
|
} : {}),
|
|
462
541
|
};
|
|
@@ -478,6 +557,7 @@ function stripActionBody(action, keepFailure = false) {
|
|
|
478
557
|
const projected = { ...action };
|
|
479
558
|
delete projected.response;
|
|
480
559
|
delete projected.messages;
|
|
560
|
+
delete projected.thread;
|
|
481
561
|
delete projected.liveMessage;
|
|
482
562
|
if (!keepFailure) delete projected.failure;
|
|
483
563
|
if (projected.brief) {
|
|
@@ -539,6 +619,7 @@ function enforceWorkItemBrowserDtoBudget(value, options = {}) {
|
|
|
539
619
|
if (keep) {
|
|
540
620
|
delete keep.response;
|
|
541
621
|
delete keep.messages;
|
|
622
|
+
delete keep.thread;
|
|
542
623
|
delete keep.liveMessage;
|
|
543
624
|
}
|
|
544
625
|
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
@@ -844,12 +925,18 @@ export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
|
844
925
|
};
|
|
845
926
|
}
|
|
846
927
|
|
|
928
|
+
export function actionThreadIncludesRun(action, runs, runId) {
|
|
929
|
+
return threadRuns(action, runs).some(run => run.id === runId);
|
|
930
|
+
}
|
|
931
|
+
|
|
847
932
|
export function projectActionRequestIndex(action, entries) {
|
|
933
|
+
const source = Array.isArray(entries) ? entries : [];
|
|
934
|
+
const allowedRunIds = new Set(threadRuns(action, source.map(({ run }) => run)).map(run => run.id));
|
|
848
935
|
return {
|
|
849
936
|
actionId: action.id,
|
|
850
937
|
generation: Math.max(1, count(action.generation) || 1),
|
|
851
|
-
requests:
|
|
852
|
-
.filter(({ run }) =>
|
|
938
|
+
requests: source
|
|
939
|
+
.filter(({ run }) => allowedRunIds.has(run?.id))
|
|
853
940
|
.map(({ run, turn }) => ({
|
|
854
941
|
id: turn.turnId,
|
|
855
942
|
runId: run.id,
|
|
@@ -872,8 +959,8 @@ export function projectActionRequestIndex(action, entries) {
|
|
|
872
959
|
};
|
|
873
960
|
}
|
|
874
961
|
|
|
875
|
-
export function projectActionRequestDetail(action, run, history) {
|
|
876
|
-
if (!
|
|
962
|
+
export function projectActionRequestDetail(action, run, history, runs = [run]) {
|
|
963
|
+
if (!actionThreadIncludesRun(action, runs, run?.id)) return null;
|
|
877
964
|
const turn = Array.isArray(history?.turns) ? history.turns[0] : null;
|
|
878
965
|
if (!turn) return null;
|
|
879
966
|
const sourceLoops = Array.isArray(history?.loops) ? history.loops : [];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { runMatchesActionIdentity } from './action-identity.js';
|
|
5
4
|
import { WorkItemStore } from './store.js';
|
|
6
5
|
import { WorkflowController } from './controller.js';
|
|
7
6
|
import { WorkItemWatcher } from './watcher.js';
|
|
@@ -143,8 +142,7 @@ export class WorkCenterService {
|
|
|
143
142
|
const detail = this.#requiredItem(payload.id);
|
|
144
143
|
const action = this.#requiredAction(detail, payload.actionId);
|
|
145
144
|
const entries = [];
|
|
146
|
-
for (const run of detail.runs.filter(item => item.actionId === action.id
|
|
147
|
-
&& runMatchesActionIdentity(item, action))) {
|
|
145
|
+
for (const run of detail.runs.filter(item => item.actionId === action.id)) {
|
|
148
146
|
const history = await this.#debugHistory(run, { indexOnly: true });
|
|
149
147
|
for (const turn of Array.isArray(history?.turns) ? history.turns : []) {
|
|
150
148
|
entries.push({ run, turn });
|
|
@@ -156,11 +154,10 @@ export class WorkCenterService {
|
|
|
156
154
|
const detail = this.#requiredItem(payload.id);
|
|
157
155
|
const action = this.#requiredAction(detail, payload.actionId);
|
|
158
156
|
const requestId = requiredString(payload.requestId, 'requestId');
|
|
159
|
-
const run = detail.runs.find(item => item.actionId === action.id
|
|
160
|
-
&& item.id === payload.runId && runMatchesActionIdentity(item, action));
|
|
157
|
+
const run = detail.runs.find(item => item.actionId === action.id && item.id === payload.runId);
|
|
161
158
|
if (!run) throw new Error('Action request not found');
|
|
162
159
|
const history = await this.#debugHistory(run, { detailTurnId: requestId });
|
|
163
|
-
const projected = projectActionRequestDetail(action, run, history);
|
|
160
|
+
const projected = projectActionRequestDetail(action, run, history, detail.runs);
|
|
164
161
|
if (!projected) throw new Error('Action request detail is no longer available');
|
|
165
162
|
return projected;
|
|
166
163
|
}
|
|
@@ -158,6 +158,7 @@ function mapAction(row) {
|
|
|
158
158
|
contractRevision: row.contract_revision,
|
|
159
159
|
generation: Math.max(1, Number(row.generation) || 1),
|
|
160
160
|
specHash: row.spec_hash || '',
|
|
161
|
+
identityHistory: parseJson(row.identity_history, []),
|
|
161
162
|
resultRunId: row.result_run_id || null,
|
|
162
163
|
status: row.status,
|
|
163
164
|
attempt: row.attempt,
|
|
@@ -170,6 +171,19 @@ function mapAction(row) {
|
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
function actionIdentityHistory(action, generation = action?.generation, specHash = action?.specHash) {
|
|
175
|
+
const byGeneration = new Map();
|
|
176
|
+
for (const identity of Array.isArray(action?.identityHistory) ? action.identityHistory : []) {
|
|
177
|
+
const value = Math.max(1, Number(identity?.generation) || 1);
|
|
178
|
+
if (typeof identity?.specHash === 'string' && identity.specHash) {
|
|
179
|
+
byGeneration.set(value, { generation: value, specHash: identity.specHash });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const value = Math.max(1, Number(generation) || 1);
|
|
183
|
+
if (typeof specHash === 'string' && specHash) byGeneration.set(value, { generation: value, specHash });
|
|
184
|
+
return [...byGeneration.values()].sort((left, right) => left.generation - right.generation).slice(-100);
|
|
185
|
+
}
|
|
186
|
+
|
|
173
187
|
function mapRun(row) {
|
|
174
188
|
if (!row) return null;
|
|
175
189
|
return {
|
|
@@ -324,6 +338,7 @@ export class WorkItemStore {
|
|
|
324
338
|
contract_revision INTEGER NOT NULL DEFAULT 1,
|
|
325
339
|
generation INTEGER NOT NULL DEFAULT 1,
|
|
326
340
|
spec_hash TEXT NOT NULL DEFAULT '',
|
|
341
|
+
identity_history TEXT NOT NULL DEFAULT '[]',
|
|
327
342
|
result_run_id TEXT,
|
|
328
343
|
status TEXT NOT NULL,
|
|
329
344
|
attempt INTEGER NOT NULL DEFAULT 0,
|
|
@@ -549,6 +564,16 @@ export class WorkItemStore {
|
|
|
549
564
|
if (!hasColumn(this.db, 'actions', 'spec_hash')) {
|
|
550
565
|
this.db.exec("ALTER TABLE actions ADD COLUMN spec_hash TEXT NOT NULL DEFAULT ''");
|
|
551
566
|
}
|
|
567
|
+
if (!hasColumn(this.db, 'actions', 'identity_history')) {
|
|
568
|
+
this.db.exec("ALTER TABLE actions ADD COLUMN identity_history TEXT NOT NULL DEFAULT '[]'");
|
|
569
|
+
const seedIdentity = this.db.prepare('UPDATE actions SET identity_history = ? WHERE id = ?');
|
|
570
|
+
for (const row of this.db.prepare('SELECT id, generation, spec_hash FROM actions').all()) {
|
|
571
|
+
seedIdentity.run(stringify(actionIdentityHistory({
|
|
572
|
+
generation: row.generation,
|
|
573
|
+
specHash: row.spec_hash,
|
|
574
|
+
})), row.id);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
552
577
|
if (!hasColumn(this.db, 'actions', 'result_run_id')) {
|
|
553
578
|
this.db.exec('ALTER TABLE actions ADD COLUMN result_run_id TEXT');
|
|
554
579
|
}
|
|
@@ -838,12 +863,13 @@ export class WorkItemStore {
|
|
|
838
863
|
updatedAt: now,
|
|
839
864
|
};
|
|
840
865
|
action.specHash = actionSpecHash(action);
|
|
866
|
+
action.identityHistory = actionIdentityHistory(action);
|
|
841
867
|
this.db.prepare(`INSERT INTO actions
|
|
842
868
|
(id, work_item_id, sequence, type, required_role, stage_id, assignment_policy, model_policy,
|
|
843
869
|
depends_on_stage_ids, workspace_mode, changes_requested_stage_id, workspace, instruction, brief, context, contract_revision,
|
|
844
|
-
generation, spec_hash, result_run_id, status, attempt, max_attempts, current_run_id, lease_epoch,
|
|
870
|
+
generation, spec_hash, identity_history, result_run_id, status, attempt, max_attempts, current_run_id, lease_epoch,
|
|
845
871
|
replaces_action_id, created_at, updated_at)
|
|
846
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, ?, ?, ?)`).run(
|
|
872
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, ?, ?, ?)`).run(
|
|
847
873
|
action.id,
|
|
848
874
|
workItemId,
|
|
849
875
|
action.sequence,
|
|
@@ -862,6 +888,7 @@ export class WorkItemStore {
|
|
|
862
888
|
action.contractRevision,
|
|
863
889
|
action.generation,
|
|
864
890
|
action.specHash,
|
|
891
|
+
stringify(action.identityHistory),
|
|
865
892
|
action.resultRunId,
|
|
866
893
|
action.status,
|
|
867
894
|
action.attempt,
|
|
@@ -926,10 +953,13 @@ export class WorkItemStore {
|
|
|
926
953
|
const nextAction = action.id === target.id && replacement
|
|
927
954
|
? { ...action, ...replacement, generation: action.generation + 1 }
|
|
928
955
|
: { ...action, generation: action.generation + 1 };
|
|
956
|
+
const nextSpecHash = actionSpecHash(nextAction);
|
|
929
957
|
this.db.prepare(`UPDATE actions SET status = 'ready', attempt = 0, current_run_id = NULL,
|
|
930
|
-
lease_epoch = ?, generation = generation + 1, spec_hash = ?, result_run_id = NULL,
|
|
958
|
+
lease_epoch = ?, generation = generation + 1, spec_hash = ?, identity_history = ?, result_run_id = NULL,
|
|
931
959
|
workspace = ?, updated_at = ? WHERE id = ?`).run(
|
|
932
|
-
nextEpoch.get(action.id),
|
|
960
|
+
nextEpoch.get(action.id), nextSpecHash,
|
|
961
|
+
stringify(actionIdentityHistory(action, nextAction.generation, nextSpecHash)),
|
|
962
|
+
stringify(workspace), now, action.id,
|
|
933
963
|
);
|
|
934
964
|
}
|
|
935
965
|
}
|
|
@@ -976,13 +1006,18 @@ export class WorkItemStore {
|
|
|
976
1006
|
const nextWorkspaceMode = workspaceMode || action.workspaceMode;
|
|
977
1007
|
const specChanged = nextWorkspaceMode !== action.workspaceMode;
|
|
978
1008
|
const nextAction = { ...action, workspaceMode: nextWorkspaceMode };
|
|
1009
|
+
const nextSpecHash = specChanged ? actionSpecHash(nextAction) : action.specHash;
|
|
979
1010
|
const changed = this.db.prepare(`UPDATE actions SET workspace = ?, workspace_mode = ?,
|
|
980
|
-
generation = generation + ?, spec_hash = ?,
|
|
1011
|
+
generation = generation + ?, spec_hash = ?, identity_history = ?,
|
|
1012
|
+
result_run_id = CASE WHEN ? = 1 THEN NULL ELSE result_run_id END,
|
|
981
1013
|
updated_at = ? WHERE id = ? AND generation = ?`).run(
|
|
982
1014
|
stringify(workspace),
|
|
983
1015
|
nextWorkspaceMode,
|
|
984
1016
|
specChanged ? 1 : 0,
|
|
985
|
-
|
|
1017
|
+
nextSpecHash,
|
|
1018
|
+
stringify(specChanged
|
|
1019
|
+
? actionIdentityHistory(action, action.generation + 1, nextSpecHash)
|
|
1020
|
+
: action.identityHistory),
|
|
986
1021
|
specChanged ? 1 : 0,
|
|
987
1022
|
this.now(),
|
|
988
1023
|
actionId,
|
|
@@ -1027,13 +1062,17 @@ export class WorkItemStore {
|
|
|
1027
1062
|
}
|
|
1028
1063
|
}
|
|
1029
1064
|
const changed = this.db.prepare(`UPDATE actions SET workspace = ?, workspace_mode = ?,
|
|
1030
|
-
generation = generation + ?, spec_hash = ?,
|
|
1065
|
+
generation = generation + ?, spec_hash = ?, identity_history = ?,
|
|
1066
|
+
result_run_id = CASE WHEN ? = 1 THEN NULL ELSE result_run_id END,
|
|
1031
1067
|
updated_at = ? WHERE id = ? AND status = 'running' AND current_run_id = ?
|
|
1032
1068
|
AND lease_epoch = ? AND generation = ?`).run(
|
|
1033
1069
|
stringify(workspace),
|
|
1034
1070
|
nextWorkspaceMode,
|
|
1035
1071
|
specChanged ? 1 : 0,
|
|
1036
1072
|
nextSpecHash,
|
|
1073
|
+
stringify(specChanged
|
|
1074
|
+
? actionIdentityHistory(action, nextGeneration, nextSpecHash)
|
|
1075
|
+
: action.identityHistory),
|
|
1037
1076
|
specChanged ? 1 : 0,
|
|
1038
1077
|
now,
|
|
1039
1078
|
actionId,
|
|
@@ -1067,11 +1106,13 @@ export class WorkItemStore {
|
|
|
1067
1106
|
for (const row of pendingRows) {
|
|
1068
1107
|
const pending = mapAction(row);
|
|
1069
1108
|
const fallback = { ...pending, workspaceMode: 'shared', workspace: null };
|
|
1109
|
+
const fallbackSpecHash = actionSpecHash(fallback);
|
|
1070
1110
|
const repaired = this.db.prepare(`UPDATE actions SET workspace = NULL, workspace_mode = 'shared',
|
|
1071
|
-
generation = generation + 1, spec_hash = ?, result_run_id = NULL, updated_at = ?
|
|
1111
|
+
generation = generation + 1, spec_hash = ?, identity_history = ?, result_run_id = NULL, updated_at = ?
|
|
1072
1112
|
WHERE id = ? AND status = 'ready' AND current_run_id IS NULL
|
|
1073
1113
|
AND generation = ? AND workspace_mode = ?`).run(
|
|
1074
|
-
|
|
1114
|
+
fallbackSpecHash,
|
|
1115
|
+
stringify(actionIdentityHistory(pending, pending.generation + 1, fallbackSpecHash)),
|
|
1075
1116
|
now,
|
|
1076
1117
|
pending.id,
|
|
1077
1118
|
pending.generation,
|
|
@@ -1406,12 +1447,18 @@ export class WorkItemStore {
|
|
|
1406
1447
|
for (const action of openActions) {
|
|
1407
1448
|
if (action.status === 'ready') {
|
|
1408
1449
|
const instruction = updateActionInstruction(updatedWorkItem, action);
|
|
1409
|
-
|
|
1410
|
-
|
|
1450
|
+
const nextGeneration = action.generation + 1;
|
|
1451
|
+
const nextSpecHash = actionSpecHash({ ...action, instruction, generation: nextGeneration });
|
|
1452
|
+
this.db.prepare(`UPDATE actions SET instruction = ?, generation = ?, spec_hash = ?,
|
|
1453
|
+
identity_history = ?, result_run_id = NULL, updated_at = ?
|
|
1454
|
+
WHERE id = ? AND status = 'ready' AND generation = ?`).run(
|
|
1411
1455
|
instruction,
|
|
1412
|
-
|
|
1456
|
+
nextGeneration,
|
|
1457
|
+
nextSpecHash,
|
|
1458
|
+
stringify(actionIdentityHistory(action, nextGeneration, nextSpecHash)),
|
|
1413
1459
|
now,
|
|
1414
1460
|
action.id,
|
|
1461
|
+
action.generation,
|
|
1415
1462
|
);
|
|
1416
1463
|
continue;
|
|
1417
1464
|
}
|
|
@@ -2210,10 +2257,11 @@ export class WorkItemStore {
|
|
|
2210
2257
|
resultRunId: null,
|
|
2211
2258
|
contractRevision: nextWorkItem.revision,
|
|
2212
2259
|
};
|
|
2260
|
+
const candidateSpecHash = actionSpecHash(candidate);
|
|
2213
2261
|
const changed = this.db.prepare(`UPDATE actions SET type = ?, required_role = ?, stage_id = ?,
|
|
2214
2262
|
assignment_policy = ?, model_policy = ?, depends_on_stage_ids = ?, workspace_mode = ?,
|
|
2215
2263
|
changes_requested_stage_id = ?, workspace = NULL, instruction = ?, brief = ?, context = ?,
|
|
2216
|
-
contract_revision = ?, generation = ?, spec_hash = ?, result_run_id = NULL, status = 'ready',
|
|
2264
|
+
contract_revision = ?, generation = ?, spec_hash = ?, identity_history = ?, result_run_id = NULL, status = 'ready',
|
|
2217
2265
|
attempt = 0, max_attempts = ?, current_run_id = NULL, lease_epoch = lease_epoch + 1,
|
|
2218
2266
|
updated_at = ? WHERE id = ? AND work_item_id = ? AND status = 'superseded' AND generation = ?`).run(
|
|
2219
2267
|
candidate.type, candidate.requiredRole || '', candidate.stageId,
|
|
@@ -2221,7 +2269,8 @@ export class WorkItemStore {
|
|
|
2221
2269
|
stringify(candidate.dependsOnStageIds || []), candidate.workspaceMode || 'shared',
|
|
2222
2270
|
candidate.changesRequestedStageId || null, candidate.instruction || '', stringify(candidate.brief || null),
|
|
2223
2271
|
stringify(candidate.context || []), candidate.contractRevision, candidate.generation,
|
|
2224
|
-
|
|
2272
|
+
candidateSpecHash, stringify(actionIdentityHistory(prior, candidate.generation, candidateSpecHash)),
|
|
2273
|
+
candidate.maxAttempts || 2, now,
|
|
2225
2274
|
prior.id, workItem.id, prior.generation,
|
|
2226
2275
|
);
|
|
2227
2276
|
if (Number(changed.changes) !== 1) throw new Error('Work Center retained Action lost its superseded identity fence');
|