@myagentroam/node 0.9.4 → 0.9.5
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/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +9 -0
- package/dist/codex-app-server.d.ts +1 -0
- package/dist/codex-app-server.js +4 -2
- package/dist/connector.d.ts +1 -0
- package/dist/connector.js +16 -11
- package/dist/native-session-history.d.ts +3 -2
- package/dist/native-session-history.js +199 -30
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +1 -0
- package/dist/runner/abstract-runner.d.ts +11 -2
- package/dist/runner/abstract-runner.js +4 -0
- package/dist/runner/claude/managed-run-controller.js +3 -0
- package/dist/runner/claude-code-runner.d.ts +2 -1
- package/dist/runner/claude-code-runner.js +4 -0
- package/dist/runner/codex/managed-run-controller.js +6 -0
- package/dist/runner/codex-runner.d.ts +2 -1
- package/dist/runner/codex-runner.js +11 -1
- package/dist/runner/opencode/managed-run-controller.js +4 -0
- package/dist/runner/opencode-runner.d.ts +10 -3
- package/dist/runner/opencode-runner.js +87 -14
- package/dist/runner/runner-registry.js +16 -1
- package/dist/runtime-command-detector.js +1 -6
- package/dist/runtime-state.js +1 -0
- package/dist/service/conversation-history-service.d.ts +5 -4
- package/dist/service/conversation-history-service.js +110 -52
- package/dist/service/external-session-resume-service.d.ts +2 -0
- package/dist/service/external-session-resume-service.js +45 -34
- package/dist/service/native-session-projection-service.d.ts +1 -0
- package/dist/service/native-session-projection-service.js +4 -0
- package/dist/service/native-session-watch-service.d.ts +18 -0
- package/dist/service/native-session-watch-service.js +232 -34
- package/dist/service/run-workbench-service.d.ts +2 -4
- package/dist/service/run-workbench-service.js +2 -7
- package/dist/service/session-command-service.js +1 -1
- package/dist/service/session-identity-service.d.ts +1 -1
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-lifecycle-service.js +3 -0
- package/dist/service/session-message-service.d.ts +0 -1
- package/dist/service/session-message-service.js +15 -23
- package/dist/service/skill-directory-service.d.ts +1 -0
- package/dist/service/skill-directory-service.js +40 -6
- package/dist/service/workspace-domain-state.d.ts +3 -0
- package/dist/service/workspace-domain-state.js +1 -0
- package/dist/service/workspace-file-service.d.ts +1 -0
- package/dist/service/workspace-file-service.js +14 -1
- package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
- package/dist/service/workspace-queue-workbench-service.js +66 -6
- package/dist/service/workspace-watch-service.d.ts +7 -2
- package/dist/service/workspace-watch-service.js +32 -6
- package/dist/service/workspace-workbench-service.d.ts +14 -0
- package/dist/service/workspace-workbench-service.js +215 -16
- package/dist/util/personal-instructions.d.ts +2 -0
- package/dist/util/personal-instructions.js +31 -0
- package/dist/util/runner-native-session-parsers.d.ts +1 -0
- package/dist/util/runner-native-session-parsers.js +8 -1
- package/dist/workspace.d.ts +14 -8
- package/dist/workspace.js +65 -42
- package/package.json +2 -2
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const RESUME_FAILURE_TTL_MS = 30 * 60_000;
|
|
2
2
|
export class ExternalSessionResumeService {
|
|
3
3
|
options;
|
|
4
|
+
transitions = new Set();
|
|
4
5
|
constructor(options) {
|
|
5
6
|
this.options = options;
|
|
6
7
|
}
|
|
8
|
+
transitioning(sessionId) {
|
|
9
|
+
return this.transitions.has(sessionId);
|
|
10
|
+
}
|
|
7
11
|
async resume(external, force = false) {
|
|
8
12
|
this.clearFailure(external.id);
|
|
9
13
|
const priorId = this.options.state.resumedByDirectId.get(external.id);
|
|
@@ -15,40 +19,47 @@ export class ExternalSessionResumeService {
|
|
|
15
19
|
this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
|
|
16
20
|
return undefined;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
22
|
+
this.transitions.add(external.id);
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
result = await runner.resumeExternal(external, {
|
|
26
|
+
available: this.options.available(external.runner),
|
|
27
|
+
projectionFor: this.options.projectionFor,
|
|
28
|
+
managedAliasFor: this.options.managedAliasFor,
|
|
29
|
+
currentResumed: (sessionId) => {
|
|
30
|
+
const resumedId = this.options.state.resumedByDirectId.get(sessionId);
|
|
31
|
+
return resumedId === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: this.options.runtime.getAgentSession(resumedId);
|
|
34
|
+
},
|
|
35
|
+
promote: (sessionId) => {
|
|
36
|
+
this.options.runtime.setNativeControl(sessionId, 'MAR_MANAGED');
|
|
37
|
+
return this.options.runtime.getAgentSession(sessionId);
|
|
38
|
+
},
|
|
39
|
+
ensureConfiguration: this.options.ensureConfiguration,
|
|
40
|
+
create: (session) => {
|
|
41
|
+
const configuration = this.options.configuration(session);
|
|
42
|
+
return this.options.runtime.createAgentSession({
|
|
43
|
+
workspaceId: session.workspaceId,
|
|
44
|
+
runner: session.runner,
|
|
45
|
+
...(session.externalSessionId === null
|
|
46
|
+
? {}
|
|
47
|
+
: { externalSessionId: session.externalSessionId }),
|
|
48
|
+
nativeControl: 'MAR_MANAGED',
|
|
49
|
+
cwd: session.cwd,
|
|
50
|
+
model: configuration.model,
|
|
51
|
+
effort: configuration.effort,
|
|
52
|
+
access: configuration.access,
|
|
53
|
+
...(session.runnerTitle === null ? {} : { title: session.runnerTitle })
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
workspacePath: this.options.workspacePath,
|
|
57
|
+
rememberManagedNative: this.options.rememberNative
|
|
58
|
+
}, force);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.transitions.delete(external.id);
|
|
62
|
+
}
|
|
52
63
|
if (result.activity !== undefined)
|
|
53
64
|
this.options.state.externalActivity.set(external.id, result.activity);
|
|
54
65
|
if (result.session !== undefined) {
|
|
@@ -32,6 +32,7 @@ export declare class NativeSessionProjectionService {
|
|
|
32
32
|
})[]>;
|
|
33
33
|
discoverWorkspace(workspaceId: string): Promise<readonly NodeAgentSession[]>;
|
|
34
34
|
resolve(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
35
|
+
resolveCurrent(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
35
36
|
createMetadataProjection(sessionId: string): Promise<NodeAgentSession | undefined>;
|
|
36
37
|
resumedConfiguration(session: NodeAgentSession): RunnerDefaultConfiguration;
|
|
37
38
|
ensureResumedConfiguration(session: NodeAgentSession): NodeAgentSession;
|
|
@@ -81,6 +81,10 @@ export class NativeSessionProjectionService {
|
|
|
81
81
|
createdAt: 0
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
async resolveCurrent(sessionId) {
|
|
85
|
+
const direct = await this.resolve(sessionId);
|
|
86
|
+
return direct === undefined ? undefined : (this.projectionForNative(direct) ?? direct);
|
|
87
|
+
}
|
|
84
88
|
async createMetadataProjection(sessionId) {
|
|
85
89
|
const direct = await this.resolve(sessionId);
|
|
86
90
|
if (direct === undefined || direct.externalSessionId === null)
|
|
@@ -6,6 +6,7 @@ export interface NativeSessionWatchOptions<TPage extends {
|
|
|
6
6
|
}> {
|
|
7
7
|
readonly resolve: (sessionId: string) => Promise<NodeAgentSession | undefined>;
|
|
8
8
|
readonly managedActive: (session: NodeAgentSession) => boolean;
|
|
9
|
+
readonly suspended?: (sessionId: string) => boolean;
|
|
9
10
|
readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
|
|
10
11
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
11
12
|
readonly readPage: (session: NodeAgentSession, limit: number) => Promise<TPage>;
|
|
@@ -16,6 +17,9 @@ export interface NativeSessionWatchOptions<TPage extends {
|
|
|
16
17
|
readonly projectInitialPage?: (session: NodeAgentSession, page: TPage, unit: 'SEGMENT', limit: number) => unknown;
|
|
17
18
|
readonly emitSession: (session: unknown) => void;
|
|
18
19
|
readonly emitPage: (session: NodeAgentSession, page: TPage) => void;
|
|
20
|
+
readonly nodeId?: () => string;
|
|
21
|
+
readonly nodeGeneration?: () => string;
|
|
22
|
+
readonly emitWatchEvent?: (payload: unknown) => void;
|
|
19
23
|
}
|
|
20
24
|
export declare class NativeSessionWatchService<TPage extends {
|
|
21
25
|
readonly turns: readonly unknown[];
|
|
@@ -23,9 +27,12 @@ export declare class NativeSessionWatchService<TPage extends {
|
|
|
23
27
|
}> {
|
|
24
28
|
private readonly options;
|
|
25
29
|
private readonly watches;
|
|
30
|
+
private readonly leases;
|
|
31
|
+
private snapshotSequence;
|
|
26
32
|
constructor(options: NativeSessionWatchOptions<TPage>);
|
|
27
33
|
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
28
34
|
private executeWatch;
|
|
35
|
+
private executeUnwatch;
|
|
29
36
|
watch(session: NodeAgentSession): {
|
|
30
37
|
readonly expiresAt: number;
|
|
31
38
|
} | undefined;
|
|
@@ -41,4 +48,15 @@ export declare class NativeSessionWatchService<TPage extends {
|
|
|
41
48
|
get size(): number;
|
|
42
49
|
refresh(sessionId: string, limit?: number, emit?: boolean): Promise<TPage | undefined>;
|
|
43
50
|
private performRefresh;
|
|
51
|
+
private sessionHash;
|
|
52
|
+
private ownerKey;
|
|
53
|
+
private accepted;
|
|
54
|
+
private initializeLease;
|
|
55
|
+
private removeLease;
|
|
56
|
+
private emitObservedPage;
|
|
57
|
+
private failSession;
|
|
58
|
+
private releaseSessionLeases;
|
|
59
|
+
private leaseTimer;
|
|
60
|
+
private latestLeaseExpiry;
|
|
61
|
+
private refreshTimer;
|
|
44
62
|
}
|
|
@@ -7,12 +7,15 @@ const EMITTED_TURN_LIMIT = 10;
|
|
|
7
7
|
export class NativeSessionWatchService {
|
|
8
8
|
options;
|
|
9
9
|
watches = new Map();
|
|
10
|
+
leases = new Map();
|
|
11
|
+
snapshotSequence = 0;
|
|
10
12
|
constructor(options) {
|
|
11
13
|
this.options = options;
|
|
12
14
|
}
|
|
13
15
|
operations() {
|
|
14
16
|
return {
|
|
15
|
-
'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {})
|
|
17
|
+
'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {}),
|
|
18
|
+
'session.unwatch': (data) => this.executeUnwatch(isPlainRecord(data) ? data : {})
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
async executeWatch(input) {
|
|
@@ -20,39 +23,75 @@ export class NativeSessionWatchService {
|
|
|
20
23
|
if (input.unit === 'SEGMENT' && projectInitialPage === undefined)
|
|
21
24
|
throw new Error('SESSION_INVALID');
|
|
22
25
|
if (typeof input.sessionId !== 'string' ||
|
|
26
|
+
(input.workspaceId !== undefined && typeof input.workspaceId !== 'string') ||
|
|
27
|
+
typeof input.watchId !== 'string' ||
|
|
28
|
+
typeof input.workbenchConnectionId !== 'string' ||
|
|
23
29
|
(input.mode !== 'initial' && input.mode !== 'renew') ||
|
|
24
30
|
(input.unit !== undefined && input.unit !== 'SEGMENT'))
|
|
25
31
|
throw new Error('SESSION_INVALID');
|
|
26
|
-
const
|
|
27
|
-
if (session === undefined || session.externalSessionId === null)
|
|
28
|
-
throw new Error('SESSION_NOT_FOUND');
|
|
32
|
+
const ownerKey = this.ownerKey(input);
|
|
29
33
|
if (input.mode === 'renew') {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
34
|
+
const lease = this.leases.get(ownerKey);
|
|
35
|
+
if (lease === undefined ||
|
|
36
|
+
lease.watchId !== input.watchId ||
|
|
37
|
+
lease.sessionId !== input.sessionId ||
|
|
38
|
+
(input.workspaceId !== undefined && lease.workspaceId !== input.workspaceId))
|
|
39
|
+
throw new Error('SESSION_WATCH_NOT_FOUND');
|
|
40
|
+
if (lease.expiresAt <= Date.now()) {
|
|
41
|
+
this.removeLease(ownerKey);
|
|
32
42
|
throw new Error('SESSION_WATCH_NOT_FOUND');
|
|
33
|
-
return { renewed: true };
|
|
34
|
-
}
|
|
35
|
-
const watch = this.watch(session);
|
|
36
|
-
const turnSnapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
|
|
37
|
-
if (watch !== undefined && turnSnapshot === undefined) {
|
|
38
|
-
this.stop(session.id);
|
|
39
|
-
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
40
|
-
}
|
|
41
|
-
let snapshot = turnSnapshot;
|
|
42
|
-
if (turnSnapshot !== undefined && input.unit === 'SEGMENT') {
|
|
43
|
-
try {
|
|
44
|
-
snapshot = projectInitialPage(session, turnSnapshot, 'SEGMENT', 10);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
this.stop(session.id);
|
|
48
|
-
throw error;
|
|
49
43
|
}
|
|
44
|
+
lease.expiresAt = Date.now() + TTL_MS;
|
|
45
|
+
const observer = this.watches.get(lease.sessionId);
|
|
46
|
+
if (observer !== undefined)
|
|
47
|
+
observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
|
|
48
|
+
clearTimeout(lease.timer);
|
|
49
|
+
lease.timer = this.leaseTimer(ownerKey);
|
|
50
|
+
return this.accepted(lease);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const existing = this.leases.get(ownerKey);
|
|
53
|
+
const unit = input.unit === 'SEGMENT' ? 'SEGMENT' : 'TURN';
|
|
54
|
+
if (existing?.watchId === input.watchId &&
|
|
55
|
+
existing.sessionId === input.sessionId &&
|
|
56
|
+
(input.workspaceId === undefined || existing.workspaceId === input.workspaceId) &&
|
|
57
|
+
existing.unit === unit &&
|
|
58
|
+
existing.expiresAt > Date.now()) {
|
|
59
|
+
existing.expiresAt = Date.now() + TTL_MS;
|
|
60
|
+
clearTimeout(existing.timer);
|
|
61
|
+
existing.timer = this.leaseTimer(ownerKey);
|
|
62
|
+
return this.accepted(existing);
|
|
63
|
+
}
|
|
64
|
+
this.removeLease(ownerKey);
|
|
65
|
+
const lease = {
|
|
66
|
+
ownerKey,
|
|
67
|
+
sessionHash: this.sessionHash(input),
|
|
68
|
+
workbenchConnectionId: input.workbenchConnectionId,
|
|
69
|
+
watchId: input.watchId,
|
|
70
|
+
sessionId: input.sessionId,
|
|
71
|
+
unit,
|
|
72
|
+
nodeGeneration: this.options.nodeGeneration?.() ?? 'local-test-generation',
|
|
73
|
+
...(typeof input.workspaceId === 'string' ? { workspaceId: input.workspaceId } : {}),
|
|
74
|
+
expiresAt: Date.now() + TTL_MS,
|
|
75
|
+
timer: undefined
|
|
55
76
|
};
|
|
77
|
+
lease.timer = this.leaseTimer(ownerKey);
|
|
78
|
+
this.leases.set(ownerKey, lease);
|
|
79
|
+
const observer = this.watches.get(lease.sessionId);
|
|
80
|
+
if (observer !== undefined)
|
|
81
|
+
observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
|
|
82
|
+
void this.initializeLease(lease, projectInitialPage);
|
|
83
|
+
return this.accepted(lease);
|
|
84
|
+
}
|
|
85
|
+
executeUnwatch(input) {
|
|
86
|
+
if (typeof input.sessionId !== 'string' ||
|
|
87
|
+
typeof input.watchId !== 'string' ||
|
|
88
|
+
typeof input.workbenchConnectionId !== 'string')
|
|
89
|
+
throw new Error('SESSION_INVALID');
|
|
90
|
+
const ownerKey = this.ownerKey(input);
|
|
91
|
+
const lease = this.leases.get(ownerKey);
|
|
92
|
+
if (lease?.watchId === input.watchId && lease.sessionId === input.sessionId)
|
|
93
|
+
this.removeLease(ownerKey);
|
|
94
|
+
return { stopped: true };
|
|
56
95
|
}
|
|
57
96
|
watch(session) {
|
|
58
97
|
if (this.options.managedActive(session)) {
|
|
@@ -75,8 +114,7 @@ export class NativeSessionWatchService {
|
|
|
75
114
|
failures: 0,
|
|
76
115
|
timer: undefined
|
|
77
116
|
};
|
|
78
|
-
watch.timer =
|
|
79
|
-
watch.timer.unref();
|
|
117
|
+
watch.timer = this.refreshTimer(session.id);
|
|
80
118
|
this.watches.set(session.id, watch);
|
|
81
119
|
return { expiresAt: watch.expiresAt };
|
|
82
120
|
}
|
|
@@ -97,13 +135,16 @@ export class NativeSessionWatchService {
|
|
|
97
135
|
const watch = this.watches.get(sessionId);
|
|
98
136
|
if (watch === undefined)
|
|
99
137
|
return;
|
|
100
|
-
|
|
138
|
+
clearTimeout(watch.timer);
|
|
101
139
|
this.watches.delete(sessionId);
|
|
102
140
|
}
|
|
103
141
|
clear() {
|
|
104
142
|
for (const watch of this.watches.values())
|
|
105
|
-
|
|
143
|
+
clearTimeout(watch.timer);
|
|
106
144
|
this.watches.clear();
|
|
145
|
+
for (const lease of this.leases.values())
|
|
146
|
+
clearTimeout(lease.timer);
|
|
147
|
+
this.leases.clear();
|
|
107
148
|
}
|
|
108
149
|
has(sessionId) {
|
|
109
150
|
return this.watches.has(sessionId);
|
|
@@ -130,20 +171,27 @@ export class NativeSessionWatchService {
|
|
|
130
171
|
return await refresh;
|
|
131
172
|
}
|
|
132
173
|
finally {
|
|
133
|
-
if (this.watches.get(sessionId) === watch)
|
|
174
|
+
if (this.watches.get(sessionId) === watch) {
|
|
134
175
|
watch.refresh = undefined;
|
|
176
|
+
clearTimeout(watch.timer);
|
|
177
|
+
watch.timer = this.refreshTimer(sessionId);
|
|
178
|
+
}
|
|
135
179
|
}
|
|
136
180
|
}
|
|
137
181
|
async performRefresh(sessionId, watch, limit, emit) {
|
|
138
182
|
try {
|
|
183
|
+
if (this.options.suspended?.(sessionId) === true)
|
|
184
|
+
return watch.page;
|
|
139
185
|
const session = await this.options.resolve(sessionId);
|
|
140
186
|
if (session === undefined ||
|
|
141
187
|
session.externalSessionId === null ||
|
|
142
188
|
this.options.managedActive(session)) {
|
|
143
|
-
this.
|
|
189
|
+
this.releaseSessionLeases(sessionId);
|
|
144
190
|
return undefined;
|
|
145
191
|
}
|
|
146
192
|
await this.options.refreshActivity(session);
|
|
193
|
+
if (this.options.suspended?.(sessionId) === true)
|
|
194
|
+
return watch.page;
|
|
147
195
|
const presented = this.options.present(session);
|
|
148
196
|
const sessionSignature = JSON.stringify(presented);
|
|
149
197
|
if (sessionSignature !== watch.sessionSignature) {
|
|
@@ -172,14 +220,164 @@ export class NativeSessionWatchService {
|
|
|
172
220
|
watch.signature = signature;
|
|
173
221
|
watch.page = accepted;
|
|
174
222
|
if (emit)
|
|
175
|
-
this.
|
|
223
|
+
this.emitObservedPage(session, accepted);
|
|
176
224
|
return emit ? accepted : page;
|
|
177
225
|
}
|
|
178
226
|
catch {
|
|
179
227
|
watch.failures += 1;
|
|
180
228
|
if (watch.failures >= MAX_FAILURES)
|
|
181
|
-
this.
|
|
229
|
+
this.failSession(sessionId);
|
|
182
230
|
return undefined;
|
|
183
231
|
}
|
|
184
232
|
}
|
|
233
|
+
sessionHash(input) {
|
|
234
|
+
return typeof input.__workspaceWatchSessionHash === 'string'
|
|
235
|
+
? input.__workspaceWatchSessionHash
|
|
236
|
+
: 'local-test';
|
|
237
|
+
}
|
|
238
|
+
ownerKey(input) {
|
|
239
|
+
return `${this.sessionHash(input)}:${input.workbenchConnectionId}`;
|
|
240
|
+
}
|
|
241
|
+
accepted(lease) {
|
|
242
|
+
return {
|
|
243
|
+
accepted: true,
|
|
244
|
+
watchId: lease.watchId,
|
|
245
|
+
nodeGeneration: lease.nodeGeneration,
|
|
246
|
+
expiresAt: lease.expiresAt
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async initializeLease(lease, projectInitialPage) {
|
|
250
|
+
try {
|
|
251
|
+
const session = await this.options.resolve(lease.sessionId);
|
|
252
|
+
if (session === undefined || session.externalSessionId === null)
|
|
253
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
254
|
+
if (lease.workspaceId !== undefined && session.workspaceId !== lease.workspaceId)
|
|
255
|
+
throw new Error('SESSION_WORKSPACE_MISMATCH');
|
|
256
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
257
|
+
return;
|
|
258
|
+
lease.workspaceId = session.workspaceId;
|
|
259
|
+
const watch = this.watch(session);
|
|
260
|
+
const page = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
|
|
261
|
+
if (page === undefined)
|
|
262
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
263
|
+
const payload = lease.unit === 'SEGMENT' ? projectInitialPage(session, page, 'SEGMENT', 10) : page;
|
|
264
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
265
|
+
return;
|
|
266
|
+
this.options.emitWatchEvent?.({
|
|
267
|
+
type: 'watch.session.snapshot',
|
|
268
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
269
|
+
watchId: lease.watchId,
|
|
270
|
+
nodeGeneration: lease.nodeGeneration,
|
|
271
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
272
|
+
workspaceId: session.workspaceId,
|
|
273
|
+
sessionId: session.id,
|
|
274
|
+
revision: 1,
|
|
275
|
+
snapshotSequence: ++this.snapshotSequence,
|
|
276
|
+
expiresAt: lease.expiresAt,
|
|
277
|
+
payload
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (this.leases.get(lease.ownerKey) !== lease)
|
|
282
|
+
return;
|
|
283
|
+
this.options.emitWatchEvent?.({
|
|
284
|
+
type: 'watch.failed',
|
|
285
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
286
|
+
watchType: 'session',
|
|
287
|
+
watchId: lease.watchId,
|
|
288
|
+
nodeGeneration: lease.nodeGeneration,
|
|
289
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
290
|
+
workspaceId: lease.workspaceId ?? 'unknown',
|
|
291
|
+
sessionId: lease.sessionId,
|
|
292
|
+
code: error instanceof Error && /^[A-Z][A-Z0-9_]+$/.test(error.message)
|
|
293
|
+
? error.message
|
|
294
|
+
: 'SESSION_WATCH_SNAPSHOT_FAILED',
|
|
295
|
+
message: 'Session watch snapshot failed.'
|
|
296
|
+
});
|
|
297
|
+
this.removeLease(lease.ownerKey);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
removeLease(ownerKey) {
|
|
301
|
+
const lease = this.leases.get(ownerKey);
|
|
302
|
+
if (lease === undefined)
|
|
303
|
+
return;
|
|
304
|
+
clearTimeout(lease.timer);
|
|
305
|
+
this.leases.delete(ownerKey);
|
|
306
|
+
const observer = this.watches.get(lease.sessionId);
|
|
307
|
+
const nextExpiry = this.latestLeaseExpiry(lease.sessionId);
|
|
308
|
+
if (nextExpiry === 0)
|
|
309
|
+
this.stop(lease.sessionId);
|
|
310
|
+
else if (observer !== undefined)
|
|
311
|
+
observer.expiresAt = nextExpiry;
|
|
312
|
+
}
|
|
313
|
+
emitObservedPage(session, page) {
|
|
314
|
+
const leases = [...this.leases.values()].filter((lease) => lease.sessionId === session.id);
|
|
315
|
+
if (leases.length === 0) {
|
|
316
|
+
this.options.emitPage(session, page);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
for (const lease of leases) {
|
|
320
|
+
const payload = lease.unit === 'SEGMENT'
|
|
321
|
+
? this.options.projectInitialPage?.(session, page, 'SEGMENT', 10)
|
|
322
|
+
: page;
|
|
323
|
+
if (payload === undefined)
|
|
324
|
+
continue;
|
|
325
|
+
this.options.emitWatchEvent?.({
|
|
326
|
+
type: 'watch.session.snapshot',
|
|
327
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
328
|
+
watchId: lease.watchId,
|
|
329
|
+
nodeGeneration: lease.nodeGeneration,
|
|
330
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
331
|
+
workspaceId: session.workspaceId,
|
|
332
|
+
sessionId: session.id,
|
|
333
|
+
revision: 1,
|
|
334
|
+
snapshotSequence: ++this.snapshotSequence,
|
|
335
|
+
expiresAt: lease.expiresAt,
|
|
336
|
+
payload
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
failSession(sessionId) {
|
|
341
|
+
for (const lease of [...this.leases.values()]) {
|
|
342
|
+
if (lease.sessionId !== sessionId)
|
|
343
|
+
continue;
|
|
344
|
+
this.options.emitWatchEvent?.({
|
|
345
|
+
type: 'watch.failed',
|
|
346
|
+
workbenchConnectionId: lease.workbenchConnectionId,
|
|
347
|
+
watchType: 'session',
|
|
348
|
+
watchId: lease.watchId,
|
|
349
|
+
nodeGeneration: lease.nodeGeneration,
|
|
350
|
+
nodeId: this.options.nodeId?.() ?? 'local-test-node',
|
|
351
|
+
workspaceId: lease.workspaceId ?? 'unknown',
|
|
352
|
+
sessionId,
|
|
353
|
+
code: 'NATIVE_TRANSCRIPT_UNAVAILABLE',
|
|
354
|
+
message: 'Session watch snapshot failed.'
|
|
355
|
+
});
|
|
356
|
+
this.removeLease(lease.ownerKey);
|
|
357
|
+
}
|
|
358
|
+
this.stop(sessionId);
|
|
359
|
+
}
|
|
360
|
+
releaseSessionLeases(sessionId) {
|
|
361
|
+
for (const lease of [...this.leases.values()])
|
|
362
|
+
if (lease.sessionId === sessionId)
|
|
363
|
+
this.removeLease(lease.ownerKey);
|
|
364
|
+
this.stop(sessionId);
|
|
365
|
+
}
|
|
366
|
+
leaseTimer(ownerKey) {
|
|
367
|
+
const timer = setTimeout(() => this.removeLease(ownerKey), TTL_MS);
|
|
368
|
+
timer.unref();
|
|
369
|
+
return timer;
|
|
370
|
+
}
|
|
371
|
+
latestLeaseExpiry(sessionId) {
|
|
372
|
+
let latest = 0;
|
|
373
|
+
for (const lease of this.leases.values())
|
|
374
|
+
if (lease.sessionId === sessionId)
|
|
375
|
+
latest = Math.max(latest, lease.expiresAt);
|
|
376
|
+
return latest;
|
|
377
|
+
}
|
|
378
|
+
refreshTimer(sessionId) {
|
|
379
|
+
const timer = setTimeout(() => void this.refresh(sessionId), INTERVAL_MS);
|
|
380
|
+
timer.unref();
|
|
381
|
+
return timer;
|
|
382
|
+
}
|
|
185
383
|
}
|
|
@@ -4,10 +4,8 @@ export interface RunWorkbenchServiceOptions {
|
|
|
4
4
|
readonly runtime: NodeRuntimeState;
|
|
5
5
|
readonly readImage: (runId: string, imageIndex: number) => Promise<unknown | undefined>;
|
|
6
6
|
readonly respondUserInput: (runId: string, requestId: string, answers: Record<string, unknown>) => void;
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly cleanupAttachments: (runId: string) => void;
|
|
10
|
-
readonly emitQueue: (workspaceId: string, sessionId: string) => void;
|
|
7
|
+
readonly cancelQueued: (runId: string) => void;
|
|
8
|
+
readonly pauseQueue: (sessionId: string) => void;
|
|
11
9
|
readonly emitRun: (runId: string, eventType: string, payload: unknown, status?: 'CANCELLED') => void;
|
|
12
10
|
readonly control: (operation: string, payload: Record<string, unknown>) => void;
|
|
13
11
|
readonly markInterrupted: (runId: string) => void;
|
|
@@ -55,11 +55,7 @@ export class RunWorkbenchService {
|
|
|
55
55
|
if (operation === 'run.cancel' &&
|
|
56
56
|
run.status === 'QUEUED' &&
|
|
57
57
|
this.options.runtime.isQueuedRun(run.id)) {
|
|
58
|
-
this.options.
|
|
59
|
-
this.options.deleteQueuedStart(run.id);
|
|
60
|
-
this.options.clearImages(run.id);
|
|
61
|
-
this.options.cleanupAttachments(run.id);
|
|
62
|
-
this.options.emitQueue(run.workspaceId, run.sessionId);
|
|
58
|
+
this.options.cancelQueued(run.id);
|
|
63
59
|
return { cancelled: true };
|
|
64
60
|
}
|
|
65
61
|
const next = this.options.runtime.transitionRun(run.id, operation === 'run.cancel' && run.status === 'QUEUED' ? 'CANCELLED' : 'CANCELLING');
|
|
@@ -68,8 +64,7 @@ export class RunWorkbenchService {
|
|
|
68
64
|
return { run: next };
|
|
69
65
|
}
|
|
70
66
|
if (operation === 'run.interrupt') {
|
|
71
|
-
this.options.
|
|
72
|
-
this.options.emitQueue(run.workspaceId, run.sessionId);
|
|
67
|
+
this.options.pauseQueue(run.sessionId);
|
|
73
68
|
this.options.markInterrupted(run.id);
|
|
74
69
|
}
|
|
75
70
|
this.options.control(operation, { runId: run.id });
|
|
@@ -36,7 +36,7 @@ export class SessionCommandService {
|
|
|
36
36
|
input.input.length > 20_000)
|
|
37
37
|
throw new Error('RUNNER_COMMAND_INVALID');
|
|
38
38
|
const resolved = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
39
|
-
(await this.options.projection.
|
|
39
|
+
(await this.options.projection.resolveCurrent(input.sessionId));
|
|
40
40
|
if (resolved === undefined)
|
|
41
41
|
throw new Error('SESSION_NOT_FOUND');
|
|
42
42
|
const session = resolved.nativeControl === 'EXTERNAL' ? await this.options.resume.resume(resolved) : resolved;
|
|
@@ -8,7 +8,7 @@ export interface SessionIdentityServiceOptions {
|
|
|
8
8
|
readonly migrateRunnerState: (previousId: string, nextId: string) => void;
|
|
9
9
|
readonly emitSession: (session: NodeAgentSession) => void;
|
|
10
10
|
readonly emitRun: (run: unknown) => void;
|
|
11
|
-
readonly emitQueue: (
|
|
11
|
+
readonly emitQueue: (sessionId: string) => void;
|
|
12
12
|
readonly emitTurn: (turn: unknown) => void;
|
|
13
13
|
}
|
|
14
14
|
export declare class SessionIdentityService {
|
|
@@ -29,7 +29,7 @@ export class SessionIdentityService {
|
|
|
29
29
|
this.options.emitSession(session);
|
|
30
30
|
for (const run of this.options.runtime.listRunsForSession(nativeSessionId))
|
|
31
31
|
this.options.emitRun(run);
|
|
32
|
-
this.options.emitQueue(
|
|
32
|
+
this.options.emitQueue(nativeSessionId);
|
|
33
33
|
for (const turn of this.options.runtime.listConversationTurns({ sessionId: nativeSessionId })
|
|
34
34
|
.turns)
|
|
35
35
|
if (turn.status !== 'QUEUED')
|
|
@@ -204,6 +204,9 @@ export class SessionLifecycleService {
|
|
|
204
204
|
...(input.secretEnvironment === undefined
|
|
205
205
|
? {}
|
|
206
206
|
: { secretEnvironment: input.secretEnvironment }),
|
|
207
|
+
...(input.personalInstructions === undefined
|
|
208
|
+
? {}
|
|
209
|
+
: { personalInstructions: input.personalInstructions }),
|
|
207
210
|
...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
|
|
208
211
|
}));
|
|
209
212
|
const nativeSessionId = await identity;
|
|
@@ -18,7 +18,6 @@ interface SessionMessageServiceOptions {
|
|
|
18
18
|
readonly resolve: (sessionId: string) => Promise<NodeAgentSession>;
|
|
19
19
|
readonly present: (session: NodeAgentSession) => unknown;
|
|
20
20
|
readonly emitSession: (session: NodeAgentSession) => void;
|
|
21
|
-
readonly emitQueue: (workspaceId: string, sessionId: string) => void;
|
|
22
21
|
readonly channelToken: (sessionId: string) => string | undefined;
|
|
23
22
|
}
|
|
24
23
|
export declare class SessionMessageService {
|