@myagentroam/node 0.9.62 → 0.9.65
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/capabilities.js +110 -2
- package/dist/claude-agent-sdk.js +330 -2
- package/dist/claude-channel.js +44 -2
- package/dist/codex-app-server.js +510 -2
- package/dist/config.js +106 -2
- package/dist/connector/node-connector-options.js +1 -2
- package/dist/connector/node-operation-router.js +22 -2
- package/dist/connector.js +1736 -2
- package/dist/control-outbound-scheduler.js +153 -2
- package/dist/database.js +639 -2
- package/dist/fake-runner.js +129 -2
- package/dist/git-askpass.js +12 -0
- package/dist/git-execution-helper.js +266 -0
- package/dist/health.js +3 -2
- package/dist/main.js +57 -2
- package/dist/mcp-installation-model.js +1 -2
- package/dist/migrations/v004.js +55 -3
- package/dist/migrations/v005.js +13 -3
- package/dist/migrations/v006.js +90 -3
- package/dist/migrations/v007.js +22 -3
- package/dist/migrations/v008.js +18 -3
- package/dist/migrations/v009.js +70 -4
- package/dist/migrations/v010.js +43 -3
- package/dist/migrations/v011.js +20 -3
- package/dist/migrations/v012.js +8 -0
- package/dist/native-jsonl-reader.js +186 -2
- package/dist/native-session-history.js +1143 -2
- package/dist/opencode-runtime.js +80 -2
- package/dist/opencode-server.js +259 -2
- package/dist/operational.js +122 -2
- package/dist/process-tree.js +18 -2
- package/dist/ripgrep-binary.js +31 -2
- package/dist/rotating-log.js +67 -2
- package/dist/runner/abstract-runner.js +260 -2
- package/dist/runner/claude/managed-run-controller.js +355 -2
- package/dist/runner/claude/tool-normalizer.js +120 -2
- package/dist/runner/claude-code-runner.js +303 -2
- package/dist/runner/codex/composer-image-markers.js +8 -2
- package/dist/runner/codex/conversation-parser.js +913 -2
- package/dist/runner/codex/managed-run-controller.js +494 -2
- package/dist/runner/codex/tool-normalizer.js +214 -2
- package/dist/runner/codex-runner.js +585 -2
- package/dist/runner/fake-test-runner.js +47 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +644 -2
- package/dist/runner/mar-agent/executor-stdio-client-transport.js +139 -2
- package/dist/runner/mar-agent/generated-images.js +41 -2
- package/dist/runner/mar-agent/history-projection.js +106 -2
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +446 -2
- package/dist/runner/mar-agent/managed-run-controller.js +785 -2
- package/dist/runner/mar-agent/mcp-client-pool.js +233 -2
- package/dist/runner/mar-agent/runtime-model-factory.js +326 -2
- package/dist/runner/mar-agent-runner.js +458 -2
- package/dist/runner/opencode/conversation-parser.js +221 -2
- package/dist/runner/opencode/managed-run-controller.js +588 -2
- package/dist/runner/opencode/tool-normalizer.js +80 -2
- package/dist/runner/opencode-runner.js +426 -2
- package/dist/runner/runner-registry.js +87 -2
- package/dist/runner-command-engine.js +71 -2
- package/dist/runner-profiles.js +323 -2
- package/dist/runner-usage.js +190 -2
- package/dist/runtime-command-detector.js +73 -2
- package/dist/runtime-model.js +1 -2
- package/dist/runtime-state.js +1509 -2
- package/dist/service/attachment-domain-state.js +7 -2
- package/dist/service/bubblewrap-sandbox-executor.js +543 -2
- package/dist/service/continuous-run-coordinator.js +425 -2
- package/dist/service/conversation-history-service.js +552 -2
- package/dist/service/conversation-hosting-capability-service.js +61 -2
- package/dist/service/conversation-hosting-enablement-service.js +91 -2
- package/dist/service/conversation-hosting-maintenance-service.js +174 -2
- package/dist/service/conversation-segment-service.js +162 -2
- package/dist/service/conversation-template-node-service.js +992 -2
- package/dist/service/external-session-resume-service.js +152 -2
- package/dist/service/fake-managed-run-service.js +60 -2
- package/dist/service/git-execution-service.js +418 -0
- package/dist/service/git-operation-service.js +57 -0
- package/dist/service/hosted-conversation-node-service.js +1982 -2
- package/dist/service/hosted-conversation-store.js +1405 -2
- package/dist/service/managed-runner-session-service.js +35 -2
- package/dist/service/mar-agent-model-transport-test-service.js +88 -2
- package/dist/service/mcp-installation-verifier.js +87 -2
- package/dist/service/mcp-node-operation-service.js +90 -2
- package/dist/service/mission-mcp-server.js +124 -2
- package/dist/service/mission-mcp-stdio.js +40 -2
- package/dist/service/mission-runtime.js +142 -2
- package/dist/service/native-session-projection-service.js +274 -2
- package/dist/service/native-session-watch-service.js +683 -2
- package/dist/service/node-connection-lifecycle-service.js +88 -2
- package/dist/service/node-control-channel.js +85 -2
- package/dist/service/node-control-message-service.js +109 -2
- package/dist/service/node-request-service.js +62 -2
- package/dist/service/node-run-event-bridge.js +88 -2
- package/dist/service/node-terminal-channel.js +202 -2
- package/dist/service/node-upgrade-coordinator.js +137 -2
- package/dist/service/node-workspace-coordinator.js +97 -2
- package/dist/service/recent-run-projection-service.js +99 -2
- package/dist/service/run-attachment-service.js +321 -2
- package/dist/service/run-domain-state.js +4 -2
- package/dist/service/run-event-service.js +246 -2
- package/dist/service/run-ownership-policy.js +22 -2
- package/dist/service/run-workbench-service.js +120 -2
- package/dist/service/runner-channel-message-service.js +20 -2
- package/dist/service/runner-interaction-service.js +243 -2
- package/dist/service/runner-service.js +69 -2
- package/dist/service/runtime-credential-control-service.js +95 -2
- package/dist/service/session-catalog-service.js +404 -2
- package/dist/service/session-command-service.js +125 -2
- package/dist/service/session-context-service.js +128 -2
- package/dist/service/session-domain-state.js +9 -2
- package/dist/service/session-identity-service.js +86 -2
- package/dist/service/session-lifecycle-service.js +248 -2
- package/dist/service/session-message-service.js +270 -2
- package/dist/service/session-presentation-service.js +43 -2
- package/dist/service/session-query-service.js +82 -2
- package/dist/service/session-watch-slot-coordinator.js +21 -2
- package/dist/service/skill-directory-service.js +238 -2
- package/dist/service/skill-install-service.js +434 -2
- package/dist/service/skill-node-operation-service.js +206 -2
- package/dist/service/terminal-relay-state.js +3 -2
- package/dist/service/terminal-service.js +90 -2
- package/dist/service/web-search-error.js +40 -2
- package/dist/service/workbench-event-service.js +21 -2
- package/dist/service/workbench-manifest-service.js +69 -2
- package/dist/service/workspace-change-service.js +290 -2
- package/dist/service/workspace-content-search-service.js +457 -2
- package/dist/service/workspace-domain-state.js +41 -2
- package/dist/service/workspace-file-service.js +150 -2
- package/dist/service/workspace-git-exclude-service.js +119 -2
- package/dist/service/workspace-queue-workbench-service.js +176 -2
- package/dist/service/workspace-service.js +92 -2
- package/dist/service/workspace-session-service.js +154 -2
- package/dist/service/workspace-upload-service.js +262 -2
- package/dist/service/workspace-watch-controller.js +68 -2
- package/dist/service/workspace-watch-service.js +170 -2
- package/dist/service/workspace-watch-snapshot-service.js +218 -2
- package/dist/service/workspace-workbench-service.js +239 -2
- package/dist/service.js +91 -2
- package/dist/storage.js +32 -2
- package/dist/supervisor.js +136 -2
- package/dist/terminal.js +496 -2
- package/dist/util/git-credential-client.js +32 -0
- package/dist/util/node-operation-data.js +9 -2
- package/dist/util/node-operation-parsers.js +382 -2
- package/dist/util/personal-instructions.js +34 -2
- package/dist/util/runner-error.js +70 -2
- package/dist/util/runner-native-session-parsers.js +510 -2
- package/dist/util/safe-error.js +8 -2
- package/dist/util/secret-environment.js +25 -2
- package/dist/workspace.js +1793 -2
- package/package.json +11 -8
- package/vendor/ripgrep/darwin-arm64/rg +0 -0
- package/vendor/ripgrep/darwin-x64/rg +0 -0
- package/vendor/ripgrep/manifest.json +12 -0
- package/README.md +0 -42
- package/dist/event-buffer.js +0 -2
- package/dist/migrations/v001.js +0 -107
- package/dist/migrations/v002.js +0 -2
- package/dist/migrations/v003.js +0 -2
- package/dist/service/mcp-package-installer.js +0 -2
package/dist/fake-runner.js
CHANGED
|
@@ -1,2 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Test-only deterministic runner; it is never part of node capabilities. */
|
|
2
|
+
export class FakeRunner {
|
|
3
|
+
now;
|
|
4
|
+
active = new Map();
|
|
5
|
+
constructor(now = Date.now) {
|
|
6
|
+
this.now = now;
|
|
7
|
+
}
|
|
8
|
+
async start(runId, script, callbacks) {
|
|
9
|
+
if (this.active.has(runId))
|
|
10
|
+
return;
|
|
11
|
+
const active = {
|
|
12
|
+
callbacks,
|
|
13
|
+
interrupted: false,
|
|
14
|
+
cancelled: false,
|
|
15
|
+
decisions: new Map(),
|
|
16
|
+
waiters: new Map()
|
|
17
|
+
};
|
|
18
|
+
this.active.set(runId, active);
|
|
19
|
+
callbacks.event('run.started', {}, 'STARTING');
|
|
20
|
+
callbacks.event('run.running', {}, 'RUNNING');
|
|
21
|
+
try {
|
|
22
|
+
for (const step of script) {
|
|
23
|
+
const stopped = this.stopped(active);
|
|
24
|
+
if (stopped !== undefined) {
|
|
25
|
+
callbacks.event('run.stopped', {}, stopped);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (step.type === 'text')
|
|
29
|
+
callbacks.event('text', { text: step.text });
|
|
30
|
+
if (step.type === 'tool')
|
|
31
|
+
callbacks.event('tool', { name: step.name, input: step.input ?? null });
|
|
32
|
+
if (step.type === 'success') {
|
|
33
|
+
callbacks.event('run.succeeded', step.payload ?? {}, 'SUCCEEDED');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (step.type === 'failure') {
|
|
37
|
+
callbacks.event('run.failed', { code: step.code ?? 'FAKE_FAILURE' }, 'FAILED');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (step.type === 'disconnect') {
|
|
41
|
+
callbacks.disconnect();
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (step.type === 'long-running') {
|
|
45
|
+
await new Promise((resolve) => {
|
|
46
|
+
const timer = setInterval(() => {
|
|
47
|
+
if (this.stopped(active) !== undefined) {
|
|
48
|
+
clearInterval(timer);
|
|
49
|
+
resolve();
|
|
50
|
+
}
|
|
51
|
+
}, 5);
|
|
52
|
+
});
|
|
53
|
+
callbacks.event('run.stopped', {}, this.stopped(active) ?? 'CANCELLED');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (step.type === 'approval') {
|
|
57
|
+
const id = step.id ?? `${runId}-approval-${this.now()}`;
|
|
58
|
+
const expiresAt = this.now() + (step.timeoutMs ?? 30_000);
|
|
59
|
+
callbacks.approval(id, step.payload ?? {}, expiresAt);
|
|
60
|
+
const decision = await this.waitForDecision(active, id, step.timeoutMs ?? 30_000);
|
|
61
|
+
const stoppedAfterApproval = this.stopped(active);
|
|
62
|
+
if (stoppedAfterApproval !== undefined) {
|
|
63
|
+
callbacks.event('run.stopped', {}, stoppedAfterApproval);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (decision === 'REJECTED') {
|
|
67
|
+
callbacks.event('approval.rejected', { approvalId: id }, 'FAILED');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (decision === undefined) {
|
|
71
|
+
callbacks.event('approval.expired', { approvalId: id }, 'FAILED');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
callbacks.event('approval.approved', { approvalId: id });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
this.active.delete(runId);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
decide(runId, approvalId, decision) {
|
|
83
|
+
const active = this.active.get(runId);
|
|
84
|
+
if (active === undefined || active.decisions.has(approvalId))
|
|
85
|
+
return false;
|
|
86
|
+
active.decisions.set(approvalId, decision);
|
|
87
|
+
active.waiters.get(approvalId)?.(decision);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
interrupt(runId) {
|
|
91
|
+
const active = this.active.get(runId);
|
|
92
|
+
if (active === undefined)
|
|
93
|
+
return false;
|
|
94
|
+
active.interrupted = true;
|
|
95
|
+
this.stopWaitingForApproval(active);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
cancel(runId) {
|
|
99
|
+
const active = this.active.get(runId);
|
|
100
|
+
if (active === undefined)
|
|
101
|
+
return false;
|
|
102
|
+
active.cancelled = true;
|
|
103
|
+
this.stopWaitingForApproval(active);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
stopped(active) {
|
|
107
|
+
return active.cancelled ? 'CANCELLED' : active.interrupted ? 'INTERRUPTED' : undefined;
|
|
108
|
+
}
|
|
109
|
+
waitForDecision(active, approvalId, timeoutMs) {
|
|
110
|
+
const prior = active.decisions.get(approvalId);
|
|
111
|
+
if (prior !== undefined)
|
|
112
|
+
return Promise.resolve(prior);
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
active.waiters.delete(approvalId);
|
|
116
|
+
resolve(undefined);
|
|
117
|
+
}, timeoutMs);
|
|
118
|
+
active.waiters.set(approvalId, (decision) => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
active.waiters.delete(approvalId);
|
|
121
|
+
resolve(decision);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
stopWaitingForApproval(active) {
|
|
126
|
+
for (const resolve of [...active.waiters.values()])
|
|
127
|
+
resolve(undefined);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { gitCredentialRequest } from './util/git-credential-client.js';
|
|
2
|
+
// SSH_ASKPASS points to node itself; NODE_OPTIONS imports this before its prompt argument.
|
|
3
|
+
try {
|
|
4
|
+
const result = await gitCredentialRequest('/passphrase', { id: process.env.MAR_GIT_KEY_ID });
|
|
5
|
+
// Keep the preload pending until stdout drains; otherwise Node tries to load the SSH prompt as its main module.
|
|
6
|
+
await new Promise((resolve) => process.stdout.write(`${typeof result.passphrase === 'string' ? result.passphrase : ''}\n`, () => resolve()));
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
await new Promise((resolve) => process.stderr.write('Git SSH key could not be unlocked.\n', () => resolve()));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { gitCredentialRequest, originalGitEnvironment } from './util/git-credential-client.js';
|
|
5
|
+
async function run(command, args, options = {}) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(command, args, {
|
|
8
|
+
env: options.environment ?? process.env,
|
|
9
|
+
windowsHide: true,
|
|
10
|
+
stdio: [options.input === undefined ? 'inherit' : 'pipe', 'inherit', 'inherit']
|
|
11
|
+
});
|
|
12
|
+
if (options.input !== undefined)
|
|
13
|
+
child.stdin.end(options.input);
|
|
14
|
+
child.once('error', () => reject(new Error('GIT_TRANSPORT_UNAVAILABLE')));
|
|
15
|
+
child.once('exit', (code) => resolve(code ?? 1));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function credential(operation) {
|
|
19
|
+
if (!['get', 'store', 'erase'].includes(operation ?? ''))
|
|
20
|
+
return 1;
|
|
21
|
+
let input = '';
|
|
22
|
+
for await (const chunk of process.stdin) {
|
|
23
|
+
input += String(chunk);
|
|
24
|
+
if (Buffer.byteLength(input) > 64 * 1024)
|
|
25
|
+
throw new Error('GIT_CREDENTIAL_REQUEST_INVALID');
|
|
26
|
+
}
|
|
27
|
+
const values = {};
|
|
28
|
+
for (const line of input.split('\n')) {
|
|
29
|
+
if (line === '')
|
|
30
|
+
break;
|
|
31
|
+
const equals = line.indexOf('=');
|
|
32
|
+
if (equals > 0)
|
|
33
|
+
values[line.slice(0, equals)] = line.slice(equals + 1);
|
|
34
|
+
}
|
|
35
|
+
const matched = await gitCredentialRequest('/credential', {
|
|
36
|
+
protocol: values.protocol,
|
|
37
|
+
host: values.host,
|
|
38
|
+
path: values.path ?? ''
|
|
39
|
+
});
|
|
40
|
+
if (matched.matched === true) {
|
|
41
|
+
if (operation === 'get') {
|
|
42
|
+
if (typeof matched.username !== 'string' ||
|
|
43
|
+
typeof matched.password !== 'string' ||
|
|
44
|
+
/[\r\n\0]/u.test(matched.username + matched.password))
|
|
45
|
+
throw new Error('GIT_CREDENTIAL_REQUEST_INVALID');
|
|
46
|
+
process.stdout.write(`username=${matched.username}\npassword=${matched.password}\n\n`);
|
|
47
|
+
}
|
|
48
|
+
// Matched credentials are never written to another helper or replaced after rejection.
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
return run(gitExecutable(), ['credential', operation === 'get' ? 'fill' : operation === 'store' ? 'approve' : 'reject'], {
|
|
52
|
+
environment: await originalGitEnvironment(),
|
|
53
|
+
input
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function invoke(args) {
|
|
57
|
+
const remote = await currentRemote(args);
|
|
58
|
+
const matched = remote === undefined ? { matched: false } : await gitCredentialRequest('/identity', { remote });
|
|
59
|
+
const identity = matched.matched === true &&
|
|
60
|
+
typeof matched.name === 'string' &&
|
|
61
|
+
typeof matched.email === 'string' &&
|
|
62
|
+
!/[\r\n\0]/u.test(matched.name + matched.email)
|
|
63
|
+
? { name: matched.name, email: matched.email }
|
|
64
|
+
: undefined;
|
|
65
|
+
const environment = identity === undefined
|
|
66
|
+
? process.env
|
|
67
|
+
: {
|
|
68
|
+
...process.env,
|
|
69
|
+
GIT_AUTHOR_NAME: identity.name,
|
|
70
|
+
GIT_COMMITTER_NAME: identity.name,
|
|
71
|
+
GIT_AUTHOR_EMAIL: identity.email,
|
|
72
|
+
GIT_COMMITTER_EMAIL: identity.email
|
|
73
|
+
};
|
|
74
|
+
return run(gitExecutable(), identity === undefined
|
|
75
|
+
? args
|
|
76
|
+
: ['-c', `user.name=${identity.name}`, '-c', `user.email=${identity.email}`, ...args], { environment });
|
|
77
|
+
}
|
|
78
|
+
async function currentRemote(args) {
|
|
79
|
+
const cwd = gitWorkingDirectory(args);
|
|
80
|
+
const branch = await gitOutput(['branch', '--show-current'], cwd);
|
|
81
|
+
const configured = branch === undefined || branch === ''
|
|
82
|
+
? undefined
|
|
83
|
+
: await gitOutput(['config', '--get', `branch.${branch}.remote`], cwd);
|
|
84
|
+
for (const name of new Set([configured, 'origin'])) {
|
|
85
|
+
if (name === undefined || name === '')
|
|
86
|
+
continue;
|
|
87
|
+
const remote = await gitOutput(['remote', 'get-url', name], cwd);
|
|
88
|
+
if (remote !== undefined && remote !== '')
|
|
89
|
+
return remote;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
async function gitOutput(args, cwd) {
|
|
94
|
+
try {
|
|
95
|
+
const child = await new Promise((resolve, reject) => {
|
|
96
|
+
const child = spawn(gitExecutable(), args, {
|
|
97
|
+
cwd,
|
|
98
|
+
env: process.env,
|
|
99
|
+
windowsHide: true,
|
|
100
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
101
|
+
});
|
|
102
|
+
let output = '';
|
|
103
|
+
child.stdout.on('data', (chunk) => {
|
|
104
|
+
output += String(chunk);
|
|
105
|
+
});
|
|
106
|
+
child.once('error', reject);
|
|
107
|
+
child.once('exit', (code) => (code === 0 ? resolve({ output }) : resolve({ output: '' })));
|
|
108
|
+
});
|
|
109
|
+
return child.output.trim();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function gitWorkingDirectory(args) {
|
|
116
|
+
let cwd = process.cwd();
|
|
117
|
+
for (let index = 0; index < args.length; index++) {
|
|
118
|
+
if (args[index] === '-C') {
|
|
119
|
+
const directory = args[++index];
|
|
120
|
+
if (directory === undefined)
|
|
121
|
+
return cwd;
|
|
122
|
+
cwd = resolve(cwd, directory);
|
|
123
|
+
}
|
|
124
|
+
else if (args[index]?.startsWith('-C') && args[index].length > 2)
|
|
125
|
+
cwd = resolve(cwd, args[index].slice(2));
|
|
126
|
+
}
|
|
127
|
+
return cwd;
|
|
128
|
+
}
|
|
129
|
+
async function ssh(args) {
|
|
130
|
+
const forwarded = [];
|
|
131
|
+
let port = 22;
|
|
132
|
+
let index = 0;
|
|
133
|
+
while (args[index]?.startsWith('-')) {
|
|
134
|
+
const option = args[index++];
|
|
135
|
+
if (option === '-p') {
|
|
136
|
+
const value = args[index++] ?? '';
|
|
137
|
+
if (!/^\d+$/u.test(value) || Number(value) < 1 || Number(value) > 65535)
|
|
138
|
+
throw new Error('GIT_SSH_ARGUMENT_INVALID');
|
|
139
|
+
port = Number(value);
|
|
140
|
+
forwarded.push(option, value);
|
|
141
|
+
}
|
|
142
|
+
else if (option === '-o' && args[index] === 'SendEnv=GIT_PROTOCOL') {
|
|
143
|
+
forwarded.push(option, args[index++]);
|
|
144
|
+
}
|
|
145
|
+
else if (['-4', '-6', '-v'].includes(option))
|
|
146
|
+
forwarded.push(option);
|
|
147
|
+
else
|
|
148
|
+
throw new Error('GIT_SSH_ARGUMENT_INVALID');
|
|
149
|
+
}
|
|
150
|
+
const destination = args[index++];
|
|
151
|
+
const command = args[index++];
|
|
152
|
+
if (destination === undefined || command === undefined || index !== args.length)
|
|
153
|
+
throw new Error('GIT_SSH_ARGUMENT_INVALID');
|
|
154
|
+
const host = destination.slice(destination.lastIndexOf('@') + 1);
|
|
155
|
+
const repository = /^(?:git-upload-pack|git-receive-pack|git-upload-archive) '(.*)'$/su.exec(command)?.[1];
|
|
156
|
+
if (repository === undefined)
|
|
157
|
+
throw new Error('GIT_SSH_ARGUMENT_INVALID');
|
|
158
|
+
const path = repository.replaceAll("'\\''", "'").replaceAll("'\\!'", '!');
|
|
159
|
+
const match = await gitCredentialRequest('/ssh', { host, port, path });
|
|
160
|
+
if (match.matched !== true) {
|
|
161
|
+
const environment = await originalGitEnvironment();
|
|
162
|
+
let originalCommand = environment.GIT_SSH_COMMAND;
|
|
163
|
+
if (originalCommand === undefined) {
|
|
164
|
+
try {
|
|
165
|
+
originalCommand = execFileSync(gitExecutable(), ['config', '--get', 'core.sshCommand'], {
|
|
166
|
+
env: environment,
|
|
167
|
+
encoding: 'utf8',
|
|
168
|
+
windowsHide: true,
|
|
169
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
170
|
+
}).replace(/\r?\n$/u, '');
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error.status !== 1)
|
|
174
|
+
throw new Error('GIT_TRANSPORT_UNAVAILABLE');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (originalCommand !== undefined) {
|
|
178
|
+
return run(gitShell(), ['-c', `${originalCommand} "$@"`, 'mar-git-ssh', ...args], {
|
|
179
|
+
environment
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return run(environment.GIT_SSH ?? 'ssh', args, { environment });
|
|
183
|
+
}
|
|
184
|
+
if (typeof match.username !== 'string' ||
|
|
185
|
+
typeof match.keyFile !== 'string' ||
|
|
186
|
+
typeof match.configFile !== 'string' ||
|
|
187
|
+
typeof match.knownHostsFile !== 'string' ||
|
|
188
|
+
typeof match.askpassImport !== 'string' ||
|
|
189
|
+
typeof match.id !== 'string')
|
|
190
|
+
throw new Error('GIT_CREDENTIAL_REQUEST_INVALID');
|
|
191
|
+
return run('ssh', [
|
|
192
|
+
'-F',
|
|
193
|
+
match.configFile,
|
|
194
|
+
'-o',
|
|
195
|
+
'IdentitiesOnly=yes',
|
|
196
|
+
'-o',
|
|
197
|
+
'IdentityAgent=none',
|
|
198
|
+
'-o',
|
|
199
|
+
'StrictHostKeyChecking=yes',
|
|
200
|
+
'-o',
|
|
201
|
+
`UserKnownHostsFile=${sshConfigValue(match.knownHostsFile)}`,
|
|
202
|
+
'-o',
|
|
203
|
+
'PasswordAuthentication=no',
|
|
204
|
+
'-o',
|
|
205
|
+
'KbdInteractiveAuthentication=no',
|
|
206
|
+
'-o',
|
|
207
|
+
'PreferredAuthentications=publickey',
|
|
208
|
+
'-o',
|
|
209
|
+
'BatchMode=no',
|
|
210
|
+
'-i',
|
|
211
|
+
match.keyFile,
|
|
212
|
+
...forwarded,
|
|
213
|
+
'-l',
|
|
214
|
+
match.username,
|
|
215
|
+
host,
|
|
216
|
+
command
|
|
217
|
+
], {
|
|
218
|
+
environment: {
|
|
219
|
+
...process.env,
|
|
220
|
+
SSH_ASKPASS: process.execPath,
|
|
221
|
+
SSH_ASKPASS_REQUIRE: 'force',
|
|
222
|
+
DISPLAY: process.env.DISPLAY || 'mar:0',
|
|
223
|
+
NODE_OPTIONS: match.askpassImport,
|
|
224
|
+
MAR_GIT_KEY_ID: match.id
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function sshConfigValue(value) {
|
|
229
|
+
if (/[\r\n\0"]/u.test(value))
|
|
230
|
+
throw new Error('GIT_SSH_ARGUMENT_INVALID');
|
|
231
|
+
return `"${value.replaceAll('\\', '/')}"`;
|
|
232
|
+
}
|
|
233
|
+
function gitShell() {
|
|
234
|
+
if (process.platform !== 'win32')
|
|
235
|
+
return '/bin/sh';
|
|
236
|
+
const execPath = execFileSync(gitExecutable(), ['--exec-path'], {
|
|
237
|
+
encoding: 'utf8',
|
|
238
|
+
windowsHide: true
|
|
239
|
+
}).trim();
|
|
240
|
+
const shell = resolve(execPath, '../../../usr/bin/sh.exe');
|
|
241
|
+
if (!existsSync(shell))
|
|
242
|
+
throw new Error('GIT_TRANSPORT_UNAVAILABLE');
|
|
243
|
+
return shell;
|
|
244
|
+
}
|
|
245
|
+
function gitExecutable() {
|
|
246
|
+
return process.env.MAR_GIT_BINARY ?? 'git';
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const args = process.argv.slice(2);
|
|
250
|
+
const mode = args.shift();
|
|
251
|
+
process.exitCode =
|
|
252
|
+
mode === 'credential'
|
|
253
|
+
? await credential(args[0])
|
|
254
|
+
: mode === 'ssh'
|
|
255
|
+
? await ssh(args)
|
|
256
|
+
: mode === 'invoke'
|
|
257
|
+
? await invoke(args)
|
|
258
|
+
: 1;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Never serialize caught errors: child-process errors may contain credential output.
|
|
262
|
+
if (process.argv[2] === 'credential')
|
|
263
|
+
process.stdout.write('quit=true\n\n');
|
|
264
|
+
process.stderr.write('Personal Git authentication failed. Check your credentials and the Node SSH host trust.\n');
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
}
|
package/dist/health.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export function getNodeHealth() {
|
|
2
|
+
return { status: 'ok', platform: process.platform };
|
|
3
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { NodeConnector } from './connector.js';
|
|
5
|
+
import { loadNodeConfig, nodeConfigPath, nodeDatabasePath, nodeDataDirectory } from './config.js';
|
|
6
|
+
import { getNodeHealth } from './health.js';
|
|
7
|
+
import { removeServiceDefinition, writeServiceDefinition } from './service.js';
|
|
8
|
+
import { superviseNode } from './supervisor.js';
|
|
9
|
+
import { configureNodeLog } from './rotating-log.js';
|
|
10
|
+
const [command = 'status', ...args] = process.argv.slice(2);
|
|
11
|
+
const config = readOption(args, '--config') ?? nodeConfigPath();
|
|
12
|
+
const output = readOption(args, '--output') ?? defaultServicePath();
|
|
13
|
+
if (command === 'run') {
|
|
14
|
+
configureNodeLog(nodeDataDirectory(await loadNodeConfig(config), config));
|
|
15
|
+
const connector = new NodeConnector({ configPath: config });
|
|
16
|
+
await connector.start();
|
|
17
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
18
|
+
process.once(signal, () => connector.stop());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (command === 'supervise') {
|
|
22
|
+
if (process.argv[1] === undefined)
|
|
23
|
+
throw new Error('NODE_ENTRYPOINT_UNAVAILABLE');
|
|
24
|
+
configureNodeLog(nodeDataDirectory(await loadNodeConfig(config), config));
|
|
25
|
+
await superviseNode(config, process.argv[1]);
|
|
26
|
+
}
|
|
27
|
+
else if (command === 'install') {
|
|
28
|
+
await writeCurrentServiceDefinition(output, config);
|
|
29
|
+
process.stdout.write(`${output}\n`);
|
|
30
|
+
}
|
|
31
|
+
else if (command === 'upgrade') {
|
|
32
|
+
await writeCurrentServiceDefinition(output, config);
|
|
33
|
+
process.stdout.write(`${output}\n`);
|
|
34
|
+
}
|
|
35
|
+
else if (command === 'uninstall') {
|
|
36
|
+
await removeServiceDefinition(output);
|
|
37
|
+
}
|
|
38
|
+
else if (command === 'status') {
|
|
39
|
+
process.stdout.write(`${JSON.stringify(getNodeHealth())}\n`);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw new Error('NODE_COMMAND_INVALID');
|
|
43
|
+
}
|
|
44
|
+
function readOption(args, name) {
|
|
45
|
+
const index = args.indexOf(name);
|
|
46
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
47
|
+
}
|
|
48
|
+
function defaultServicePath() {
|
|
49
|
+
if (process.platform === 'win32')
|
|
50
|
+
return resolve('data/myagentroam-node-service.json');
|
|
51
|
+
if (process.platform === 'darwin')
|
|
52
|
+
return resolve(homedir(), 'Library', 'LaunchAgents', 'com.myagentroam.node.plist');
|
|
53
|
+
return resolve('data/myagentroam-node.service');
|
|
54
|
+
}
|
|
55
|
+
async function writeCurrentServiceDefinition(output, configPath) {
|
|
56
|
+
const nodeConfig = await loadNodeConfig(configPath);
|
|
57
|
+
await writeServiceDefinition(output, process.execPath, configPath, process.platform, process.argv[1], [dirname(nodeDatabasePath(nodeConfig, configPath)), ...nodeConfig.allowedRoots]);
|
|
58
|
+
}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export{};
|
|
1
|
+
export {};
|
package/dist/migrations/v004.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const NODE_SCHEMA_VERSION_V4 = 4;
|
|
2
|
+
/** Last supported released schema. Fresh databases start from this baseline. */
|
|
3
|
+
export const NODE_SCHEMA_V4 = String.raw `CREATE TABLE node_schema_migrations (
|
|
3
4
|
version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL
|
|
4
5
|
) STRICT;
|
|
5
6
|
CREATE TABLE workspaces (
|
|
@@ -36,4 +37,55 @@ const _0x3c7063=_0x1c8f;(function(_0x26be94,_0x7e3713){const _0xcf6fb2=_0x1c8f,_
|
|
|
36
37
|
configuration TEXT NOT NULL CHECK (json_valid(configuration)), installed_at INTEGER NOT NULL,
|
|
37
38
|
PRIMARY KEY (target_kind,target_id,server_name),
|
|
38
39
|
CHECK ((target_kind='NODE' AND target_id='') OR (target_kind='WORKSPACE' AND target_id<>''))
|
|
39
|
-
) STRICT;`;
|
|
40
|
+
) STRICT;`;
|
|
41
|
+
export function assertNodeSchemaV4(database) {
|
|
42
|
+
const required = {
|
|
43
|
+
node_schema_migrations: ['version', 'applied_at'],
|
|
44
|
+
workspaces: ['id', 'path', 'kind', 'git_summary', 'created_at', 'updated_at'],
|
|
45
|
+
agent_sessions: ['id', 'workspace_id', 'runner', 'pin_order', 'metadata'],
|
|
46
|
+
terminal_sessions: [
|
|
47
|
+
'id',
|
|
48
|
+
'workspace_id',
|
|
49
|
+
'profile_id',
|
|
50
|
+
'state',
|
|
51
|
+
'exit_code',
|
|
52
|
+
'rows',
|
|
53
|
+
'cols',
|
|
54
|
+
'latest_sequence',
|
|
55
|
+
'created_at',
|
|
56
|
+
'updated_at'
|
|
57
|
+
],
|
|
58
|
+
runner_defaults: ['runner', 'model', 'effort', 'access'],
|
|
59
|
+
mcp_installations: [
|
|
60
|
+
'target_kind',
|
|
61
|
+
'target_id',
|
|
62
|
+
'catalog_entry_id',
|
|
63
|
+
'server_name',
|
|
64
|
+
'display_name',
|
|
65
|
+
'version',
|
|
66
|
+
'enabled',
|
|
67
|
+
'runtime',
|
|
68
|
+
'configuration',
|
|
69
|
+
'installed_at'
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
for (const [table, expected] of Object.entries(required)) {
|
|
73
|
+
const columns = database.prepare(`PRAGMA table_info(${table})`).all();
|
|
74
|
+
const actual = columns.map((column) => column.name);
|
|
75
|
+
// Later schema versions may add columns to this baseline table.
|
|
76
|
+
if (expected.some((name) => !actual.includes(name)))
|
|
77
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}`);
|
|
78
|
+
}
|
|
79
|
+
for (const table of ['agent_sessions', 'runner_defaults']) {
|
|
80
|
+
const definition = database
|
|
81
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?")
|
|
82
|
+
.get(table);
|
|
83
|
+
if (typeof definition?.sql !== 'string' || !definition.sql.includes("'opencode'"))
|
|
84
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}:runner`);
|
|
85
|
+
}
|
|
86
|
+
const row = database
|
|
87
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='mcp_installations'")
|
|
88
|
+
.get();
|
|
89
|
+
if (typeof row?.sql !== 'string' || !row.sql.includes("target_kind IN ('NODE','WORKSPACE')"))
|
|
90
|
+
throw new Error('NODE_DATABASE_SCHEMA_INVALID:mcp_installations');
|
|
91
|
+
}
|
package/dist/migrations/v005.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export const NODE_SCHEMA_VERSION_V5 = 5;
|
|
2
|
+
/** Adds MAR Agent to every persisted Runner discriminator. */
|
|
3
|
+
export const NODE_SCHEMA_MIGRATION_V5 = String.raw `
|
|
3
4
|
CREATE TABLE agent_sessions_v5 (
|
|
4
5
|
id TEXT PRIMARY KEY,
|
|
5
6
|
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
@@ -20,4 +21,13 @@ const _0x19b848=_0x4032;function _0x13d5(){const _0x4b4946=['mZy1mdm2De5nwLrb','
|
|
|
20
21
|
SELECT runner, model, effort, access FROM runner_defaults;
|
|
21
22
|
DROP TABLE runner_defaults;
|
|
22
23
|
ALTER TABLE runner_defaults_v5 RENAME TO runner_defaults;
|
|
23
|
-
`;
|
|
24
|
+
`;
|
|
25
|
+
export function assertNodeSchemaV5(database) {
|
|
26
|
+
for (const table of ['agent_sessions', 'runner_defaults']) {
|
|
27
|
+
const definition = database
|
|
28
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?")
|
|
29
|
+
.get(table);
|
|
30
|
+
if (typeof definition?.sql !== 'string' || !definition.sql.includes("'mar-agent'"))
|
|
31
|
+
throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}:runner`);
|
|
32
|
+
}
|
|
33
|
+
}
|