@yeaft/webchat-agent 1.0.211 → 1.0.213
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/version.json +1 -1
- package/local-runtime/web/app.bundle.js +142 -112
- 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 +77 -0
- package/yeaft/work-center/service.js +42 -2
- package/yeaft/work-center/store.js +64 -8
|
Binary file
|
package/package.json
CHANGED
|
@@ -42,6 +42,77 @@ function projectCurrentActionSummary(action, projectedAction = action) {
|
|
|
42
42
|
assignmentMode: projectedAction.assignmentPolicy?.mode || (projectedAction.requiredRole ? 'fixed' : null),
|
|
43
43
|
status: projectedAction.status,
|
|
44
44
|
objective: truncateUtf8(action?.brief?.objective, 1_000) || null,
|
|
45
|
+
...(projectedAction.assignedVp ? { assignedVp: projectedAction.assignedVp } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const BOARD_ACTION_STATUSES = ['completed', 'running', 'ready', 'waiting', 'failed'];
|
|
50
|
+
|
|
51
|
+
function boardActionCounts(actions) {
|
|
52
|
+
const counts = Object.fromEntries(BOARD_ACTION_STATUSES.map(status => [status, 0]));
|
|
53
|
+
for (const action of Array.isArray(actions) ? actions : []) {
|
|
54
|
+
if (Object.hasOwn(counts, action?.status)) counts[action.status] += 1;
|
|
55
|
+
}
|
|
56
|
+
return counts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function workItemBoardLane(detail) {
|
|
60
|
+
const actions = (Array.isArray(detail?.actions) ? detail.actions : [])
|
|
61
|
+
.filter(action => !['superseded', 'cancelled'].includes(action?.status));
|
|
62
|
+
if (['done', 'cancelled'].includes(detail?.status)) return 'closed';
|
|
63
|
+
if (['draft', 'waiting', 'needs_attention'].includes(detail?.status)
|
|
64
|
+
|| actions.some(action => ['waiting', 'failed'].includes(action?.status))) {
|
|
65
|
+
return 'needs_attention';
|
|
66
|
+
}
|
|
67
|
+
return 'active';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function boardActionSummary(action, runs, events) {
|
|
71
|
+
if (!action) return null;
|
|
72
|
+
const projected = projectAction(action, runs, events, false);
|
|
73
|
+
return {
|
|
74
|
+
id: projected.id,
|
|
75
|
+
type: projected.type,
|
|
76
|
+
stageId: projected.stageId,
|
|
77
|
+
status: projected.status,
|
|
78
|
+
objective: truncateUtf8(action?.brief?.objective, 1_000) || null,
|
|
79
|
+
assignedVp: projected.assignedVp || null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function boardFields(detail) {
|
|
84
|
+
const actions = (Array.isArray(detail?.actions) ? detail.actions : [])
|
|
85
|
+
.filter(action => !['superseded', 'cancelled'].includes(action?.status));
|
|
86
|
+
const runs = Array.isArray(detail?.runs) ? detail.runs : [];
|
|
87
|
+
const events = Array.isArray(detail?.events) ? detail.events : [];
|
|
88
|
+
const bySequence = (left, right) => count(left?.sequence) - count(right?.sequence)
|
|
89
|
+
|| String(left?.id || '').localeCompare(String(right?.id || ''));
|
|
90
|
+
const attentionAction = [...actions]
|
|
91
|
+
.filter(action => ['waiting', 'failed'].includes(action?.status))
|
|
92
|
+
.sort((left, right) => {
|
|
93
|
+
const priority = { waiting: 0, failed: 1 };
|
|
94
|
+
return priority[left.status] - priority[right.status] || bySequence(left, right);
|
|
95
|
+
})[0] || null;
|
|
96
|
+
const activeAction = [...actions]
|
|
97
|
+
.filter(action => ['running', 'ready'].includes(action?.status))
|
|
98
|
+
.sort((left, right) => {
|
|
99
|
+
const priority = { running: 0, ready: 1 };
|
|
100
|
+
return priority[left.status] - priority[right.status] || bySequence(left, right);
|
|
101
|
+
})[0] || null;
|
|
102
|
+
const executors = [];
|
|
103
|
+
const seenExecutors = new Set();
|
|
104
|
+
for (const action of actions) {
|
|
105
|
+
const assignedVp = projectAction(action, runs, events, false).assignedVp;
|
|
106
|
+
if (!assignedVp?.id || seenExecutors.has(assignedVp.id)) continue;
|
|
107
|
+
seenExecutors.add(assignedVp.id);
|
|
108
|
+
executors.push(assignedVp);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
boardLane: workItemBoardLane({ ...detail, actions }),
|
|
112
|
+
actionCounts: boardActionCounts(actions),
|
|
113
|
+
attentionAction: boardActionSummary(attentionAction, runs, events),
|
|
114
|
+
activeAction: boardActionSummary(activeAction, runs, events),
|
|
115
|
+
executors,
|
|
45
116
|
};
|
|
46
117
|
}
|
|
47
118
|
|
|
@@ -673,6 +744,11 @@ export function projectWorkItemSummary(detail) {
|
|
|
673
744
|
attachmentCount: Array.isArray(detail.attachments) ? detail.attachments.length : 0,
|
|
674
745
|
createdAt: detail.createdAt,
|
|
675
746
|
updatedAt: detail.updatedAt,
|
|
747
|
+
boardLane: detail.boardLane || workItemBoardLane(detail),
|
|
748
|
+
actionCounts: detail.actionCounts || boardActionCounts([]),
|
|
749
|
+
attentionAction: detail.attentionAction || null,
|
|
750
|
+
activeAction: detail.activeAction || null,
|
|
751
|
+
executors: Array.isArray(detail.executors) ? detail.executors : [],
|
|
676
752
|
};
|
|
677
753
|
}
|
|
678
754
|
const action = currentAction(detail);
|
|
@@ -704,6 +780,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
704
780
|
attachmentCount: Array.isArray(detail.attachments) ? detail.attachments.length : 0,
|
|
705
781
|
createdAt: detail.createdAt,
|
|
706
782
|
updatedAt: detail.updatedAt,
|
|
783
|
+
...boardFields(detail),
|
|
707
784
|
};
|
|
708
785
|
}
|
|
709
786
|
|
|
@@ -44,6 +44,44 @@ function requiredWorkDir(value) {
|
|
|
44
44
|
return canonical;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function boardCursor(item) {
|
|
48
|
+
return Buffer.from(JSON.stringify([Number(item.updatedAt) || 0, String(item.id || '')]), 'utf8')
|
|
49
|
+
.toString('base64url');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseBoardCursor(value) {
|
|
53
|
+
if (typeof value !== 'string' || !value) return null;
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
56
|
+
if (!Array.isArray(parsed) || parsed.length !== 2 || !Number.isFinite(Number(parsed[0]))) return null;
|
|
57
|
+
return [Number(parsed[0]), String(parsed[1] || '')];
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listBoardItems(store, payload) {
|
|
64
|
+
const limit = Math.min(Math.max(Number(payload.limit) || 100, 1), 200);
|
|
65
|
+
const cursor = parseBoardCursor(payload.cursor);
|
|
66
|
+
const lane = ['needs_attention', 'active', 'closed'].includes(payload.lane) ? payload.lane : null;
|
|
67
|
+
const vpId = typeof payload.vpId === 'string' ? payload.vpId.trim() : '';
|
|
68
|
+
const workItemType = typeof payload.workItemType === 'string' ? payload.workItemType.trim() : '';
|
|
69
|
+
const projected = store.listWorkItems({
|
|
70
|
+
...payload,
|
|
71
|
+
lane,
|
|
72
|
+
vpId,
|
|
73
|
+
workItemType,
|
|
74
|
+
cursorUpdatedAt: cursor?.[0],
|
|
75
|
+
cursorId: cursor?.[1],
|
|
76
|
+
limit: limit + 1,
|
|
77
|
+
}).map(projectWorkItemSummary);
|
|
78
|
+
const items = projected.slice(0, limit);
|
|
79
|
+
return {
|
|
80
|
+
items,
|
|
81
|
+
nextCursor: projected.length > limit && items.length > 0 ? boardCursor(items.at(-1)) : null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
export class WorkCenterService {
|
|
48
86
|
constructor(options) {
|
|
49
87
|
const yeaftDir = requiredString(options?.yeaftDir, 'yeaftDir');
|
|
@@ -83,11 +121,13 @@ export class WorkCenterService {
|
|
|
83
121
|
|
|
84
122
|
async handle(op, payload = {}, requestContext = {}) {
|
|
85
123
|
switch (op) {
|
|
86
|
-
case 'list':
|
|
124
|
+
case 'list': {
|
|
125
|
+
const page = listBoardItems(this.store, payload);
|
|
87
126
|
return {
|
|
88
|
-
|
|
127
|
+
...page,
|
|
89
128
|
watcher: this.watcher.status(),
|
|
90
129
|
};
|
|
130
|
+
}
|
|
91
131
|
case 'get':
|
|
92
132
|
return this.#requiredItem(payload.id);
|
|
93
133
|
case 'get_action_messages': {
|
|
@@ -1192,11 +1192,55 @@ export class WorkItemStore {
|
|
|
1192
1192
|
where.push('(instr(w.origin, ?) > 0 OR instr(w.linked_session_ids, ?) > 0)');
|
|
1193
1193
|
values.push(`\"sessionId\":${JSON.stringify(sessionId)}`, JSON.stringify(sessionId));
|
|
1194
1194
|
}
|
|
1195
|
-
|
|
1195
|
+
const keyword = typeof filters.keyword === 'string' ? filters.keyword.trim()
|
|
1196
|
+
: typeof filters.search === 'string' ? filters.search.trim() : '';
|
|
1197
|
+
if (keyword) {
|
|
1196
1198
|
where.push('(w.title LIKE ? OR w.goal LIKE ?)');
|
|
1197
|
-
const query = `%${
|
|
1199
|
+
const query = `%${keyword}%`;
|
|
1198
1200
|
values.push(query, query);
|
|
1199
1201
|
}
|
|
1202
|
+
const createdFrom = Number(filters.createdFrom);
|
|
1203
|
+
const createdTo = Number(filters.createdTo);
|
|
1204
|
+
const updatedFrom = Number(filters.updatedFrom);
|
|
1205
|
+
const updatedTo = Number(filters.updatedTo);
|
|
1206
|
+
if (Number.isFinite(createdFrom) && createdFrom > 0) { where.push('w.created_at >= ?'); values.push(createdFrom); }
|
|
1207
|
+
if (Number.isFinite(createdTo) && createdTo > 0) { where.push('w.created_at <= ?'); values.push(createdTo); }
|
|
1208
|
+
if (Number.isFinite(updatedFrom) && updatedFrom > 0) { where.push('w.updated_at >= ?'); values.push(updatedFrom); }
|
|
1209
|
+
if (Number.isFinite(updatedTo) && updatedTo > 0) { where.push('w.updated_at <= ?'); values.push(updatedTo); }
|
|
1210
|
+
if (typeof filters.workItemType === 'string' && filters.workItemType.trim()) {
|
|
1211
|
+
where.push('instr(w.workflow_snapshot, ?) > 0');
|
|
1212
|
+
values.push(`\"workItemType\":${JSON.stringify(filters.workItemType.trim())}`);
|
|
1213
|
+
}
|
|
1214
|
+
if (typeof filters.vpId === 'string' && filters.vpId.trim()) {
|
|
1215
|
+
where.push(`EXISTS (SELECT 1 FROM actions executor_action
|
|
1216
|
+
JOIN runs executor_run ON executor_run.id = (
|
|
1217
|
+
SELECT latest_executor_run.id FROM runs latest_executor_run
|
|
1218
|
+
WHERE latest_executor_run.action_id = executor_action.id
|
|
1219
|
+
ORDER BY latest_executor_run.started_at DESC, latest_executor_run.progress_revision DESC
|
|
1220
|
+
LIMIT 1)
|
|
1221
|
+
WHERE executor_action.work_item_id = w.id
|
|
1222
|
+
AND executor_action.status NOT IN ('superseded', 'cancelled')
|
|
1223
|
+
AND instr(executor_run.vp_snapshot, ?) > 0)`);
|
|
1224
|
+
values.push(`\"id\":${JSON.stringify(filters.vpId.trim())}`);
|
|
1225
|
+
}
|
|
1226
|
+
if (filters.lane === 'closed') {
|
|
1227
|
+
where.push("w.status IN ('done', 'cancelled')");
|
|
1228
|
+
} else if (filters.lane === 'needs_attention') {
|
|
1229
|
+
where.push(`w.status NOT IN ('done', 'cancelled') AND (
|
|
1230
|
+
w.status IN ('draft', 'waiting', 'needs_attention') OR EXISTS (
|
|
1231
|
+
SELECT 1 FROM actions attention_action WHERE attention_action.work_item_id = w.id
|
|
1232
|
+
AND attention_action.status IN ('waiting', 'failed')))`);
|
|
1233
|
+
} else if (filters.lane === 'active') {
|
|
1234
|
+
where.push(`w.status NOT IN ('done', 'cancelled', 'draft', 'waiting', 'needs_attention')
|
|
1235
|
+
AND NOT EXISTS (SELECT 1 FROM actions attention_action WHERE attention_action.work_item_id = w.id
|
|
1236
|
+
AND attention_action.status IN ('waiting', 'failed'))`);
|
|
1237
|
+
}
|
|
1238
|
+
const cursorUpdatedAt = Number(filters.cursorUpdatedAt);
|
|
1239
|
+
const cursorId = typeof filters.cursorId === 'string' ? filters.cursorId : '';
|
|
1240
|
+
if (Number.isFinite(cursorUpdatedAt) && cursorUpdatedAt >= 0 && cursorId) {
|
|
1241
|
+
where.push('(w.updated_at < ? OR (w.updated_at = ? AND w.id < ?))');
|
|
1242
|
+
values.push(cursorUpdatedAt, cursorUpdatedAt, cursorId);
|
|
1243
|
+
}
|
|
1200
1244
|
const limit = Math.min(Math.max(Number(filters.limit) || 100, 1), 500);
|
|
1201
1245
|
const sql = `SELECT w.*,
|
|
1202
1246
|
current_action.type AS current_action_type,
|
|
@@ -1219,12 +1263,24 @@ export class WorkItemStore {
|
|
|
1219
1263
|
LEFT JOIN actions current_action ON current_action.id = w.current_action_id
|
|
1220
1264
|
LEFT JOIN runs r ON r.work_item_id = w.id
|
|
1221
1265
|
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
1222
|
-
GROUP BY w.id ORDER BY w.updated_at DESC LIMIT ?`;
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1266
|
+
GROUP BY w.id ORDER BY w.updated_at DESC, w.id DESC LIMIT ?`;
|
|
1267
|
+
const workItems = this.db.prepare(sql).all(...values, limit).map(mapWorkItem);
|
|
1268
|
+
if (workItems.length === 0) return [];
|
|
1269
|
+
const placeholders = workItems.map(() => '?').join(',');
|
|
1270
|
+
const ids = workItems.map(item => item.id);
|
|
1271
|
+
const actionsByWorkItem = new Map(ids.map(id => [id, []]));
|
|
1272
|
+
for (const row of this.db.prepare(`SELECT * FROM actions WHERE work_item_id IN (${placeholders})
|
|
1273
|
+
ORDER BY work_item_id, sequence`).all(...ids)) {
|
|
1274
|
+
actionsByWorkItem.get(row.work_item_id).push(mapAction(row));
|
|
1275
|
+
}
|
|
1276
|
+
const runsByWorkItem = new Map(ids.map(id => [id, []]));
|
|
1277
|
+
for (const row of this.db.prepare(`SELECT * FROM runs WHERE work_item_id IN (${placeholders})
|
|
1278
|
+
ORDER BY work_item_id, started_at DESC`).all(...ids)) {
|
|
1279
|
+
runsByWorkItem.get(row.work_item_id).push(mapRun(row));
|
|
1280
|
+
}
|
|
1281
|
+
return workItems.map(workItem => {
|
|
1282
|
+
const actions = actionsByWorkItem.get(workItem.id) || [];
|
|
1283
|
+
return graphExecutionState({ ...workItem, actions, runs: runsByWorkItem.get(workItem.id) || [] }, actions);
|
|
1228
1284
|
});
|
|
1229
1285
|
}
|
|
1230
1286
|
|