@yeaft/webchat-agent 1.0.217 → 1.0.219

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.
@@ -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), actionSpecHash(nextAction), stringify(workspace), now, 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 = ?, result_run_id = CASE WHEN ? = 1 THEN NULL ELSE result_run_id END,
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
- specChanged ? actionSpecHash(nextAction) : action.specHash,
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 = ?, result_run_id = CASE WHEN ? = 1 THEN NULL ELSE result_run_id END,
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
- actionSpecHash(fallback),
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
- this.db.prepare(`UPDATE actions SET instruction = ?, spec_hash = ?, updated_at = ?
1410
- WHERE id = ? AND status = 'ready'`).run(
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
- actionSpecHash({ ...action, instruction }),
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
  }
@@ -2094,6 +2141,15 @@ export class WorkItemStore {
2094
2141
  throw new Error('Work Center terminal transition lost the current Action fence');
2095
2142
  }
2096
2143
 
2144
+ if (transition.planConflict) {
2145
+ this.db.prepare(`INSERT INTO plan_conflicts
2146
+ (id, work_item_id, action_id, generation, kind, status, details, created_at, updated_at, resolved_at)
2147
+ VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, NULL)`).run(
2148
+ randomUUID(), workItem.id, action.id, action.generation,
2149
+ transition.planConflict.kind || 'plan', stringify(transition.planConflict), now, now,
2150
+ );
2151
+ }
2152
+
2097
2153
  let nextWorkItem = workItem;
2098
2154
  if (transition.contractPatch) {
2099
2155
  const patch = transition.contractPatch;
@@ -2175,10 +2231,68 @@ export class WorkItemStore {
2175
2231
  nextWorkItem = this.getWorkItem(workItem.id);
2176
2232
  nextAction = this.#insertAction(workItem.id, {
2177
2233
  ...barrier.action,
2234
+ context: [
2235
+ ...(Array.isArray(barrier.action.context) ? barrier.action.context : []),
2236
+ {
2237
+ type: 'replan-barrier',
2238
+ proposalId: barrier.proposalId,
2239
+ basePlanRevision: nextWorkItem.planRevision,
2240
+ candidateActionIds: unfinished.map(candidate => candidate.id),
2241
+ },
2242
+ ],
2178
2243
  contractRevision: nextWorkItem.revision,
2179
2244
  status: 'ready',
2180
2245
  }, this.#nextSequence(workItem.id), now);
2181
2246
  }
2247
+ if (transition.replanMutation) {
2248
+ for (const retained of transition.replanMutation.retain) {
2249
+ const prior = retained.action;
2250
+ const candidate = {
2251
+ ...prior,
2252
+ ...retained.nextAction,
2253
+ status: 'ready',
2254
+ generation: prior.generation + 1,
2255
+ attempt: 0,
2256
+ currentRunId: null,
2257
+ resultRunId: null,
2258
+ contractRevision: nextWorkItem.revision,
2259
+ };
2260
+ const candidateSpecHash = actionSpecHash(candidate);
2261
+ const changed = this.db.prepare(`UPDATE actions SET type = ?, required_role = ?, stage_id = ?,
2262
+ assignment_policy = ?, model_policy = ?, depends_on_stage_ids = ?, workspace_mode = ?,
2263
+ changes_requested_stage_id = ?, workspace = NULL, instruction = ?, brief = ?, context = ?,
2264
+ contract_revision = ?, generation = ?, spec_hash = ?, identity_history = ?, result_run_id = NULL, status = 'ready',
2265
+ attempt = 0, max_attempts = ?, current_run_id = NULL, lease_epoch = lease_epoch + 1,
2266
+ updated_at = ? WHERE id = ? AND work_item_id = ? AND status = 'superseded' AND generation = ?`).run(
2267
+ candidate.type, candidate.requiredRole || '', candidate.stageId,
2268
+ stringify(candidate.assignmentPolicy || null), stringify(candidate.modelPolicy || null),
2269
+ stringify(candidate.dependsOnStageIds || []), candidate.workspaceMode || 'shared',
2270
+ candidate.changesRequestedStageId || null, candidate.instruction || '', stringify(candidate.brief || null),
2271
+ stringify(candidate.context || []), candidate.contractRevision, candidate.generation,
2272
+ candidateSpecHash, stringify(actionIdentityHistory(prior, candidate.generation, candidateSpecHash)),
2273
+ candidate.maxAttempts || 2, now,
2274
+ prior.id, workItem.id, prior.generation,
2275
+ );
2276
+ if (Number(changed.changes) !== 1) throw new Error('Work Center retained Action lost its superseded identity fence');
2277
+ if (!nextAction) nextAction = this.getAction(prior.id);
2278
+ }
2279
+ for (const replacement of transition.replanMutation.replace) {
2280
+ const inserted = this.#insertAction(workItem.id, {
2281
+ ...replacement.nextAction,
2282
+ contractRevision: nextWorkItem.revision,
2283
+ status: 'ready',
2284
+ }, this.#nextSequence(workItem.id), now);
2285
+ if (!nextAction) nextAction = inserted;
2286
+ }
2287
+ for (const added of transition.replanMutation.add) {
2288
+ const inserted = this.#insertAction(workItem.id, {
2289
+ ...added,
2290
+ contractRevision: nextWorkItem.revision,
2291
+ status: 'ready',
2292
+ }, this.#nextSequence(workItem.id), now);
2293
+ if (!nextAction) nextAction = inserted;
2294
+ }
2295
+ }
2182
2296
  if (transition.graphResetStageId) {
2183
2297
  nextAction = this.#resetGraphFromStage(
2184
2298
  workItem.id,
@@ -2205,7 +2319,13 @@ export class WorkItemStore {
2205
2319
  let workItemStatus = transition.workItemStatus;
2206
2320
  let currentActionId = nextAction?.id ?? (transition.keepCurrentAction ? action.id : null);
2207
2321
  let changedWorkItem;
2208
- if (transition.graphAdvance) {
2322
+ if (transition.planConflict) {
2323
+ changedWorkItem = this.db.prepare(`UPDATE work_items SET status = ?, current_action_id = ?,
2324
+ current_run_id = NULL, ledger_revision = ledger_revision + ?, updated_at = ?
2325
+ WHERE id = ? AND status IN ('ready', 'running', 'waiting', 'needs_attention') AND revision = ?`).run(
2326
+ workItemStatus, currentActionId, ledgerIncrement, now, workItem.id, nextWorkItem.revision,
2327
+ );
2328
+ } else if (transition.graphAdvance) {
2209
2329
  const graphState = this.#graphWorkItemState(workItem.id);
2210
2330
  workItemStatus = graphState.status;
2211
2331
  currentActionId = graphState.currentActionId;
@@ -2233,7 +2353,9 @@ export class WorkItemStore {
2233
2353
  (work_item_id, proposal_id, base_plan_revision, plan_revision, kind, action_id, run_id, data, created_at)
2234
2354
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
2235
2355
  workItem.id, proposalId, workItem.planRevision, nextWorkItem.planRevision,
2236
- transition.replanBarrier ? 'replan' : (workItem.planRevision === 0 ? 'initial' : 'expand'),
2356
+ (transition.replanBarrier || transition.replanMutation)
2357
+ ? 'replan'
2358
+ : (workItem.planRevision === 0 ? 'initial' : 'expand'),
2237
2359
  action.id, runId, stringify(transition.eventData || {}), now,
2238
2360
  );
2239
2361
  } catch (error) {