@myagentroam/node 0.9.124 → 0.9.125
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/dist/connector.js +217 -6
- package/dist/database.js +2 -1
- package/dist/migrations/v014.js +107 -0
- package/dist/runner/abstract-runner.js +11 -5
- package/dist/runner/claude-code-runner.js +3 -0
- package/dist/runner/codex/conversation-parser.js +142 -2
- package/dist/runner/codex/managed-run-controller.js +26 -18
- package/dist/runner/codex-runner.js +127 -57
- package/dist/runner/opencode-runner.js +14 -12
- package/dist/runtime-state.js +6 -1
- package/dist/service/continuous-run-coordinator.js +8 -2
- package/dist/service/conversation-history-service.js +14 -8
- package/dist/service/managed-runner-session-service.js +2 -1
- package/dist/service/native-session-projection-service.js +3 -1
- package/dist/service/node-control-message-service.js +2 -0
- package/dist/service/run-event-service.js +18 -2
- package/dist/service/runner-interaction-service.js +9 -8
- package/dist/service/scheduled-run-authorization-control.js +72 -0
- package/dist/service/scheduled-session-coordinator.js +95 -0
- package/dist/service/scheduled-session-recurrence.js +72 -0
- package/dist/service/scheduled-session-store.js +234 -0
- package/dist/service/scheduled-task-mcp-server.js +179 -0
- package/dist/service/scheduled-task-mcp-stdio.js +63 -0
- package/dist/service/session-catalog-service.js +3 -1
- package/dist/service/session-lifecycle-service.js +6 -0
- package/dist/service/session-message-service.js +25 -2
- package/dist/service/session-presentation-service.js +19 -2
- package/dist/service/session-schedule-service.js +153 -0
- package/dist/util/runner-native-session-parsers.js +19 -11
- package/package.json +4 -4
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { assertScheduleTimezone, parseScheduledCron } from './scheduled-session-recurrence.js';
|
|
2
|
+
import { ScheduledSessionStore } from './scheduled-session-store.js';
|
|
3
|
+
export class SessionScheduleService {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
async createFromMcp(input) {
|
|
9
|
+
const session = this.options.runtime.getAgentSession(input.sessionId);
|
|
10
|
+
if (session?.externalSessionId === null)
|
|
11
|
+
await this.options.awaitNativeSessionId(session.id);
|
|
12
|
+
const result = (await this.save({
|
|
13
|
+
sessionId: input.sessionId,
|
|
14
|
+
workspaceId: input.workspaceId,
|
|
15
|
+
__requestUserId: input.actorUserId,
|
|
16
|
+
objective: input.objective,
|
|
17
|
+
cron: input.cron
|
|
18
|
+
}));
|
|
19
|
+
return result.task;
|
|
20
|
+
}
|
|
21
|
+
operations() {
|
|
22
|
+
return {
|
|
23
|
+
'session.schedule.get': (data) => this.get(record(data)),
|
|
24
|
+
'session.schedule.save': (data) => this.save(record(data)),
|
|
25
|
+
'session.schedule.remove': (data) => this.remove(record(data)),
|
|
26
|
+
'session.schedule.proposal.resolve': (data) => this.resolveProposal(record(data))
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async get(input) {
|
|
30
|
+
const session = await this.requireSession(input);
|
|
31
|
+
const store = this.store();
|
|
32
|
+
const task = store.get(session.externalSessionId ?? session.id);
|
|
33
|
+
return {
|
|
34
|
+
task: task ?? null,
|
|
35
|
+
nodeTimezone: nodeTimezone(),
|
|
36
|
+
canResolveProposals: task !== undefined && input.__requestUserId === task.ownerUserId,
|
|
37
|
+
occurrences: task === undefined ? [] : store.recent(task.id),
|
|
38
|
+
proposals: task === undefined ? [] : store.pendingProposals(task.id)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async save(input) {
|
|
42
|
+
const session = await this.requireSession(input);
|
|
43
|
+
const actor = input.__requestUserId;
|
|
44
|
+
if (!Number.isSafeInteger(actor) || actor < 1)
|
|
45
|
+
throw new Error('USER_AUTHORIZATION_REQUIRED');
|
|
46
|
+
const sessionId = session.externalSessionId ?? session.id;
|
|
47
|
+
const store = this.store();
|
|
48
|
+
const current = store.get(sessionId);
|
|
49
|
+
const objective = input.objective;
|
|
50
|
+
const cron = input.cron;
|
|
51
|
+
const timezone = current === undefined ? nodeTimezone() : undefined;
|
|
52
|
+
if ((objective !== undefined &&
|
|
53
|
+
(typeof objective !== 'string' ||
|
|
54
|
+
objective.trim().length < 1 ||
|
|
55
|
+
objective.trim().length > 20_000)) ||
|
|
56
|
+
(cron !== undefined && typeof cron !== 'string') ||
|
|
57
|
+
input.timezone !== undefined ||
|
|
58
|
+
(input.enabled !== undefined && typeof input.enabled !== 'boolean'))
|
|
59
|
+
throw new Error('SCHEDULE_INVALID');
|
|
60
|
+
if (typeof cron === 'string')
|
|
61
|
+
parseScheduledCron(cron);
|
|
62
|
+
if (timezone !== undefined)
|
|
63
|
+
assertScheduleTimezone(timezone);
|
|
64
|
+
if (current === undefined &&
|
|
65
|
+
(typeof objective !== 'string' || typeof cron !== 'string' || typeof timezone !== 'string'))
|
|
66
|
+
throw new Error('SCHEDULE_INVALID');
|
|
67
|
+
if (current !== undefined &&
|
|
68
|
+
(!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision !== current.revision))
|
|
69
|
+
throw new Error('SCHEDULE_REVISION_CONFLICT');
|
|
70
|
+
const enabled = input.enabled !== false;
|
|
71
|
+
if ((current === undefined || input.enabled === true) && enabled)
|
|
72
|
+
await this.pin(session);
|
|
73
|
+
else if (current === undefined)
|
|
74
|
+
this.options.database().saveNativeSessionMetadata({
|
|
75
|
+
runner: session.runner,
|
|
76
|
+
externalSessionId: sessionId,
|
|
77
|
+
workspaceId: session.workspaceId
|
|
78
|
+
});
|
|
79
|
+
const task = current === undefined
|
|
80
|
+
? store.create({
|
|
81
|
+
sessionId,
|
|
82
|
+
workspaceId: session.workspaceId,
|
|
83
|
+
runner: session.runner,
|
|
84
|
+
ownerUserId: actor,
|
|
85
|
+
objective: objective,
|
|
86
|
+
cron: cron,
|
|
87
|
+
timezone: timezone,
|
|
88
|
+
enabled
|
|
89
|
+
})
|
|
90
|
+
: store.update(sessionId, current.revision, {
|
|
91
|
+
...(objective === undefined ? {} : { objective: objective }),
|
|
92
|
+
...(cron === undefined ? {} : { cron: cron }),
|
|
93
|
+
...(timezone === undefined ? {} : { timezone: timezone }),
|
|
94
|
+
...(input.enabled === undefined ? {} : { enabled: input.enabled })
|
|
95
|
+
});
|
|
96
|
+
const updated = this.options.runtime.getAgentSession(session.id) ?? session;
|
|
97
|
+
this.options.emitSession(updated);
|
|
98
|
+
this.options.changed?.();
|
|
99
|
+
return { task, session: this.options.present(updated) };
|
|
100
|
+
}
|
|
101
|
+
async remove(input) {
|
|
102
|
+
const session = await this.requireSession(input);
|
|
103
|
+
if (!Number.isSafeInteger(input.expectedRevision))
|
|
104
|
+
throw new Error('SCHEDULE_REVISION_CONFLICT');
|
|
105
|
+
this.store().remove(session.externalSessionId ?? session.id, input.expectedRevision);
|
|
106
|
+
this.options.emitSession(session);
|
|
107
|
+
this.options.changed?.();
|
|
108
|
+
return { removed: true, session: this.options.present(session) };
|
|
109
|
+
}
|
|
110
|
+
async resolveProposal(input) {
|
|
111
|
+
const session = await this.requireSession(input);
|
|
112
|
+
if (typeof input.proposalId !== 'string' ||
|
|
113
|
+
typeof input.accept !== 'boolean' ||
|
|
114
|
+
!Number.isSafeInteger(input.__requestUserId))
|
|
115
|
+
throw new Error('SCHEDULE_PROPOSAL_INVALID');
|
|
116
|
+
const task = this.store().resolveProposal(session.externalSessionId ?? session.id, input.proposalId, input.__requestUserId, input.accept);
|
|
117
|
+
this.options.emitSession(session);
|
|
118
|
+
this.options.changed?.();
|
|
119
|
+
return { task, proposals: this.store().pendingProposals(task.id) };
|
|
120
|
+
}
|
|
121
|
+
async requireSession(input) {
|
|
122
|
+
if (typeof input.sessionId !== 'string' || typeof input.workspaceId !== 'string')
|
|
123
|
+
throw new Error('SCHEDULE_INVALID');
|
|
124
|
+
const session = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
125
|
+
(await this.options.projection.createMetadataProjection(input.sessionId));
|
|
126
|
+
if (session === undefined ||
|
|
127
|
+
session.workspaceId !== input.workspaceId ||
|
|
128
|
+
session.externalSessionId === null)
|
|
129
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
130
|
+
return session;
|
|
131
|
+
}
|
|
132
|
+
async pin(session) {
|
|
133
|
+
const sessionId = session.externalSessionId ?? session.id;
|
|
134
|
+
const existing = this.options
|
|
135
|
+
.database()
|
|
136
|
+
.getNativeSessionMetadata(session.runner, sessionId, session.workspaceId);
|
|
137
|
+
if (existing?.pinOrder !== null && existing?.pinOrder !== undefined)
|
|
138
|
+
return;
|
|
139
|
+
await this.options.projection.rename(session, { pinned: true });
|
|
140
|
+
this.options.runtime.updateSessionMetadata({ sessionId: session.id, pinned: true });
|
|
141
|
+
}
|
|
142
|
+
store() {
|
|
143
|
+
return new ScheduledSessionStore(this.options.database().raw);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function nodeTimezone() {
|
|
147
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
148
|
+
}
|
|
149
|
+
function record(value) {
|
|
150
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
151
|
+
? value
|
|
152
|
+
: {};
|
|
153
|
+
}
|
|
@@ -59,12 +59,11 @@ export function extractCodexThreads(result) {
|
|
|
59
59
|
sourceRecord?.kind === 'subagent' ||
|
|
60
60
|
isPlainRecord(sourceRecord?.subagent) ||
|
|
61
61
|
isPlainRecord(sourceRecord?.subAgent);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return [{ id: value.id, source, derived, cwd: value.cwd, title }];
|
|
62
|
+
const name = typeof value.name === 'string' ? value.name.trim() : '';
|
|
63
|
+
const preview = typeof value.preview === 'string' ? value.preview.trim() : '';
|
|
64
|
+
const title = name || preview || undefined;
|
|
65
|
+
const titleOrigin = name ? 'OFFICIAL' : preview ? 'PREVIEW' : undefined;
|
|
66
|
+
return [{ id: value.id, source, derived, cwd: value.cwd, title, titleOrigin }];
|
|
68
67
|
});
|
|
69
68
|
}
|
|
70
69
|
export function codexThreadCwd(value) {
|
|
@@ -103,13 +102,22 @@ export function claudeDiscoveredSession(value, fallbackCwd) {
|
|
|
103
102
|
: undefined;
|
|
104
103
|
if (externalSessionId === undefined || externalSessionId.length === 0)
|
|
105
104
|
return undefined;
|
|
106
|
-
const
|
|
105
|
+
const officialTitle = [record.customTitle, record.firstPrompt, record.title].find((candidate) => typeof candidate === 'string' && candidate.trim().length > 0);
|
|
106
|
+
const previewTitle = typeof record.summary === 'string' && record.summary.trim().length > 0
|
|
107
107
|
? record.summary
|
|
108
|
-
:
|
|
109
|
-
|
|
110
|
-
: undefined;
|
|
108
|
+
: undefined;
|
|
109
|
+
const title = officialTitle ?? previewTitle;
|
|
111
110
|
const cwd = typeof record.cwd === 'string' && record.cwd.length > 0 ? record.cwd : fallbackCwd;
|
|
112
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
externalSessionId,
|
|
113
|
+
cwd,
|
|
114
|
+
...(title === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: {
|
|
117
|
+
title: title.slice(0, 80),
|
|
118
|
+
titleOrigin: officialTitle === undefined ? 'PREVIEW' : 'OFFICIAL'
|
|
119
|
+
})
|
|
120
|
+
};
|
|
113
121
|
}
|
|
114
122
|
export function latestNativeActivity(history) {
|
|
115
123
|
return history.lastActivityAt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.125",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"npm": "11.12.1",
|
|
31
31
|
"ws": "^8.21.3",
|
|
32
32
|
"zod": "4.4.3",
|
|
33
|
-
"@myagentroam/agent": "0.9.
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
33
|
+
"@myagentroam/agent": "0.9.125",
|
|
34
|
+
"@myagentroam/code-navigation": "0.9.125",
|
|
35
|
+
"@myagentroam/protocol": "0.9.125"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/ws": "^8.18.1"
|