@myagentroam/node 0.9.96 → 0.9.98
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/codex-app-server.js +4 -15
- package/dist/connector.js +0 -18
- package/dist/ctags-binary.js +10 -0
- package/dist/runner/claude/managed-run-controller.js +1 -1
- package/dist/runner/claude-code-runner.js +14 -48
- package/dist/runner/codex/managed-run-controller.js +1 -1
- package/dist/runner/codex-runner.js +6 -13
- package/dist/runner/mar-agent-runner.js +3 -10
- package/dist/runner/opencode-runner.js +7 -19
- package/dist/service/code-navigation/c-family-path-specifier-resolver.js +40 -0
- package/dist/service/code-navigation/csharp-path-specifier-resolver.js +32 -0
- package/dist/service/code-navigation/ctags-index-storage.js +290 -0
- package/dist/service/code-navigation/ctags-query.js +140 -0
- package/dist/service/code-navigation/ecmascript-path-specifier-resolver.js +85 -0
- package/dist/service/code-navigation/file-reference-parser.js +28 -0
- package/dist/service/code-navigation/go-path-specifier-resolver.js +31 -0
- package/dist/service/code-navigation/jvm-path-specifier-resolver.js +28 -0
- package/dist/service/code-navigation/markdown-path-specifier-resolver.js +80 -0
- package/dist/service/code-navigation/navigation-resolution-definition.js +1 -0
- package/dist/service/code-navigation/navigation-resolution-registry.js +36 -0
- package/dist/service/code-navigation/navigation-target-validator.js +121 -0
- package/dist/service/code-navigation/path-specifier-resolver.js +6 -0
- package/dist/service/code-navigation/python-path-specifier-resolver.js +41 -0
- package/dist/service/code-navigation/rust-path-specifier-resolver.js +31 -0
- package/dist/service/code-navigation/stylesheet-path-specifier-resolver.js +55 -0
- package/dist/service/conversation-history-service.js +1 -1
- package/dist/service/external-session-resume-service.js +5 -22
- package/dist/service/node-control-message-service.js +1 -1
- package/dist/service/node-upgrade-coordinator.js +0 -1
- package/dist/service/session-catalog-service.js +2 -27
- package/dist/service/session-command-service.js +3 -3
- package/dist/service/session-message-service.js +3 -15
- package/dist/service/session-presentation-service.js +0 -1
- package/dist/service/workspace-code-navigation-service.js +80 -694
- package/package.json +4 -3
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cFamilyNavigationResolutionDefinition, cNavigationResolutionDefinition, cppNavigationResolutionDefinition, objectiveCNavigationResolutionDefinition, objectiveCppNavigationResolutionDefinition } from './c-family-path-specifier-resolver.js';
|
|
2
|
+
import { csharpNavigationResolutionDefinition } from './csharp-path-specifier-resolver.js';
|
|
3
|
+
import { ecmascriptNavigationResolutionDefinition } from './ecmascript-path-specifier-resolver.js';
|
|
4
|
+
import { goNavigationResolutionDefinition } from './go-path-specifier-resolver.js';
|
|
5
|
+
import { jvmNavigationResolutionDefinition } from './jvm-path-specifier-resolver.js';
|
|
6
|
+
import { markdownNavigationResolutionDefinition } from './markdown-path-specifier-resolver.js';
|
|
7
|
+
import { pythonNavigationResolutionDefinition } from './python-path-specifier-resolver.js';
|
|
8
|
+
import { rustNavigationResolutionDefinition } from './rust-path-specifier-resolver.js';
|
|
9
|
+
import { cssNavigationResolutionDefinition, htmlNavigationResolutionDefinition, lessNavigationResolutionDefinition, sassNavigationResolutionDefinition, scssNavigationResolutionDefinition, vueNavigationResolutionDefinition } from './stylesheet-path-specifier-resolver.js';
|
|
10
|
+
const definitionsByLanguage = {
|
|
11
|
+
css: cssNavigationResolutionDefinition,
|
|
12
|
+
ecmascript: ecmascriptNavigationResolutionDefinition,
|
|
13
|
+
go: goNavigationResolutionDefinition,
|
|
14
|
+
html: htmlNavigationResolutionDefinition,
|
|
15
|
+
jvm: jvmNavigationResolutionDefinition,
|
|
16
|
+
csharp: csharpNavigationResolutionDefinition,
|
|
17
|
+
less: lessNavigationResolutionDefinition,
|
|
18
|
+
markdown: markdownNavigationResolutionDefinition,
|
|
19
|
+
python: pythonNavigationResolutionDefinition,
|
|
20
|
+
sass: sassNavigationResolutionDefinition,
|
|
21
|
+
scss: scssNavigationResolutionDefinition,
|
|
22
|
+
c: cNavigationResolutionDefinition,
|
|
23
|
+
cpp: cppNavigationResolutionDefinition,
|
|
24
|
+
'objective-c': objectiveCNavigationResolutionDefinition,
|
|
25
|
+
'objective-cpp': objectiveCppNavigationResolutionDefinition,
|
|
26
|
+
'c-family': cFamilyNavigationResolutionDefinition,
|
|
27
|
+
rust: rustNavigationResolutionDefinition,
|
|
28
|
+
vue: vueNavigationResolutionDefinition
|
|
29
|
+
};
|
|
30
|
+
export function navigationResolutionDefinitions() {
|
|
31
|
+
return Object.values(definitionsByLanguage);
|
|
32
|
+
}
|
|
33
|
+
export function pathSpecifierResolver(languageId, specifier) {
|
|
34
|
+
const definition = definitionsByLanguage[languageId];
|
|
35
|
+
return definition?.resolver.accepts(specifier) === true ? definition.resolver : undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { lstat, open, readFile, realpath } from 'node:fs/promises';
|
|
2
|
+
import { relative, resolve, sep } from 'node:path';
|
|
3
|
+
const MAX_SOURCE_BYTES = 4 * 1024 * 1024;
|
|
4
|
+
const READ_BLOCK_BYTES = 4096;
|
|
5
|
+
const MAX_TARGET_PREVIEW_CHARACTERS = 512;
|
|
6
|
+
export function sourceFileFingerprint(size, modifiedAt) {
|
|
7
|
+
return `${size}:${modifiedAt ?? -1}`;
|
|
8
|
+
}
|
|
9
|
+
export async function validateSymbolTarget(root, target, symbol, expectedFingerprint) {
|
|
10
|
+
const actual = await validatedFile(root, target.path, expectedFingerprint);
|
|
11
|
+
if (actual === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const line = await lineAt(actual, target.line);
|
|
14
|
+
return line !== undefined && containsSymbol(line, symbol) ? targetPreview(line) : undefined;
|
|
15
|
+
}
|
|
16
|
+
export async function validatePathTarget(root, path, expectedFingerprint, anchor, anchorLine) {
|
|
17
|
+
const actual = await validatedFile(root, path, expectedFingerprint);
|
|
18
|
+
if (actual === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const line = anchor === undefined || anchorLine === undefined
|
|
21
|
+
? 1
|
|
22
|
+
: anchorLine(await readFile(actual, 'utf8').catch(() => ''), anchor);
|
|
23
|
+
if (line === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const preview = await lineAt(actual, line);
|
|
26
|
+
return preview === undefined ? undefined : { line, preview };
|
|
27
|
+
}
|
|
28
|
+
async function validatedFile(root, path, expectedFingerprint) {
|
|
29
|
+
const requested = resolve(root, ...path.split('/'));
|
|
30
|
+
const requestedMetadata = await lstat(requested).catch(() => undefined);
|
|
31
|
+
if (requestedMetadata === undefined ||
|
|
32
|
+
!requestedMetadata.isFile() ||
|
|
33
|
+
requestedMetadata.isSymbolicLink() ||
|
|
34
|
+
requestedMetadata.size > MAX_SOURCE_BYTES)
|
|
35
|
+
return undefined;
|
|
36
|
+
const actual = await realpath(requested).catch(() => undefined);
|
|
37
|
+
if (actual === undefined ||
|
|
38
|
+
relative(root, actual)
|
|
39
|
+
.split(sep)
|
|
40
|
+
.some((part) => part === '..'))
|
|
41
|
+
return undefined;
|
|
42
|
+
const metadata = await lstat(actual).catch(() => undefined);
|
|
43
|
+
if (metadata === undefined ||
|
|
44
|
+
!metadata.isFile() ||
|
|
45
|
+
metadata.isSymbolicLink() ||
|
|
46
|
+
metadata.size > MAX_SOURCE_BYTES ||
|
|
47
|
+
(expectedFingerprint !== undefined &&
|
|
48
|
+
sourceFileFingerprint(metadata.size, metadata.mtimeMs) !== expectedFingerprint))
|
|
49
|
+
return undefined;
|
|
50
|
+
return actual;
|
|
51
|
+
}
|
|
52
|
+
export function targetPreview(line) {
|
|
53
|
+
if (line.length <= MAX_TARGET_PREVIEW_CHARACTERS)
|
|
54
|
+
return line;
|
|
55
|
+
const contentLimit = MAX_TARGET_PREVIEW_CHARACTERS - 1;
|
|
56
|
+
const end = line.charCodeAt(contentLimit - 1);
|
|
57
|
+
const safeEnd = end >= 0xd800 && end <= 0xdbff ? contentLimit - 1 : contentLimit;
|
|
58
|
+
return `${line.slice(0, safeEnd)}…`;
|
|
59
|
+
}
|
|
60
|
+
async function lineAt(path, targetLine) {
|
|
61
|
+
const handle = await open(path, 'r');
|
|
62
|
+
try {
|
|
63
|
+
let offset = 0;
|
|
64
|
+
let line = 1;
|
|
65
|
+
let pending = Buffer.alloc(0);
|
|
66
|
+
while (offset < MAX_SOURCE_BYTES) {
|
|
67
|
+
const chunk = Buffer.alloc(READ_BLOCK_BYTES);
|
|
68
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
69
|
+
if (bytesRead === 0)
|
|
70
|
+
return line === targetLine ? pending.toString('utf8') : undefined;
|
|
71
|
+
offset += bytesRead;
|
|
72
|
+
let content = pending.length === 0
|
|
73
|
+
? chunk.subarray(0, bytesRead)
|
|
74
|
+
: Buffer.concat([pending, chunk.subarray(0, bytesRead)]);
|
|
75
|
+
let newline;
|
|
76
|
+
while ((newline = content.indexOf(0x0a)) >= 0) {
|
|
77
|
+
if (line === targetLine)
|
|
78
|
+
return content.subarray(0, newline).toString('utf8');
|
|
79
|
+
line += 1;
|
|
80
|
+
content = content.subarray(newline + 1);
|
|
81
|
+
}
|
|
82
|
+
pending = content;
|
|
83
|
+
}
|
|
84
|
+
return line === targetLine ? pending.toString('utf8') : undefined;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await handle.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function containsSymbol(line, symbol) {
|
|
91
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
92
|
+
const optionalScssPrefix = symbol.startsWith('$') ? '' : '\\$?';
|
|
93
|
+
return new RegExp(`(?:^|[^\\p{L}\\p{N}_$])${optionalScssPrefix}${escaped}(?![\\p{L}\\p{N}_$])`, 'u').test(line);
|
|
94
|
+
}
|
|
95
|
+
export function compareTargets(left, right, currentPath) {
|
|
96
|
+
const leftScore = targetScore(left, currentPath);
|
|
97
|
+
const rightScore = targetScore(right, currentPath);
|
|
98
|
+
return (rightScore - leftScore ||
|
|
99
|
+
left.path.localeCompare(right.path) ||
|
|
100
|
+
left.line - right.line ||
|
|
101
|
+
(left.kind ?? '').localeCompare(right.kind ?? ''));
|
|
102
|
+
}
|
|
103
|
+
function targetScore(target, currentPath) {
|
|
104
|
+
if (target.path === currentPath)
|
|
105
|
+
return 100;
|
|
106
|
+
const currentDirectory = currentPath.includes('/')
|
|
107
|
+
? currentPath.slice(0, currentPath.lastIndexOf('/'))
|
|
108
|
+
: '';
|
|
109
|
+
const targetDirectory = target.path.includes('/')
|
|
110
|
+
? target.path.slice(0, target.path.lastIndexOf('/'))
|
|
111
|
+
: '';
|
|
112
|
+
return commonPathSegments(currentDirectory, targetDirectory);
|
|
113
|
+
}
|
|
114
|
+
function commonPathSegments(left, right) {
|
|
115
|
+
const leftParts = left === '' ? [] : left.split('/');
|
|
116
|
+
const rightParts = right === '' ? [] : right.split('/');
|
|
117
|
+
let count = 0;
|
|
118
|
+
while (count < leftParts.length && leftParts[count] === rightParts[count])
|
|
119
|
+
count += 1;
|
|
120
|
+
return count;
|
|
121
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { matchesPathSuffix, shortestPathScore } from './path-specifier-resolver.js';
|
|
2
|
+
import { posix } from 'node:path';
|
|
3
|
+
function accepts(value) {
|
|
4
|
+
return (/^[\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)*$/u.test(value) ||
|
|
5
|
+
/^\.+(?:[\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)*)?$/u.test(value));
|
|
6
|
+
}
|
|
7
|
+
function matches(path, currentPath, specifier) {
|
|
8
|
+
return candidates(currentPath, specifier).some((candidate) => matchesPathSuffix(path, candidate));
|
|
9
|
+
}
|
|
10
|
+
function directCandidates(currentPath, specifier) {
|
|
11
|
+
return specifier.startsWith('.') ? candidates(currentPath, specifier) : [];
|
|
12
|
+
}
|
|
13
|
+
function candidates(currentPath, specifier) {
|
|
14
|
+
const packagePath = relativePackagePath(currentPath, specifier);
|
|
15
|
+
if (packagePath === undefined)
|
|
16
|
+
return [];
|
|
17
|
+
return packagePath === '.'
|
|
18
|
+
? ['__init__.py']
|
|
19
|
+
: [`${packagePath}.py`, `${packagePath}.pyw`, `${packagePath}/__init__.py`];
|
|
20
|
+
}
|
|
21
|
+
function relativePackagePath(currentPath, specifier) {
|
|
22
|
+
if (!specifier.startsWith('.'))
|
|
23
|
+
return specifier.replaceAll('.', '/');
|
|
24
|
+
const levels = specifier.match(/^\.+/u)?.[0].length ?? 0;
|
|
25
|
+
const module = specifier.slice(levels).replaceAll('.', '/');
|
|
26
|
+
const base = posix.normalize(posix.join(posix.dirname(currentPath), ...Array.from({ length: levels - 1 }, () => '..')));
|
|
27
|
+
if (base === '..' || base.startsWith('../'))
|
|
28
|
+
return undefined;
|
|
29
|
+
return module.length === 0 ? base : posix.join(base, module);
|
|
30
|
+
}
|
|
31
|
+
export const pythonPathSpecifierResolver = {
|
|
32
|
+
id: 'python.path-specifier',
|
|
33
|
+
accepts,
|
|
34
|
+
directCandidates,
|
|
35
|
+
matches,
|
|
36
|
+
score: shortestPathScore
|
|
37
|
+
};
|
|
38
|
+
export const pythonNavigationResolutionDefinition = {
|
|
39
|
+
languageId: 'python',
|
|
40
|
+
resolver: pythonPathSpecifierResolver
|
|
41
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { posix } from 'node:path';
|
|
2
|
+
function accepts(value) {
|
|
3
|
+
return (/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value) || /^(?:\.\.?\/)?[A-Za-z0-9_./-]+\.rs$/u.test(value));
|
|
4
|
+
}
|
|
5
|
+
function matches(path, currentPath, specifier) {
|
|
6
|
+
return candidates(currentPath, specifier).includes(path);
|
|
7
|
+
}
|
|
8
|
+
function score(path, currentPath, specifier) {
|
|
9
|
+
const index = candidates(currentPath, specifier).indexOf(path);
|
|
10
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
11
|
+
}
|
|
12
|
+
function candidates(currentPath, specifier) {
|
|
13
|
+
const directory = posix.dirname(currentPath);
|
|
14
|
+
if (specifier.endsWith('.rs')) {
|
|
15
|
+
const resolved = posix.normalize(posix.join(directory, specifier));
|
|
16
|
+
return resolved.startsWith('../') || resolved === '..' ? [] : [resolved];
|
|
17
|
+
}
|
|
18
|
+
const base = directory === '.' ? specifier : `${directory}/${specifier}`;
|
|
19
|
+
return [`${base}.rs`, `${base}/mod.rs`];
|
|
20
|
+
}
|
|
21
|
+
export const rustPathSpecifierResolver = {
|
|
22
|
+
id: 'rust.path-specifier',
|
|
23
|
+
accepts,
|
|
24
|
+
directCandidates: candidates,
|
|
25
|
+
matches,
|
|
26
|
+
score
|
|
27
|
+
};
|
|
28
|
+
export const rustNavigationResolutionDefinition = {
|
|
29
|
+
languageId: 'rust',
|
|
30
|
+
resolver: rustPathSpecifierResolver
|
|
31
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { posix } from 'node:path';
|
|
2
|
+
function accepts(value) {
|
|
3
|
+
return /^(?:\.\.?\/)?[A-Za-z0-9_./$+@ -]+$/u.test(value);
|
|
4
|
+
}
|
|
5
|
+
function createResolver(languageId, extensions, partials = false) {
|
|
6
|
+
const candidates = (currentPath, specifier) => pathCandidates(currentPath, specifier, extensions, partials);
|
|
7
|
+
return {
|
|
8
|
+
id: `${languageId}.path-specifier`,
|
|
9
|
+
accepts,
|
|
10
|
+
directCandidates: candidates,
|
|
11
|
+
matches: (path, currentPath, specifier) => candidates(currentPath, specifier).includes(path),
|
|
12
|
+
score: (path, currentPath, specifier) => {
|
|
13
|
+
const index = candidates(currentPath, specifier).indexOf(path);
|
|
14
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function pathCandidates(currentPath, specifier, extensions, partials) {
|
|
19
|
+
const resolved = posix.normalize(posix.join(posix.dirname(currentPath), specifier));
|
|
20
|
+
if (resolved === '..' || resolved.startsWith('../'))
|
|
21
|
+
return [];
|
|
22
|
+
const path = resolved.replace(/^\.\//u, '');
|
|
23
|
+
if (path.slice(path.lastIndexOf('/') + 1).includes('.'))
|
|
24
|
+
return [path];
|
|
25
|
+
const base = extensions.flatMap((extension) => [
|
|
26
|
+
`${path}${extension}`,
|
|
27
|
+
`${path}/index${extension}`
|
|
28
|
+
]);
|
|
29
|
+
if (!partials)
|
|
30
|
+
return base;
|
|
31
|
+
const directory = posix.dirname(path);
|
|
32
|
+
const name = path.slice(path.lastIndexOf('/') + 1);
|
|
33
|
+
return [...base, ...extensions.map((extension) => posix.join(directory, `_${name}${extension}`))];
|
|
34
|
+
}
|
|
35
|
+
function definition(languageId, extensions, partials = false) {
|
|
36
|
+
return { languageId, resolver: createResolver(languageId, extensions, partials) };
|
|
37
|
+
}
|
|
38
|
+
export const cssNavigationResolutionDefinition = definition('css', ['.css']);
|
|
39
|
+
export const scssNavigationResolutionDefinition = definition('scss', ['.scss', '.css'], true);
|
|
40
|
+
export const sassNavigationResolutionDefinition = definition('sass', ['.sass', '.scss', '.css'], true);
|
|
41
|
+
export const lessNavigationResolutionDefinition = definition('less', ['.less', '.css']);
|
|
42
|
+
export const htmlNavigationResolutionDefinition = definition('html', [
|
|
43
|
+
'.html',
|
|
44
|
+
'.htm',
|
|
45
|
+
'.css',
|
|
46
|
+
'.js'
|
|
47
|
+
]);
|
|
48
|
+
export const vueNavigationResolutionDefinition = definition('vue', [
|
|
49
|
+
'.vue',
|
|
50
|
+
'.html',
|
|
51
|
+
'.css',
|
|
52
|
+
'.scss',
|
|
53
|
+
'.sass',
|
|
54
|
+
'.less'
|
|
55
|
+
]);
|
|
@@ -317,7 +317,7 @@ export class ConversationHistoryService {
|
|
|
317
317
|
async refreshExternalActivity(session) {
|
|
318
318
|
const runner = this.options.runners.require(session.runner);
|
|
319
319
|
if (!runner.available(this.options.capabilities())) {
|
|
320
|
-
this.options.setExternalActivity(session.id, '
|
|
320
|
+
this.options.setExternalActivity(session.id, 'IDLE');
|
|
321
321
|
return;
|
|
322
322
|
}
|
|
323
323
|
const activity = await runner.readExternalActivity(session);
|
|
@@ -31,6 +31,7 @@ export class ExternalSessionResumeService {
|
|
|
31
31
|
return this.options.ensureConfiguration(prior);
|
|
32
32
|
const runner = this.options.runners.product(external.runner);
|
|
33
33
|
if (runner === undefined) {
|
|
34
|
+
this.options.state.externalActivity.set(external.id, 'IDLE');
|
|
34
35
|
this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
|
|
35
36
|
return undefined;
|
|
36
37
|
}
|
|
@@ -78,6 +79,8 @@ export class ExternalSessionResumeService {
|
|
|
78
79
|
}
|
|
79
80
|
if (result.activity !== undefined)
|
|
80
81
|
this.options.state.externalActivity.set(external.id, result.activity);
|
|
82
|
+
else if (result.failureCode === 'RUNNER_UNAVAILABLE')
|
|
83
|
+
this.options.state.externalActivity.set(external.id, 'IDLE');
|
|
81
84
|
if (result.session !== undefined) {
|
|
82
85
|
this.options.state.resumedByDirectId.set(external.id, result.session.id);
|
|
83
86
|
return result.session;
|
|
@@ -102,32 +105,12 @@ export class ExternalSessionResumeService {
|
|
|
102
105
|
const resumed = await this.resume(current);
|
|
103
106
|
if (resumed !== undefined)
|
|
104
107
|
return resumed;
|
|
105
|
-
if (this.options.state.externalActivity.get(current.id) === '
|
|
108
|
+
if (this.options.state.externalActivity.get(current.id) === 'BUSY')
|
|
106
109
|
throw new Error('EXTERNAL_SESSION_ACTIVE');
|
|
107
110
|
throw new Error(this.options.state.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
|
|
108
111
|
}
|
|
109
112
|
async acceptForMessage(sessionId) {
|
|
110
|
-
|
|
111
|
-
const current = this.options.runtime.getAgentSession(resumedId ?? sessionId) ??
|
|
112
|
-
(await this.options.resolve(sessionId));
|
|
113
|
-
if (current === undefined)
|
|
114
|
-
throw new Error('SESSION_NOT_FOUND');
|
|
115
|
-
if (current.nativeControl !== 'EXTERNAL')
|
|
116
|
-
return { session: current, ready: Promise.resolve(current), deferred: false };
|
|
117
|
-
const staged = this.options.projectionFor(current) ?? this.options.stage(current);
|
|
118
|
-
const ready = this.resume(current).then((session) => {
|
|
119
|
-
if (session !== undefined)
|
|
120
|
-
return session;
|
|
121
|
-
if (this.options.state.externalActivity.get(current.id) === 'EXTERNAL_ACTIVE')
|
|
122
|
-
throw new Error('EXTERNAL_SESSION_ACTIVE');
|
|
123
|
-
throw new Error(this.options.state.messageResumeFailures.get(current.id) ?? this.failure(current.id).code);
|
|
124
|
-
});
|
|
125
|
-
return { session: staged, ready, deferred: true };
|
|
126
|
-
}
|
|
127
|
-
delivery(session) {
|
|
128
|
-
return session.nativeControl === 'EXTERNAL' && session.externalSessionId !== null
|
|
129
|
-
? 'RESUMABLE'
|
|
130
|
-
: 'REJECTED';
|
|
113
|
+
return { session: await this.resolveForMessage(sessionId) };
|
|
131
114
|
}
|
|
132
115
|
clearFailure(sessionId) {
|
|
133
116
|
const timer = this.options.state.failureTimers.get(sessionId);
|
|
@@ -68,7 +68,7 @@ export class NodeControlMessageService {
|
|
|
68
68
|
this.cancelRun(envelope);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
if (envelope.type === '
|
|
71
|
+
if (envelope.type === 'lease.reconcile') {
|
|
72
72
|
this.options.send('ack', { type: envelope.type }, envelope.id);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
@@ -62,7 +62,6 @@ export class NodeUpgradeCoordinator {
|
|
|
62
62
|
blocks(operation) {
|
|
63
63
|
return (this.pending() &&
|
|
64
64
|
(operation === 'session.start' ||
|
|
65
|
-
operation === 'session.resume' ||
|
|
66
65
|
operation === 'session.message' ||
|
|
67
66
|
operation === 'session.command.execute' ||
|
|
68
67
|
operation === 'runner.command.execute' ||
|
|
@@ -11,7 +11,6 @@ export class SessionCatalogService {
|
|
|
11
11
|
operations() {
|
|
12
12
|
return {
|
|
13
13
|
'session.list': (data) => this.list(record(data)),
|
|
14
|
-
'session.resume': (data) => this.resume(record(data)),
|
|
15
14
|
'session.remove': (data) => this.enqueueMutation(() => this.remove(record(data))),
|
|
16
15
|
'workspace.sessions.cleanup': (data) => this.enqueueMutation(() => this.cleanupWorkspaceSessions(record(data))),
|
|
17
16
|
'session.rewind': (data) => this.enqueueMutation(() => this.rewind(record(data))),
|
|
@@ -100,30 +99,6 @@ export class SessionCatalogService {
|
|
|
100
99
|
}));
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
|
-
async resume(input) {
|
|
104
|
-
if (typeof input.sessionId !== 'string')
|
|
105
|
-
throw new Error('SESSION_INVALID');
|
|
106
|
-
const value = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
107
|
-
(await this.options.projection.resolve(input.sessionId));
|
|
108
|
-
if (value === undefined)
|
|
109
|
-
throw new Error('SESSION_NOT_FOUND');
|
|
110
|
-
if (value.nativeControl !== 'EXTERNAL')
|
|
111
|
-
return {
|
|
112
|
-
session: this.options.present(value),
|
|
113
|
-
delivery: this.options.resume.delivery(value)
|
|
114
|
-
};
|
|
115
|
-
const resumed = await this.options.resume.resume(value);
|
|
116
|
-
const failure = resumed === undefined ? this.options.resume.failure(value.id) : undefined;
|
|
117
|
-
return {
|
|
118
|
-
session: this.options.present(resumed ?? value),
|
|
119
|
-
delivery: resumed === undefined ? 'REJECTED' : 'RESUMABLE',
|
|
120
|
-
...(resumed === undefined
|
|
121
|
-
? {
|
|
122
|
-
reasonCode: failure?.code ?? 'SESSION_RESUME_REJECTED'
|
|
123
|
-
}
|
|
124
|
-
: {})
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
102
|
enqueueMutation(operation) {
|
|
128
103
|
const result = this.rewindQueue.then(operation);
|
|
129
104
|
this.rewindQueue = result.then(() => undefined, () => undefined);
|
|
@@ -263,7 +238,7 @@ export class SessionCatalogService {
|
|
|
263
238
|
this.options.runtime
|
|
264
239
|
.listRunsForSession(session.id)
|
|
265
240
|
.some((run) => !isTerminalRunStatus(run.status)) ||
|
|
266
|
-
this.options.externalActivity(session.id) === '
|
|
241
|
+
this.options.externalActivity(session.id) === 'BUSY') {
|
|
267
242
|
skipped += 1;
|
|
268
243
|
continue;
|
|
269
244
|
}
|
|
@@ -317,7 +292,7 @@ export class SessionCatalogService {
|
|
|
317
292
|
throw new Error('SESSION_ACTIVE');
|
|
318
293
|
if (session.nativeControl === 'EXTERNAL') {
|
|
319
294
|
await this.options.refreshActivity(session);
|
|
320
|
-
if (this.options.externalActivity(session.id) === '
|
|
295
|
+
if (this.options.externalActivity(session.id) === 'BUSY')
|
|
321
296
|
throw new Error('SESSION_ACTIVE');
|
|
322
297
|
}
|
|
323
298
|
if (requireCleanupSafety) {
|
|
@@ -39,9 +39,9 @@ export class SessionCommandService {
|
|
|
39
39
|
(await this.options.projection.resolveCurrent(input.sessionId));
|
|
40
40
|
if (resolved === undefined)
|
|
41
41
|
throw new Error('SESSION_NOT_FOUND');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (resolved.nativeControl === 'EXTERNAL')
|
|
43
|
+
throw new Error('SESSION_ACTIVATION_REQUIRED');
|
|
44
|
+
const session = resolved;
|
|
45
45
|
const command = this.options
|
|
46
46
|
.profiles()
|
|
47
47
|
.find((profile) => profile.runner === session.runner)
|
|
@@ -88,7 +88,7 @@ export class SessionMessageService {
|
|
|
88
88
|
const publicContent = missionMessage?.activated === true ? 'Mission Start' : content;
|
|
89
89
|
const intent = missionMessage === undefined ? requestedIntent : 'REPLACE_CURRENT';
|
|
90
90
|
const operation = (async () => {
|
|
91
|
-
const acceptedSession = accepted.
|
|
91
|
+
const acceptedSession = accepted.session;
|
|
92
92
|
const session = this.options.runtime.getAgentSession(acceptedSession.id) ?? acceptedSession;
|
|
93
93
|
const runId = randomUUID();
|
|
94
94
|
const queueBeforeCreate = this.options.runtime.sessionQueue(session.id);
|
|
@@ -108,16 +108,12 @@ export class SessionMessageService {
|
|
|
108
108
|
const sessionForPresentation = input.__mission === true
|
|
109
109
|
? session
|
|
110
110
|
: this.options.runtime.setTemporaryTitleIfMissing(session.id, sessionTitleFromFirstMessage(content));
|
|
111
|
-
const deferPreparation = accepted.deferred && attachmentIds.length === 0 && inlineAttachments.length === 0;
|
|
112
111
|
this.options.emitSession(sessionForPresentation);
|
|
113
112
|
if (created.queueItem === undefined)
|
|
114
113
|
this.options.coordinator.publishCurrentTurn(created.run.id);
|
|
115
114
|
const prepare = async () => {
|
|
116
115
|
try {
|
|
117
|
-
const
|
|
118
|
-
const currentSession = this.options.runtime.getAgentSession(readySession.id) ?? readySession;
|
|
119
|
-
if (deferPreparation)
|
|
120
|
-
this.options.emitSession(currentSession);
|
|
116
|
+
const currentSession = this.options.runtime.getAgentSession(session.id) ?? session;
|
|
121
117
|
const uploaded = await this.options.uploads.resolve(session.workspaceId, attachmentIds);
|
|
122
118
|
const attachments = attachmentIds.length === 0
|
|
123
119
|
? inlineAttachments
|
|
@@ -190,15 +186,7 @@ export class SessionMessageService {
|
|
|
190
186
|
throw error;
|
|
191
187
|
}
|
|
192
188
|
};
|
|
193
|
-
|
|
194
|
-
void prepare().catch(() => undefined);
|
|
195
|
-
const delivery = deferPreparation
|
|
196
|
-
? created.queueItem === undefined
|
|
197
|
-
? 'STARTED'
|
|
198
|
-
: intent === 'REPLACE_CURRENT'
|
|
199
|
-
? 'RESTARTING'
|
|
200
|
-
: 'QUEUED'
|
|
201
|
-
: await prepare();
|
|
189
|
+
const delivery = await prepare();
|
|
202
190
|
return {
|
|
203
191
|
session: this.options.present(sessionForPresentation),
|
|
204
192
|
run: created.run,
|
|
@@ -14,7 +14,6 @@ export class SessionPresentationService {
|
|
|
14
14
|
.require(session.runner)
|
|
15
15
|
.presentSession(session, this.options.capabilities(), {
|
|
16
16
|
managedActive: this.options.hasManagedActiveRun(session),
|
|
17
|
-
externalActivity: this.options.externalActivity(session.id),
|
|
18
17
|
commandStates: this.options.commands.list(session.id)
|
|
19
18
|
});
|
|
20
19
|
return {
|