@yeaft/webchat-agent 1.0.316 → 1.0.317

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.316",
3
+ "version": "1.0.317",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -654,6 +654,10 @@ export class WorkItemCoordinator {
654
654
  text, recovery, addedAttachments, options, abortController,
655
655
  }) {
656
656
  let providerTurn = null;
657
+ let candidateSpeaker = null;
658
+ let speaker = (started.detail.messages || []).find(message => (
659
+ message?.turnId === started.turnId && message.role === 'assistant'
660
+ ))?.speaker || null;
657
661
  try {
658
662
  let normalized = null;
659
663
  let mutation = null;
@@ -697,6 +701,10 @@ export class WorkItemCoordinator {
697
701
  vps,
698
702
  priorRuns: started.detail.runs || [],
699
703
  });
704
+ candidateSpeaker = {
705
+ id: assignment.vp.id,
706
+ name: assignment.vp.name || assignment.vp.id,
707
+ };
700
708
  const coordinatorPolicy = settings?.coordinatorModelPolicy || {
701
709
  ...(settings?.modelPolicy || {}),
702
710
  effort: settings?.actionModelPolicies?.triage?.effort || settings?.modelPolicy?.effort || 'high',
@@ -736,9 +744,10 @@ export class WorkItemCoordinator {
736
744
  };
737
745
  const claim = started.fence.claim;
738
746
  providerTurn = this.store.prepareCoordinatorProviderTurn(
739
- started.detail.id, started.turnId, attemptCount, requestBody, claim,
747
+ started.detail.id, started.turnId, attemptCount, requestBody, claim, candidateSpeaker,
740
748
  );
741
749
  if (!providerTurn) return started.detail;
750
+ speaker = providerTurn.speaker;
742
751
  if (providerTurn.status === 'unknown') {
743
752
  throw new Error('Coordinator provider dispatch outcome is unknown and requires review');
744
753
  }
@@ -844,6 +853,7 @@ export class WorkItemCoordinator {
844
853
  }
845
854
  const detail = this.store.completeCoordinatorTurn(started.turnId, {
846
855
  reply: normalized.reply,
856
+ speaker,
847
857
  decision: normalized.decision,
848
858
  mutation,
849
859
  attemptCount,
@@ -855,7 +865,10 @@ export class WorkItemCoordinator {
855
865
  if (providerTurn?.status === 'responded') {
856
866
  this.store.rejectCoordinatorProviderTurn(providerTurn.id, error, started.fence.claim);
857
867
  }
858
- const detail = this.store.failCoordinatorTurn(started.turnId, error, started.fence);
868
+ const detail = this.store.failCoordinatorTurn(started.turnId, error, {
869
+ ...started.fence,
870
+ speaker,
871
+ });
859
872
  if (detail) {
860
873
  options.onUpdate?.('coordinator.turn_failed', detail);
861
874
  return detail;
@@ -19,6 +19,8 @@ const MAX_ACTION_REQUEST_NAME_BYTES = 512;
19
19
  const MAX_ACTION_REQUEST_MODEL_BYTES = 1_024;
20
20
  const MAX_ACTION_REQUEST_STOP_REASON_BYTES = 256;
21
21
  const MAX_ACTION_REQUEST_METADATA_BYTES = 4 * 1_024;
22
+ const MAX_SPEAKER_ID_BYTES = 256;
23
+ const MAX_SPEAKER_NAME_BYTES = 512;
22
24
  const MAX_HISTORICAL_BRIEF_CHARS = 256;
23
25
  const MAX_CURRENT_BRIEF_BYTES = 8 * 1024;
24
26
  export const MAX_WORK_ITEM_BROWSER_DTO_BYTES = 512 * 1024;
@@ -207,9 +209,12 @@ function conversationRuns(action, runs) {
207
209
 
208
210
  function projectVpSpeaker(snapshot) {
209
211
  if (!snapshot || typeof snapshot !== 'object') return null;
210
- const id = typeof snapshot.id === 'string' && snapshot.id ? snapshot.id : null;
211
- const name = typeof snapshot.name === 'string' && snapshot.name ? snapshot.name : id;
212
- return id || name ? { id, name } : null;
212
+ const sourceId = typeof snapshot.id === 'string' ? snapshot.id.trim() : '';
213
+ if (!sourceId) return null;
214
+ const id = truncateUtf8(sourceId, MAX_SPEAKER_ID_BYTES);
215
+ const sourceName = typeof snapshot.name === 'string' ? snapshot.name.trim() : '';
216
+ const name = truncateUtf8(sourceName || id, MAX_SPEAKER_NAME_BYTES) || id;
217
+ return { id, name };
213
218
  }
214
219
 
215
220
  function compareEventIds(leftId, rightId) {
@@ -896,6 +901,8 @@ export function projectWorkItemDetail(detail, options = {}) {
896
901
  id: String(message.id || ''),
897
902
  turnId: String(message.turnId || message.id || ''),
898
903
  role: message.role === 'assistant' ? 'assistant' : message.role === 'legacy_instruction' ? 'legacy_instruction' : 'user',
904
+ ...(message.role === 'assistant' && projectVpSpeaker(message.speaker)
905
+ ? { speaker: projectVpSpeaker(message.speaker) } : {}),
899
906
  text: truncateUtf8(message.text || '', MAX_ACTION_MESSAGE_CHARS),
900
907
  attachments: projectAttachments(message.attachments),
901
908
  status: ['thinking', 'completed', 'failed'].includes(message.status) ? message.status : 'completed',
@@ -1361,7 +1361,14 @@ export class WorkItemStore {
1361
1361
  WHERE status = 'claimed' AND lease_expires_at <= ?`).run(now, now).changes);
1362
1362
  }
1363
1363
 
1364
- prepareCoordinatorProviderTurn(workItemId, coordinatorTurnId, attemptNumber, requestBody, claim = {}) {
1364
+ prepareCoordinatorProviderTurn(
1365
+ workItemId,
1366
+ coordinatorTurnId,
1367
+ attemptNumber,
1368
+ requestBody,
1369
+ claim = {},
1370
+ speaker = null,
1371
+ ) {
1365
1372
  return withTransaction(this.db, () => {
1366
1373
  const mailbox = this.#activeCoordinatorMailboxClaim(coordinatorTurnId, claim);
1367
1374
  if (!mailbox || mailbox.work_item_id !== workItemId) return null;
@@ -1374,8 +1381,14 @@ export class WorkItemStore {
1374
1381
  }
1375
1382
  if (existing.claim_owner !== claim.ownerBootId
1376
1383
  || Number(existing.claim_epoch) !== Number(claim.claimEpoch)) return null;
1377
- return this.#mapCoordinatorProviderTurn(existing);
1384
+ const persistedSpeaker = this.#persistCoordinatorSpeaker(
1385
+ workItemId, coordinatorTurnId, null,
1386
+ );
1387
+ return { ...this.#mapCoordinatorProviderTurn(existing), speaker: persistedSpeaker };
1378
1388
  }
1389
+ const persistedSpeaker = this.#persistCoordinatorSpeaker(
1390
+ workItemId, coordinatorTurnId, speaker,
1391
+ );
1379
1392
  const now = this.now();
1380
1393
  const id = durableId('coordinator-provider-turn');
1381
1394
  this.db.prepare(`INSERT INTO coordinator_provider_turns
@@ -1385,10 +1398,49 @@ export class WorkItemStore {
1385
1398
  id, workItemId, coordinatorTurnId, attemptNumber, stringify(requestBody), requestHash,
1386
1399
  claim.ownerBootId, claim.claimEpoch, now, now,
1387
1400
  );
1388
- return this.getCoordinatorProviderTurn(id);
1401
+ return { ...this.getCoordinatorProviderTurn(id), speaker: persistedSpeaker };
1389
1402
  });
1390
1403
  }
1391
1404
 
1405
+ #persistCoordinatorSpeaker(workItemId, coordinatorTurnId, speaker) {
1406
+ const workItem = this.getWorkItem(workItemId);
1407
+ if (!workItem) return null;
1408
+ const messages = [...(workItem.messages || [])];
1409
+ const index = messages.findIndex(message => (
1410
+ message?.turnId === coordinatorTurnId
1411
+ && message.role === 'assistant'
1412
+ && message.status === 'thinking'
1413
+ ));
1414
+ if (index < 0) return null;
1415
+ const persistedSpeaker = messages[index].speaker;
1416
+ const persistedId = typeof persistedSpeaker?.id === 'string' ? persistedSpeaker.id.trim() : '';
1417
+ const persistedName = typeof persistedSpeaker?.name === 'string'
1418
+ ? persistedSpeaker.name.trim()
1419
+ : '';
1420
+ if (persistedId || persistedName) {
1421
+ return { id: persistedId || null, name: persistedName || persistedId };
1422
+ }
1423
+ const id = typeof speaker?.id === 'string' ? speaker.id.trim() : '';
1424
+ const name = typeof speaker?.name === 'string' ? speaker.name.trim() : '';
1425
+ if (!id && !name) return null;
1426
+ if (!id || !name) throw new Error('Coordinator speaker identity is incomplete');
1427
+ const persisted = { id, name };
1428
+ messages[index] = { ...messages[index], speaker: persisted };
1429
+ const changed = this.db.prepare(`UPDATE work_items SET messages = ?
1430
+ WHERE id = ? AND coordinator_revision = ?`).run(
1431
+ stringify(messages), workItemId, workItem.coordinatorRevision,
1432
+ );
1433
+ if (Number(changed.changes) !== 1) {
1434
+ throw new Error('Coordinator speaker lost its revision fence');
1435
+ }
1436
+ this.#appendConversationEntry(
1437
+ workItemId,
1438
+ messages[index],
1439
+ `coordinator:turn:${coordinatorTurnId}:assistant`,
1440
+ );
1441
+ return persisted;
1442
+ }
1443
+
1392
1444
  #activeCoordinatorMailboxClaim(coordinatorTurnId, claim = {}) {
1393
1445
  if (!claim.mailboxId || !claim.ownerBootId || !Number.isInteger(Number(claim.claimEpoch))) return null;
1394
1446
  return this.db.prepare(`SELECT * FROM coordinator_mailbox_entries WHERE id = ? AND status = 'claimed'
@@ -3363,7 +3415,11 @@ export class WorkItemStore {
3363
3415
 
3364
3416
  const graphState = ['answer'].includes(decision.kind) ? null : this.#graphWorkItemState(workItem.id);
3365
3417
  messages[assistantIndex] = {
3366
- ...messages[assistantIndex], text: result.reply, status: 'completed', updatedAt: now,
3418
+ ...messages[assistantIndex],
3419
+ text: result.reply,
3420
+ status: 'completed',
3421
+ updatedAt: now,
3422
+ ...(result.speaker ? { speaker: result.speaker } : {}),
3367
3423
  decision: {
3368
3424
  kind: decision.kind,
3369
3425
  reason: decision.reason,
@@ -3423,7 +3479,10 @@ export class WorkItemStore {
3423
3479
  if (index < 0) return null;
3424
3480
  const now = this.now();
3425
3481
  messages[index] = {
3426
- ...messages[index], status: 'failed', updatedAt: now,
3482
+ ...messages[index],
3483
+ status: 'failed',
3484
+ updatedAt: now,
3485
+ ...(expected.speaker ? { speaker: expected.speaker } : {}),
3427
3486
  error: String(error?.message || error || 'Coordinator failed').slice(0, 8_000),
3428
3487
  };
3429
3488
  this.#appendConversationEntry(