@miruzero/zero-agent 0.3.6 → 0.3.8
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/README.md +31 -22
- package/package.json +6 -3
- package/src/agent.js +193 -167
- package/src/args.js +1 -3
- package/src/autostart.js +44 -68
- package/src/capabilities.js +95 -138
- package/src/chatgpt/auth/index.js +114 -0
- package/src/chatgpt/config/paths.js +25 -0
- package/src/chatgpt/index.js +5 -0
- package/src/chatgpt/session/direct-transport.js +68 -0
- package/src/chatgpt/session/exchange.js +17 -0
- package/src/chatgpt/session/index.js +102 -0
- package/src/chatgpt/session/local-storage.js +106 -0
- package/src/chatgpt/transport/direct/http-client.js +191 -0
- package/src/chatgpt-auth.js +17 -111
- package/src/chatgpt-runtime.js +8 -0
- package/src/chatgpt-session-exchange.js +8 -0
- package/src/chatgpt-session.js +26 -0
- package/src/cli.js +108 -71
- package/src/command/exec.js +34 -0
- package/src/command/filesystem/append-file.js +7 -12
- package/src/command/filesystem/copy-directory.js +8 -12
- package/src/command/filesystem/copy-file.js +8 -13
- package/src/command/filesystem/create-directory.js +7 -11
- package/src/command/filesystem/create-file.js +8 -13
- package/src/command/filesystem/delete-directory.js +7 -11
- package/src/command/filesystem/delete-file.js +8 -12
- package/src/command/filesystem/exists.js +6 -10
- package/src/command/filesystem/get-file-info.js +7 -12
- package/src/command/filesystem/glob-files.js +8 -13
- package/src/command/filesystem/grep-files.js +9 -15
- package/src/command/filesystem/hash-file.js +6 -11
- package/src/command/filesystem/index.js +33 -34
- package/src/command/filesystem/list-directory.js +6 -10
- package/src/command/filesystem/move-directory.js +9 -14
- package/src/command/filesystem/move-file.js +8 -13
- package/src/command/filesystem/path-policy.js +33 -16
- package/src/command/filesystem/read-file.js +7 -11
- package/src/command/filesystem/read-files.js +5 -8
- package/src/command/filesystem/rename-file.js +5 -8
- package/src/command/filesystem/replace-file.js +7 -12
- package/src/command/filesystem/text.js +5 -7
- package/src/command/filesystem/touch-file.js +8 -13
- package/src/command/filesystem/truncate-file.js +6 -10
- package/src/command/filesystem/walk.js +36 -39
- package/src/command/filesystem/write-file.js +8 -13
- package/src/command/index.js +19 -24
- package/src/command/path/index.js +46 -53
- package/src/config.js +21 -33
- package/src/device-paths.js +53 -0
- package/src/enrollment-client.js +19 -0
- package/src/http.js +27 -2
- package/src/identity-state.js +27 -0
- package/src/index.js +2 -4
- package/src/instance.js +85 -0
- package/src/local-server.js +174 -0
- package/src/local-tools.js +205 -205
- package/src/shared-zero/catalog.js +33 -0
- package/src/shared-zero/contract.js +18 -0
- package/src/shared-zero/descriptor.js +25 -0
- package/src/shared-zero/errors.js +12 -0
- package/src/shared-zero/index.js +9 -0
- package/src/shared-zero/runtime.js +14 -0
- package/src/shared-zero/target.js +19 -0
- package/src/shared-zero/tool-id.js +26 -0
- package/src/storage-roots.js +21 -0
- package/src/zero-runtime.js +1 -11
- package/src/zero.js +78 -0
package/src/cli.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const os = require('node:os');
|
|
5
|
-
const pkg = require('../package.json');
|
|
6
|
-
const config = require('./config');
|
|
7
|
-
const autostart = require('./autostart');
|
|
8
|
-
const caps = require('./capabilities');
|
|
9
|
-
const agent = require('./agent');
|
|
10
|
-
const { parseArgs } = require('./args');
|
|
3
|
+
const zero = require('./zero');
|
|
11
4
|
|
|
12
|
-
|
|
5
|
+
|
|
6
|
+
exports.usage = () => `Zero Agent
|
|
13
7
|
|
|
14
8
|
Usage:
|
|
15
9
|
zero-agent setup [--server <url>] [--name <device>] [--code <pairing-code>] [--no-global]
|
|
@@ -28,7 +22,7 @@ const printJson = (value) => console.log(JSON.stringify(value, null, 2));
|
|
|
28
22
|
const okMark = (ok) => ok ? 'OK' : 'FAIL';
|
|
29
23
|
const DEFAULT_SERVER = 'https://zero.miru.work';
|
|
30
24
|
|
|
31
|
-
const autostartInterval = (parsed) => Number(parsed.interval || autostart.DEFAULT_INTERVAL);
|
|
25
|
+
const autostartInterval = (parsed) => Number(parsed.interval || zero.autostart.DEFAULT_INTERVAL);
|
|
32
26
|
|
|
33
27
|
const printAutostartInfo = (prepared) => {
|
|
34
28
|
console.log('Autostart file');
|
|
@@ -39,24 +33,24 @@ const printAutostartInfo = (prepared) => {
|
|
|
39
33
|
console.log(prepared.launcherFile);
|
|
40
34
|
console.log('ตั้งค่า:');
|
|
41
35
|
console.log('ZERO_TRACE=1');
|
|
42
|
-
console.log('ZERO_AGENT_ROOTS=' + prepared.roots);
|
|
36
|
+
console.log(prepared.roots ? ('ZERO_AGENT_ROOTS=' + prepared.roots) : 'storageRoots=auto');
|
|
43
37
|
console.log('interval=' + prepared.interval + 'ms');
|
|
44
38
|
console.log('log=' + prepared.logFile);
|
|
45
39
|
};
|
|
46
40
|
|
|
47
41
|
const ensureLocalBootstrap = async (parsed, env) => {
|
|
48
|
-
let device = config.loadDevice(env);
|
|
42
|
+
let device = zero.config.loadDevice(env);
|
|
49
43
|
if (!device) {
|
|
50
|
-
const init = agent.initDevice({ name: parsed.name, server: parsed.server || DEFAULT_SERVER }, env);
|
|
44
|
+
const init = zero.agent.initDevice({ name: parsed.name, server: parsed.server || DEFAULT_SERVER }, env);
|
|
51
45
|
console.log('autoInit=OK file=' + init.file);
|
|
52
|
-
device = config.loadDevice(env);
|
|
46
|
+
device = zero.config.loadDevice(env);
|
|
53
47
|
}
|
|
54
|
-
const prepared = autostart.isPrepared(env)
|
|
55
|
-
? { ...autostart.paths(env), ok: true, reused: true }
|
|
56
|
-
: autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
48
|
+
const prepared = zero.autostart.isPrepared(env)
|
|
49
|
+
? { ...zero.autostart.paths(env), ok: true, reused: true }
|
|
50
|
+
: zero.autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
57
51
|
console.log('autoSetup=' + (prepared.reused ? 'REUSED' : 'OK') + ' launcher=' + prepared.launcherFile);
|
|
58
52
|
if (parsed.code && (!device || !device.device_id)) {
|
|
59
|
-
const paired = await agent.pairDevice({
|
|
53
|
+
const paired = await zero.agent.pairDevice({
|
|
60
54
|
name: parsed.name || device?.name,
|
|
61
55
|
server: parsed.server || device?.server || DEFAULT_SERVER,
|
|
62
56
|
code: parsed.code
|
|
@@ -71,10 +65,10 @@ const runGlobalInstall = (parsed, env) => {
|
|
|
71
65
|
console.log('globalInstall=SKIPPED');
|
|
72
66
|
return { skipped: true };
|
|
73
67
|
}
|
|
74
|
-
const spec = parsed.package || (pkg.name + '@' + pkg.version);
|
|
68
|
+
const spec = parsed.package || (zero.pkg.name + '@' + zero.pkg.version);
|
|
75
69
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
76
70
|
console.log('globalInstall=START ' + spec);
|
|
77
|
-
const result = childProcess.spawnSync(npmCmd, ['install', '-g', spec], { stdio: 'inherit', env });
|
|
71
|
+
const result = zero.childProcess.spawnSync(npmCmd, ['install', '-g', spec], { stdio: 'inherit', env });
|
|
78
72
|
if (result.status === 0) {
|
|
79
73
|
console.log('globalInstall=OK');
|
|
80
74
|
return { ok: true };
|
|
@@ -88,23 +82,23 @@ const runSetup = async (parsed, env) => {
|
|
|
88
82
|
const name = parsed.name || undefined;
|
|
89
83
|
console.log('Zero Agent Setup');
|
|
90
84
|
console.log('server=' + server);
|
|
91
|
-
console.log('name=' + (name || os.hostname()));
|
|
85
|
+
console.log('name=' + (name || zero.os.hostname()));
|
|
92
86
|
const installed = runGlobalInstall(parsed, env);
|
|
93
87
|
if (!installed.skipped && !installed.ok) {
|
|
94
88
|
console.error('setup=FAILED global installation did not complete');
|
|
95
89
|
return 70;
|
|
96
90
|
}
|
|
97
|
-
const init = agent.initDevice({ name, server }, env);
|
|
91
|
+
const init = zero.agent.initDevice({ name, server }, env);
|
|
98
92
|
console.log('init=OK file=' + init.file);
|
|
99
|
-
const prepared = autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
93
|
+
const prepared = zero.autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
100
94
|
printAutostartInfo(prepared);
|
|
101
95
|
if (!parsed.code) {
|
|
102
96
|
console.log('pairing=NEEDS_CODE');
|
|
103
97
|
console.log('Next: zero-agent pair --server ' + server + ' --code ZERO-XXXXXX --name ' + init.device.name);
|
|
104
|
-
console.log('Or: npx ' + pkg.name + '@latest setup --server ' + server + ' --name ' + init.device.name + ' --code ZERO-XXXXXX');
|
|
98
|
+
console.log('Or: npx ' + zero.pkg.name + '@latest setup --server ' + server + ' --name ' + init.device.name + ' --code ZERO-XXXXXX');
|
|
105
99
|
return 0;
|
|
106
100
|
}
|
|
107
|
-
const paired = await agent.pairDevice({ name: init.device.name, server, code: parsed.code }, env);
|
|
101
|
+
const paired = await zero.agent.pairDevice({ name: init.device.name, server, code: parsed.code }, env);
|
|
108
102
|
console.log('pair=OK file=' + paired.file);
|
|
109
103
|
printJson(paired.device);
|
|
110
104
|
const heartbeat = await runStartOnce(env);
|
|
@@ -115,19 +109,19 @@ const runSetup = async (parsed, env) => {
|
|
|
115
109
|
};
|
|
116
110
|
|
|
117
111
|
|
|
118
|
-
|
|
119
|
-
const device = config.loadDevice(env);
|
|
120
|
-
if (!device) return { paired: false, home: config.agentHome(env) };
|
|
121
|
-
return { paired: Boolean(device.device_id), home: config.agentHome(env), device: config.redactedDevice(device) };
|
|
112
|
+
exports.status = (env) => {
|
|
113
|
+
const device = zero.config.loadDevice(env);
|
|
114
|
+
if (!device) return { paired: false, home: zero.config.agentHome(env) };
|
|
115
|
+
return { paired: Boolean(device.device_id), home: zero.config.agentHome(env), device: zero.config.redactedDevice(device) };
|
|
122
116
|
};
|
|
123
117
|
|
|
124
|
-
|
|
125
|
-
const capability =
|
|
118
|
+
exports.doctor = (env) => {
|
|
119
|
+
const capability = zero.capabilities.buildCapabilities(env);
|
|
126
120
|
return {
|
|
127
121
|
ok: true,
|
|
128
122
|
node: process.version,
|
|
129
123
|
platform: process.platform,
|
|
130
|
-
agent_home: config.agentHome(env),
|
|
124
|
+
agent_home: zero.config.agentHome(env),
|
|
131
125
|
mcp_root: capability.mcp_root,
|
|
132
126
|
providers: capability.providers.length,
|
|
133
127
|
capability_hash: capability.capability_hash
|
|
@@ -135,14 +129,13 @@ const doctor = (env) => {
|
|
|
135
129
|
};
|
|
136
130
|
|
|
137
131
|
const runStartOnce = async (env) => {
|
|
138
|
-
const heartbeat = await agent.heartbeatOnce(env);
|
|
132
|
+
const heartbeat = await zero.agent.heartbeatOnce(env);
|
|
139
133
|
if (!heartbeat.ok && heartbeat.code === 'NOT_PAIRED') return heartbeat;
|
|
140
|
-
const capability = await agent.syncCapabilitiesOnce(env);
|
|
141
|
-
|
|
142
|
-
return { heartbeat, capability, task };
|
|
134
|
+
const capability = await zero.agent.syncCapabilitiesOnce(env);
|
|
135
|
+
return { heartbeat, capability };
|
|
143
136
|
};
|
|
144
137
|
|
|
145
|
-
|
|
138
|
+
exports.runSafeStartCycle = async (env, runOnce = runStartOnce) => {
|
|
146
139
|
try {
|
|
147
140
|
return { ok: true, result: await runOnce(env) };
|
|
148
141
|
} catch (error) {
|
|
@@ -158,15 +151,18 @@ const runSafeStartCycle = async (env, runOnce = runStartOnce) => {
|
|
|
158
151
|
};
|
|
159
152
|
|
|
160
153
|
const printStartBanner = (env) => {
|
|
161
|
-
const device = config.loadDevice(env);
|
|
154
|
+
const device = zero.config.loadDevice(env);
|
|
155
|
+
let chatgptAuth = 'unknown';
|
|
156
|
+
try { chatgptAuth = zero.chatgptAuth.status({ env }).auth?.status || 'unknown'; } catch {}
|
|
162
157
|
console.log('🚀 Starting Zero Agent...');
|
|
163
|
-
console.log('🔁
|
|
158
|
+
console.log('🔁 Device pairing restored: ' + (device?.device_id ? 'yes' : 'no'));
|
|
159
|
+
console.log('🔐 ChatGPT Auth: ' + chatgptAuth);
|
|
164
160
|
console.log('👤 Device Name: ' + (device?.name || '(unpaired)'));
|
|
165
161
|
console.log('🆔 Device ID: ' + (device?.device_id || '(not paired)'));
|
|
166
162
|
console.log('🌐 Server: ' + (device?.server || 'https://zero.miru.work'));
|
|
167
163
|
};
|
|
168
164
|
|
|
169
|
-
|
|
165
|
+
exports.loopResultEvent = (result) => {
|
|
170
166
|
if (!result || result.code === 'NOT_PAIRED') {
|
|
171
167
|
return { level: 'error', text: '❌ Device not paired: run zero-agent pair first' };
|
|
172
168
|
}
|
|
@@ -183,6 +179,10 @@ const loopResultEvent = (result) => {
|
|
|
183
179
|
at: new Date().toISOString(),
|
|
184
180
|
heartbeat: okMark(Boolean(hb.ok)),
|
|
185
181
|
capabilities: okMark(Boolean(cap.ok)),
|
|
182
|
+
heartbeat_http_status: hb.status ?? null,
|
|
183
|
+
heartbeat_error_code: hb.body?.error?.code || hb.code || null,
|
|
184
|
+
capabilities_http_status: cap.status ?? null,
|
|
185
|
+
capabilities_error_code: cap.body?.error?.code || cap.code || null,
|
|
186
186
|
status: device?.status || null,
|
|
187
187
|
name: device?.name || null,
|
|
188
188
|
host: device?.host || null,
|
|
@@ -194,48 +194,76 @@ const loopResultEvent = (result) => {
|
|
|
194
194
|
};
|
|
195
195
|
|
|
196
196
|
const printLoopResult = (result) => {
|
|
197
|
-
const event = loopResultEvent(result);
|
|
197
|
+
const event = exports.loopResultEvent(result);
|
|
198
198
|
if (!event) return;
|
|
199
199
|
if (event.level === 'error') console.error(event.text);
|
|
200
200
|
else console.log(event.text);
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
const
|
|
203
|
+
exports.connectionTransition = (previous = 'connecting', cycle = {}, env = process.env) => {
|
|
204
|
+
const result = cycle.result || {};
|
|
205
|
+
const hb = result.heartbeat || {};
|
|
206
|
+
const cap = result.capability || {};
|
|
207
|
+
const healthy = Boolean(cycle.ok) && Boolean(hb.ok) && Boolean(cap.ok);
|
|
208
|
+
const device = cap.body?.device || hb.body?.device || zero.config.loadDevice(env) || {};
|
|
209
|
+
const lastSeen = hb.body?.device?.last_seen || cap.body?.device?.last_seen || null;
|
|
210
|
+
const server = zero.config.loadDevice(env)?.server || env.ZERO_SERVER_URL || DEFAULT_SERVER;
|
|
211
|
+
|
|
212
|
+
if (healthy) {
|
|
213
|
+
if (previous === 'connected') return { state: 'connected', level: 'info', text: null };
|
|
214
|
+
const verb = previous === 'disconnected' ? 'Reconnected' : 'Connected';
|
|
215
|
+
return {
|
|
216
|
+
state: 'connected',
|
|
217
|
+
level: 'info',
|
|
218
|
+
text: `✅ ${verb} to Zero Core\n Status: Online\n Device: ${device.name || device.host || '(unnamed)'}\n Core: ${server}\n Heartbeat: OK (HTTP ${hb.status ?? 'OK'})\n Capabilities: Synced (HTTP ${cap.status ?? 'OK'})\n Last seen: ${lastSeen || 'now'}\n Agent version: ${zero.pkg.version}\n Contract: ${cap.body?.device?.contract_version || hb.body?.device?.contract_version || zero.contract.version}\n Compatibility: ${String(cap.body?.device?.compatibility?.status || hb.body?.device?.compatibility?.status || 'unknown').toUpperCase()}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (previous === 'disconnected') return { state: 'disconnected', level: 'error', text: null };
|
|
223
|
+
const status = hb.status != null ? `heartbeat HTTP ${hb.status}` : cap.status != null ? `capabilities HTTP ${cap.status}` : (cycle.error?.code || 'connection error');
|
|
224
|
+
return {
|
|
225
|
+
state: 'disconnected',
|
|
226
|
+
level: 'error',
|
|
227
|
+
text: `🔴 Disconnected from Zero Core — ${server}\n↻ Reconnecting automatically (${status})`
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
exports.run = async (argv = [], env = process.env) => {
|
|
232
|
+
const parsed = zero.args.parseArgs(argv);
|
|
205
233
|
const command = parsed._[0] || 'help';
|
|
206
234
|
if (command === 'help' || parsed.help) {
|
|
207
|
-
console.log(usage());
|
|
235
|
+
console.log(exports.usage());
|
|
208
236
|
return 0;
|
|
209
237
|
}
|
|
210
238
|
if (command === 'setup') {
|
|
211
239
|
return runSetup(parsed, env);
|
|
212
240
|
}
|
|
213
241
|
if (command === 'init') {
|
|
214
|
-
const result = agent.initDevice({ name: parsed.name, server: parsed.server }, env);
|
|
242
|
+
const result = zero.agent.initDevice({ name: parsed.name, server: parsed.server }, env);
|
|
215
243
|
console.log(`init=OK file=${result.file}`);
|
|
216
244
|
printJson(result.device);
|
|
217
245
|
return 0;
|
|
218
246
|
}
|
|
219
247
|
if (command === 'pair') {
|
|
220
|
-
const result = await agent.pairDevice({ name: parsed.name, server: parsed.server, code: parsed.code }, env);
|
|
248
|
+
const result = await zero.agent.pairDevice({ name: parsed.name, server: parsed.server, code: parsed.code }, env);
|
|
221
249
|
console.log(`pair=OK file=${result.file}`);
|
|
222
250
|
printJson(result.device);
|
|
223
251
|
return 0;
|
|
224
252
|
}
|
|
225
253
|
if (command === 'status') {
|
|
226
|
-
printJson(status(env));
|
|
254
|
+
printJson(exports.status(env));
|
|
227
255
|
return 0;
|
|
228
256
|
}
|
|
229
257
|
if (command === 'devices') {
|
|
230
|
-
printJson(await agent.listDevices(env));
|
|
258
|
+
printJson(await zero.agent.listDevices(env));
|
|
231
259
|
return 0;
|
|
232
260
|
}
|
|
233
261
|
if (command === 'doctor') {
|
|
234
|
-
printJson(doctor(env));
|
|
262
|
+
printJson(exports.doctor(env));
|
|
235
263
|
return 0;
|
|
236
264
|
}
|
|
237
265
|
if (command === 'providers') {
|
|
238
|
-
printJson(
|
|
266
|
+
printJson(zero.capabilities.manifestProviders(env).map((item) => ({
|
|
239
267
|
name: item.name,
|
|
240
268
|
type: item.type,
|
|
241
269
|
description: item.description,
|
|
@@ -244,37 +272,46 @@ async function run(argv = [], env = process.env) {
|
|
|
244
272
|
return 0;
|
|
245
273
|
}
|
|
246
274
|
if (command === 'tools' || command === 'capabilities') {
|
|
247
|
-
printJson(
|
|
275
|
+
printJson(zero.capabilities.buildCapabilities(env));
|
|
248
276
|
return 0;
|
|
249
277
|
}
|
|
250
278
|
if (command === 'start') {
|
|
251
279
|
await ensureLocalBootstrap(parsed, env);
|
|
280
|
+
const ownership = await zero.instance.acquire(env);
|
|
281
|
+
console.log(JSON.stringify({ event: 'zero.agent.zero.instance.active', pid: ownership.pid, replaced_pid: ownership.replaced_pid }));
|
|
282
|
+
const local = await zero.localServer.startLocalServer({ env });
|
|
283
|
+
console.log(JSON.stringify({ event: 'zero.agent.local.ready', host: '127.0.0.1', port: local.port }));
|
|
252
284
|
if (parsed.once) {
|
|
253
|
-
printJson(await runStartOnce(env));
|
|
285
|
+
try { printJson(await runStartOnce(env)); }
|
|
286
|
+
finally {
|
|
287
|
+
await new Promise((resolve) => local.server.close(resolve));
|
|
288
|
+
ownership.release();
|
|
289
|
+
}
|
|
254
290
|
return 0;
|
|
255
291
|
}
|
|
256
|
-
const interval = Number(parsed.interval || autostart.DEFAULT_INTERVAL);
|
|
257
292
|
printStartBanner(env);
|
|
258
|
-
console.log('
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})
|
|
293
|
+
console.log('🔌 Connecting to Zero Core...');
|
|
294
|
+
const cycle = await exports.runSafeStartCycle(env);
|
|
295
|
+
const transition = exports.connectionTransition('connecting', cycle, env);
|
|
296
|
+
if (transition.text) {
|
|
297
|
+
if (transition.level === 'error') console.error(transition.text);
|
|
298
|
+
else console.log(transition.text);
|
|
299
|
+
}
|
|
300
|
+
if (transition.state !== 'connected') return 69;
|
|
301
|
+
console.log('📡 Task channel: listening for Core events');
|
|
302
|
+
for (;;) {
|
|
303
|
+
try {
|
|
304
|
+
const stream = await zero.agent.listenTaskStreamOnce(env);
|
|
305
|
+
if (!stream.ok) throw Object.assign(new Error('task stream unavailable'), { code: `HTTP_${stream.status || 0}` });
|
|
306
|
+
console.error('🔴 Task channel closed by Zero Core');
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error(`🔴 Task channel disconnected (${error.code || error.message || 'connection error'})`);
|
|
271
309
|
}
|
|
272
|
-
|
|
310
|
+
console.log('↻ Reconnecting task channel after disconnect event...');
|
|
311
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
273
312
|
}
|
|
274
313
|
}
|
|
275
314
|
console.error('unknown command: ' + command);
|
|
276
|
-
console.error(usage());
|
|
315
|
+
console.error(exports.usage());
|
|
277
316
|
return 64;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
module.exports = { run, usage, status, doctor, runSafeStartCycle, loopResultEvent };
|
|
317
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const zero = require('../zero');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
exports.execDescriptor = zero.descriptor.validate({
|
|
7
|
+
name: 'zero.command.exec.run',
|
|
8
|
+
aliases: ['zero.command.exec'],
|
|
9
|
+
description: 'Execute an allowed local command through the agent process adapter.',
|
|
10
|
+
capability: 'process.execute',
|
|
11
|
+
risk: 'execute',
|
|
12
|
+
execution: 'target',
|
|
13
|
+
adapters: ['native'],
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
command: { type: 'string' },
|
|
18
|
+
args: { type: 'array', items: { type: 'string' } }
|
|
19
|
+
},
|
|
20
|
+
required: ['command'],
|
|
21
|
+
additionalProperties: false
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
exports.exec = async (input = {}) => new Promise((resolve, reject) => {
|
|
26
|
+
zero.childProcess.execFile(input.command, Array.isArray(input.args) ? input.args : [], { shell: false }, (error, stdout, stderr) => {
|
|
27
|
+
if (error) {
|
|
28
|
+
reject(error);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve({ ok: true, stdout, stderr });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const zero = require('../../zero-runtime');
|
|
6
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
9
7
|
name: 'zero.command.filesystem.appendFile',
|
|
10
8
|
aliases: ['zero.command.appendFile'],
|
|
11
9
|
description: 'Append UTF-8 text content to a file inside allowed roots.',
|
|
@@ -21,13 +19,10 @@ const descriptor = zero.descriptor.validate({
|
|
|
21
19
|
}
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
const file = pathPolicy.resolveAllowed(args.path, context);
|
|
22
|
+
exports.appendFile = (args = {}, context = {}) => {
|
|
23
|
+
const file = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
26
24
|
const data = String(args.content ?? '');
|
|
27
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
28
|
-
fs.appendFileSync(file, data, 'utf8');
|
|
25
|
+
zero.fs.mkdirSync(zero.path.dirname(file), { recursive: true });
|
|
26
|
+
zero.fs.appendFileSync(file, data, 'utf8');
|
|
29
27
|
return { ok: true, path: file, mode: 'append', bytes: Buffer.byteLength(data) };
|
|
30
28
|
};
|
|
31
|
-
|
|
32
|
-
const appendFileModule = { descriptor, appendFile };
|
|
33
|
-
Object.assign(exports, appendFileModule);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const zero = require('../../zero-runtime');
|
|
5
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
8
7
|
name: 'zero.command.filesystem.copyDirectory',
|
|
9
8
|
aliases: ['zero.command.copyDirectory'],
|
|
10
9
|
description: 'Copy a directory recursively inside allowed roots.',
|
|
@@ -21,13 +20,10 @@ const descriptor = zero.descriptor.validate({
|
|
|
21
20
|
});
|
|
22
21
|
|
|
23
22
|
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
24
|
-
|
|
25
|
-
const source = pathPolicy.resolveAllowed(args.source, context);
|
|
26
|
-
const destination = pathPolicy.resolveAllowed(args.destination, context);
|
|
27
|
-
if (!fs.statSync(source).isDirectory()) throw fail('NOT_A_DIRECTORY', 'source is not a directory');
|
|
28
|
-
fs.cpSync(source, destination, { recursive: true, force: args.overwrite === true, errorOnExist: args.overwrite !== true });
|
|
23
|
+
exports.copyDirectory = (args = {}, context = {}) => {
|
|
24
|
+
const source = zero.pathPolicy.resolveAllowed(args.source, context);
|
|
25
|
+
const destination = zero.pathPolicy.resolveAllowed(args.destination, context);
|
|
26
|
+
if (!zero.fs.statSync(source).isDirectory()) throw fail('NOT_A_DIRECTORY', 'source is not a directory');
|
|
27
|
+
zero.fs.cpSync(source, destination, { recursive: true, force: args.overwrite === true, errorOnExist: args.overwrite !== true });
|
|
29
28
|
return { ok: true, source, destination };
|
|
30
29
|
};
|
|
31
|
-
|
|
32
|
-
const copyDirectoryModule = { descriptor, copyDirectory };
|
|
33
|
-
Object.assign(exports, copyDirectoryModule);
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const zero = require('../../zero-runtime');
|
|
6
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
9
7
|
name: 'zero.command.filesystem.copyFile',
|
|
10
8
|
aliases: ['zero.command.copyFile'],
|
|
11
9
|
description: 'Copy one file inside allowed roots.',
|
|
@@ -21,13 +19,10 @@ const descriptor = zero.descriptor.validate({
|
|
|
21
19
|
}
|
|
22
20
|
});
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
const source = pathPolicy.resolveAllowed(args.source, context);
|
|
26
|
-
const destination = pathPolicy.resolveAllowed(args.destination, context);
|
|
27
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
28
|
-
fs.copyFileSync(source, destination);
|
|
22
|
+
exports.copyFile = (args = {}, context = {}) => {
|
|
23
|
+
const source = zero.pathPolicy.resolveAllowed(args.source, context);
|
|
24
|
+
const destination = zero.pathPolicy.resolveAllowed(args.destination, context);
|
|
25
|
+
zero.fs.mkdirSync(zero.path.dirname(destination), { recursive: true });
|
|
26
|
+
zero.fs.copyFileSync(source, destination);
|
|
29
27
|
return { ok: true, source, destination };
|
|
30
28
|
};
|
|
31
|
-
|
|
32
|
-
const copyFileModule = { descriptor, copyFile };
|
|
33
|
-
Object.assign(exports, copyFileModule);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const zero = require('../../zero-runtime');
|
|
5
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
8
7
|
name: 'zero.command.filesystem.createDirectory',
|
|
9
8
|
aliases: ['zero.command.createDirectory'],
|
|
10
9
|
description: 'Create a directory recursively inside allowed roots.',
|
|
@@ -20,11 +19,8 @@ const descriptor = zero.descriptor.validate({
|
|
|
20
19
|
}
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
const dir = pathPolicy.resolveAllowed(args.path, context);
|
|
25
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
26
|
-
return { ok: true, path: dir, created: fs.existsSync(dir) && fs.statSync(dir).isDirectory() };
|
|
22
|
+
exports.createDirectory = (args = {}, context = {}) => {
|
|
23
|
+
const dir = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
24
|
+
zero.fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
return { ok: true, path: dir, created: zero.fs.existsSync(dir) && zero.fs.statSync(dir).isDirectory() };
|
|
27
26
|
};
|
|
28
|
-
|
|
29
|
-
const createDirectoryModule = { descriptor, createDirectory };
|
|
30
|
-
Object.assign(exports, createDirectoryModule);
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const zero = require('../../zero-runtime');
|
|
6
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
9
7
|
name: 'zero.command.filesystem.createFile',
|
|
10
8
|
aliases: ['zero.command.createFile'],
|
|
11
9
|
description: 'Create a UTF-8 text file inside allowed roots.',
|
|
@@ -22,14 +20,11 @@ const descriptor = zero.descriptor.validate({
|
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
25
|
-
|
|
26
|
-
const file = pathPolicy.resolveAllowed(args.path, context);
|
|
27
|
-
if (fs.existsSync(file) && args.overwrite !== true) throw fail('FILE_EXISTS', 'file already exists');
|
|
23
|
+
exports.createFile = (args = {}, context = {}) => {
|
|
24
|
+
const file = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
25
|
+
if (zero.fs.existsSync(file) && args.overwrite !== true) throw fail('FILE_EXISTS', 'file already exists');
|
|
28
26
|
const data = String(args.content ?? '');
|
|
29
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
30
|
-
fs.writeFileSync(file, data, 'utf8');
|
|
27
|
+
zero.fs.mkdirSync(zero.path.dirname(file), { recursive: true });
|
|
28
|
+
zero.fs.writeFileSync(file, data, 'utf8');
|
|
31
29
|
return { ok: true, path: file, created: true, bytes: Buffer.byteLength(data) };
|
|
32
30
|
};
|
|
33
|
-
|
|
34
|
-
const createFileModule = { descriptor, createFile };
|
|
35
|
-
Object.assign(exports, createFileModule);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const zero = require('../../zero-runtime');
|
|
5
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
8
7
|
name: 'zero.command.filesystem.deleteDirectory',
|
|
9
8
|
aliases: ['zero.command.deleteDirectory'],
|
|
10
9
|
description: 'Delete a directory inside allowed roots.',
|
|
@@ -16,12 +15,9 @@ const descriptor = zero.descriptor.validate({
|
|
|
16
15
|
});
|
|
17
16
|
|
|
18
17
|
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
19
|
-
|
|
20
|
-
const dir = pathPolicy.resolveAllowed(args.path, context);
|
|
21
|
-
if (!fs.statSync(dir).isDirectory()) throw fail('NOT_A_DIRECTORY', 'path is not a directory');
|
|
22
|
-
fs.rmSync(dir, { recursive: args.recursive === true, force: false });
|
|
18
|
+
exports.deleteDirectory = (args = {}, context = {}) => {
|
|
19
|
+
const dir = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
20
|
+
if (!zero.fs.statSync(dir).isDirectory()) throw fail('NOT_A_DIRECTORY', 'path is not a directory');
|
|
21
|
+
zero.fs.rmSync(dir, { recursive: args.recursive === true, force: false });
|
|
23
22
|
return { ok: true, path: dir, deleted: true };
|
|
24
23
|
};
|
|
25
|
-
|
|
26
|
-
const deleteDirectoryModule = { descriptor, deleteDirectory };
|
|
27
|
-
Object.assign(exports, deleteDirectoryModule);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const zero = require('../../zero-runtime');
|
|
5
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
8
7
|
name: 'zero.command.filesystem.deleteFile',
|
|
9
8
|
aliases: ['zero.command.deleteFile'],
|
|
10
9
|
description: 'Delete one file inside allowed roots. Directories are rejected.',
|
|
@@ -21,13 +20,10 @@ const descriptor = zero.descriptor.validate({
|
|
|
21
20
|
});
|
|
22
21
|
|
|
23
22
|
const fail = (code, message) => Object.assign(new Error(message), { code });
|
|
24
|
-
|
|
25
|
-
const file = pathPolicy.resolveAllowed(args.path, context);
|
|
26
|
-
if (!fs.existsSync(file)) throw fail('FILE_NOT_FOUND', 'file does not exist');
|
|
27
|
-
if (!fs.statSync(file).isFile()) throw fail('NOT_A_FILE', 'deleteFile supports files only');
|
|
28
|
-
fs.unlinkSync(file);
|
|
23
|
+
exports.deleteFile = (args = {}, context = {}) => {
|
|
24
|
+
const file = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
25
|
+
if (!zero.fs.existsSync(file)) throw fail('FILE_NOT_FOUND', 'file does not exist');
|
|
26
|
+
if (!zero.fs.statSync(file).isFile()) throw fail('NOT_A_FILE', 'deleteFile supports files only');
|
|
27
|
+
zero.fs.unlinkSync(file);
|
|
29
28
|
return { ok: true, path: file, deleted: true };
|
|
30
29
|
};
|
|
31
|
-
|
|
32
|
-
const deleteFileModule = { descriptor, deleteFile };
|
|
33
|
-
Object.assign(exports, deleteFileModule);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const zero = require('../../zero-runtime');
|
|
5
|
-
const pathPolicy = require('./path-policy');
|
|
3
|
+
const zero = require('../../zero');
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
exports.descriptor = zero.descriptor.validate({
|
|
8
7
|
name: 'zero.command.filesystem.exists',
|
|
9
8
|
aliases: ['zero.command.exists'],
|
|
10
9
|
description: 'Check whether a path exists inside allowed roots.',
|
|
@@ -20,10 +19,7 @@ const descriptor = zero.descriptor.validate({
|
|
|
20
19
|
}
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
const pathValue = pathPolicy.resolveAllowed(args.path, context);
|
|
25
|
-
return { ok: true, path: pathValue, exists: fs.existsSync(pathValue) };
|
|
22
|
+
exports.exists = (args = {}, context = {}) => {
|
|
23
|
+
const pathValue = zero.pathPolicy.resolveAllowed(args.path, context);
|
|
24
|
+
return { ok: true, path: pathValue, exists: zero.fs.existsSync(pathValue) };
|
|
26
25
|
};
|
|
27
|
-
|
|
28
|
-
const existsModule = { descriptor, exists };
|
|
29
|
-
Object.assign(exports, existsModule);
|