@myagentroam/node 0.1.8 → 0.9.2
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/capabilities.js +6 -3
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +8 -1
- package/dist/codex-app-server.d.ts +6 -0
- package/dist/codex-app-server.js +6 -0
- package/dist/connector/node-connector-options.d.ts +1 -0
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +43 -14
- package/dist/database.d.ts +23 -0
- package/dist/database.js +84 -2
- package/dist/migrations/v004.d.ts +4 -0
- package/dist/migrations/v004.js +35 -0
- package/dist/native-session-history.js +72 -14
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +12 -0
- package/dist/runner/abstract-runner.d.ts +13 -3
- package/dist/runner/abstract-runner.js +78 -4
- package/dist/runner/claude/managed-run-controller.js +18 -7
- package/dist/runner/claude-code-runner.d.ts +1 -0
- package/dist/runner/claude-code-runner.js +17 -4
- package/dist/runner/codex/conversation-parser.d.ts +2 -2
- package/dist/runner/codex/conversation-parser.js +30 -12
- package/dist/runner/codex/managed-run-controller.js +11 -8
- package/dist/runner/codex-runner.d.ts +17 -3
- package/dist/runner/codex-runner.js +225 -12
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +15 -15
- package/dist/runner/opencode/managed-run-controller.d.ts +2 -1
- package/dist/runner/opencode/managed-run-controller.js +36 -12
- package/dist/runner/opencode-runner.d.ts +2 -1
- package/dist/runner/opencode-runner.js +23 -3
- package/dist/runner/runner-registry.d.ts +1 -1
- package/dist/runner/runner-registry.js +2 -2
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -41
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-command-detector.d.ts +3 -0
- package/dist/runtime-command-detector.js +78 -0
- package/dist/service/conversation-history-service.js +55 -24
- package/dist/service/conversation-segment-service.d.ts +20 -0
- package/dist/service/conversation-segment-service.js +155 -0
- package/dist/service/mcp-installation-verifier.d.ts +9 -0
- package/dist/service/mcp-installation-verifier.js +87 -0
- package/dist/service/mcp-node-operation-service.d.ts +11 -0
- package/dist/service/mcp-node-operation-service.js +90 -0
- package/dist/service/mcp-package-installer.d.ts +8 -0
- package/dist/service/mcp-package-installer.js +136 -0
- package/dist/service/native-session-watch-service.d.ts +1 -0
- package/dist/service/native-session-watch-service.js +21 -4
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +21 -11
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +8 -2
- package/dist/service/runner-service.d.ts +2 -0
- package/dist/service/runner-service.js +5 -2
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-lifecycle-service.js +7 -4
- package/dist/service/session-presentation-service.d.ts +1 -0
- package/dist/service/session-presentation-service.js +8 -1
- package/dist/service/session-query-service.d.ts +4 -0
- package/dist/service/session-query-service.js +6 -1
- package/dist/service/skill-directory-service.js +3 -26
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.js +2 -1
- package/dist/service/workbench-manifest-service.d.ts +2 -0
- package/dist/service/workspace-git-exclude-service.d.ts +4 -0
- package/dist/service/workspace-git-exclude-service.js +119 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
- package/dist/service/workspace-queue-workbench-service.js +5 -3
- package/dist/service/workspace-service.js +2 -0
- package/dist/supervisor.js +2 -1
- package/dist/terminal.js +1 -1
- package/dist/util/node-operation-parsers.js +2 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +5 -14
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -30,7 +30,14 @@ export class SessionPresentationService {
|
|
|
30
30
|
const synchronized = session.runnerTitle === null
|
|
31
31
|
? projected
|
|
32
32
|
: this.options.runtime.setRunnerTitleFromNative(projected.id, session.runnerTitle);
|
|
33
|
-
return this.present({
|
|
33
|
+
return this.present({
|
|
34
|
+
...synchronized,
|
|
35
|
+
lastActivityAt: latestSessionActivityAt(synchronized.lastActivityAt, session.lastActivityAt)
|
|
36
|
+
});
|
|
34
37
|
});
|
|
35
38
|
}
|
|
36
39
|
}
|
|
40
|
+
export function latestSessionActivityAt(current, discovered) {
|
|
41
|
+
const known = [current, discovered].filter((value) => Number.isFinite(value) && value > 0);
|
|
42
|
+
return known.length === 0 ? 0 : Math.max(...known);
|
|
43
|
+
}
|
|
@@ -13,6 +13,10 @@ export interface SessionQueryServiceOptions {
|
|
|
13
13
|
readonly cursor?: string;
|
|
14
14
|
readonly limit?: number;
|
|
15
15
|
}) => Promise<unknown>;
|
|
16
|
+
readonly segments: (session: NodeAgentSession, input: {
|
|
17
|
+
readonly cursor?: string;
|
|
18
|
+
readonly limit?: number;
|
|
19
|
+
}) => Promise<unknown>;
|
|
16
20
|
readonly context: SessionContextService;
|
|
17
21
|
readonly discover: (runner: RunnerName, workspaceId?: string) => Promise<readonly NodeAgentSession[]>;
|
|
18
22
|
readonly workspaceIdForCwd: (cwd: string) => string | undefined;
|
|
@@ -23,7 +23,12 @@ export class SessionQueryService {
|
|
|
23
23
|
}
|
|
24
24
|
async turns(input) {
|
|
25
25
|
const page = pageInput(input);
|
|
26
|
-
|
|
26
|
+
const session = await this.requireSession(input.sessionId);
|
|
27
|
+
if (input.unit === 'SEGMENT')
|
|
28
|
+
return this.options.segments(session, page);
|
|
29
|
+
if (input.unit !== undefined && input.unit !== 'TURN')
|
|
30
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
31
|
+
return this.options.history(session, page);
|
|
27
32
|
}
|
|
28
33
|
events(input) {
|
|
29
34
|
const page = pageInput(input);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { lstat, open,
|
|
2
|
-
import { execFile } from 'node:child_process';
|
|
3
|
-
import { promisify } from 'node:util';
|
|
1
|
+
import { lstat, open, readdir, readlink, realpath } from 'node:fs/promises';
|
|
4
2
|
import path from 'node:path';
|
|
5
3
|
import { skillInstallMetadataSchema } from '@myagentroam/protocol';
|
|
4
|
+
import { hasBrokenWorkspaceSkillGitExclude } from './workspace-git-exclude-service.js';
|
|
6
5
|
const MAX_METADATA_BYTES = 64 * 1024;
|
|
7
6
|
const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
8
7
|
const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u;
|
|
@@ -21,7 +20,7 @@ export class SkillDirectoryService {
|
|
|
21
20
|
const agents = await scanRoot(path.join(workspace, '.agents', 'skills'), 'AGENTS');
|
|
22
21
|
const claude = await scanRoot(path.join(workspace, '.claude', 'skills'), 'CLAUDE');
|
|
23
22
|
const skills = mergeWorkspaceCopies(agents, claude);
|
|
24
|
-
const gitExcludeBroken = await
|
|
23
|
+
const gitExcludeBroken = await hasBrokenWorkspaceSkillGitExclude(workspace, skills.map((skill) => skill.name));
|
|
25
24
|
return {
|
|
26
25
|
targetKind: 'WORKSPACE',
|
|
27
26
|
compatibilityMode: 'MANAGED_COPY',
|
|
@@ -31,28 +30,6 @@ export class SkillDirectoryService {
|
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
async function hasBrokenGitExclude(workspace, names) {
|
|
35
|
-
if (names.length === 0)
|
|
36
|
-
return false;
|
|
37
|
-
try {
|
|
38
|
-
await lstat(path.join(workspace, '.git'));
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
if (isMissing(error))
|
|
42
|
-
return false;
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const result = await promisify(execFile)('git', ['-C', workspace, 'rev-parse', '--git-path', 'info/exclude'], { timeout: 5_000, windowsHide: true });
|
|
47
|
-
const exclude = path.resolve(workspace, result.stdout.trim());
|
|
48
|
-
const content = await readFile(exclude, 'utf8');
|
|
49
|
-
return names.some((name) => !content.includes(`/.agents/skills/${name}/`) ||
|
|
50
|
-
!content.includes(`/.claude/skills/${name}/`));
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
33
|
async function scanRoot(root, source) {
|
|
57
34
|
let entries;
|
|
58
35
|
try {
|
|
@@ -4,6 +4,7 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
import { lstat, mkdir, readFile, readdir, readlink, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { skillInstallMetadataSchema } from '@myagentroam/protocol';
|
|
7
|
+
import { rewriteWorkspaceGitExclude, updateWorkspaceSkillGitExclude } from './workspace-git-exclude-service.js';
|
|
7
8
|
export class SkillInstallService {
|
|
8
9
|
async recoverNodeHome(home) {
|
|
9
10
|
const agentsRoot = path.join(home, '.agents', 'skills');
|
|
@@ -58,7 +59,7 @@ export class SkillInstallService {
|
|
|
58
59
|
await mkdir(claudeRoot, { recursive: true });
|
|
59
60
|
await assertContainedDirectory(workspace, agentsRoot);
|
|
60
61
|
await assertContainedDirectory(workspace, claudeRoot);
|
|
61
|
-
await replacePair(agentsRoot, claudeRoot, name, bundle, metadata, () =>
|
|
62
|
+
await replacePair(agentsRoot, claudeRoot, name, bundle, metadata, () => updateWorkspaceSkillGitExclude(workspace, name, true), () => updateWorkspaceSkillGitExclude(workspace, name, false));
|
|
62
63
|
}
|
|
63
64
|
async removeWorkspace(workspace, name) {
|
|
64
65
|
assertName(name);
|
|
@@ -66,7 +67,7 @@ export class SkillInstallService {
|
|
|
66
67
|
const claudeRoot = path.join(workspace, '.claude', 'skills');
|
|
67
68
|
await assertExistingContainedDirectory(workspace, agentsRoot);
|
|
68
69
|
await assertExistingContainedDirectory(workspace, claudeRoot);
|
|
69
|
-
await removePair(agentsRoot, claudeRoot, name, () =>
|
|
70
|
+
await removePair(agentsRoot, claudeRoot, name, () => updateWorkspaceSkillGitExclude(workspace, name, false), () => updateWorkspaceSkillGitExclude(workspace, name, true));
|
|
70
71
|
}
|
|
71
72
|
async removeNodeHome(home, name) {
|
|
72
73
|
assertName(name);
|
|
@@ -100,7 +101,7 @@ export class SkillInstallService {
|
|
|
100
101
|
if (!isMissing(error))
|
|
101
102
|
throw error;
|
|
102
103
|
}
|
|
103
|
-
await
|
|
104
|
+
await rewriteWorkspaceGitExclude(workspace, names);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
export async function workspaceSkillTracked(workspace, name) {
|
|
@@ -405,74 +406,6 @@ async function ensureClaudeRoot(agentsRoot, claudeRoot) {
|
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
}
|
|
408
|
-
async function updateGitExclude(workspace, name, add) {
|
|
409
|
-
await mutateGitExclude(workspace, (entries) => {
|
|
410
|
-
for (const root of ['.agents', '.claude']) {
|
|
411
|
-
const line = `/${root}/skills/${name}/`;
|
|
412
|
-
if (add)
|
|
413
|
-
entries.add(line);
|
|
414
|
-
else
|
|
415
|
-
entries.delete(line);
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
async function rewriteGitExclude(workspace, names) {
|
|
420
|
-
await mutateGitExclude(workspace, (entries) => {
|
|
421
|
-
entries.clear();
|
|
422
|
-
for (const name of names)
|
|
423
|
-
for (const root of ['.agents', '.claude'])
|
|
424
|
-
entries.add(`/${root}/skills/${name}/`);
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
async function mutateGitExclude(workspace, mutate) {
|
|
428
|
-
const git = path.join(workspace, '.git');
|
|
429
|
-
if (!(await exists(git)))
|
|
430
|
-
return;
|
|
431
|
-
let exclude;
|
|
432
|
-
try {
|
|
433
|
-
const result = await promisify(execFile)('git', ['-C', workspace, 'rev-parse', '--git-path', 'info/exclude', '--git-common-dir'], { timeout: 5_000, windowsHide: true });
|
|
434
|
-
const [excludeOutput, commonOutput] = result.stdout.trim().split(/\r?\n/u);
|
|
435
|
-
if (!excludeOutput || !commonOutput)
|
|
436
|
-
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
437
|
-
exclude = path.resolve(workspace, excludeOutput);
|
|
438
|
-
const common = path.resolve(workspace, commonOutput);
|
|
439
|
-
const relative = path.relative(common, exclude);
|
|
440
|
-
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
441
|
-
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
445
|
-
}
|
|
446
|
-
await mkdir(path.dirname(exclude), { recursive: true });
|
|
447
|
-
const begin = '# BEGIN MyAgentRoam Skills';
|
|
448
|
-
const end = '# END MyAgentRoam Skills';
|
|
449
|
-
let content = '';
|
|
450
|
-
try {
|
|
451
|
-
const excludeStat = await lstat(exclude);
|
|
452
|
-
if (!excludeStat.isFile() || excludeStat.isSymbolicLink())
|
|
453
|
-
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
454
|
-
content = await readFile(exclude, 'utf8');
|
|
455
|
-
}
|
|
456
|
-
catch (error) {
|
|
457
|
-
if (!isMissing(error))
|
|
458
|
-
throw error;
|
|
459
|
-
}
|
|
460
|
-
const pattern = new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}\\n?`, 'u');
|
|
461
|
-
const existing = pattern.exec(content)?.[0] ?? '';
|
|
462
|
-
const entries = new Set(existing.split(/\r?\n/u).filter((line) => line.startsWith('/.')));
|
|
463
|
-
mutate(entries);
|
|
464
|
-
const block = entries.size === 0 ? '' : `${begin}\n${[...entries].sort().join('\n')}\n${end}\n`;
|
|
465
|
-
const next = content.replace(pattern, '').replace(/\s*$/u, '\n') + block;
|
|
466
|
-
const staging = path.join(path.dirname(exclude), `.mar-git-exclude-${randomUUID()}.tmp`);
|
|
467
|
-
try {
|
|
468
|
-
await writeFile(staging, next, { encoding: 'utf8', flag: 'wx' });
|
|
469
|
-
await rename(staging, exclude);
|
|
470
|
-
}
|
|
471
|
-
catch (error) {
|
|
472
|
-
await rm(staging, { force: true });
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
409
|
function assertName(name) {
|
|
477
410
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) ||
|
|
478
411
|
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u.test(name))
|
|
@@ -499,6 +432,3 @@ async function exists(value) {
|
|
|
499
432
|
function isMissing(error) {
|
|
500
433
|
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
501
434
|
}
|
|
502
|
-
function escapeRegExp(value) {
|
|
503
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
504
|
-
}
|
|
@@ -3,6 +3,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { skillInstallMetadataSchema } from '@myagentroam/protocol';
|
|
4
4
|
import { SkillDirectoryService } from './skill-directory-service.js';
|
|
5
5
|
import { SkillInstallService, workspaceSkillTracked } from './skill-install-service.js';
|
|
6
|
+
import { safeErrorCode } from '../util/safe-error.js';
|
|
6
7
|
export class SkillNodeOperationService {
|
|
7
8
|
database;
|
|
8
9
|
config;
|
|
@@ -76,7 +77,7 @@ export class SkillNodeOperationService {
|
|
|
76
77
|
catch (error) {
|
|
77
78
|
terminal = {
|
|
78
79
|
status: 'FAILED',
|
|
79
|
-
errorCode: error
|
|
80
|
+
errorCode: safeErrorCode(error, 'SKILL_INSTALL_FAILED')
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
83
|
this.activeTargets.delete(targetKey);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function ensureWorkspaceGitExclude(workspace: string): Promise<void>;
|
|
2
|
+
export declare function updateWorkspaceSkillGitExclude(workspace: string, name: string, add: boolean): Promise<void>;
|
|
3
|
+
export declare function rewriteWorkspaceGitExclude(workspace: string, names: ReadonlySet<string>): Promise<void>;
|
|
4
|
+
export declare function hasBrokenWorkspaceSkillGitExclude(workspace: string, names: readonly string[]): Promise<boolean>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
const MAR_TEMP_DIRECTORY = '/.mar/';
|
|
7
|
+
const BLOCK_BEGIN = '# BEGIN MyAgentRoam';
|
|
8
|
+
const BLOCK_END = '# END MyAgentRoam';
|
|
9
|
+
const LEGACY_BLOCK_BEGIN = '# BEGIN MyAgentRoam Skills';
|
|
10
|
+
const LEGACY_BLOCK_END = '# END MyAgentRoam Skills';
|
|
11
|
+
export async function ensureWorkspaceGitExclude(workspace) {
|
|
12
|
+
await mutateGitExclude(workspace, (entries) => entries.add(MAR_TEMP_DIRECTORY));
|
|
13
|
+
}
|
|
14
|
+
export async function updateWorkspaceSkillGitExclude(workspace, name, add) {
|
|
15
|
+
await mutateGitExclude(workspace, (entries) => {
|
|
16
|
+
entries.add(MAR_TEMP_DIRECTORY);
|
|
17
|
+
for (const root of ['.agents', '.claude']) {
|
|
18
|
+
const line = `/${root}/skills/${name}/`;
|
|
19
|
+
if (add)
|
|
20
|
+
entries.add(line);
|
|
21
|
+
else
|
|
22
|
+
entries.delete(line);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function rewriteWorkspaceGitExclude(workspace, names) {
|
|
27
|
+
await mutateGitExclude(workspace, (entries) => {
|
|
28
|
+
entries.clear();
|
|
29
|
+
entries.add(MAR_TEMP_DIRECTORY);
|
|
30
|
+
for (const name of names)
|
|
31
|
+
for (const root of ['.agents', '.claude'])
|
|
32
|
+
entries.add(`/${root}/skills/${name}/`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function hasBrokenWorkspaceSkillGitExclude(workspace, names) {
|
|
36
|
+
if (!(await exists(path.join(workspace, '.git'))))
|
|
37
|
+
return false;
|
|
38
|
+
try {
|
|
39
|
+
const exclude = await resolveGitExclude(workspace);
|
|
40
|
+
const content = await readFile(exclude, 'utf8');
|
|
41
|
+
return names
|
|
42
|
+
.flatMap((name) => [`/.agents/skills/${name}/`, `/.claude/skills/${name}/`])
|
|
43
|
+
.some((entry) => !content.includes(entry));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function mutateGitExclude(workspace, mutate) {
|
|
50
|
+
if (!(await exists(path.join(workspace, '.git'))))
|
|
51
|
+
return;
|
|
52
|
+
const exclude = await resolveGitExclude(workspace);
|
|
53
|
+
await mkdir(path.dirname(exclude), { recursive: true });
|
|
54
|
+
let content = '';
|
|
55
|
+
try {
|
|
56
|
+
const excludeStat = await lstat(exclude);
|
|
57
|
+
if (!excludeStat.isFile() || excludeStat.isSymbolicLink())
|
|
58
|
+
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
59
|
+
content = await readFile(exclude, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!isMissing(error))
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const pattern = managedBlockPattern();
|
|
66
|
+
const existing = [...content.matchAll(pattern)].map((match) => match[0]).join('\n');
|
|
67
|
+
const entries = new Set(existing.split(/\r?\n/u).filter((line) => line.startsWith('/.')));
|
|
68
|
+
mutate(entries);
|
|
69
|
+
const block = `${BLOCK_BEGIN}\n${[...entries].sort().join('\n')}\n${BLOCK_END}\n`;
|
|
70
|
+
const next = content.replace(pattern, '').replace(/\s*$/u, '\n') + block;
|
|
71
|
+
const staging = path.join(path.dirname(exclude), `.mar-git-exclude-${randomUUID()}.tmp`);
|
|
72
|
+
try {
|
|
73
|
+
await writeFile(staging, next, { encoding: 'utf8', flag: 'wx' });
|
|
74
|
+
await rename(staging, exclude);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
await rm(staging, { force: true });
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function resolveGitExclude(workspace) {
|
|
82
|
+
try {
|
|
83
|
+
const result = await promisify(execFile)('git', ['-C', workspace, 'rev-parse', '--git-path', 'info/exclude', '--git-common-dir'], { timeout: 5_000, windowsHide: true });
|
|
84
|
+
const [excludeOutput, commonOutput] = result.stdout.trim().split(/\r?\n/u);
|
|
85
|
+
if (!excludeOutput || !commonOutput)
|
|
86
|
+
throw new Error();
|
|
87
|
+
const exclude = path.resolve(workspace, excludeOutput);
|
|
88
|
+
const common = path.resolve(workspace, commonOutput);
|
|
89
|
+
const relative = path.relative(common, exclude);
|
|
90
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
91
|
+
throw new Error();
|
|
92
|
+
return exclude;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error('WORKSPACE_SKILL_GIT_EXCLUDE_FAILED');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function exists(value) {
|
|
99
|
+
try {
|
|
100
|
+
await lstat(value);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (isMissing(error))
|
|
105
|
+
return false;
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function isMissing(error) {
|
|
110
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
111
|
+
}
|
|
112
|
+
function escapeRegExp(value) {
|
|
113
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
114
|
+
}
|
|
115
|
+
function managedBlockPattern() {
|
|
116
|
+
const current = `${escapeRegExp(BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp(BLOCK_END)}(?:\\r?\\n)?`;
|
|
117
|
+
const legacy = `${escapeRegExp(LEGACY_BLOCK_BEGIN)}\\r?\\n[\\s\\S]*?${escapeRegExp(LEGACY_BLOCK_END)}(?:\\r?\\n)?`;
|
|
118
|
+
return new RegExp(`(?:${current}|${legacy})`, 'gu');
|
|
119
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
2
2
|
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
3
3
|
import type { RunnerName } from '@myagentroam/protocol';
|
|
4
|
+
import type { NodeMcpInstallation } from '../database.js';
|
|
4
5
|
export interface QueuedRunStart<TAttachment = unknown> {
|
|
5
6
|
readonly runId: string;
|
|
6
7
|
readonly sessionId: string;
|
|
@@ -28,6 +29,7 @@ export interface WorkspaceQueueWorkbenchServiceOptions {
|
|
|
28
29
|
readonly stopped: () => boolean;
|
|
29
30
|
readonly markInterrupted: (runId: string) => void;
|
|
30
31
|
readonly prepareInput: (runner: RunnerName, input: string, collaborationMode: 'default' | 'plan') => string;
|
|
32
|
+
readonly mcps: (workspaceId: string) => readonly NodeMcpInstallation[];
|
|
31
33
|
}
|
|
32
34
|
export declare class WorkspaceQueueWorkbenchService<TAttachment> {
|
|
33
35
|
private readonly options;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainRecord } from '../runner/codex/conversation-parser.js';
|
|
2
2
|
import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
|
|
3
3
|
import { withNonImageAttachmentPrompt } from '../runner/codex/conversation-parser.js';
|
|
4
|
+
import { safeErrorCode } from '../util/safe-error.js';
|
|
4
5
|
export class WorkspaceQueueWorkbenchService {
|
|
5
6
|
options;
|
|
6
7
|
starts = new Map();
|
|
@@ -178,11 +179,12 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
178
179
|
...(pending.access === null ? {} : { access: pending.access }),
|
|
179
180
|
collaborationMode: pending.collaborationMode,
|
|
180
181
|
...(pending.serviceTier === undefined ? {} : { serviceTier: pending.serviceTier }),
|
|
181
|
-
secretEnvironment: pending.secretEnvironment
|
|
182
|
+
secretEnvironment: pending.secretEnvironment,
|
|
183
|
+
mcpInstallations: this.options.mcps(pending.workspaceId)
|
|
182
184
|
});
|
|
183
185
|
}
|
|
184
186
|
catch (error) {
|
|
185
|
-
this.options.emitRun(run.id, 'run.rejected', { code: error
|
|
187
|
+
this.options.emitRun(run.id, 'run.rejected', { code: safeErrorCode(error, 'QUEUE_START_FAILED') }, 'FAILED');
|
|
186
188
|
}
|
|
187
189
|
}
|
|
188
190
|
present(workspaceId, sessionId) {
|
|
@@ -202,7 +204,7 @@ export class WorkspaceQueueWorkbenchService {
|
|
|
202
204
|
{
|
|
203
205
|
run,
|
|
204
206
|
sessionId: run.sessionId,
|
|
205
|
-
sessionTitle: queuedSession?.customTitle ?? queuedSession?.runnerTitle ?? '
|
|
207
|
+
sessionTitle: queuedSession?.customTitle ?? queuedSession?.runnerTitle ?? 'Untitled session',
|
|
206
208
|
content: this.options.runtime.queuedMessageText(run.id)
|
|
207
209
|
}
|
|
208
210
|
];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rmdir } from 'node:fs/promises';
|
|
2
2
|
import { createWorkspaceDirectory, inspectWorkspace, listWorkspaceDirectories } from '../workspace.js';
|
|
3
|
+
import { ensureWorkspaceGitExclude } from './workspace-git-exclude-service.js';
|
|
3
4
|
export class WorkspaceService {
|
|
4
5
|
database;
|
|
5
6
|
config;
|
|
@@ -31,6 +32,7 @@ export class WorkspaceService {
|
|
|
31
32
|
? (createdPath = await createWorkspaceDirectory(input.path, input.createDirectoryName, this.config().allowedRoots))
|
|
32
33
|
: input.path;
|
|
33
34
|
const inspected = await inspectWorkspace(path, this.config().allowedRoots);
|
|
35
|
+
await ensureWorkspaceGitExclude(inspected.path);
|
|
34
36
|
return {
|
|
35
37
|
workspace: this.database().saveWorkspace({
|
|
36
38
|
path: inspected.path,
|
package/dist/supervisor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { loadNodeConfig, nodeDatabasePath } from './config.js';
|
|
5
5
|
import { nodeLog, platformCliInvocation } from './operational.js';
|
|
6
6
|
import { nodeUpgradeRequestPath } from './service/node-upgrade-coordinator.js';
|
|
7
|
+
import { safeErrorCode } from './util/safe-error.js';
|
|
7
8
|
const UPGRADE_EXIT_CODE = 75;
|
|
8
9
|
const STABLE_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
9
10
|
export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
@@ -73,7 +74,7 @@ export async function superviseNode(configPath, bootstrapEntrypoint) {
|
|
|
73
74
|
catch (error) {
|
|
74
75
|
await writeResult(dataDirectory, {
|
|
75
76
|
state: 'FAILED',
|
|
76
|
-
errorCode: error
|
|
77
|
+
errorCode: safeErrorCode(error, 'NODE_UPGRADE_INSTALL_FAILED'),
|
|
77
78
|
updatedAt: Date.now()
|
|
78
79
|
});
|
|
79
80
|
await rm(requestPath, { force: true });
|
package/dist/terminal.js
CHANGED
|
@@ -2,7 +2,7 @@ import { nodeCapabilitiesSchema } from '@myagentroam/protocol';
|
|
|
2
2
|
const MAX_COMPOSER_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
3
3
|
const MAX_COMPOSER_ATTACHMENTS_BYTES = 10 * 1024 * 1024;
|
|
4
4
|
const USER_AUTHORIZATION_CLOCK_SKEW_MS = 5_000;
|
|
5
|
-
const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\n
|
|
5
|
+
const NON_IMAGE_ATTACHMENT_PROMPT_PREFIX = '\n\nNon-image attachments for this turn were uploaded to the Workspace. Read these files as needed:\n';
|
|
6
6
|
const COMPOSER_IMAGE_MIME_TYPES = new Set([
|
|
7
7
|
'image/png',
|
|
8
8
|
'image/jpeg',
|
|
@@ -100,7 +100,7 @@ export function compactRunnerValue(value, limit) {
|
|
|
100
100
|
text = JSON.stringify(value);
|
|
101
101
|
}
|
|
102
102
|
catch {
|
|
103
|
-
return { text: '
|
|
103
|
+
return { text: '(Unable to display)', truncated: false };
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
return { text: compactRunnerText(text, limit), truncated: text.length > limit };
|
|
@@ -25,7 +25,6 @@ export declare function extractCodexThreads(result: unknown): readonly {
|
|
|
25
25
|
readonly title: string | undefined;
|
|
26
26
|
}[];
|
|
27
27
|
export declare function codexThreadActivity(result: unknown): SessionActivityState;
|
|
28
|
-
export declare function externalResumeFailureDetail(error: unknown): string;
|
|
29
28
|
export declare function codexActiveTurnId(result: unknown): string | undefined;
|
|
30
29
|
export declare function codexThreadCwd(value: unknown): string;
|
|
31
30
|
export declare function isWithinWorkspace(cwd: string, workspacePath: string): boolean;
|
|
@@ -46,8 +45,8 @@ export declare function nativeConversationPage(session: NodeAgentSession, histor
|
|
|
46
45
|
readonly nextCursor: string | null;
|
|
47
46
|
};
|
|
48
47
|
/**
|
|
49
|
-
* Claude JSONL
|
|
50
|
-
*
|
|
48
|
+
* Claude JSONL does not retain browser request IDs. While the Node process is alive, match current
|
|
49
|
+
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
51
50
|
*/
|
|
52
51
|
export declare function nativeUserClientMessageId(entry: Extract<NativeSessionHistory['items'][number], {
|
|
53
52
|
readonly kind: 'message';
|
|
@@ -69,15 +69,6 @@ export function codexThreadActivity(result) {
|
|
|
69
69
|
return 'IDLE';
|
|
70
70
|
return 'UNAVAILABLE';
|
|
71
71
|
}
|
|
72
|
-
export function externalResumeFailureDetail(error) {
|
|
73
|
-
const message = error instanceof Error ? error.message : 'SESSION_RESUME_REJECTED';
|
|
74
|
-
return [...message]
|
|
75
|
-
.map((character) => (character <= '\u001f' || character === '\u007f' ? ' ' : character))
|
|
76
|
-
.join('')
|
|
77
|
-
.replace(/\s+/g, ' ')
|
|
78
|
-
.trim()
|
|
79
|
-
.slice(0, 240);
|
|
80
|
-
}
|
|
81
72
|
export function codexActiveTurnId(result) {
|
|
82
73
|
if (!isPlainRecord(result) ||
|
|
83
74
|
!isPlainRecord(result.thread) ||
|
|
@@ -228,8 +219,8 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
228
219
|
const clientMessageId = entry.kind === 'message' && entry.role === 'USER'
|
|
229
220
|
? nativeUserClientMessageId(entry, time, runtimeTurns)
|
|
230
221
|
: null;
|
|
231
|
-
// Claude
|
|
232
|
-
//
|
|
222
|
+
// Claude tagged Plan Mode strips the injected planning instruction label from user content
|
|
223
|
+
// and normalizes a complete <proposed_plan> Agent response into a plan card.
|
|
233
224
|
const entryText = entry.kind === 'message'
|
|
234
225
|
? entry.role === 'USER'
|
|
235
226
|
? stripClaudePlanTag(entry.text)
|
|
@@ -304,8 +295,8 @@ function nativeTranscriptConversationItem(session, entry, index, turnId, runtime
|
|
|
304
295
|
return item;
|
|
305
296
|
}
|
|
306
297
|
/**
|
|
307
|
-
* Claude JSONL
|
|
308
|
-
*
|
|
298
|
+
* Claude JSONL does not retain browser request IDs. While the Node process is alive, match current
|
|
299
|
+
* runtime user messages by content and creation time so native history replaces the optimistic Turn.
|
|
309
300
|
*/
|
|
310
301
|
export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
311
302
|
for (const turn of runtimeTurns) {
|
|
@@ -314,7 +305,7 @@ export function nativeUserClientMessageId(entry, createdAt, runtimeTurns) {
|
|
|
314
305
|
continue;
|
|
315
306
|
const payload = user.payload;
|
|
316
307
|
const clientMessageId = typeof payload.clientMessageId === 'string' ? payload.clientMessageId : null;
|
|
317
|
-
//
|
|
308
|
+
// Tagged Plan Mode instruction labels exist only in native transcripts; runtime bubbles retain the original content.
|
|
318
309
|
if (clientMessageId === null || payload.text !== stripClaudePlanTag(entry.text))
|
|
319
310
|
continue;
|
|
320
311
|
const runtimeCreatedAt = user.startedAt ?? turn.startedAt;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const STABLE_ERROR_CODE = /^[A-Z][A-Z0-9_]{0,127}$/u;
|
|
2
|
+
/** Returns only a stable public error code, never a raw runtime exception message. */
|
|
3
|
+
export function safeErrorCode(error, fallback) {
|
|
4
|
+
return error instanceof Error && STABLE_ERROR_CODE.test(error.message) ? error.message : fallback;
|
|
5
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-pty": "1.1.0",
|
|
25
25
|
"ws": "^8.21.3",
|
|
26
26
|
"zod": "4.4.3",
|
|
27
|
-
"@myagentroam/protocol": "^0.
|
|
27
|
+
"@myagentroam/protocol": "^0.9.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/ws": "^8.18.1"
|