@myagentroam/node 0.1.7 → 0.9.0

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 (56) 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/config.d.ts +3 -5
  7. package/dist/config.js +9 -20
  8. package/dist/connector/node-connector-options.d.ts +1 -0
  9. package/dist/connector.js +12 -3
  10. package/dist/database.d.ts +23 -0
  11. package/dist/database.js +84 -2
  12. package/dist/main.js +4 -1
  13. package/dist/migrations/v004.d.ts +4 -0
  14. package/dist/migrations/v004.js +35 -0
  15. package/dist/opencode-server.d.ts +1 -0
  16. package/dist/opencode-server.js +13 -1
  17. package/dist/operational.js +4 -1
  18. package/dist/rotating-log.d.ts +17 -0
  19. package/dist/rotating-log.js +67 -0
  20. package/dist/runner/abstract-runner.d.ts +12 -2
  21. package/dist/runner/abstract-runner.js +76 -2
  22. package/dist/runner/claude/managed-run-controller.js +2 -1
  23. package/dist/runner/claude-code-runner.d.ts +1 -0
  24. package/dist/runner/claude-code-runner.js +14 -0
  25. package/dist/runner/codex/managed-run-controller.js +5 -3
  26. package/dist/runner/codex-runner.d.ts +16 -2
  27. package/dist/runner/codex-runner.js +215 -4
  28. package/dist/runner/opencode/managed-run-controller.js +13 -3
  29. package/dist/runner/opencode-runner.d.ts +2 -1
  30. package/dist/runner/opencode-runner.js +23 -3
  31. package/dist/runner/runner-registry.d.ts +1 -1
  32. package/dist/runner/runner-registry.js +2 -2
  33. package/dist/runner-profiles.js +5 -25
  34. package/dist/runtime-command-detector.d.ts +3 -0
  35. package/dist/runtime-command-detector.js +78 -0
  36. package/dist/service/mcp-installation-verifier.d.ts +9 -0
  37. package/dist/service/mcp-installation-verifier.js +87 -0
  38. package/dist/service/mcp-node-operation-service.d.ts +11 -0
  39. package/dist/service/mcp-node-operation-service.js +90 -0
  40. package/dist/service/mcp-package-installer.d.ts +8 -0
  41. package/dist/service/mcp-package-installer.js +136 -0
  42. package/dist/service/node-connection-lifecycle-service.js +1 -2
  43. package/dist/service/runner-service.d.ts +4 -2
  44. package/dist/service/runner-service.js +5 -2
  45. package/dist/service/session-lifecycle-service.js +7 -4
  46. package/dist/service/skill-directory-service.d.ts +17 -0
  47. package/dist/service/skill-directory-service.js +203 -0
  48. package/dist/service/skill-install-service.d.ts +21 -0
  49. package/dist/service/skill-install-service.js +504 -0
  50. package/dist/service/skill-node-operation-service.d.ts +44 -0
  51. package/dist/service/skill-node-operation-service.js +203 -0
  52. package/dist/service/workbench-manifest-service.d.ts +4 -2
  53. package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
  54. package/dist/service/workspace-queue-workbench-service.js +2 -1
  55. package/dist/supervisor.js +1 -20
  56. package/package.json +2 -2
@@ -0,0 +1,203 @@
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';
4
+ import path from 'node:path';
5
+ import { skillInstallMetadataSchema } from '@myagentroam/protocol';
6
+ const MAX_METADATA_BYTES = 64 * 1024;
7
+ const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
8
+ const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/u;
9
+ export class SkillDirectoryService {
10
+ async inspectNodeHome(home) {
11
+ const agentsRoot = path.join(home, '.agents', 'skills');
12
+ const claudeRoot = path.join(home, '.claude', 'skills');
13
+ const skills = await scanRoot(agentsRoot, 'AGENTS');
14
+ return {
15
+ targetKind: 'NODE',
16
+ compatibilityMode: await nodeCompatibilityMode(agentsRoot, claudeRoot),
17
+ skills
18
+ };
19
+ }
20
+ async inspectWorkspace(workspace) {
21
+ const agents = await scanRoot(path.join(workspace, '.agents', 'skills'), 'AGENTS');
22
+ const claude = await scanRoot(path.join(workspace, '.claude', 'skills'), 'CLAUDE');
23
+ const skills = mergeWorkspaceCopies(agents, claude);
24
+ const gitExcludeBroken = await hasBrokenGitExclude(workspace, skills.map((skill) => skill.name));
25
+ return {
26
+ targetKind: 'WORKSPACE',
27
+ compatibilityMode: 'MANAGED_COPY',
28
+ skills: gitExcludeBroken
29
+ ? skills.map((skill) => ({ ...skill, localStatus: 'GIT_EXCLUDE_BROKEN' }))
30
+ : skills
31
+ };
32
+ }
33
+ }
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
+ async function scanRoot(root, source) {
57
+ let entries;
58
+ try {
59
+ entries = await readdir(root, { withFileTypes: true });
60
+ }
61
+ catch (error) {
62
+ if (isMissing(error))
63
+ return [];
64
+ throw error;
65
+ }
66
+ const results = [];
67
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
68
+ if (entry.name.startsWith('.mar-'))
69
+ continue;
70
+ const itemPath = path.join(root, entry.name);
71
+ const stat = await lstat(itemPath);
72
+ if (stat.isSymbolicLink() || (!stat.isDirectory() && process.platform === 'win32')) {
73
+ results.push({ name: entry.name, description: '', localStatus: 'INVALID_LINK', source });
74
+ continue;
75
+ }
76
+ if (!stat.isDirectory())
77
+ continue;
78
+ if (!SKILL_NAME.test(entry.name) || WINDOWS_DEVICE_NAME.test(entry.name)) {
79
+ results.push({ name: entry.name, description: '', localStatus: 'INVALID', source });
80
+ continue;
81
+ }
82
+ results.push({ ...(await inspectSkill(itemPath, entry.name)), source });
83
+ }
84
+ return results;
85
+ }
86
+ async function inspectSkill(directory, directoryName) {
87
+ try {
88
+ const frontmatter = parseSkillFrontmatter(await readBounded(path.join(directory, 'SKILL.md')));
89
+ if (frontmatter.name !== directoryName ||
90
+ !SKILL_NAME.test(frontmatter.name) ||
91
+ WINDOWS_DEVICE_NAME.test(frontmatter.name))
92
+ return { name: directoryName, description: frontmatter.description, localStatus: 'INVALID' };
93
+ const install = await readInstallMetadata(path.join(directory, '.mar-skill-install.json'));
94
+ return {
95
+ name: directoryName,
96
+ description: frontmatter.description,
97
+ localStatus: install === undefined ? 'UNKNOWN_VERSION' : 'VALID',
98
+ ...(install === undefined ? {} : { install })
99
+ };
100
+ }
101
+ catch {
102
+ return { name: directoryName, description: '', localStatus: 'INVALID' };
103
+ }
104
+ }
105
+ async function readInstallMetadata(file) {
106
+ try {
107
+ return skillInstallMetadataSchema.parse(JSON.parse(await readBounded(file)));
108
+ }
109
+ catch (error) {
110
+ if (isMissing(error))
111
+ return undefined;
112
+ return undefined;
113
+ }
114
+ }
115
+ async function readBounded(file) {
116
+ const handle = await open(file, 'r');
117
+ try {
118
+ const stat = await handle.stat();
119
+ if (!stat.isFile() || stat.size > MAX_METADATA_BYTES)
120
+ throw new Error('SKILL_METADATA_INVALID');
121
+ const buffer = Buffer.alloc(stat.size);
122
+ await handle.read(buffer, 0, buffer.length, 0);
123
+ return buffer.toString('utf8');
124
+ }
125
+ finally {
126
+ await handle.close();
127
+ }
128
+ }
129
+ function parseSkillFrontmatter(content) {
130
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(content);
131
+ if (match === null)
132
+ throw new Error('SKILL_METADATA_INVALID');
133
+ const values = new Map();
134
+ for (const line of match[1].split(/\r?\n/u)) {
135
+ const field = /^(name|description):\s*(.*?)\s*$/u.exec(line);
136
+ if (field !== null)
137
+ values.set(field[1], unquote(field[2]));
138
+ }
139
+ const name = values.get('name');
140
+ const description = values.get('description');
141
+ if (name === undefined || description === undefined || description.length > 4096)
142
+ throw new Error('SKILL_METADATA_INVALID');
143
+ return { name, description };
144
+ }
145
+ function unquote(value) {
146
+ if (value.length >= 2 &&
147
+ ((value.startsWith('"') && value.endsWith('"')) ||
148
+ (value.startsWith("'") && value.endsWith("'"))))
149
+ return value.slice(1, -1);
150
+ return value;
151
+ }
152
+ function mergeWorkspaceCopies(agents, claude) {
153
+ const byName = new Map();
154
+ for (const item of agents)
155
+ byName.set(item.name, { agents: item });
156
+ for (const item of claude)
157
+ byName.set(item.name, { ...byName.get(item.name), claude: item });
158
+ return [...byName.entries()]
159
+ .sort(([left], [right]) => left.localeCompare(right))
160
+ .map(([name, copies]) => {
161
+ if (copies.agents === undefined || copies.claude === undefined)
162
+ return {
163
+ ...withoutSource((copies.agents ?? copies.claude)),
164
+ name,
165
+ localStatus: 'PARTIAL'
166
+ };
167
+ if (copies.agents.localStatus !== copies.claude.localStatus ||
168
+ JSON.stringify(copies.agents.install) !== JSON.stringify(copies.claude.install))
169
+ return { name, description: copies.agents.description, localStatus: 'PARTIAL' };
170
+ return withoutSource(copies.agents);
171
+ });
172
+ }
173
+ function withoutSource(entry) {
174
+ return {
175
+ name: entry.name,
176
+ description: entry.description,
177
+ localStatus: entry.localStatus,
178
+ ...(entry.install === undefined ? {} : { install: entry.install })
179
+ };
180
+ }
181
+ async function nodeCompatibilityMode(agentsRoot, claudeRoot) {
182
+ try {
183
+ const stat = await lstat(claudeRoot);
184
+ if (stat.isSymbolicLink()) {
185
+ const target = await realpath(path.resolve(path.dirname(claudeRoot), await readlink(claudeRoot)));
186
+ const agents = await realpath(agentsRoot);
187
+ return target === agents
188
+ ? process.platform === 'win32'
189
+ ? 'JUNCTION'
190
+ : 'SYMLINK'
191
+ : 'UNAVAILABLE';
192
+ }
193
+ return stat.isDirectory() ? 'MANAGED_COPY' : 'UNAVAILABLE';
194
+ }
195
+ catch (error) {
196
+ if (isMissing(error))
197
+ return 'UNAVAILABLE';
198
+ return 'UNAVAILABLE';
199
+ }
200
+ }
201
+ function isMissing(error) {
202
+ return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
203
+ }
@@ -0,0 +1,21 @@
1
+ import { type SkillInstallMetadata } from '@myagentroam/protocol';
2
+ export interface SkillBundleFile {
3
+ readonly path: string;
4
+ readonly contentBase64: string;
5
+ }
6
+ export interface InstallableSkillBundle {
7
+ readonly schemaVersion: 1;
8
+ readonly files: readonly SkillBundleFile[];
9
+ }
10
+ export declare class SkillInstallService {
11
+ recoverNodeHome(home: string): Promise<void>;
12
+ recoverWorkspace(workspace: string): Promise<void>;
13
+ installNodeHome(home: string, name: string, bundle: InstallableSkillBundle, metadata: SkillInstallMetadata): Promise<void>;
14
+ installWorkspace(workspace: string, name: string, bundle: InstallableSkillBundle, metadata: SkillInstallMetadata, options?: {
15
+ readonly confirmTracked?: boolean;
16
+ }): Promise<void>;
17
+ removeWorkspace(workspace: string, name: string): Promise<void>;
18
+ removeNodeHome(home: string, name: string): Promise<void>;
19
+ repairWorkspaceGitExclude(workspace: string): Promise<void>;
20
+ }
21
+ export declare function workspaceSkillTracked(workspace: string, name: string): Promise<boolean>;