@yeaft/webchat-agent 1.0.266 → 1.0.268
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 +62 -63
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/engine.js +19 -21
- package/yeaft/sub-agent/runner.js +3 -3
- package/yeaft/tasks/manager.js +35 -6
- package/yeaft/tasks/store.js +31 -2
- package/yeaft/tools/agent.js +2 -0
- package/yeaft/tools/bash.js +1 -8
- package/yeaft/web-bridge.js +9 -7
- package/yeaft/work-center/coordinator.js +117 -48
- package/yeaft/work-center/plan-mutation.js +17 -3
- package/yeaft/work-center/runner.js +15 -3
- package/yeaft/work-center/service.js +1 -56
- package/yeaft/work-center/workflow.js +12 -2
|
Binary file
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -551,11 +551,12 @@ export class Engine {
|
|
|
551
551
|
#externalUserWakePending = false;
|
|
552
552
|
|
|
553
553
|
/**
|
|
554
|
-
*
|
|
555
|
-
* the
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
554
|
+
* Result-producing async tasks (currently sub-agent spawns) launched DURING
|
|
555
|
+
* the running query() and not yet terminated. Persistent shell tasks are
|
|
556
|
+
* deliberately status-only and never enter this set. The query loop refuses
|
|
557
|
+
* to finalize end_turn while this set is non-empty — instead it parks on
|
|
558
|
+
* `#asyncTaskWaiters` until a terminal event or a new user append wakes it.
|
|
559
|
+
* Cleared at the top of each query() and again in
|
|
559
560
|
* the finally block so a stale set never leaks across turns.
|
|
560
561
|
* @type {Set<string>}
|
|
561
562
|
*/
|
|
@@ -1272,12 +1273,10 @@ export class Engine {
|
|
|
1272
1273
|
// can mark "after this batch, end the turn — do NOT call adapter
|
|
1273
1274
|
// again". Honored at the top of the tool-loop continuation.
|
|
1274
1275
|
requestEndTurn: vpCtx?.requestEndTurn,
|
|
1275
|
-
//
|
|
1276
|
-
//
|
|
1277
|
-
//
|
|
1278
|
-
//
|
|
1279
|
-
// next adapter loop in the SAME turn. Tools that don't produce
|
|
1280
|
-
// async tasks ignore it.
|
|
1276
|
+
// Result-producing async-task ownership hook. Tools such as SpawnAgent
|
|
1277
|
+
// call this with the new `task.id` so the engine keeps the current query
|
|
1278
|
+
// parked at end_turn until the result arrives. Persistent background
|
|
1279
|
+
// shell tasks are status-only and intentionally do not call this hook.
|
|
1281
1280
|
registerAsyncTask: (taskId, meta = {}) => {
|
|
1282
1281
|
const current = typeof vpCtx?.currentToolCall === 'function' ? vpCtx.currentToolCall() : null;
|
|
1283
1282
|
this.#registerAsyncTask(taskId, { ...(current || {}), ...(meta || {}) });
|
|
@@ -1305,11 +1304,9 @@ export class Engine {
|
|
|
1305
1304
|
// to. Null when the parent has no stats wired (e.g. tests).
|
|
1306
1305
|
toolStats: this.#toolStats || null,
|
|
1307
1306
|
taskManager: this.#taskManager || null,
|
|
1308
|
-
// Propagate the async-task coordinator so sub-agents launched
|
|
1309
|
-
//
|
|
1310
|
-
//
|
|
1311
|
-
// background bash terminal event would not find its engine and
|
|
1312
|
-
// would fall through to the legacy rescue path.
|
|
1307
|
+
// Propagate the async-task coordinator so sub-agents launched from
|
|
1308
|
+
// this engine register result-producing child tasks against the same
|
|
1309
|
+
// owner map the bridge uses.
|
|
1313
1310
|
asyncTaskCoordinator: this.#asyncTaskCoordinator || null,
|
|
1314
1311
|
},
|
|
1315
1312
|
};
|
|
@@ -3199,9 +3196,10 @@ export class Engine {
|
|
|
3199
3196
|
}
|
|
3200
3197
|
|
|
3201
3198
|
// If no tool calls, we're done — UNLESS we still own a pending
|
|
3202
|
-
// async task.
|
|
3203
|
-
//
|
|
3204
|
-
// terminate (or the user appends,
|
|
3199
|
+
// result-producing async task. Persistent shell tasks never register
|
|
3200
|
+
// here; they remain visible in TaskManager without holding this turn.
|
|
3201
|
+
// Registered tasks stay live until they terminate (or the user appends,
|
|
3202
|
+
// or abort). The model already
|
|
3205
3203
|
// said end_turn; we just defer finalization by parking on the
|
|
3206
3204
|
// wait queue, then splice the synthetic task-result message in
|
|
3207
3205
|
// and run one more adapter loop. This matches the contract the
|
|
@@ -4166,8 +4164,8 @@ export class Engine {
|
|
|
4166
4164
|
}
|
|
4167
4165
|
|
|
4168
4166
|
/**
|
|
4169
|
-
* Register a
|
|
4170
|
-
*
|
|
4167
|
+
* Register a result-producing async task as belonging to the current query.
|
|
4168
|
+
* Called by tools such as SpawnAgent via `toolCtx.registerAsyncTask`.
|
|
4171
4169
|
* @param {string} taskId
|
|
4172
4170
|
* @param {{ id?: string, name?: string, threadId?: string, toolCallId?: string, toolName?: string }} [meta]
|
|
4173
4171
|
* @returns {void}
|
|
@@ -157,9 +157,9 @@ export function startSubAgent(agent, deps = {}) {
|
|
|
157
157
|
toolStats: deps.toolStats || null,
|
|
158
158
|
taskManager: deps.taskManager || null,
|
|
159
159
|
});
|
|
160
|
-
// Same-turn
|
|
161
|
-
//
|
|
162
|
-
//
|
|
160
|
+
// Same-turn result plumbing: inherit the parent's coordinator so any
|
|
161
|
+
// result-producing child task launched from this sub-agent uses the shared
|
|
162
|
+
// owner map. Persistent shell tasks remain status-only and do not register.
|
|
163
163
|
if (deps.asyncTaskCoordinator && typeof subEngine.setAsyncTaskCoordinator === 'function') {
|
|
164
164
|
subEngine.setAsyncTaskCoordinator(deps.asyncTaskCoordinator);
|
|
165
165
|
}
|
package/yeaft/tasks/manager.js
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from 'crypto';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
TaskStore,
|
|
11
|
+
TASK_RESULT_DELIVERY,
|
|
12
|
+
TASK_STATUS,
|
|
13
|
+
isTerminalTaskStatus,
|
|
14
|
+
normalizeTaskResultDelivery,
|
|
15
|
+
taskResultDeliveryFor,
|
|
16
|
+
} from './store.js';
|
|
10
17
|
import { startShellProcess } from './shell-runner.js';
|
|
11
18
|
import { getRuntimePlatformInfo } from '../runtime-platform.js';
|
|
12
19
|
|
|
@@ -35,6 +42,7 @@ function publicSnapshot(task) {
|
|
|
35
42
|
kind: task.kind,
|
|
36
43
|
title: task.title,
|
|
37
44
|
status: task.status,
|
|
45
|
+
resultDelivery: taskResultDeliveryFor(task),
|
|
38
46
|
createdAt: task.createdAt,
|
|
39
47
|
startedAt: task.startedAt,
|
|
40
48
|
updatedAt: task.updatedAt,
|
|
@@ -63,20 +71,35 @@ export class TaskManager {
|
|
|
63
71
|
this.active = new Map();
|
|
64
72
|
this.processes = new Map();
|
|
65
73
|
this.cancelEscalationTimers = new Map();
|
|
74
|
+
this.pendingStartupEvents = [];
|
|
66
75
|
this.#loadPersistedRunningTasks();
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
setEventSink(onEvent) {
|
|
70
|
-
|
|
79
|
+
const sink = typeof onEvent === 'function' ? onEvent : null;
|
|
80
|
+
this.onEvent = sink;
|
|
81
|
+
if (!sink || this.pendingStartupEvents.length === 0) return;
|
|
82
|
+
|
|
83
|
+
const pending = this.pendingStartupEvents;
|
|
84
|
+
this.pendingStartupEvents = [];
|
|
85
|
+
for (const event of pending) this.#deliverEvent(event, sink);
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
#key(sessionId, taskId) {
|
|
74
89
|
return `${sessionId || 'default'}::${taskId}`;
|
|
75
90
|
}
|
|
76
91
|
|
|
77
|
-
#
|
|
92
|
+
#deliverEvent(event, sink = this.onEvent) {
|
|
93
|
+
try { sink?.(event); } catch { /* event sinks must not break tasks */ }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#emit(event, task, extra = {}, { deferUntilSink = false } = {}) {
|
|
78
97
|
const payload = { type: 'yeaft_task_event', event, task: publicSnapshot(task), ...extra };
|
|
79
|
-
|
|
98
|
+
if (!this.onEvent && deferUntilSink) {
|
|
99
|
+
this.pendingStartupEvents.push(payload);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.#deliverEvent(payload);
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
#loadPersistedRunningTasks() {
|
|
@@ -93,13 +116,17 @@ export class TaskManager {
|
|
|
93
116
|
};
|
|
94
117
|
this.store.writeTask(orphaned);
|
|
95
118
|
this.store.appendEvent(orphaned.sessionId, { event: 'orphaned', taskId: orphaned.id });
|
|
96
|
-
this.#emit('completed', orphaned);
|
|
119
|
+
this.#emit('completed', orphaned, {}, { deferUntilSink: true });
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
startTask({ sessionId, ownerVpId = null, kind = 'tool', title = '', runtime = {}, source = {}, logPath = null } = {}) {
|
|
123
|
+
startTask({ sessionId, ownerVpId = null, kind = 'tool', title = '', runtime = {}, source = {}, logPath = null, resultDelivery = TASK_RESULT_DELIVERY.STATUS_ONLY } = {}) {
|
|
101
124
|
const taskId = makeTaskId();
|
|
102
125
|
const resolvedSessionId = sessionId || 'default';
|
|
126
|
+
const normalizedResultDelivery = normalizeTaskResultDelivery(resultDelivery);
|
|
127
|
+
if (normalizedResultDelivery !== resultDelivery) {
|
|
128
|
+
console.warn(`[Yeaft] Invalid task resultDelivery ${String(resultDelivery)}; using ${TASK_RESULT_DELIVERY.STATUS_ONLY}.`);
|
|
129
|
+
}
|
|
103
130
|
const task = {
|
|
104
131
|
id: taskId,
|
|
105
132
|
sessionId: resolvedSessionId,
|
|
@@ -107,6 +134,7 @@ export class TaskManager {
|
|
|
107
134
|
kind,
|
|
108
135
|
title: title || kind,
|
|
109
136
|
status: TASK_STATUS.RUNNING,
|
|
137
|
+
resultDelivery: normalizedResultDelivery,
|
|
110
138
|
createdAt: nowIso(),
|
|
111
139
|
startedAt: nowIso(),
|
|
112
140
|
updatedAt: nowIso(),
|
|
@@ -140,6 +168,7 @@ export class TaskManager {
|
|
|
140
168
|
kind: 'shell',
|
|
141
169
|
title: title || command.slice(0, 120),
|
|
142
170
|
status: TASK_STATUS.RUNNING,
|
|
171
|
+
resultDelivery: TASK_RESULT_DELIVERY.STATUS_ONLY,
|
|
143
172
|
createdAt: nowIso(),
|
|
144
173
|
startedAt: nowIso(),
|
|
145
174
|
updatedAt: nowIso(),
|
package/yeaft/tasks/store.js
CHANGED
|
@@ -27,6 +27,35 @@ export const TASK_STATUS = Object.freeze({
|
|
|
27
27
|
ORPHANED: 'orphaned',
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
export const TASK_RESULT_DELIVERY = Object.freeze({
|
|
31
|
+
MODEL_REENTRY: 'model_reentry',
|
|
32
|
+
STATUS_ONLY: 'status_only',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export function normalizeTaskResultDelivery(value) {
|
|
36
|
+
if (value === TASK_RESULT_DELIVERY.MODEL_REENTRY || value === TASK_RESULT_DELIVERY.STATUS_ONLY) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
return TASK_RESULT_DELIVERY.STATUS_ONLY;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function taskResultDeliveryFor(task) {
|
|
43
|
+
return normalizeTaskResultDelivery(task?.resultDelivery);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizePersistedTask(task) {
|
|
47
|
+
if (!task || typeof task !== 'object') return task;
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(task, 'resultDelivery')) {
|
|
49
|
+
return { ...task, resultDelivery: normalizeTaskResultDelivery(task.resultDelivery) };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...task,
|
|
53
|
+
resultDelivery: task.kind === 'sub_agent'
|
|
54
|
+
? TASK_RESULT_DELIVERY.MODEL_REENTRY
|
|
55
|
+
: TASK_RESULT_DELIVERY.STATUS_ONLY,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
const TERMINAL = new Set([
|
|
31
60
|
TASK_STATUS.SUCCEEDED,
|
|
32
61
|
TASK_STATUS.FAILED,
|
|
@@ -133,7 +162,7 @@ export class TaskStore {
|
|
|
133
162
|
readTask(sessionId, taskId) {
|
|
134
163
|
const path = this.taskPath(sessionId, taskId);
|
|
135
164
|
if (!existsSync(path)) return null;
|
|
136
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
165
|
+
return normalizePersistedTask(JSON.parse(readFileSync(path, 'utf8')));
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
loadActiveTasks() {
|
|
@@ -145,7 +174,7 @@ export class TaskStore {
|
|
|
145
174
|
for (const entry of readdirSync(sessionDir, { withFileTypes: true })) {
|
|
146
175
|
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
147
176
|
try {
|
|
148
|
-
const task = JSON.parse(readFileSync(join(sessionDir, entry.name), 'utf8'));
|
|
177
|
+
const task = normalizePersistedTask(JSON.parse(readFileSync(join(sessionDir, entry.name), 'utf8')));
|
|
149
178
|
if (task && task.status === TASK_STATUS.RUNNING) out.push(task);
|
|
150
179
|
} catch {
|
|
151
180
|
// Ignore corrupt task metadata; one bad task must not block boot.
|
package/yeaft/tools/agent.js
CHANGED
|
@@ -26,6 +26,7 @@ import { getPersona, listPersonaIds } from '../personas.js';
|
|
|
26
26
|
import { startSubAgent } from '../sub-agent/runner.js';
|
|
27
27
|
import { STATUS, isTerminalAgentStatus } from '../sub-agent/status.js';
|
|
28
28
|
import { diagnoseAgentLiveness, makeLiveness } from '../sub-agent/liveness.js';
|
|
29
|
+
import { TASK_RESULT_DELIVERY } from '../tasks/store.js';
|
|
29
30
|
|
|
30
31
|
/** In-memory sub-agent registry. */
|
|
31
32
|
const agents = new Map();
|
|
@@ -424,6 +425,7 @@ use it as the default workflow or call it repeatedly in a loop.`,
|
|
|
424
425
|
ownerVpId: callerScope.parentVpId || ctx?.currentVpId || null,
|
|
425
426
|
kind: 'sub_agent',
|
|
426
427
|
title: spec.mission || spec.task || name,
|
|
428
|
+
resultDelivery: TASK_RESULT_DELIVERY.MODEL_REENTRY,
|
|
427
429
|
runtime: { subAgentId: agentId, name, cwd: agent.cwd },
|
|
428
430
|
source: { threadId: callerScope.parentThreadId || ctx?.threadId || 'main' },
|
|
429
431
|
});
|
package/yeaft/tools/bash.js
CHANGED
|
@@ -255,14 +255,7 @@ Guidelines:
|
|
|
255
255
|
threadId: ctx.threadId || 'main',
|
|
256
256
|
},
|
|
257
257
|
});
|
|
258
|
-
|
|
259
|
-
// task in flight". The engine refuses to finalize end_turn
|
|
260
|
-
// while the set is non-empty and will splice the task result
|
|
261
|
-
// into the next adapter loop when it terminates. No-op when
|
|
262
|
-
// the engine didn't wire the hook (legacy callers / tests).
|
|
263
|
-
const currentToolCall = typeof ctx.currentToolCall === 'function' ? ctx.currentToolCall() : null;
|
|
264
|
-
try { ctx.registerAsyncTask?.(task.id, currentToolCall || {}); } catch { /* never block tool return on coord errors */ }
|
|
265
|
-
return `Started background task ${task.id}.\nStatus: ${task.status}\nLog: ${task.log?.path || ''}\nUse ListTasks, ReadTaskLog, or CancelTask to inspect or control it.`;
|
|
258
|
+
return `Started background task ${task.id}.\nStatus: ${task.status}\nLog: ${task.log?.path || ''}\nThe task is detached from this turn. Use ListTasks, ReadTaskLog, or CancelTask to inspect or control it.`;
|
|
266
259
|
} catch (err) {
|
|
267
260
|
throw new Error(err?.message || String(err));
|
|
268
261
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -76,6 +76,7 @@ import { getAgentRegistry, agentBelongsToScope } from './tools/agent.js';
|
|
|
76
76
|
import { isPromptableAgentStatus } from './sub-agent/status.js';
|
|
77
77
|
import { perfNowMs, recordAgentPerfTrace } from './perf-trace.js';
|
|
78
78
|
import { recordAgentSessionCreated, recordAgentTurn } from '../metrics.js';
|
|
79
|
+
import { TASK_RESULT_DELIVERY, taskResultDeliveryFor } from './tasks/store.js';
|
|
79
80
|
|
|
80
81
|
const LEGACY_SKILL_COMMAND_PREFIX = 'skill:';
|
|
81
82
|
const YEAFT_SKILL_COMMAND_PREFIX = 'yeaft-skills:';
|
|
@@ -1706,10 +1707,9 @@ function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
|
|
|
1706
1707
|
sessionId,
|
|
1707
1708
|
vpId,
|
|
1708
1709
|
});
|
|
1709
|
-
// Install the async-task coordinator so
|
|
1710
|
-
//
|
|
1711
|
-
//
|
|
1712
|
-
// the same query() instead of opening a fresh turn.
|
|
1710
|
+
// Install the async-task coordinator so result-producing child tasks
|
|
1711
|
+
// register against the shared owner map. Persistent shell tasks are
|
|
1712
|
+
// status-only and never enter this ownership path.
|
|
1713
1713
|
try {
|
|
1714
1714
|
if (typeof eng.setAsyncTaskCoordinator === 'function') {
|
|
1715
1715
|
eng.setAsyncTaskCoordinator(buildAsyncTaskCoordinator());
|
|
@@ -1852,6 +1852,7 @@ function scheduleTaskResultRescue({ taskId, sessionId, vpId, threadId = 'main',
|
|
|
1852
1852
|
function scheduleTaskResultReentry(event) {
|
|
1853
1853
|
if (!event || event.event !== 'completed' || !event.task) return;
|
|
1854
1854
|
const task = event.task;
|
|
1855
|
+
if (taskResultDeliveryFor(task) !== TASK_RESULT_DELIVERY.MODEL_REENTRY) return;
|
|
1855
1856
|
const sessionId = task.sessionId || event.sessionId || null;
|
|
1856
1857
|
const vpId = task.ownerVpId || null;
|
|
1857
1858
|
if (!sessionId || !vpId) return;
|
|
@@ -3990,9 +3991,10 @@ function handleEngineEvent(event, hctx) {
|
|
|
3990
3991
|
}, envelope);
|
|
3991
3992
|
break;
|
|
3992
3993
|
|
|
3993
|
-
// Same-turn
|
|
3994
|
-
//
|
|
3995
|
-
//
|
|
3994
|
+
// Same-turn result-producing task wait. Engine parks at end_turn while a
|
|
3995
|
+
// registered child task is still running and re-enters the same turn when
|
|
3996
|
+
// the terminal event arrives. Persistent shell tasks are status-only and
|
|
3997
|
+
// never emit this wait edge (see engine.js
|
|
3996
3998
|
// `#runQuery` wait block). Bridge forwards both edges so the debug
|
|
3997
3999
|
// panel (and any other in-process subscriber) can render the park
|
|
3998
4000
|
// window with the live list of pending taskIds. Wire types stay
|