@yeaft/webchat-agent 1.0.648 → 1.0.650
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/connection/index.js +3 -0
- package/connection/message-router.js +6 -1
- package/index.js +1 -1
- package/local-runtime/server/db/connection.js +5 -2
- package/local-runtime/server/db/yeaft-project-db.js +2 -0
- package/local-runtime/server/db/yeaft-session-db.js +4 -1
- package/local-runtime/server/handlers/agent-output.js +48 -8
- package/local-runtime/server/handlers/client-conversation.js +14 -2
- package/local-runtime/server/handlers/client-scheduled-tasks.js +121 -0
- package/local-runtime/server/session-catalog.js +1 -0
- package/local-runtime/server/ws-client.js +1 -0
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +507 -322
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/spreadsheet-worker.js +2 -0
- 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/workbench/file-ops.js +47 -15
- package/workbench/utils.js +8 -0
- package/yeaft/sessions/scheduled-task-definition.js +74 -0
- package/yeaft/sessions/scheduled-task-scheduler.js +257 -0
- package/yeaft/sessions/session-crud.js +5 -0
- package/yeaft/sessions/session-store.js +18 -10
- package/yeaft/web-bridge.js +100 -20
package/yeaft/web-bridge.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* style strips on incoming/outgoing message bodies.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { delimiter, join } from 'node:path';
|
|
18
|
+
import { delimiter, join, resolve } from 'node:path';
|
|
19
19
|
import { COLLAB_TOOL_POLICY } from './tools/registry.js';
|
|
20
20
|
import { createFullRegistry } from './tools/index.js';
|
|
21
21
|
import { existsSync, lstatSync } from 'node:fs';
|
|
@@ -102,6 +102,8 @@ import {
|
|
|
102
102
|
} from './sessions/session-crud.js';
|
|
103
103
|
import { openSession, loadSessionMeta, SESSION_META_FILE } from './sessions/session-store.js';
|
|
104
104
|
import { validateSessionId } from './sessions/ids.js';
|
|
105
|
+
import { publicScheduledTask } from './sessions/scheduled-task-definition.js';
|
|
106
|
+
import { ScheduledTaskScheduler, SCHEDULED_TASK_DISPATCH_GUARD } from './sessions/scheduled-task-scheduler.js';
|
|
105
107
|
import { loadSessionConfig, normalizeSessionConfig, resolveSessionConfig, SessionConfigError } from './sessions/session-config.js';
|
|
106
108
|
import { updateSessionConfig } from './sessions/session-crud.js';
|
|
107
109
|
import { createCoordinator } from './sessions/coordinator.js';
|
|
@@ -2693,6 +2695,7 @@ function resolveGroupDefaultVpId(sessionId) {
|
|
|
2693
2695
|
}
|
|
2694
2696
|
|
|
2695
2697
|
function sendSessionOutputFrame(data, { sessionId, chatId, vpId, turnId, threadId, perfTraceId } = {}) {
|
|
2698
|
+
scheduledTaskScheduler?.observe(sessionId, data);
|
|
2696
2699
|
const resolvedVpId = vpId || (sessionId ? resolveGroupDefaultVpId(sessionId) : null);
|
|
2697
2700
|
sendToServer({
|
|
2698
2701
|
type: 'yeaft_output',
|
|
@@ -3156,6 +3159,7 @@ function mergedStatusForProjectRuntime(runtime, ownerSession = session) {
|
|
|
3156
3159
|
|
|
3157
3160
|
/** Send a Yeaft Session metadata event over the legacy-compatible envelope. */
|
|
3158
3161
|
function sendSessionEvent(event, { sessionId, chatId, vpId, turnId, threadId, requestId, requestClientId, perfTraceId, conversationId = yeaftConversationId } = {}) {
|
|
3162
|
+
scheduledTaskScheduler?.observe(sessionId || event?.sessionId, event);
|
|
3159
3163
|
sendToServer({
|
|
3160
3164
|
type: 'yeaft_output',
|
|
3161
3165
|
conversationId,
|
|
@@ -3498,11 +3502,12 @@ async function sharedProjectContext(yeaftDir, sessionId, options = {}) {
|
|
|
3498
3502
|
return context;
|
|
3499
3503
|
}
|
|
3500
3504
|
|
|
3501
|
-
function sendSessionCrudResult(payload) {
|
|
3505
|
+
function sendSessionCrudResult(payload, envelope) {
|
|
3502
3506
|
const next = payload && payload.ok && Array.isArray(payload.sessions)
|
|
3503
3507
|
? { ...payload, sessions: decorateSessionsWithRuntimeState(payload.sessions) }
|
|
3504
3508
|
: payload;
|
|
3505
|
-
|
|
3509
|
+
const projected = next?.session?.task ? { ...next, session: { ...next.session, task: publicScheduledTask(next.session.task) } } : next;
|
|
3510
|
+
sendSessionEvent({ type: 'session_crud_result', ...projected }, envelope);
|
|
3506
3511
|
}
|
|
3507
3512
|
|
|
3508
3513
|
function isMcpServerEnabled(name, plugins = session?.config?.plugins) {
|
|
@@ -3708,6 +3713,72 @@ function sendSessionSnapshotBroadcast() {
|
|
|
3708
3713
|
*/
|
|
3709
3714
|
export { sendSessionSnapshotBroadcast as broadcastYeaftSessionSnapshotEager };
|
|
3710
3715
|
|
|
3716
|
+
let scheduledTaskScheduler = null;
|
|
3717
|
+
|
|
3718
|
+
/** Server authority is connection-scoped; cached context must not survive reconnect. */
|
|
3719
|
+
export function invalidateYeaftProjectContexts() {
|
|
3720
|
+
projectContextBySession.clear();
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
function scheduledSessionReady(sessionId) {
|
|
3724
|
+
return ctx.ws?.readyState === 1 && projectContextBySession.has(sessionId);
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
/** Start once per Agent data root, not per browser subscription or reconnect. */
|
|
3728
|
+
export function startYeaftScheduledTasks() {
|
|
3729
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3730
|
+
if (!yeaftDir) return null;
|
|
3731
|
+
if (scheduledTaskScheduler?.yeaftDir !== resolve(yeaftDir)) {
|
|
3732
|
+
scheduledTaskScheduler?.stop();
|
|
3733
|
+
scheduledTaskScheduler = new ScheduledTaskScheduler({
|
|
3734
|
+
yeaftDir,
|
|
3735
|
+
canDispatch: scheduledSessionReady,
|
|
3736
|
+
dispatch: msg => {
|
|
3737
|
+
const guard = msg[SCHEDULED_TASK_DISPATCH_GUARD];
|
|
3738
|
+
const input = { ...msg, projectContext: projectContextBySession.get(msg.sessionId) };
|
|
3739
|
+
input[SCHEDULED_TASK_DISPATCH_GUARD] = () => {
|
|
3740
|
+
guard(); // Includes readiness plus the persisted claim/config/identity checks.
|
|
3741
|
+
// Runtime boot may have spanned a Project edit or reconnect. Use the
|
|
3742
|
+
// latest authority at ingress, never the pre-boot context or legacy cache.
|
|
3743
|
+
input.projectContext = projectContextBySession.get(msg.sessionId);
|
|
3744
|
+
};
|
|
3745
|
+
return handleYeaftSessionSend(input);
|
|
3746
|
+
},
|
|
3747
|
+
isActive: sessionHasActiveWork,
|
|
3748
|
+
onChange: sendSessionSnapshotBroadcast,
|
|
3749
|
+
notify: event => sendSessionEvent(event, { sessionId: event.sessionId }),
|
|
3750
|
+
});
|
|
3751
|
+
}
|
|
3752
|
+
return scheduledTaskScheduler.start();
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
export function stopYeaftScheduledTasks() {
|
|
3756
|
+
scheduledTaskScheduler?.stop();
|
|
3757
|
+
scheduledTaskScheduler = null;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
export function handleYeaftScheduledTask(msg = {}) {
|
|
3761
|
+
const op = msg.op === 'update' ? 'update_task' : 'run_task';
|
|
3762
|
+
const requestId = msg.requestId || null;
|
|
3763
|
+
const respond = payload => sendSessionEvent({ type: 'session_crud_result', op, requestId, ...payload },
|
|
3764
|
+
{ requestId, requestClientId: msg._requestClientId });
|
|
3765
|
+
try {
|
|
3766
|
+
if (!['update', 'run'].includes(msg.op)) throw new Error('Invalid scheduled task operation');
|
|
3767
|
+
if (msg.op === 'run' && msg.task !== undefined) throw new Error('task is only accepted for update');
|
|
3768
|
+
if (!validateSessionId(msg.sessionId).ok) throw new Error('Invalid Session id');
|
|
3769
|
+
if (msg.op === 'run' && Object.hasOwn(msg, 'projectContext')) {
|
|
3770
|
+
handleYeaftProjectContextSync({ contexts: [{ sessionId: msg.sessionId, projectContext: msg.projectContext }] });
|
|
3771
|
+
}
|
|
3772
|
+
const scheduler = startYeaftScheduledTasks();
|
|
3773
|
+
if (!scheduler) throw new Error('No Yeaft directory configured');
|
|
3774
|
+
const result = msg.op === 'update' ? scheduler.update(msg.sessionId, msg.task, msg.expectedRevision) : scheduler.run(msg.sessionId);
|
|
3775
|
+
result.config = loadSessionConfig(scheduler.yeaftDir, msg.sessionId);
|
|
3776
|
+
respond({ ok: true, session: result });
|
|
3777
|
+
} catch (error) {
|
|
3778
|
+
respond({ ok: false, error: { code: error?.code || 'scheduled_task_error', sessionId: msg.sessionId, message: error?.message || String(error) } });
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3711
3782
|
function sendSessionRosterChanged(session) {
|
|
3712
3783
|
if (!session) return;
|
|
3713
3784
|
const payload = {
|
|
@@ -3749,6 +3820,7 @@ export function handleYeaftProjectContextSync(msg) {
|
|
|
3749
3820
|
const sessionId = typeof row?.sessionId === 'string' ? row.sessionId.trim() : '';
|
|
3750
3821
|
if (!sessionId) continue;
|
|
3751
3822
|
const projectContext = normalizeProjectContext(row.projectContext, sessionId);
|
|
3823
|
+
if (!projectContext && row.projectContext !== null) continue;
|
|
3752
3824
|
projectContextBySession.set(sessionId, projectContext || {
|
|
3753
3825
|
projectId: null,
|
|
3754
3826
|
projectName: null,
|
|
@@ -3801,15 +3873,18 @@ export function handleYeaftProjectMutation(msg) {
|
|
|
3801
3873
|
export function handleYeaftCreateSession(msg) {
|
|
3802
3874
|
const requestId = msg && msg.requestId;
|
|
3803
3875
|
const payload = (msg && msg.payload) || {};
|
|
3876
|
+
// Server authenticates/overwrites the requester identity. Task create results
|
|
3877
|
+
// (including validation failures) must be routed back to that browser only.
|
|
3878
|
+
const envelope = payload.task !== undefined ? { requestId, requestClientId: msg?._requestClientId } : undefined;
|
|
3804
3879
|
try {
|
|
3805
3880
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
3806
3881
|
const group = createSessionFromSpec(yeaftDir, payload, configuredVpPaths());
|
|
3807
3882
|
recordAgentSessionCreated();
|
|
3808
3883
|
group.config = loadSessionConfig(yeaftDir, group.id);
|
|
3809
|
-
sendSessionCrudResult({ op: 'create', requestId, ok: true, session: group });
|
|
3884
|
+
sendSessionCrudResult({ op: 'create', requestId, ok: true, session: group }, envelope);
|
|
3810
3885
|
sendSessionSnapshotBroadcast();
|
|
3811
3886
|
} catch (err) {
|
|
3812
|
-
sendSessionCrudResult({ op: 'create', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
3887
|
+
sendSessionCrudResult({ op: 'create', requestId, ok: false, error: sessionErrorPayload(err) }, envelope);
|
|
3813
3888
|
}
|
|
3814
3889
|
}
|
|
3815
3890
|
|
|
@@ -5028,6 +5103,23 @@ async function runYeaftSessionSend(msg) {
|
|
|
5028
5103
|
const sessionId = (typeof msg.sessionId === 'string' && msg.sessionId.trim())
|
|
5029
5104
|
? msg.sessionId.trim()
|
|
5030
5105
|
: 'grp_default';
|
|
5106
|
+
// Cache authority before the first await: an accepted send from the previous
|
|
5107
|
+
// transport must not repopulate stale context after disconnect invalidation.
|
|
5108
|
+
const hasInboundProjectContext = Object.prototype.hasOwnProperty.call(msg, 'projectContext');
|
|
5109
|
+
const inboundProjectContext = normalizeProjectContext(msg.projectContext, sessionId);
|
|
5110
|
+
if (hasInboundProjectContext) {
|
|
5111
|
+
projectContextBySession.set(sessionId, inboundProjectContext || {
|
|
5112
|
+
projectId: null,
|
|
5113
|
+
projectName: null,
|
|
5114
|
+
projectInstruction: '',
|
|
5115
|
+
sessionIds: [],
|
|
5116
|
+
});
|
|
5117
|
+
} else {
|
|
5118
|
+
// An old Server does not know this field. Drop any context cached from a
|
|
5119
|
+
// newer Server before falling back to this Agent's legacy projects.json.
|
|
5120
|
+
projectContextBySession.delete(sessionId);
|
|
5121
|
+
}
|
|
5122
|
+
|
|
5031
5123
|
const perfTraceId = typeof msg.perfTraceId === 'string' && msg.perfTraceId.trim()
|
|
5032
5124
|
? msg.perfTraceId.trim()
|
|
5033
5125
|
: null;
|
|
@@ -5128,6 +5220,9 @@ async function dispatchAcceptedSessionSend({
|
|
|
5128
5220
|
}) {
|
|
5129
5221
|
const ensureSessionStart = perfNowMs();
|
|
5130
5222
|
await ensureSessionLoaded({ sessionMeta: sessionMetaForRuntime, perfTraceId });
|
|
5223
|
+
// Runtime boot may await I/O. Re-read the claim immediately before ingress
|
|
5224
|
+
// so edits, disable, archive or deletion during boot cannot execute stale work.
|
|
5225
|
+
msg[SCHEDULED_TASK_DISPATCH_GUARD]?.();
|
|
5131
5226
|
if (!sessionHandle) {
|
|
5132
5227
|
try {
|
|
5133
5228
|
const dir = join(sessionRoot, sessionId);
|
|
@@ -5244,21 +5339,6 @@ async function dispatchAcceptedSessionSend({
|
|
|
5244
5339
|
},
|
|
5245
5340
|
});
|
|
5246
5341
|
|
|
5247
|
-
const hasInboundProjectContext = Object.prototype.hasOwnProperty.call(msg, 'projectContext');
|
|
5248
|
-
const inboundProjectContext = normalizeProjectContext(msg.projectContext, sessionId);
|
|
5249
|
-
if (hasInboundProjectContext) {
|
|
5250
|
-
projectContextBySession.set(sessionId, inboundProjectContext || {
|
|
5251
|
-
projectId: null,
|
|
5252
|
-
projectName: null,
|
|
5253
|
-
projectInstruction: '',
|
|
5254
|
-
sessionIds: [],
|
|
5255
|
-
});
|
|
5256
|
-
} else {
|
|
5257
|
-
// An old Server does not know this field. Drop any context cached from a
|
|
5258
|
-
// newer Server before falling back to this Agent's legacy projects.json.
|
|
5259
|
-
projectContextBySession.delete(sessionId);
|
|
5260
|
-
}
|
|
5261
|
-
|
|
5262
5342
|
// Ingest user text. The coordinator persists, applies mention/fanout
|
|
5263
5343
|
// rules, and calls deliver() (== enqueueForVp) for each chosen VP —
|
|
5264
5344
|
// which both (a) emits vp_typing_start and (b) ensures a driver runs.
|