@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.
Files changed (79) hide show
  1. package/dist/capabilities.js +6 -3
  2. package/dist/claude-agent-sdk.d.ts +1 -0
  3. package/dist/claude-agent-sdk.js +8 -1
  4. package/dist/codex-app-server.d.ts +6 -0
  5. package/dist/codex-app-server.js +6 -0
  6. package/dist/connector/node-connector-options.d.ts +1 -0
  7. package/dist/connector.d.ts +2 -0
  8. package/dist/connector.js +43 -14
  9. package/dist/database.d.ts +23 -0
  10. package/dist/database.js +84 -2
  11. package/dist/migrations/v004.d.ts +4 -0
  12. package/dist/migrations/v004.js +35 -0
  13. package/dist/native-session-history.js +72 -14
  14. package/dist/opencode-server.d.ts +1 -0
  15. package/dist/opencode-server.js +12 -0
  16. package/dist/runner/abstract-runner.d.ts +13 -3
  17. package/dist/runner/abstract-runner.js +78 -4
  18. package/dist/runner/claude/managed-run-controller.js +18 -7
  19. package/dist/runner/claude-code-runner.d.ts +1 -0
  20. package/dist/runner/claude-code-runner.js +17 -4
  21. package/dist/runner/codex/conversation-parser.d.ts +2 -2
  22. package/dist/runner/codex/conversation-parser.js +30 -12
  23. package/dist/runner/codex/managed-run-controller.js +11 -8
  24. package/dist/runner/codex-runner.d.ts +17 -3
  25. package/dist/runner/codex-runner.js +225 -12
  26. package/dist/runner/opencode/conversation-parser.d.ts +11 -7
  27. package/dist/runner/opencode/conversation-parser.js +15 -15
  28. package/dist/runner/opencode/managed-run-controller.d.ts +2 -1
  29. package/dist/runner/opencode/managed-run-controller.js +36 -12
  30. package/dist/runner/opencode-runner.d.ts +2 -1
  31. package/dist/runner/opencode-runner.js +23 -3
  32. package/dist/runner/runner-registry.d.ts +1 -1
  33. package/dist/runner/runner-registry.js +2 -2
  34. package/dist/runner-command-engine.js +1 -1
  35. package/dist/runner-profiles.js +29 -41
  36. package/dist/runner-usage.js +5 -5
  37. package/dist/runtime-command-detector.d.ts +3 -0
  38. package/dist/runtime-command-detector.js +78 -0
  39. package/dist/service/conversation-history-service.js +55 -24
  40. package/dist/service/conversation-segment-service.d.ts +20 -0
  41. package/dist/service/conversation-segment-service.js +155 -0
  42. package/dist/service/mcp-installation-verifier.d.ts +9 -0
  43. package/dist/service/mcp-installation-verifier.js +87 -0
  44. package/dist/service/mcp-node-operation-service.d.ts +11 -0
  45. package/dist/service/mcp-node-operation-service.js +90 -0
  46. package/dist/service/mcp-package-installer.d.ts +8 -0
  47. package/dist/service/mcp-package-installer.js +136 -0
  48. package/dist/service/native-session-watch-service.d.ts +1 -0
  49. package/dist/service/native-session-watch-service.js +21 -4
  50. package/dist/service/node-request-service.js +2 -1
  51. package/dist/service/run-attachment-service.d.ts +3 -2
  52. package/dist/service/run-attachment-service.js +21 -11
  53. package/dist/service/run-event-service.d.ts +1 -0
  54. package/dist/service/run-event-service.js +8 -2
  55. package/dist/service/runner-service.d.ts +2 -0
  56. package/dist/service/runner-service.js +5 -2
  57. package/dist/service/session-catalog-service.js +4 -4
  58. package/dist/service/session-lifecycle-service.js +7 -4
  59. package/dist/service/session-presentation-service.d.ts +1 -0
  60. package/dist/service/session-presentation-service.js +8 -1
  61. package/dist/service/session-query-service.d.ts +4 -0
  62. package/dist/service/session-query-service.js +6 -1
  63. package/dist/service/skill-directory-service.js +3 -26
  64. package/dist/service/skill-install-service.js +4 -74
  65. package/dist/service/skill-node-operation-service.js +2 -1
  66. package/dist/service/workbench-manifest-service.d.ts +2 -0
  67. package/dist/service/workspace-git-exclude-service.d.ts +4 -0
  68. package/dist/service/workspace-git-exclude-service.js +119 -0
  69. package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
  70. package/dist/service/workspace-queue-workbench-service.js +5 -3
  71. package/dist/service/workspace-service.js +2 -0
  72. package/dist/supervisor.js +2 -1
  73. package/dist/terminal.js +1 -1
  74. package/dist/util/node-operation-parsers.js +2 -2
  75. package/dist/util/runner-native-session-parsers.d.ts +2 -3
  76. package/dist/util/runner-native-session-parsers.js +5 -14
  77. package/dist/util/safe-error.d.ts +2 -0
  78. package/dist/util/safe-error.js +5 -0
  79. 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({ ...synchronized, lastActivityAt: session.lastActivityAt });
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
- return this.options.history(await this.requireSession(input.sessionId), page);
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, readFile, readdir, readlink, realpath } from 'node:fs/promises';
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 hasBrokenGitExclude(workspace, skills.map((skill) => skill.name));
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, () => updateGitExclude(workspace, name, true), () => updateGitExclude(workspace, name, false));
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, () => updateGitExclude(workspace, name, false), () => updateGitExclude(workspace, name, true));
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 rewriteGitExclude(workspace, names);
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 instanceof Error ? error.message : 'SKILL_INSTALL_FAILED'
80
+ errorCode: safeErrorCode(error, 'SKILL_INSTALL_FAILED')
80
81
  };
81
82
  }
82
83
  this.activeTargets.delete(targetKey);
@@ -33,6 +33,8 @@ export declare class WorkbenchManifestService {
33
33
  id: string;
34
34
  label: string;
35
35
  supportedEfforts: string[];
36
+ isDefault?: boolean | undefined;
37
+ defaultEffort?: string | undefined;
36
38
  }[];
37
39
  accessOptions: {
38
40
  id: string;
@@ -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 instanceof Error ? error.message : 'QUEUE_START_FAILED' }, 'FAILED');
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,
@@ -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 instanceof Error ? error.message : 'NODE_UPGRADE_INSTALL_FAILED',
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
@@ -384,7 +384,7 @@ function defaultTerminalProfiles(platform) {
384
384
  return [
385
385
  {
386
386
  id: 'default',
387
- label: '用户 Shell',
387
+ label: 'User Shell',
388
388
  shell: process.env['SHELL']?.trim() || '/bin/bash',
389
389
  args: ['-l', '-i'],
390
390
  isDefault: true
@@ -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本轮非图片附件已上传到工作区,请按需读取以下文件:\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: '(无法显示)', truncated: false };
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 不保存浏览器请求 ID。Node 进程仍存活时,以正文和生成时刻
50
- * 对齐当前运行态的用户消息,让 native 历史页替换同一乐观 Turn,而不是重复一条。
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 标签式 Plan Mode:用户正文剥离注入的规划指令标签,
232
- // 完整的 <proposed_plan> Agent 文本归一化为计划卡。
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 不保存浏览器请求 ID。Node 进程仍存活时,以正文和生成时刻
308
- * 对齐当前运行态的用户消息,让 native 历史页替换同一乐观 Turn,而不是重复一条。
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
- // 标签式 Plan Mode 的指令标签只存在于原生 transcript,运行态气泡始终是原文。
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,2 @@
1
+ /** Returns only a stable public error code, never a raw runtime exception message. */
2
+ export declare function safeErrorCode(error: unknown, fallback: string): string;
@@ -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.1.8",
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.1.8"
27
+ "@myagentroam/protocol": "^0.9.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/ws": "^8.18.1"