@yeaft/webchat-agent 1.0.173 → 1.0.175

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.
@@ -12,7 +12,13 @@ import {
12
12
  removeWorkItemAttachments,
13
13
  } from './attachments.js';
14
14
  import { normalizeSessionContextSnapshot } from './session-context.js';
15
- import { projectWorkItemDetail, projectWorkItemSummary } from './projection.js';
15
+ import {
16
+ projectActionMessagePage,
17
+ projectActionRequestDetail,
18
+ projectActionRequestIndex,
19
+ projectWorkItemDetail,
20
+ projectWorkItemSummary,
21
+ } from './projection.js';
16
22
  import { readWorkCenterSettings, writeWorkCenterSettings } from './settings.js';
17
23
  import {
18
24
  defaultWorkCenterStageInstructions,
@@ -81,7 +87,38 @@ export class WorkCenterService {
81
87
  watcher: this.watcher.status(),
82
88
  };
83
89
  case 'get':
84
- return projectWorkItemDetail(this.#requiredItem(payload.id));
90
+ return this.#requiredItem(payload.id);
91
+ case 'get_action_messages': {
92
+ const detail = this.#requiredItem(payload.id);
93
+ const action = this.#requiredAction(detail, payload.actionId);
94
+ return projectActionMessagePage(action, detail.runs, detail.events, {
95
+ cursor: payload.cursor,
96
+ limit: payload.limit,
97
+ });
98
+ }
99
+ case 'get_action_requests': {
100
+ const detail = this.#requiredItem(payload.id);
101
+ const action = this.#requiredAction(detail, payload.actionId);
102
+ const entries = [];
103
+ for (const run of detail.runs.filter(item => item.actionId === action.id)) {
104
+ const history = await this.#debugHistory(run, { indexOnly: true });
105
+ for (const turn of Array.isArray(history?.turns) ? history.turns : []) {
106
+ entries.push({ run, turn });
107
+ }
108
+ }
109
+ return projectActionRequestIndex(action, entries);
110
+ }
111
+ case 'get_action_request': {
112
+ const detail = this.#requiredItem(payload.id);
113
+ const action = this.#requiredAction(detail, payload.actionId);
114
+ const requestId = requiredString(payload.requestId, 'requestId');
115
+ const run = detail.runs.find(item => item.actionId === action.id && item.id === payload.runId);
116
+ if (!run) throw new Error('Action request not found');
117
+ const history = await this.#debugHistory(run, { detailTurnId: requestId });
118
+ const projected = projectActionRequestDetail(action, run, history);
119
+ if (!projected) throw new Error('Action request detail is no longer available');
120
+ return projected;
121
+ }
85
122
  case 'get_settings': {
86
123
  const settings = this.settingsReader(this.yeaftDir);
87
124
  return { settings, runtime: await this.runtimeInfo() };
@@ -168,6 +205,38 @@ export class WorkCenterService {
168
205
  this.#emit({ type: 'work_item.cancelled', workItem: detail });
169
206
  return detail;
170
207
  }
208
+ case 'action_input': {
209
+ const id = requiredString(payload.id, 'id');
210
+ const workItem = this.#requiredItem(id);
211
+ let addedAttachments = [];
212
+ let detail;
213
+ try {
214
+ addedAttachments = appendWorkItemAttachments(workItem.attachments, payload.files, {
215
+ root: this.attachmentRoot,
216
+ workItemId: id,
217
+ });
218
+ detail = this.controller.input(id, {
219
+ text: typeof payload.text === 'string' ? payload.text : '',
220
+ actionId: typeof payload.actionId === 'string' ? payload.actionId : '',
221
+ revision: payload.revision,
222
+ addedAttachmentCount: addedAttachments.length,
223
+ addedAttachments,
224
+ attachments: [...(workItem.attachments || []), ...addedAttachments],
225
+ });
226
+ } catch (error) {
227
+ try {
228
+ if ((workItem.attachments || []).length === 0 && addedAttachments.length > 0) {
229
+ removeWorkItemAttachments(this.attachmentRoot, id);
230
+ } else {
231
+ removeWorkItemAttachmentFiles(this.attachmentRoot, id, addedAttachments);
232
+ }
233
+ } catch {}
234
+ throw error;
235
+ }
236
+ this.watcher.abortInvalidWorkItemRuns(id);
237
+ this.#emit({ type: 'action.input_added', workItem: detail });
238
+ return detail;
239
+ }
171
240
  case 'guide': {
172
241
  const id = requiredString(payload.id, 'id');
173
242
  const workItem = this.#requiredItem(id);
@@ -183,6 +252,7 @@ export class WorkCenterService {
183
252
  actionId: typeof payload.actionId === 'string' ? payload.actionId : '',
184
253
  revision: payload.revision,
185
254
  addedAttachmentCount: addedAttachments.length,
255
+ addedAttachments,
186
256
  attachments: [...(workItem.attachments || []), ...addedAttachments],
187
257
  });
188
258
  } catch (error) {
@@ -221,13 +291,6 @@ export class WorkCenterService {
221
291
  },
222
292
  };
223
293
  }
224
- case 'retry': {
225
- const detail = this.controller.retry(requiredString(payload.id, 'id'), {
226
- answer: typeof payload.answer === 'string' ? payload.answer : '',
227
- });
228
- this.#emit({ type: 'work_item.retried', workItem: detail });
229
- return detail;
230
- }
231
294
  case 'set_watcher':
232
295
  if (payload.enabled === false) await this.watcher.stop();
233
296
  else this.watcher.start();
@@ -243,6 +306,28 @@ export class WorkCenterService {
243
306
  return item;
244
307
  }
245
308
 
309
+ #requiredAction(detail, actionId) {
310
+ const id = requiredString(actionId, 'actionId');
311
+ const action = detail.actions.find(item => item.id === id);
312
+ if (!action) throw new Error(`Action not found: ${id}`);
313
+ return action;
314
+ }
315
+
316
+ async #debugHistory(run, options = {}) {
317
+ const trace = this.watcher.runner?.trace;
318
+ if (!trace || typeof trace.fetchRecentDebugHistory !== 'function') {
319
+ return { turns: [], loops: [] };
320
+ }
321
+ return trace.fetchRecentDebugHistory({
322
+ limit: 10,
323
+ dreamLimit: 0,
324
+ sessionId: `work-item-${run.workItemId}`,
325
+ threadId: run.id,
326
+ indexOnly: options.indexOnly === true,
327
+ detailTurnId: options.detailTurnId || null,
328
+ });
329
+ }
330
+
246
331
  #emit(event) {
247
332
  try { this.onEvent(event); } catch {}
248
333
  }
@@ -253,6 +338,7 @@ export class WorkCenterService {
253
338
 
254
339
  async shutdown() {
255
340
  await this.watcher.stop();
341
+ try { await this.watcher.runner?.trace?.close?.(); } catch {}
256
342
  this.store.close();
257
343
  }
258
344
  }
@@ -755,7 +755,7 @@ export class WorkItemStore {
755
755
  });
756
756
  }
757
757
 
758
- addActionGuidance(id, guidance, expected, makeAction, attachments = null) {
758
+ addActionGuidance(id, guidance, expected, makeAction, attachments = null, addedAttachments = []) {
759
759
  return withTransaction(this.db, () => {
760
760
  const workItem = this.getWorkItem(id);
761
761
  if (!workItem) return null;
@@ -788,7 +788,16 @@ export class WorkItemStore {
788
788
  now,
789
789
  id,
790
790
  );
791
- this.appendEvent(id, 'action.guidance_added', { guidance }, { actionId: action.id });
791
+ this.appendEvent(id, 'action.guidance_added', {
792
+ guidance,
793
+ attachments: (Array.isArray(addedAttachments) ? addedAttachments : []).map(attachment => ({
794
+ id: attachment.id,
795
+ name: attachment.name,
796
+ mimeType: attachment.mimeType,
797
+ size: Math.max(0, Number(attachment.size) || 0),
798
+ isImage: attachment.isImage === true,
799
+ })),
800
+ }, { actionId: action.id });
792
801
  return this.getWorkItemDetail(id);
793
802
  });
794
803
  }
@@ -906,13 +915,18 @@ export class WorkItemStore {
906
915
  });
907
916
  }
908
917
 
909
- retryWorkItemAtomic(id, makeAction) {
918
+ retryWorkItemAtomic(id, makeAction, options = {}) {
910
919
  return withTransaction(this.db, () => {
911
920
  const workItem = this.getWorkItem(id);
912
921
  if (!workItem) return null;
913
922
  if (!['waiting', 'needs_attention'].includes(workItem.status)) {
914
923
  throw new Error(`WorkItem in ${workItem.status} does not need retry`);
915
924
  }
925
+ if (options.expected) {
926
+ if (workItem.currentActionId !== options.expected.actionId || workItem.revision !== options.expected.revision) {
927
+ throw new Error('Action changed before input was applied; refresh and try again');
928
+ }
929
+ }
916
930
  const previous = workItem.currentActionId ? this.getAction(workItem.currentActionId) : null;
917
931
  const previousRun = previous
918
932
  ? mapRun(this.db.prepare(`SELECT * FROM runs WHERE work_item_id = ? AND action_id = ?
@@ -939,8 +953,20 @@ export class WorkItemStore {
939
953
  contractRevision: workItem.revision,
940
954
  }, this.#nextSequence(id), now);
941
955
  this.db.prepare(`UPDATE work_items SET status = 'ready', current_action_id = ?,
942
- current_run_id = NULL, updated_at = ? WHERE id = ?`).run(action.id, now, id);
943
- this.appendEvent(id, 'work_item.retried', {}, { actionId: action.id });
956
+ current_run_id = NULL, attachments = ?, updated_at = ? WHERE id = ?`).run(
957
+ action.id,
958
+ stringify(Array.isArray(options.attachments) ? options.attachments : workItem.attachments),
959
+ now,
960
+ id,
961
+ );
962
+ const inputEvent = options.inputEvent && typeof options.inputEvent === 'object'
963
+ ? options.inputEvent
964
+ : null;
965
+ if (inputEvent) {
966
+ this.appendEvent(id, 'action.input_added', inputEvent, { actionId: action.id });
967
+ } else {
968
+ this.appendEvent(id, 'work_item.retried', {}, { actionId: action.id });
969
+ }
944
970
  return this.getWorkItemDetail(id);
945
971
  });
946
972
  }