@hasna/machines 0.0.45 → 0.0.46
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 +27 -4
- package/dist/agent/index.d.ts +0 -1
- package/dist/agent/index.js +249 -14
- package/dist/agent/runtime.d.ts +0 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +1301 -210
- package/dist/cli-utils.d.ts +0 -1
- package/dist/commands/apps.d.ts +7 -5
- package/dist/commands/backup.d.ts +0 -1
- package/dist/commands/cert.d.ts +0 -1
- package/dist/commands/clipboard-daemon.d.ts +0 -1
- package/dist/commands/clipboard-server.d.ts +0 -1
- package/dist/commands/clipboard.d.ts +0 -1
- package/dist/commands/daemon.d.ts +0 -1
- package/dist/commands/diff.d.ts +0 -1
- package/dist/commands/dns.d.ts +0 -1
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/heal-daemon.d.ts +0 -1
- package/dist/commands/heal.d.ts +0 -1
- package/dist/commands/install-claude.d.ts +5 -3
- package/dist/commands/install-tailscale.d.ts +5 -3
- package/dist/commands/manifest.d.ts +0 -1
- package/dist/commands/mutation-approval.d.ts +54 -0
- package/dist/commands/notifications.d.ts +14 -2
- package/dist/commands/ports.d.ts +0 -1
- package/dist/commands/runtime.d.ts +15 -1
- package/dist/commands/screen.d.ts +4 -1
- package/dist/commands/self-test.d.ts +0 -1
- package/dist/commands/serve.d.ts +0 -1
- package/dist/commands/setup.d.ts +5 -3
- package/dist/commands/ssh.d.ts +8 -1
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/sync.d.ts +5 -3
- package/dist/commands/workspace.d.ts +0 -1
- package/dist/compatibility.d.ts +0 -1
- package/dist/consumer-schema.d.ts +0 -1
- package/dist/consumer.d.ts +0 -1
- package/dist/consumer.js +253 -12
- package/dist/cross-project-types.d.ts +0 -1
- package/dist/db.d.ts +0 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1091 -184
- package/dist/manifests.d.ts +0 -1
- package/dist/mcp/http.d.ts +26 -2
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.js +1004 -162
- package/dist/mcp/server.d.ts +5 -3
- package/dist/paths.d.ts +0 -1
- package/dist/pg-migrations.d.ts +0 -1
- package/dist/redaction.d.ts +0 -1
- package/dist/remote-storage.d.ts +0 -1
- package/dist/remote.d.ts +14 -5
- package/dist/storage-sync.d.ts +0 -1
- package/dist/storage.d.ts +0 -1
- package/dist/storage.js +18 -0
- package/dist/topology.d.ts +0 -1
- package/dist/types.d.ts +3 -1
- package/dist/version.d.ts +0 -1
- package/package.json +5 -3
- package/dist/agent/index.d.ts.map +0 -1
- package/dist/agent/runtime.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli-utils.d.ts.map +0 -1
- package/dist/commands/apps.d.ts.map +0 -1
- package/dist/commands/backup.d.ts.map +0 -1
- package/dist/commands/cert.d.ts.map +0 -1
- package/dist/commands/clipboard-daemon.d.ts.map +0 -1
- package/dist/commands/clipboard-server.d.ts.map +0 -1
- package/dist/commands/clipboard.d.ts.map +0 -1
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/dns.d.ts.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/heal-daemon.d.ts.map +0 -1
- package/dist/commands/heal.d.ts.map +0 -1
- package/dist/commands/install-claude.d.ts.map +0 -1
- package/dist/commands/install-tailscale.d.ts.map +0 -1
- package/dist/commands/manifest.d.ts.map +0 -1
- package/dist/commands/notifications.d.ts.map +0 -1
- package/dist/commands/ports.d.ts.map +0 -1
- package/dist/commands/runtime.d.ts.map +0 -1
- package/dist/commands/screen.d.ts.map +0 -1
- package/dist/commands/self-test.d.ts.map +0 -1
- package/dist/commands/serve.d.ts.map +0 -1
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/ssh.d.ts.map +0 -1
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/workspace.d.ts.map +0 -1
- package/dist/compatibility.d.ts.map +0 -1
- package/dist/consumer-schema.d.ts.map +0 -1
- package/dist/consumer.d.ts.map +0 -1
- package/dist/cross-project-types.d.ts.map +0 -1
- package/dist/db.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/manifests.d.ts.map +0 -1
- package/dist/mcp/http.d.ts.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/pg-migrations.d.ts.map +0 -1
- package/dist/redaction.d.ts.map +0 -1
- package/dist/remote-storage.d.ts.map +0 -1
- package/dist/remote.d.ts.map +0 -1
- package/dist/storage-sync.d.ts.map +0 -1
- package/dist/storage.d.ts.map +0 -1
- package/dist/topology.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/version.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -23,6 +23,12 @@ Endpoints on `127.0.0.1` only:
|
|
|
23
23
|
- `GET /health` → `{"status":"ok","name":"machines"}`
|
|
24
24
|
- `POST /mcp` → MCP Streamable HTTP
|
|
25
25
|
|
|
26
|
+
HTTP mode rejects browser requests with untrusted `Origin` headers, caps JSON
|
|
27
|
+
bodies at `MACHINES_HTTP_MAX_BODY_BYTES` (default 1 MiB), and requires either
|
|
28
|
+
`MACHINES_API_KEY` or loopback-only `MACHINES_ALLOW_UNAUTHENTICATED=1`. Use
|
|
29
|
+
`MACHINES_HTTP_ALLOWED_ORIGINS=https://ops.example` for an explicit browser
|
|
30
|
+
origin allowlist.
|
|
31
|
+
|
|
26
32
|
## Manifest
|
|
27
33
|
|
|
28
34
|
`machines.json` is the desired fleet declaration.
|
|
@@ -373,6 +379,7 @@ machines install-tailscale --machine mac-lab-01 --json
|
|
|
373
379
|
|
|
374
380
|
```bash
|
|
375
381
|
machines notifications add --id ops --type webhook --target https://example.com/hook --event sync_failed
|
|
382
|
+
machines notifications add --id cmd --type command --target /bin/sh --arg -c --arg 'printf "%s\n" "$HASNA_MACHINES_NOTIFICATION_EVENT"'
|
|
376
383
|
machines notifications list
|
|
377
384
|
machines notifications test --channel ops
|
|
378
385
|
machines notifications test --channel ops --apply --yes
|
|
@@ -381,7 +388,9 @@ machines notifications dispatch --event manual.test --message "hello fleet"
|
|
|
381
388
|
|
|
382
389
|
- `email` channels deliver through local `sendmail` or `mail` when available
|
|
383
390
|
- `webhook` channels deliver JSON via HTTP POST
|
|
384
|
-
- `command` channels execute
|
|
391
|
+
- `command` channels execute an explicit command executable plus optional `--arg`
|
|
392
|
+
values with `HASNA_MACHINES_NOTIFICATION_*` env vars; use `/bin/sh -c ...`
|
|
393
|
+
explicitly if a shell is required
|
|
385
394
|
|
|
386
395
|
## Runtime Events
|
|
387
396
|
|
|
@@ -390,19 +399,31 @@ events without sending keys, killing panes, or changing tmux state.
|
|
|
390
399
|
|
|
391
400
|
```bash
|
|
392
401
|
machines runtime tmux-watch %11 --once --json
|
|
393
|
-
machines runtime tmux-watch session:0.1 --interval-ms 5000
|
|
402
|
+
machines runtime tmux-watch session:0.1 --interval-ms 5000 --approval-token "$TOKEN"
|
|
403
|
+
machines runtime tmux-hook-plan --trusted-local-mutation --json
|
|
404
|
+
machines runtime tmux-hook-plan --approval-token "$TOKEN"
|
|
394
405
|
machines webhooks add https://example.com/hook --id tmux-alerts --type machines.tmux.pane_died
|
|
395
406
|
```
|
|
396
407
|
|
|
397
408
|
When a pane was present and later disappears, the command records
|
|
398
409
|
`machines.tmux.pane_died`. With `--once`, a missing pane records
|
|
399
410
|
`machines.tmux.pane_missing`; add `--no-deliver` to record without webhook
|
|
400
|
-
delivery.
|
|
411
|
+
delivery. Runtime event delivery requires a scoped mutation approval token; local
|
|
412
|
+
no-deliver recording remains available for diagnostics.
|
|
413
|
+
|
|
414
|
+
`machines runtime tmux-hook-plan` prints a native tmux `pane-died` hook command
|
|
415
|
+
for operators that prefer tmux hooks over polling. It is read-only and does not
|
|
416
|
+
install hooks. Pass `--approval-token` when you want the generated hook command
|
|
417
|
+
to be scoped to a short-lived approval token, or pass
|
|
418
|
+
`--trusted-local-mutation` to generate a process-local
|
|
419
|
+
`HASNA_MACHINES_ALLOW_MUTATIONS=1` prefix for local event recording.
|
|
401
420
|
|
|
402
421
|
## Dashboard
|
|
403
422
|
|
|
404
423
|
```bash
|
|
405
424
|
machines serve --json
|
|
425
|
+
machines serve --port 7676
|
|
426
|
+
# Explicitly expose beyond loopback only on a trusted network:
|
|
406
427
|
machines serve --host 0.0.0.0 --port 7676
|
|
407
428
|
```
|
|
408
429
|
|
|
@@ -416,13 +437,15 @@ The dashboard exposes:
|
|
|
416
437
|
- `/api/daemon/status` daemon heartbeat rows
|
|
417
438
|
- `/api/manifest` current manifest JSON
|
|
418
439
|
- `/api/notifications` notification channel JSON
|
|
440
|
+
- `/api/webhooks` shared event webhook channel JSON
|
|
441
|
+
- `/api/events` shared event JSON
|
|
419
442
|
- `/api/doctor` doctor report JSON
|
|
420
443
|
- `/api/self-test` smoke-check JSON
|
|
421
444
|
- `/api/apps/status` app inventory JSON
|
|
422
445
|
- `/api/apps/diff` app drift JSON
|
|
423
446
|
- `/api/install-claude/status` CLI inventory JSON
|
|
424
447
|
- `/api/install-claude/diff` CLI drift JSON
|
|
425
|
-
- `/api/notifications/test` POST
|
|
448
|
+
- `/api/events`, `/api/notifications/test`, `/api/webhooks/test` POST mutation routes require scoped dashboard mutation approval tokens
|
|
426
449
|
|
|
427
450
|
## Local development
|
|
428
451
|
|
package/dist/agent/index.d.ts
CHANGED
package/dist/agent/index.js
CHANGED
|
@@ -6999,6 +6999,7 @@ class SqliteAdapter {
|
|
|
6999
6999
|
raw;
|
|
7000
7000
|
constructor(path) {
|
|
7001
7001
|
this.raw = new Database(path);
|
|
7002
|
+
this.raw.exec("PRAGMA busy_timeout = 5000");
|
|
7002
7003
|
}
|
|
7003
7004
|
close() {
|
|
7004
7005
|
this.raw.close();
|
|
@@ -7064,6 +7065,23 @@ function createTables(db) {
|
|
|
7064
7065
|
updated_at TEXT NOT NULL
|
|
7065
7066
|
)
|
|
7066
7067
|
`);
|
|
7068
|
+
db.exec(`
|
|
7069
|
+
CREATE TABLE IF NOT EXISTS mutation_approval_nonces (
|
|
7070
|
+
nonce_sha256 TEXT PRIMARY KEY,
|
|
7071
|
+
token_sha256 TEXT NOT NULL,
|
|
7072
|
+
surface TEXT NOT NULL,
|
|
7073
|
+
operation TEXT NOT NULL,
|
|
7074
|
+
caller_id TEXT NOT NULL,
|
|
7075
|
+
run_id TEXT NOT NULL,
|
|
7076
|
+
transport TEXT NOT NULL,
|
|
7077
|
+
expires_at INTEGER NOT NULL,
|
|
7078
|
+
used_at INTEGER NOT NULL
|
|
7079
|
+
)
|
|
7080
|
+
`);
|
|
7081
|
+
db.exec(`
|
|
7082
|
+
CREATE INDEX IF NOT EXISTS mutation_approval_nonces_expires_at_idx
|
|
7083
|
+
ON mutation_approval_nonces (expires_at)
|
|
7084
|
+
`);
|
|
7067
7085
|
}
|
|
7068
7086
|
function migrateAgentHeartbeats(db) {
|
|
7069
7087
|
const columns = db.query("PRAGMA table_info(agent_heartbeats)").all();
|
|
@@ -11387,7 +11405,9 @@ function readManifestWithSource(options = {}) {
|
|
|
11387
11405
|
|
|
11388
11406
|
// src/remote.ts
|
|
11389
11407
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11390
|
-
import {
|
|
11408
|
+
import { existsSync as existsSync5, mkdtempSync, readFileSync as readFileSync3, rmSync } from "fs";
|
|
11409
|
+
import { hostname as hostname3, tmpdir } from "os";
|
|
11410
|
+
import { join as join3 } from "path";
|
|
11391
11411
|
|
|
11392
11412
|
// src/topology.ts
|
|
11393
11413
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -11845,6 +11865,16 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11845
11865
|
function shellQuote(value) {
|
|
11846
11866
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11847
11867
|
}
|
|
11868
|
+
function validateSshTarget(target) {
|
|
11869
|
+
const trimmed = target.trim();
|
|
11870
|
+
if (!trimmed || trimmed.startsWith("-") || /[\s"'`$\\;&|<>()[\]{}]/.test(trimmed)) {
|
|
11871
|
+
throw new Error(`Unsafe SSH target: ${target}`);
|
|
11872
|
+
}
|
|
11873
|
+
if (!/^(?:[A-Za-z0-9._%+-]+@)?[A-Za-z0-9._:-]+$/.test(trimmed)) {
|
|
11874
|
+
throw new Error(`Unsafe SSH target: ${target}`);
|
|
11875
|
+
}
|
|
11876
|
+
return trimmed;
|
|
11877
|
+
}
|
|
11848
11878
|
function resolveSshTarget(machineId, options = {}) {
|
|
11849
11879
|
const resolved = resolveMachineRoute(machineId, options);
|
|
11850
11880
|
if (!resolved.ok || !resolved.target) {
|
|
@@ -11855,15 +11885,22 @@ function resolveSshTarget(machineId, options = {}) {
|
|
|
11855
11885
|
}
|
|
11856
11886
|
return {
|
|
11857
11887
|
machineId: resolved.machine_id ?? machineId,
|
|
11858
|
-
target: resolved.command_target ?? resolved.target,
|
|
11888
|
+
target: validateSshTarget(resolved.command_target ?? resolved.target),
|
|
11859
11889
|
route: resolved.route,
|
|
11860
11890
|
confidence: resolved.confidence,
|
|
11861
11891
|
warnings: resolved.warnings
|
|
11862
11892
|
};
|
|
11863
11893
|
}
|
|
11864
|
-
function
|
|
11894
|
+
function buildSshCommandPlan(machineId, remoteCommand, options = {}) {
|
|
11865
11895
|
const resolved = resolveSshTarget(machineId, options);
|
|
11866
|
-
|
|
11896
|
+
const args = remoteCommand ? [resolved.target, remoteCommand] : [resolved.target];
|
|
11897
|
+
const shellCommand = `ssh ${args.map(shellQuote).join(" ")}`;
|
|
11898
|
+
return {
|
|
11899
|
+
...resolved,
|
|
11900
|
+
command: "ssh",
|
|
11901
|
+
args,
|
|
11902
|
+
shellCommand
|
|
11903
|
+
};
|
|
11867
11904
|
}
|
|
11868
11905
|
|
|
11869
11906
|
// src/remote.ts
|
|
@@ -11875,35 +11912,233 @@ function machineIsLocal(machineId, localMachineId) {
|
|
|
11875
11912
|
}
|
|
11876
11913
|
function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
|
|
11877
11914
|
if (machineIsLocal(machineId, localMachineId)) {
|
|
11878
|
-
return { source: "local", shellCommand: command };
|
|
11915
|
+
return { source: "local", command: "bash", args: ["-c", command], shellCommand: command, usesShell: true };
|
|
11879
11916
|
}
|
|
11880
11917
|
try {
|
|
11918
|
+
const plan = buildSshCommandPlan(machineId, command);
|
|
11881
11919
|
return {
|
|
11882
|
-
source:
|
|
11883
|
-
|
|
11920
|
+
source: plan.route,
|
|
11921
|
+
command: plan.command,
|
|
11922
|
+
args: plan.args,
|
|
11923
|
+
shellCommand: plan.shellCommand,
|
|
11924
|
+
usesShell: false
|
|
11884
11925
|
};
|
|
11885
11926
|
} catch (error) {
|
|
11886
11927
|
const message = String(error.message ?? error);
|
|
11887
11928
|
if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
|
|
11888
|
-
|
|
11929
|
+
const target = validateSshTarget(machineId);
|
|
11930
|
+
return {
|
|
11931
|
+
source: "ssh",
|
|
11932
|
+
command: "ssh",
|
|
11933
|
+
args: [target, command],
|
|
11934
|
+
shellCommand: `ssh ${shellQuote2(target)} ${shellQuote2(command)}`,
|
|
11935
|
+
usesShell: false
|
|
11936
|
+
};
|
|
11889
11937
|
}
|
|
11890
11938
|
throw error;
|
|
11891
11939
|
}
|
|
11892
11940
|
}
|
|
11893
|
-
function runMachineCommand(machineId, command) {
|
|
11941
|
+
function runMachineCommand(machineId, command, options = {}) {
|
|
11894
11942
|
const resolved = resolveMachineCommand(machineId, command);
|
|
11895
|
-
|
|
11943
|
+
if (options.timeoutMs && options.timeoutMs > 0 && process.platform !== "win32") {
|
|
11944
|
+
return runMachineCommandWithProcessGroupTimeout(machineId, resolved, options);
|
|
11945
|
+
}
|
|
11946
|
+
const result = spawnSync2(resolved.command, resolved.args, {
|
|
11896
11947
|
encoding: "utf8",
|
|
11897
|
-
env: process.env
|
|
11948
|
+
env: process.env,
|
|
11949
|
+
timeout: options.timeoutMs,
|
|
11950
|
+
killSignal: "SIGTERM"
|
|
11898
11951
|
});
|
|
11952
|
+
const timedOut = Boolean(result.error && "code" in result.error && result.error.code === "ETIMEDOUT");
|
|
11953
|
+
const timeoutMessage = timedOut ? `Command timed out after ${options.timeoutMs}ms.` : "";
|
|
11954
|
+
const stderr = [result.stderr || "", timeoutMessage].filter(Boolean).join(result.stderr ? `
|
|
11955
|
+
` : "");
|
|
11899
11956
|
return {
|
|
11900
11957
|
machineId,
|
|
11901
11958
|
source: resolved.source,
|
|
11902
11959
|
stdout: result.stdout || "",
|
|
11903
|
-
stderr
|
|
11904
|
-
exitCode: result.status ?? 1
|
|
11905
|
-
|
|
11960
|
+
stderr,
|
|
11961
|
+
exitCode: timedOut ? 124 : result.status ?? 1,
|
|
11962
|
+
timedOut,
|
|
11963
|
+
signal: result.signal
|
|
11964
|
+
};
|
|
11965
|
+
}
|
|
11966
|
+
function runMachineCommandWithProcessGroupTimeout(machineId, resolved, options) {
|
|
11967
|
+
const timeoutMs = Math.max(1, options.timeoutMs ?? 1);
|
|
11968
|
+
const killGraceMs = Math.max(1, options.killGraceMs ?? 1000);
|
|
11969
|
+
const helperDir = mkdtempSync(join3(tmpdir(), "machines-timeout-helper-"));
|
|
11970
|
+
const pgidFile = join3(helperDir, "pgid");
|
|
11971
|
+
const helper = spawnSync2(process.execPath, ["--eval", PROCESS_GROUP_TIMEOUT_HELPER], {
|
|
11972
|
+
input: JSON.stringify({ command: resolved.command, args: resolved.args }),
|
|
11973
|
+
encoding: "utf8",
|
|
11974
|
+
env: {
|
|
11975
|
+
...process.env,
|
|
11976
|
+
HASNA_MACHINES_COMMAND_TIMEOUT_MS: String(timeoutMs),
|
|
11977
|
+
HASNA_MACHINES_COMMAND_KILL_GRACE_MS: String(killGraceMs),
|
|
11978
|
+
HASNA_MACHINES_COMMAND_PGID_FILE: pgidFile
|
|
11979
|
+
},
|
|
11980
|
+
timeout: timeoutMs + killGraceMs + 2000,
|
|
11981
|
+
killSignal: "SIGKILL",
|
|
11982
|
+
maxBuffer: 64 * 1024 * 1024
|
|
11983
|
+
});
|
|
11984
|
+
try {
|
|
11985
|
+
const parsed = parseHelperResult(helper.stdout);
|
|
11986
|
+
if (parsed) {
|
|
11987
|
+
return {
|
|
11988
|
+
machineId,
|
|
11989
|
+
source: resolved.source,
|
|
11990
|
+
stdout: parsed.stdout,
|
|
11991
|
+
stderr: parsed.stderr,
|
|
11992
|
+
exitCode: parsed.exitCode,
|
|
11993
|
+
timedOut: parsed.timedOut,
|
|
11994
|
+
signal: parsed.signal
|
|
11995
|
+
};
|
|
11996
|
+
}
|
|
11997
|
+
const helperTimedOut = Boolean(helper.error && "code" in helper.error && helper.error.code === "ETIMEDOUT");
|
|
11998
|
+
if (helperTimedOut)
|
|
11999
|
+
killPublishedProcessGroup(pgidFile);
|
|
12000
|
+
const timeoutMessage = helperTimedOut ? `Command timed out after ${timeoutMs}ms; timeout helper exceeded cleanup grace ${killGraceMs}ms.` : "";
|
|
12001
|
+
const stderr = [helper.stderr || "", timeoutMessage].filter(Boolean).join(helper.stderr ? `
|
|
12002
|
+
` : "");
|
|
12003
|
+
return {
|
|
12004
|
+
machineId,
|
|
12005
|
+
source: resolved.source,
|
|
12006
|
+
stdout: "",
|
|
12007
|
+
stderr,
|
|
12008
|
+
exitCode: helperTimedOut ? 124 : helper.status ?? 1,
|
|
12009
|
+
timedOut: helperTimedOut,
|
|
12010
|
+
signal: helper.signal
|
|
12011
|
+
};
|
|
12012
|
+
} finally {
|
|
12013
|
+
rmSync(helperDir, { recursive: true, force: true });
|
|
12014
|
+
}
|
|
12015
|
+
}
|
|
12016
|
+
function killPublishedProcessGroup(pgidFile) {
|
|
12017
|
+
if (!existsSync5(pgidFile))
|
|
12018
|
+
return;
|
|
12019
|
+
try {
|
|
12020
|
+
const pid = Number.parseInt(readFileSync3(pgidFile, "utf8").trim(), 10);
|
|
12021
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
12022
|
+
return;
|
|
12023
|
+
process.kill(-pid, "SIGKILL");
|
|
12024
|
+
} catch {}
|
|
11906
12025
|
}
|
|
12026
|
+
function parseHelperResult(stdout) {
|
|
12027
|
+
if (!stdout)
|
|
12028
|
+
return null;
|
|
12029
|
+
try {
|
|
12030
|
+
const parsed = JSON.parse(stdout);
|
|
12031
|
+
if (typeof parsed.stdout !== "string" || typeof parsed.stderr !== "string" || typeof parsed.exitCode !== "number")
|
|
12032
|
+
return null;
|
|
12033
|
+
return {
|
|
12034
|
+
machineId: "",
|
|
12035
|
+
source: "local",
|
|
12036
|
+
stdout: parsed.stdout,
|
|
12037
|
+
stderr: parsed.stderr,
|
|
12038
|
+
exitCode: parsed.exitCode,
|
|
12039
|
+
timedOut: parsed.timedOut === true,
|
|
12040
|
+
signal: typeof parsed.signal === "string" ? parsed.signal : null
|
|
12041
|
+
};
|
|
12042
|
+
} catch {
|
|
12043
|
+
return null;
|
|
12044
|
+
}
|
|
12045
|
+
}
|
|
12046
|
+
var PROCESS_GROUP_TIMEOUT_HELPER = `
|
|
12047
|
+
const { spawn } = require("node:child_process");
|
|
12048
|
+
const { readFileSync, writeFileSync } = require("node:fs");
|
|
12049
|
+
|
|
12050
|
+
const plan = JSON.parse(readFileSync(0, "utf8"));
|
|
12051
|
+
const command = String(plan.command || "");
|
|
12052
|
+
const args = Array.isArray(plan.args) ? plan.args.map(String) : [];
|
|
12053
|
+
const timeoutMs = Math.max(1, Number.parseInt(process.env.HASNA_MACHINES_COMMAND_TIMEOUT_MS || "1", 10));
|
|
12054
|
+
const killGraceMs = Math.max(1, Number.parseInt(process.env.HASNA_MACHINES_COMMAND_KILL_GRACE_MS || "1000", 10));
|
|
12055
|
+
const pgidFile = process.env.HASNA_MACHINES_COMMAND_PGID_FILE || "";
|
|
12056
|
+
let stdout = "";
|
|
12057
|
+
let stderr = "";
|
|
12058
|
+
let timedOut = false;
|
|
12059
|
+
let finished = false;
|
|
12060
|
+
let timeoutTimer;
|
|
12061
|
+
let killTimer;
|
|
12062
|
+
let sigkillSent = false;
|
|
12063
|
+
let pendingExit = null;
|
|
12064
|
+
|
|
12065
|
+
const child = spawn(command, args, {
|
|
12066
|
+
detached: true,
|
|
12067
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
12068
|
+
env: process.env,
|
|
12069
|
+
});
|
|
12070
|
+
|
|
12071
|
+
if (pgidFile && child.pid) {
|
|
12072
|
+
try {
|
|
12073
|
+
writeFileSync(pgidFile, String(child.pid), { mode: 0o600 });
|
|
12074
|
+
} catch {}
|
|
12075
|
+
}
|
|
12076
|
+
|
|
12077
|
+
function appendText(target, chunk) {
|
|
12078
|
+
return target + String(chunk);
|
|
12079
|
+
}
|
|
12080
|
+
|
|
12081
|
+
function killTarget(signal) {
|
|
12082
|
+
if (!child.pid) return;
|
|
12083
|
+
if (process.platform === "win32") {
|
|
12084
|
+
try {
|
|
12085
|
+
process.kill(child.pid, signal);
|
|
12086
|
+
} catch {}
|
|
12087
|
+
return;
|
|
12088
|
+
}
|
|
12089
|
+
try {
|
|
12090
|
+
process.kill(-child.pid, signal);
|
|
12091
|
+
} catch {}
|
|
12092
|
+
}
|
|
12093
|
+
|
|
12094
|
+
function finish(code, signal) {
|
|
12095
|
+
if (finished) return;
|
|
12096
|
+
if (timedOut && !sigkillSent) {
|
|
12097
|
+
pendingExit = { code, signal };
|
|
12098
|
+
return;
|
|
12099
|
+
}
|
|
12100
|
+
finished = true;
|
|
12101
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
12102
|
+
if (killTimer) clearTimeout(killTimer);
|
|
12103
|
+
if (timedOut) {
|
|
12104
|
+
stderr = [stderr, "Command timed out after " + timeoutMs + "ms."].filter(Boolean).join(stderr ? "\\n" : "");
|
|
12105
|
+
}
|
|
12106
|
+
const exitCode = timedOut ? 124 : code ?? 1;
|
|
12107
|
+
process.stdout.write(JSON.stringify({
|
|
12108
|
+
stdout,
|
|
12109
|
+
stderr,
|
|
12110
|
+
exitCode,
|
|
12111
|
+
timedOut,
|
|
12112
|
+
signal: signal ?? null,
|
|
12113
|
+
}), () => process.exit(exitCode));
|
|
12114
|
+
}
|
|
12115
|
+
|
|
12116
|
+
child.stdout.setEncoding("utf8");
|
|
12117
|
+
child.stderr.setEncoding("utf8");
|
|
12118
|
+
child.stdout.on("data", (chunk) => { stdout = appendText(stdout, chunk); });
|
|
12119
|
+
child.stderr.on("data", (chunk) => { stderr = appendText(stderr, chunk); });
|
|
12120
|
+
let childExit = { code: null, signal: null };
|
|
12121
|
+
child.on("error", (error) => {
|
|
12122
|
+
stderr = [stderr, error instanceof Error ? error.message : String(error)].filter(Boolean).join(stderr ? "\\n" : "");
|
|
12123
|
+
finish(1, null);
|
|
12124
|
+
});
|
|
12125
|
+
child.on("exit", (code, signal) => {
|
|
12126
|
+
childExit = { code, signal };
|
|
12127
|
+
});
|
|
12128
|
+
child.on("close", (code, signal) => {
|
|
12129
|
+
finish(code ?? childExit.code, signal ?? childExit.signal);
|
|
12130
|
+
});
|
|
12131
|
+
|
|
12132
|
+
timeoutTimer = setTimeout(() => {
|
|
12133
|
+
timedOut = true;
|
|
12134
|
+
killTarget("SIGTERM");
|
|
12135
|
+
killTimer = setTimeout(() => {
|
|
12136
|
+
sigkillSent = true;
|
|
12137
|
+
killTarget("SIGKILL");
|
|
12138
|
+
if (pendingExit) finish(pendingExit.code, pendingExit.signal);
|
|
12139
|
+
}, killGraceMs);
|
|
12140
|
+
}, timeoutMs);
|
|
12141
|
+
`;
|
|
11907
12142
|
|
|
11908
12143
|
// src/commands/doctor.ts
|
|
11909
12144
|
var DOCTOR_OPTIONAL_ADAPTER_DOMAINS = ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
|
package/dist/agent/runtime.d.ts
CHANGED
|
@@ -37,4 +37,3 @@ export declare function writeHeartbeat(status?: "online" | "offline", options?:
|
|
|
37
37
|
export declare function writeHeartbeatTick(status?: "online" | "offline", options?: AgentTickOptions): Promise<AgentRuntimeStatus>;
|
|
38
38
|
export declare function markOffline(options?: AgentRuntimeOptions): AgentRuntimeStatus;
|
|
39
39
|
export declare function getAgentStatus(machineId?: string, options?: AgentStatusOptions): AgentRuntimeStatus[];
|
|
40
|
-
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/cli/index.d.ts
CHANGED