@yeaft/webchat-agent 1.0.210 → 1.0.212
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 +128 -98
- 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/mcp.js +36 -24
- 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
package/yeaft/mcp.js
CHANGED
|
@@ -141,8 +141,17 @@ class MCPServerConnection extends EventEmitter {
|
|
|
141
141
|
*/
|
|
142
142
|
async start() {
|
|
143
143
|
return new Promise((resolve, reject) => {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
let settled = false;
|
|
145
|
+
let timer = null;
|
|
146
|
+
const failStart = async (err) => {
|
|
147
|
+
if (settled) return;
|
|
148
|
+
settled = true;
|
|
149
|
+
if (timer) clearTimeout(timer);
|
|
150
|
+
await this.stop();
|
|
151
|
+
reject(err);
|
|
152
|
+
};
|
|
153
|
+
timer = setTimeout(() => {
|
|
154
|
+
void failStart(new Error(`MCP server "${this.#name}" startup timeout (${STARTUP_TIMEOUT_MS}ms)`));
|
|
146
155
|
}, STARTUP_TIMEOUT_MS);
|
|
147
156
|
|
|
148
157
|
try {
|
|
@@ -181,23 +190,22 @@ class MCPServerConnection extends EventEmitter {
|
|
|
181
190
|
});
|
|
182
191
|
|
|
183
192
|
this.#process.on('error', (err) => {
|
|
184
|
-
|
|
185
|
-
reject(err);
|
|
193
|
+
void failStart(err);
|
|
186
194
|
});
|
|
187
195
|
|
|
188
196
|
// Initialize MCP protocol
|
|
189
197
|
this.#initialize().then(() => {
|
|
198
|
+
if (settled) return;
|
|
199
|
+
settled = true;
|
|
190
200
|
clearTimeout(timer);
|
|
191
201
|
this.#ready = true;
|
|
192
202
|
resolve();
|
|
193
203
|
}).catch((err) => {
|
|
194
|
-
|
|
195
|
-
reject(err);
|
|
204
|
+
void failStart(err);
|
|
196
205
|
});
|
|
197
206
|
|
|
198
207
|
} catch (err) {
|
|
199
|
-
|
|
200
|
-
reject(err);
|
|
208
|
+
void failStart(err);
|
|
201
209
|
}
|
|
202
210
|
});
|
|
203
211
|
}
|
|
@@ -319,23 +327,27 @@ class MCPServerConnection extends EventEmitter {
|
|
|
319
327
|
* Stop the MCP server process.
|
|
320
328
|
*/
|
|
321
329
|
async stop() {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
330
|
+
const child = this.#process;
|
|
331
|
+
if (!child) return;
|
|
332
|
+
|
|
333
|
+
this.#ready = false;
|
|
334
|
+
let closed = this.#closed;
|
|
335
|
+
if (!closed) {
|
|
336
|
+
child.kill('SIGTERM');
|
|
337
|
+
closed = await new Promise(resolve => {
|
|
338
|
+
let timer = null;
|
|
339
|
+
const finish = (didClose) => {
|
|
340
|
+
if (timer) clearTimeout(timer);
|
|
341
|
+
child.removeListener('close', onClose);
|
|
342
|
+
resolve(didClose);
|
|
343
|
+
};
|
|
344
|
+
const onClose = () => finish(true);
|
|
345
|
+
child.once('close', onClose);
|
|
346
|
+
timer = setTimeout(() => finish(false), 2000);
|
|
347
|
+
});
|
|
348
|
+
if (!closed) child.kill('SIGKILL');
|
|
338
349
|
}
|
|
350
|
+
if (this.#process === child) this.#process = null;
|
|
339
351
|
}
|
|
340
352
|
}
|
|
341
353
|
|
|
@@ -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
|
|