@melaya/runner 1.1.76 → 1.1.78
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/dist/browserBridge.d.ts +6 -0
- package/dist/browserBridge.js +20 -4
- package/dist/connection.js +45 -15
- package/dist/hostChannel.d.ts +38 -1
- package/dist/hostChannel.js +49 -0
- package/dist/processTree.d.ts +14 -2
- package/dist/processTree.js +28 -4
- package/dist/pythonEnv.js +4 -1
- package/package.json +50 -50
package/dist/browserBridge.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { BrowserGrant } from "./browserGrantVerify.js";
|
|
2
2
|
import { type BrowserSessionRecord, type SpaceSpec } from "./sessionManager.js";
|
|
3
3
|
import { type BrowserEngineId } from "./browserProvisioner.js";
|
|
4
|
+
export declare const BATCH_MAX_STEPS = 5000;
|
|
5
|
+
/** browser.py holds a batch call 165 s: a batch still going at 150 s stops
|
|
6
|
+
* BEFORE its next step and says which steps ran, instead of running on after
|
|
7
|
+
* the caller gave up (a long batch takes minutes at ~1 s a step). The runner
|
|
8
|
+
* has no background run for its own browser: 150 s is this call's real limit. */
|
|
9
|
+
export declare const BATCH_TIME_BUDGET_MS = 150000;
|
|
4
10
|
export interface BrowserRunSpec {
|
|
5
11
|
runId: string;
|
|
6
12
|
grant: BrowserGrant;
|
package/dist/browserBridge.js
CHANGED
|
@@ -436,10 +436,16 @@ function buildDragPath(from, to, steps, moveMs, shape) {
|
|
|
436
436
|
out[out.length - 1] = { x: to.x, y: to.y, dwellMs: last.dwellMs };
|
|
437
437
|
return out;
|
|
438
438
|
}
|
|
439
|
-
// Mirrors shared/tools/browser.py's _BATCH_ALLOWED_KINDS / _BATCH_MAX_STEPS
|
|
440
|
-
// The tool layer validates
|
|
441
|
-
// bridge must not trust a
|
|
442
|
-
|
|
439
|
+
// Mirrors shared/tools/browser.py's _BATCH_ALLOWED_KINDS / _BATCH_MAX_STEPS (5000,
|
|
440
|
+
// the server's BROWSER_BATCH_MAX_STEPS, 2026-10-28). The tool layer validates
|
|
441
|
+
// too; this is the enforcement point, because the bridge must not trust a
|
|
442
|
+
// caller that skipped it.
|
|
443
|
+
export const BATCH_MAX_STEPS = 5000;
|
|
444
|
+
/** browser.py holds a batch call 165 s: a batch still going at 150 s stops
|
|
445
|
+
* BEFORE its next step and says which steps ran, instead of running on after
|
|
446
|
+
* the caller gave up (a long batch takes minutes at ~1 s a step). The runner
|
|
447
|
+
* has no background run for its own browser: 150 s is this call's real limit. */
|
|
448
|
+
export const BATCH_TIME_BUDGET_MS = 150_000;
|
|
443
449
|
const BATCH_ALLOWED_KINDS = new Set([
|
|
444
450
|
"navigate", "back", "forward", "click", "tap", "input_text", "press_key",
|
|
445
451
|
"scroll", "select_option", "submit", "wait", "wait_for_network_idle",
|
|
@@ -1457,7 +1463,17 @@ export async function startBrowserBridge(opts) {
|
|
|
1457
1463
|
throw new BridgeError("act_args_invalid", `batch takes at most ${BATCH_MAX_STEPS} steps; split the flow into several batches`);
|
|
1458
1464
|
}
|
|
1459
1465
|
const done = [];
|
|
1466
|
+
const batchStarted = Date.now();
|
|
1460
1467
|
for (let i = 0; i < rawSteps.length; i++) {
|
|
1468
|
+
if (i > 0 && Date.now() - batchStarted >= BATCH_TIME_BUDGET_MS) {
|
|
1469
|
+
// Out of time: nothing more runs, and the caller learns exactly where it stopped.
|
|
1470
|
+
return {
|
|
1471
|
+
batch: true, steps_run: done.length, steps: done, stopped_at_time_limit: true,
|
|
1472
|
+
note: `The batch stopped at the ${BATCH_TIME_BUDGET_MS / 1000} s limit of one call: steps 1-${done.length} of ` +
|
|
1473
|
+
`${rawSteps.length} ran, steps ${done.length + 1}-${rawSteps.length} were NOT run. Read the page, then send ` +
|
|
1474
|
+
"the remaining steps in another browser_batch call.",
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1461
1477
|
const raw = rawSteps[i];
|
|
1462
1478
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1463
1479
|
throw new BridgeError("act_args_invalid", `batch step ${i} is not an object`);
|
package/dist/connection.js
CHANGED
|
@@ -15,7 +15,7 @@ import chalk from "chalk";
|
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
// Every child spawned here leads its own process group (POSIX) and is tracked,
|
|
17
17
|
// so shutdown and runner:kill stop the whole tree (audit P0.5).
|
|
18
|
-
import { spawnTracked as spawn, terminateTree, terminateAllTracked, runShutdownSteps, formatShutdownResults, claimGracefulShutdown, killAllTrackedSync, setSetupBackground, lowerPriority, } from "./processTree.js";
|
|
18
|
+
import { spawnTracked as spawn, terminateTree, terminateTreeVerified, terminateAllTracked, runShutdownSteps, formatShutdownResults, claimGracefulShutdown, killAllTrackedSync, setSetupBackground, lowerPriority, } from "./processTree.js";
|
|
19
19
|
import { markStage } from "./startupTimings.js";
|
|
20
20
|
import { writeFileSync, mkdirSync, existsSync, readFileSync, copyFileSync } from "fs";
|
|
21
21
|
import { dirname, isAbsolute, join } from "path";
|
|
@@ -51,7 +51,7 @@ import { attachPlanSocketHandlers } from "./stepPlans.js";
|
|
|
51
51
|
// Local REST API); the REST key stays in ~/.melaya-runner/vaults.
|
|
52
52
|
import { ObsidianAppBridge } from "./obsidianApp.js";
|
|
53
53
|
// Desktop app host channel: state changes for the app's tray and web UI (no-op under npx).
|
|
54
|
-
import { hostStatus, isHosted, hostProgress, sendToHost, hostAppVersion } from "./hostChannel.js";
|
|
54
|
+
import { hostStatus, isHosted, hostProgress, sendToHost, hostAppVersion, hostRunnerSource, startHostActivityReporter } from "./hostChannel.js";
|
|
55
55
|
// Desktop app: files this runner wrote are offered to computer control as handles (no-op under npx).
|
|
56
56
|
import { reportHostFile, reportOutputsSince } from "./hostFiles.js";
|
|
57
57
|
// Computer control: computer:command / computer:stop relayed to the desktop app
|
|
@@ -85,6 +85,8 @@ const MAX_TRIGGER_JSON_BYTES = 48 * 1024;
|
|
|
85
85
|
const EVENT_TRIGGERS_CAPABILITY = "event_triggers_v1";
|
|
86
86
|
const HEARTBEAT_INTERVAL = 30_000;
|
|
87
87
|
const activeProcesses = new Map();
|
|
88
|
+
// Runs the server cancelled (runner:kill): their exit reports "canceled".
|
|
89
|
+
const cancelledRunIds = new Set();
|
|
88
90
|
// Melaya Browser: the versioned capability advertised in the runner hello
|
|
89
91
|
// (plan Section 4). Bumped when the browser wire contract changes.
|
|
90
92
|
const BROWSER_CONTROL_VERSION = 1;
|
|
@@ -483,6 +485,9 @@ export async function connect(opts) {
|
|
|
483
485
|
// request needs the runtime first, it does the work itself and this never
|
|
484
486
|
// runs (the request path already ensured the same things).
|
|
485
487
|
const _runtimeIdle = () => activeProcesses.size === 0 && activeAssistants.size === 0 && bootingAssistants.size === 0;
|
|
488
|
+
// Desktop app: busy / idle on change, so a newer downloaded runner replaces
|
|
489
|
+
// this one only while nothing runs (no-op outside the app).
|
|
490
|
+
startHostActivityReporter(() => !_runtimeIdle());
|
|
486
491
|
let bgTimer = null;
|
|
487
492
|
const scheduleBackgroundSetup = () => {
|
|
488
493
|
if (bgSetup || bgTimer || foregroundDemand)
|
|
@@ -626,7 +631,8 @@ export async function connect(opts) {
|
|
|
626
631
|
// the server keeps that computer's app version current on every start
|
|
627
632
|
// (the token row is minted once) and knows this runner updates with the
|
|
628
633
|
// app, never with a restart command.
|
|
629
|
-
|
|
634
|
+
// runnerSource: the app's bundled runner or a signed runner bundle it downloaded.
|
|
635
|
+
...(isHosted() ? { host: { kind: "desktop", ...(hostAppVersion() ? { appVersion: hostAppVersion() } : {}), ...(hostRunnerSource() ? { runnerSource: hostRunnerSource() } : {}) } } : {}),
|
|
630
636
|
});
|
|
631
637
|
// Index statuses produced while disconnected (or before this socket).
|
|
632
638
|
for (const ev of vaultService.latestStatuses())
|
|
@@ -867,6 +873,21 @@ export async function connect(opts) {
|
|
|
867
873
|
socket.emit("runner:runComplete", { runId: payload.runId, status: "failed" });
|
|
868
874
|
return;
|
|
869
875
|
}
|
|
876
|
+
// Register the run in the DB + set the active project for event relay
|
|
877
|
+
// FIRST, before any pre-start work (browser grant, shared modules, Python
|
|
878
|
+
// env bootstrap, model preflight). The server drops every event of a run
|
|
879
|
+
// that is not registered: registering after the env bootstrap made its
|
|
880
|
+
// progress invisible and a bootstrap failure (its message and
|
|
881
|
+
// runComplete) vanish without a trace (Mac runner, prod 2026-10-27).
|
|
882
|
+
setActiveProject(payload.project);
|
|
883
|
+
socket.emit("runner:registerRun", {
|
|
884
|
+
runId: payload.runId,
|
|
885
|
+
project: payload.project,
|
|
886
|
+
pipelineName: payload.pipelineName,
|
|
887
|
+
...(typeof payload.openedFrom === "string" && /^[a-z][a-z_]{0,15}$/.test(payload.openedFrom)
|
|
888
|
+
? { openedFrom: payload.openedFrom }
|
|
889
|
+
: {}),
|
|
890
|
+
});
|
|
870
891
|
// ── Melaya Browser grant verification + bridge start ──────────────
|
|
871
892
|
// (plan Section 4) A browser run carries a signed browserGrant. We
|
|
872
893
|
// VERIFY it BEFORE any Python spawns and reject on ANY failure. The
|
|
@@ -987,16 +1008,7 @@ export async function connect(opts) {
|
|
|
987
1008
|
}
|
|
988
1009
|
// Replace the system python with the venv python for THIS spawn.
|
|
989
1010
|
const pipelinePython = envResult.pythonPath;
|
|
990
|
-
//
|
|
991
|
-
setActiveProject(payload.project);
|
|
992
|
-
socket.emit("runner:registerRun", {
|
|
993
|
-
runId: payload.runId,
|
|
994
|
-
project: payload.project,
|
|
995
|
-
pipelineName: payload.pipelineName,
|
|
996
|
-
...(typeof payload.openedFrom === "string" && /^[a-z][a-z_]{0,15}$/.test(payload.openedFrom)
|
|
997
|
-
? { openedFrom: payload.openedFrom }
|
|
998
|
-
: {}),
|
|
999
|
-
});
|
|
1011
|
+
// (runner:registerRun was sent at the top of this handler.)
|
|
1000
1012
|
// Write main.py to temp dir
|
|
1001
1013
|
const runDir = join(tmpdir(), `melaya-run-${payload.runId}`);
|
|
1002
1014
|
mkdirSync(runDir, { recursive: true });
|
|
@@ -1372,7 +1384,8 @@ export async function connect(opts) {
|
|
|
1372
1384
|
if (browserBridge && browserBridge.hasRun(payload.runId)) {
|
|
1373
1385
|
browserBridge.teardownRun(payload.runId, `run_exit:${code}`).catch(() => { });
|
|
1374
1386
|
}
|
|
1375
|
-
|
|
1387
|
+
// A run the server cancelled reports "canceled", whatever its exit code.
|
|
1388
|
+
const status = cancelledRunIds.delete(payload.runId) ? "canceled" : code === 0 ? "done" : "failed";
|
|
1376
1389
|
// On non-zero exit, relay the captured stderr tail to the FE
|
|
1377
1390
|
// as a system agent_message so the operator sees the actual
|
|
1378
1391
|
// failure reason. Without this, a Python crash before
|
|
@@ -2118,6 +2131,15 @@ export async function connect(opts) {
|
|
|
2118
2131
|
_spareBundle = v;
|
|
2119
2132
|
}).catch(() => { });
|
|
2120
2133
|
};
|
|
2134
|
+
// Desktop app only: the server announced a signed runner bundle. The app
|
|
2135
|
+
// re-reads GET /api/v1/desktop/config itself (the one source); this only
|
|
2136
|
+
// says "look now" instead of at the next 6-hourly check.
|
|
2137
|
+
socket.on("runner:bundle", (p) => {
|
|
2138
|
+
if (!isHosted())
|
|
2139
|
+
return;
|
|
2140
|
+
const v = typeof p?.version === "string" && /^\d{1,4}\.\d{1,5}\.\d{1,6}$/.test(p.version) ? p.version : undefined;
|
|
2141
|
+
sendToHost({ type: "bundle_hint", ...(v ? { version: v } : {}) });
|
|
2142
|
+
});
|
|
2121
2143
|
// Server push: download a new shared bundle in the background (after an
|
|
2122
2144
|
// agents deploy, and on connect) so the next chat does not pay for it.
|
|
2123
2145
|
socket.on("runner:prefetch_bundle", (p) => {
|
|
@@ -3318,7 +3340,15 @@ export async function connect(opts) {
|
|
|
3318
3340
|
const proc = activeProcesses.get(data.runId);
|
|
3319
3341
|
if (proc) {
|
|
3320
3342
|
// The whole tree: a run's own children (CLI providers, browsers) too.
|
|
3321
|
-
|
|
3343
|
+
// Verified: a kill that did not take is retried and reported (run
|
|
3344
|
+
// 939e9d3f6ddb4c45 logged "Killed run" and kept running to the end).
|
|
3345
|
+
cancelledRunIds.add(data.runId);
|
|
3346
|
+
const runId = data.runId;
|
|
3347
|
+
void terminateTreeVerified(proc, { graceMs: SHUTDOWN_CHILD_GRACE_MS }).then((gone) => {
|
|
3348
|
+
if (!gone) {
|
|
3349
|
+
console.log(chalk.red(` ✗ Run ${runId.slice(0, 10)}... is still running after the kill; it stops at its next approval check.`));
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3322
3352
|
activeProcesses.delete(data.runId);
|
|
3323
3353
|
console.log(chalk.yellow(` ■ Killed run ${data.runId.slice(0, 10)}...`));
|
|
3324
3354
|
}
|
package/dist/hostChannel.d.ts
CHANGED
|
@@ -14,6 +14,22 @@
|
|
|
14
14
|
* build (tsc, rootDir src) cannot import that package, so keep both in step.
|
|
15
15
|
*/
|
|
16
16
|
export type HostedRunnerState = "starting" | "online" | "reconnecting" | "blocked" | "error";
|
|
17
|
+
/**
|
|
18
|
+
* Host channel wire version this runner speaks. A signed runner bundle declares
|
|
19
|
+
* it (manifest.hostProtocol, read from dist/hostChannel.js by
|
|
20
|
+
* desktop/scripts/runner-bundle.mjs) and the desktop app runs the bundle only
|
|
21
|
+
* when it supports it. Mirror: RUNNER_HOST_PROTOCOL in packages/desktop-contract.
|
|
22
|
+
* Bump only on a breaking change of the messages below.
|
|
23
|
+
*/
|
|
24
|
+
export declare const HOST_PROTOCOL = 1;
|
|
25
|
+
/**
|
|
26
|
+
* Oldest desktop app that may run this runner as a downloaded bundle
|
|
27
|
+
* (manifest.minAppVersion). 0.1.7 is the first app with the bundle loader;
|
|
28
|
+
* raise it when this runner needs something only a newer app provides.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MIN_HOST_APP_VERSION = "0.1.7";
|
|
31
|
+
/** Where the desktop app took this runner from (its start message). */
|
|
32
|
+
export type HostRunnerSource = "bundled" | "downloaded";
|
|
17
33
|
/** Why a terminal "error" happened, so the app can react without parsing text. */
|
|
18
34
|
export type HostedStatusCode = "token_rejected" | "server_disconnect";
|
|
19
35
|
/** Mirror of the contract's RunnerToHostMessage (computer messages kept loose). */
|
|
@@ -80,16 +96,28 @@ export type RunnerToHostMessage = {
|
|
|
80
96
|
| {
|
|
81
97
|
type: "notice";
|
|
82
98
|
code: "browser_not_installed";
|
|
99
|
+
}
|
|
100
|
+
/** Additive: work in flight (runs, Assistant hosts), sent on change; the app swaps runners only while idle. */
|
|
101
|
+
| {
|
|
102
|
+
type: "activity";
|
|
103
|
+
busy: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** Additive: the server announced a runner bundle (runner:bundle); the app re-reads its config. */
|
|
106
|
+
| {
|
|
107
|
+
type: "bundle_hint";
|
|
108
|
+
version?: string;
|
|
83
109
|
};
|
|
84
110
|
/** Mirror of the contract's HostToRunnerMessage (computer messages kept loose). */
|
|
85
111
|
export type HostToRunnerMessage = {
|
|
86
112
|
type: "stop";
|
|
87
113
|
}
|
|
88
|
-
/** `appVersion`: the desktop app's own version
|
|
114
|
+
/** `appVersion`: the desktop app's own version; `runnerSource`: bundled or downloaded
|
|
115
|
+
* (both additive; reported in runner:hello's `host` block). */
|
|
89
116
|
| {
|
|
90
117
|
type: "start";
|
|
91
118
|
token: string;
|
|
92
119
|
appVersion?: string;
|
|
120
|
+
runnerSource?: HostRunnerSource;
|
|
93
121
|
}
|
|
94
122
|
/** Additive: rescan model providers now and publish the list through runner:models. */
|
|
95
123
|
| {
|
|
@@ -118,6 +146,15 @@ export declare function redactReason(s: string): string;
|
|
|
118
146
|
export declare function hostStatus(state: HostedRunnerState, lastError?: string, code?: HostedStatusCode): void;
|
|
119
147
|
/** Background setup progress for the desktop app. No-op outside the app. */
|
|
120
148
|
export declare function hostProgress(state: "running" | "done" | "failed", step?: string, waiting?: boolean): void;
|
|
149
|
+
/** bundled | downloaded, as the desktop app's start message said; null outside
|
|
150
|
+
* the app and with an app older than the field. */
|
|
151
|
+
export declare function hostRunnerSource(): HostRunnerSource | null;
|
|
152
|
+
/**
|
|
153
|
+
* Reports busy / idle to the desktop app whenever it changes (checked every
|
|
154
|
+
* `intervalMs`), so the app swaps to a newer downloaded runner only while
|
|
155
|
+
* nothing runs. No-op outside the app. Returns a stop function.
|
|
156
|
+
*/
|
|
157
|
+
export declare function startHostActivityReporter(isBusy: () => boolean, intervalMs?: number): () => void;
|
|
121
158
|
/** The desktop app's own version, as its start message said; null outside the
|
|
122
159
|
* app and with an app older than the field. */
|
|
123
160
|
export declare function hostAppVersion(): string | null;
|
package/dist/hostChannel.js
CHANGED
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
* (RunnerToHostMessage / HostToRunnerMessage) and src/computer.ts. The runner
|
|
14
14
|
* build (tsc, rootDir src) cannot import that package, so keep both in step.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Host channel wire version this runner speaks. A signed runner bundle declares
|
|
18
|
+
* it (manifest.hostProtocol, read from dist/hostChannel.js by
|
|
19
|
+
* desktop/scripts/runner-bundle.mjs) and the desktop app runs the bundle only
|
|
20
|
+
* when it supports it. Mirror: RUNNER_HOST_PROTOCOL in packages/desktop-contract.
|
|
21
|
+
* Bump only on a breaking change of the messages below.
|
|
22
|
+
*/
|
|
23
|
+
export const HOST_PROTOCOL = 1;
|
|
24
|
+
/**
|
|
25
|
+
* Oldest desktop app that may run this runner as a downloaded bundle
|
|
26
|
+
* (manifest.minAppVersion). 0.1.7 is the first app with the bundle loader;
|
|
27
|
+
* raise it when this runner needs something only a newer app provides.
|
|
28
|
+
*/
|
|
29
|
+
export const MIN_HOST_APP_VERSION = "0.1.7";
|
|
16
30
|
function parentPort() {
|
|
17
31
|
const p = process.parentPort;
|
|
18
32
|
return p && typeof p.postMessage === "function" ? p : null;
|
|
@@ -89,6 +103,39 @@ export function hostProgress(state, step, waiting) {
|
|
|
89
103
|
});
|
|
90
104
|
}
|
|
91
105
|
let _hostAppVersion = null;
|
|
106
|
+
let _hostRunnerSource = null;
|
|
107
|
+
/** bundled | downloaded, as the desktop app's start message said; null outside
|
|
108
|
+
* the app and with an app older than the field. */
|
|
109
|
+
export function hostRunnerSource() {
|
|
110
|
+
return _hostRunnerSource;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reports busy / idle to the desktop app whenever it changes (checked every
|
|
114
|
+
* `intervalMs`), so the app swaps to a newer downloaded runner only while
|
|
115
|
+
* nothing runs. No-op outside the app. Returns a stop function.
|
|
116
|
+
*/
|
|
117
|
+
export function startHostActivityReporter(isBusy, intervalMs = 5000) {
|
|
118
|
+
if (!isHosted())
|
|
119
|
+
return () => { };
|
|
120
|
+
let last = null;
|
|
121
|
+
const tick = () => {
|
|
122
|
+
let busy = true;
|
|
123
|
+
try {
|
|
124
|
+
busy = isBusy();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
busy = true;
|
|
128
|
+
}
|
|
129
|
+
if (busy === last)
|
|
130
|
+
return;
|
|
131
|
+
last = busy;
|
|
132
|
+
sendToHost({ type: "activity", busy });
|
|
133
|
+
};
|
|
134
|
+
tick();
|
|
135
|
+
const t = setInterval(tick, intervalMs);
|
|
136
|
+
t.unref?.();
|
|
137
|
+
return () => clearInterval(t);
|
|
138
|
+
}
|
|
92
139
|
/** The desktop app's own version, as its start message said; null outside the
|
|
93
140
|
* app and with an app older than the field. */
|
|
94
141
|
export function hostAppVersion() {
|
|
@@ -110,6 +157,8 @@ export function waitForHostToken(timeoutMs = 30_000) {
|
|
|
110
157
|
return;
|
|
111
158
|
const appVersion = msg.appVersion;
|
|
112
159
|
_hostAppVersion = typeof appVersion === "string" && /^[0-9A-Za-z.+-]{1,32}$/.test(appVersion) ? appVersion : null;
|
|
160
|
+
const source = msg.runnerSource;
|
|
161
|
+
_hostRunnerSource = source === "bundled" || source === "downloaded" ? source : null;
|
|
113
162
|
clearTimeout(timer);
|
|
114
163
|
off();
|
|
115
164
|
resolve(token);
|
package/dist/processTree.d.ts
CHANGED
|
@@ -27,11 +27,23 @@ export declare const spawnTracked: typeof rawSpawn;
|
|
|
27
27
|
/**
|
|
28
28
|
* Stop a child and every descendant: a graceful request first, then a forced
|
|
29
29
|
* kill once `graceMs` has passed. Resolves when the tree is gone (or the forced
|
|
30
|
-
* kill was issued). Never throws.
|
|
30
|
+
* kill was issued), with whether the child itself has exited. Never throws.
|
|
31
31
|
*/
|
|
32
32
|
export declare function terminateTree(child: ChildProcess, opts?: {
|
|
33
33
|
graceMs?: number;
|
|
34
|
-
}): Promise<
|
|
34
|
+
}): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Stop a run's process tree and make sure it is gone: terminateTree, then a
|
|
37
|
+
* forced tree kill again, then a direct kill of the child (on Windows the venv
|
|
38
|
+
* launcher kills its interpreter with it). Run 939e9d3f6ddb4c45 was cancelled,
|
|
39
|
+
* the runner logged "Killed run", yet the process kept running for 9 minutes
|
|
40
|
+
* and finished: a single unchecked kill is not enough. Resolves with whether
|
|
41
|
+
* the child has exited. Never throws.
|
|
42
|
+
*/
|
|
43
|
+
export declare function terminateTreeVerified(child: ChildProcess, opts?: {
|
|
44
|
+
graceMs?: number;
|
|
45
|
+
terminate?: typeof terminateTree;
|
|
46
|
+
}): Promise<boolean>;
|
|
35
47
|
/** Stop every tracked child tree, in parallel, within the grace period. */
|
|
36
48
|
export declare function terminateAllTracked(opts?: {
|
|
37
49
|
graceMs?: number;
|
package/dist/processTree.js
CHANGED
|
@@ -96,24 +96,24 @@ function waitExit(child, ms) {
|
|
|
96
96
|
/**
|
|
97
97
|
* Stop a child and every descendant: a graceful request first, then a forced
|
|
98
98
|
* kill once `graceMs` has passed. Resolves when the tree is gone (or the forced
|
|
99
|
-
* kill was issued). Never throws.
|
|
99
|
+
* kill was issued), with whether the child itself has exited. Never throws.
|
|
100
100
|
*/
|
|
101
101
|
export async function terminateTree(child, opts = {}) {
|
|
102
102
|
const graceMs = Math.max(0, opts.graceMs ?? 2000);
|
|
103
103
|
const pid = child?.pid;
|
|
104
104
|
if (typeof pid !== "number")
|
|
105
|
-
return;
|
|
105
|
+
return true;
|
|
106
106
|
if (isWin) {
|
|
107
107
|
if (alive(child)) {
|
|
108
108
|
if (graceMs > 0) {
|
|
109
109
|
await taskkill(pid, false);
|
|
110
110
|
if (await waitExit(child, graceMs))
|
|
111
|
-
return;
|
|
111
|
+
return true;
|
|
112
112
|
}
|
|
113
113
|
await taskkill(pid, true);
|
|
114
114
|
await waitExit(child, 1000);
|
|
115
115
|
}
|
|
116
|
-
return;
|
|
116
|
+
return !alive(child);
|
|
117
117
|
}
|
|
118
118
|
if (graceMs > 0 && alive(child)) {
|
|
119
119
|
signalTree(child, "SIGTERM");
|
|
@@ -131,6 +131,30 @@ export async function terminateTree(child, opts = {}) {
|
|
|
131
131
|
catch { /* gone */ }
|
|
132
132
|
await waitExit(child, 1000);
|
|
133
133
|
}
|
|
134
|
+
return !alive(child);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Stop a run's process tree and make sure it is gone: terminateTree, then a
|
|
138
|
+
* forced tree kill again, then a direct kill of the child (on Windows the venv
|
|
139
|
+
* launcher kills its interpreter with it). Run 939e9d3f6ddb4c45 was cancelled,
|
|
140
|
+
* the runner logged "Killed run", yet the process kept running for 9 minutes
|
|
141
|
+
* and finished: a single unchecked kill is not enough. Resolves with whether
|
|
142
|
+
* the child has exited. Never throws.
|
|
143
|
+
*/
|
|
144
|
+
export async function terminateTreeVerified(child, opts = {}) {
|
|
145
|
+
const terminate = opts.terminate ?? terminateTree;
|
|
146
|
+
if (await terminate(child, { graceMs: opts.graceMs }))
|
|
147
|
+
return true;
|
|
148
|
+
if (await terminate(child, { graceMs: 0 }))
|
|
149
|
+
return true;
|
|
150
|
+
if (alive(child)) {
|
|
151
|
+
try {
|
|
152
|
+
child.kill("SIGKILL");
|
|
153
|
+
}
|
|
154
|
+
catch { /* gone */ }
|
|
155
|
+
await waitExit(child, 2000);
|
|
156
|
+
}
|
|
157
|
+
return !alive(child);
|
|
134
158
|
}
|
|
135
159
|
/** Stop every tracked child tree, in parallel, within the grace period. */
|
|
136
160
|
export async function terminateAllTracked(opts = {}) {
|
package/dist/pythonEnv.js
CHANGED
|
@@ -265,7 +265,10 @@ const PIP_DEPS = [
|
|
|
265
265
|
"segno", // qr_tools (QR generation)
|
|
266
266
|
"PyGithub", // github tool (imports as `github`)
|
|
267
267
|
"python-gitlab", // github tool (imports as `gitlab`)
|
|
268
|
-
|
|
268
|
+
// pandas-ta publishes for Python >= 3.12 only: on a 3.10 / 3.11 host an unmarked
|
|
269
|
+
// entry fails the WHOLE venv install (Mac on Homebrew 3.11, 2026-10-29). The marker
|
|
270
|
+
// skips it there; alphavantage_tools imports it lazily and degrades without it.
|
|
271
|
+
'pandas-ta; python_version >= "3.12"', // alphavantage_tools (TA indicators; pandas already present)
|
|
269
272
|
"python-telegram-bot", // messaging (bot-API rich path; pulls httpx, already present)
|
|
270
273
|
"aiomysql", // database (async MySQL; sync PyMySQL already present)
|
|
271
274
|
];
|
package/package.json
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@melaya/runner",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"author": "Melaya Labs LLC",
|
|
7
|
-
"private": false,
|
|
8
|
-
"type": "module",
|
|
9
|
-
"bin": {
|
|
10
|
-
"melaya-runner": "dist/cli.js"
|
|
11
|
-
},
|
|
12
|
-
"main": "dist/index.js",
|
|
13
|
-
"files": [
|
|
14
|
-
"dist/**/*.js",
|
|
15
|
-
"dist/**/*.d.ts",
|
|
16
|
-
"dist/**/*.py",
|
|
17
|
-
"localRagIngest.py",
|
|
18
|
-
"localRagRetrieve.py",
|
|
19
|
-
"localDbProbe.py",
|
|
20
|
-
"localBacklinkFetch.py",
|
|
21
|
-
"localVault.py",
|
|
22
|
-
"localVaultMirror.py",
|
|
23
|
-
"nltk_data/**",
|
|
24
|
-
"README.md",
|
|
25
|
-
"LICENSE"
|
|
26
|
-
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"build": "tsc && node -e \"const fs=require('fs'); fs.copyFileSync('localRagIngest.py','dist/localRagIngest.py'); fs.copyFileSync('localRagRetrieve.py','dist/localRagRetrieve.py'); fs.copyFileSync('localDbProbe.py','dist/localDbProbe.py'); fs.copyFileSync('localBacklinkFetch.py','dist/localBacklinkFetch.py'); fs.copyFileSync('localVault.py','dist/localVault.py'); fs.copyFileSync('localVaultMirror.py','dist/localVaultMirror.py'); fs.copyFileSync('src/assistantHost.py','dist/assistantHost.py')\"",
|
|
29
|
-
"test": "node --test --import tsx \"src/**/*.test.ts\"",
|
|
30
|
-
"prepublishOnly": "npm run build"
|
|
31
|
-
},
|
|
32
|
-
"dependencies": {
|
|
33
|
-
"chalk": "^5.3.0",
|
|
34
|
-
"commander": "^12.0.0",
|
|
35
|
-
"ora": "^8.0.0",
|
|
36
|
-
"playwright": "^1.48.0",
|
|
37
|
-
"socket.io-client": "^4.8.0"
|
|
38
|
-
},
|
|
39
|
-
"devDependencies": {
|
|
40
|
-
"@types/node": "^20.0.0",
|
|
41
|
-
"tsx": "^4.19.2",
|
|
42
|
-
"typescript": "^5.5.0"
|
|
43
|
-
},
|
|
44
|
-
"engines": {
|
|
45
|
-
"node": ">=18"
|
|
46
|
-
},
|
|
47
|
-
"publishConfig": {
|
|
48
|
-
"access": "public"
|
|
49
|
-
}
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@melaya/runner",
|
|
3
|
+
"version": "1.1.78",
|
|
4
|
+
"description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Melaya Labs LLC",
|
|
7
|
+
"private": false,
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"melaya-runner": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/**/*.js",
|
|
15
|
+
"dist/**/*.d.ts",
|
|
16
|
+
"dist/**/*.py",
|
|
17
|
+
"localRagIngest.py",
|
|
18
|
+
"localRagRetrieve.py",
|
|
19
|
+
"localDbProbe.py",
|
|
20
|
+
"localBacklinkFetch.py",
|
|
21
|
+
"localVault.py",
|
|
22
|
+
"localVaultMirror.py",
|
|
23
|
+
"nltk_data/**",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc && node -e \"const fs=require('fs'); fs.copyFileSync('localRagIngest.py','dist/localRagIngest.py'); fs.copyFileSync('localRagRetrieve.py','dist/localRagRetrieve.py'); fs.copyFileSync('localDbProbe.py','dist/localDbProbe.py'); fs.copyFileSync('localBacklinkFetch.py','dist/localBacklinkFetch.py'); fs.copyFileSync('localVault.py','dist/localVault.py'); fs.copyFileSync('localVaultMirror.py','dist/localVaultMirror.py'); fs.copyFileSync('src/assistantHost.py','dist/assistantHost.py')\"",
|
|
29
|
+
"test": "node --test --import tsx \"src/**/*.test.ts\"",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.3.0",
|
|
34
|
+
"commander": "^12.0.0",
|
|
35
|
+
"ora": "^8.0.0",
|
|
36
|
+
"playwright": "^1.48.0",
|
|
37
|
+
"socket.io-client": "^4.8.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"tsx": "^4.19.2",
|
|
42
|
+
"typescript": "^5.5.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
}
|
|
50
|
+
}
|