@intentic/constants 1.389.0 → 1.391.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/package.json +1 -1
- package/src/heavy/heavy-exec.cjs +30 -3
- package/src/heavy/heavy-rules.cjs +41 -1
- package/src/heavy/heavy-rules.d.cts +3 -0
- package/src/memory-room.d.mts +14 -2
- package/src/memory-room.mjs +32 -0
package/README.md
CHANGED
|
@@ -31,13 +31,20 @@ flowchart LR
|
|
|
31
31
|
on any Node an agent's project pins. A rule may also match the program's working directory (`cwd`), name its memory
|
|
32
32
|
price (`size`), and be judged under a program that already holds a slot (`nests`): the editor's `vue-tsc` takes the
|
|
33
33
|
`vue-typecheck` lane, one at a time, from under a `pnpm -r typecheck`. Only a pool no `nests` rule names may wait on
|
|
34
|
-
one that does, so the waits cannot form a cycle.
|
|
34
|
+
one that does, so the waits cannot form a cycle. `cargoBuildArgs` translates supported `cargo run` options into
|
|
35
|
+
a build; `heavy-exec.cjs` queues that build in a child and starts the original run once its slot is released.
|
|
36
|
+
Unknown arguments retain the original invocation. The prebuild stays local even when ordinary Cargo builds
|
|
37
|
+
are offloaded, so its artifacts warm the original run.
|
|
35
38
|
- `memory-room`'s `SIZE_BYTES` prices a toolchain command by its measured shape instead of its class: `targeted` (a test
|
|
36
39
|
run aimed at named files), `editor-typecheck` (the editor's full `vue-tsc`, 7 GiB) and `scoped-typecheck` (`lsp diag`
|
|
37
|
-
on a Vue package, 4 GiB).
|
|
40
|
+
on a Vue package, 4 GiB), and `cargo-build` (7 GiB per admission, based on Shinobi's 2026-11-02 Rust build peaks).
|
|
41
|
+
Two Cargo reservations fit an 18 GiB sandbox with headroom; the price does not cap compiler RSS.
|
|
38
42
|
- `memory-room` is the one formula for whether the sandbox has room (limit, used, free, stall, and what each workload
|
|
39
43
|
class costs). The daemon's resource budget judges by it, and it is also the `memory-room` command `queue-run` asks:
|
|
40
|
-
it asks the daemon's room socket, and applies the formula itself where no daemon answers.
|
|
44
|
+
it asks the daemon's room socket, and applies the formula itself where no daemon answers. (2026-11-02) A reading may
|
|
45
|
+
carry `host`, the machine the sandbox runs on as the daemon last heard from it: work nobody waits on then also waits
|
|
46
|
+
while its cost would take that machine under `host.reserveBytes`. Only the daemon adds it, so the fallback formula
|
|
47
|
+
never has a host to answer to.
|
|
41
48
|
- `dev-previews` is the record of a conversation's managed dev preview (its demo server): where the records live
|
|
42
49
|
(`/tmp/intentic-previews`), what makes one trustworthy (pid, start tick, conversation stamp, pid namespace), the cap
|
|
43
50
|
of three and the ten-minute idle bound, the conversation's own port in 47150-47199, and how a preview is stopped
|
package/package.json
CHANGED
package/src/heavy/heavy-exec.cjs
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
const { accessSync, constants } = require("node:fs");
|
|
9
9
|
const { delimiter, join, resolve } = require("node:path");
|
|
10
|
-
const {
|
|
10
|
+
const { spawnSync } = require("node:child_process");
|
|
11
|
+
const { constants: osConstants } = require("node:os");
|
|
12
|
+
const { takeTurn, pendingSpec } = require("./heavy-turn.cjs");
|
|
13
|
+
const { cargoBuildArgs, matchInvocation } = require("./heavy-rules.cjs");
|
|
11
14
|
|
|
12
15
|
const [shims = "", program = "", ...args] = process.argv.slice(2);
|
|
13
16
|
|
|
@@ -30,8 +33,33 @@ if (real === undefined) {
|
|
|
30
33
|
process.stderr.write(`${program}: command not found\n`);
|
|
31
34
|
process.exit(127);
|
|
32
35
|
}
|
|
36
|
+
// The child holds the slot only while compiling. The original run then starts with its untouched environment,
|
|
37
|
+
// so a server inherits neither the build's slot nor its toolchain priority. This build stays local to warm its artifacts.
|
|
38
|
+
let build;
|
|
39
|
+
let buildEnv = process.env;
|
|
40
|
+
try {
|
|
41
|
+
const spec = pendingSpec();
|
|
42
|
+
const translated = program === "cargo" ? cargoBuildArgs(args) : undefined;
|
|
43
|
+
if (spec !== undefined && translated !== undefined &&
|
|
44
|
+
matchInvocation([program, ...translated].join(" "), spec.rules, undefined, { cwd: process.cwd() })?.id === "cargo") {
|
|
45
|
+
build = translated;
|
|
46
|
+
const offload = { ...spec.offload };
|
|
47
|
+
delete offload.cargo;
|
|
48
|
+
buildEnv = { ...process.env, INTENTIC_HEAVY: JSON.stringify({ ...spec, offload }) };
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// allow(silent-catch): an unreadable table preserves the original invocation
|
|
52
|
+
}
|
|
53
|
+
if (build !== undefined) {
|
|
54
|
+
const compiled = spawnSync(process.execPath, [__filename, shims, program, ...build], { stdio: "inherit", env: buildEnv });
|
|
55
|
+
if (compiled.status !== 0) {
|
|
56
|
+
process.exit(compiled.status ?? (compiled.signal === null ? 1 : 128 + (osConstants.signals[compiled.signal] ?? 0)));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
33
59
|
try {
|
|
34
|
-
|
|
60
|
+
if (build === undefined) {
|
|
61
|
+
takeTurn({ program, args, command: [real, ...args] });
|
|
62
|
+
}
|
|
35
63
|
} catch {
|
|
36
64
|
// allow(silent-catch): a table that does not parse runs the program as if unjudged
|
|
37
65
|
}
|
|
@@ -39,6 +67,5 @@ try {
|
|
|
39
67
|
if (typeof process.execve === "function") {
|
|
40
68
|
process.execve(real, [program, ...args], process.env);
|
|
41
69
|
}
|
|
42
|
-
const { spawnSync } = require("node:child_process");
|
|
43
70
|
const run = spawnSync(real, args, { stdio: "inherit", env: process.env, argv0: program });
|
|
44
71
|
process.exit(run.status ?? 1);
|
|
@@ -113,13 +113,14 @@ const SHIPPED_HEAVY_COMMANDS = Object.freeze({
|
|
|
113
113
|
{ id: "typechecker", pattern: "(?<![-.])\\b(tsc|tsgo|vue-tsc)\\b(?![-.])" },
|
|
114
114
|
{ id: "turbo-fanout", pattern: "\\bturbo\\b.*\\brun\\b.*\\b(build|test|typecheck|check)\\b" },
|
|
115
115
|
{ id: "package-script", pattern: "\\b(pnpm|npm|yarn|bun)\\b.*\\b(test|typecheck|verify|check|build)\\b" },
|
|
116
|
-
{ id: "cargo", pattern: "^cargo\\s+(\\+\\S+\\s+)?(build|test|check|clippy|bench|doc)\\b" },
|
|
116
|
+
{ id: "cargo", pattern: "^cargo\\s+(\\+\\S+\\s+)?(build|test|check|clippy|bench|doc)\\b", size: "cargo-build" },
|
|
117
117
|
]),
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
// Every rule an earlier release shipped, as it shipped it (matched against the agent's shell line then). A stored rule
|
|
121
121
|
// equal to one of these was seeded, not chosen, so converting a stored file drops it and the current rule takes over.
|
|
122
122
|
const EARLIER_SHIPPED_RULES = Object.freeze([
|
|
123
|
+
{ id: "cargo", pattern: "^cargo\\s+(\\+\\S+\\s+)?(build|test|check|clippy|bench|doc)\\b" },
|
|
123
124
|
{
|
|
124
125
|
id: "read-only",
|
|
125
126
|
pattern: "^\\s*(grep|rg|iq|ag|cat|bat|head|tail|less|ls|find|fd|wc|which|echo|git\\s+(log|status|diff|show|blame))\\b",
|
|
@@ -342,6 +343,44 @@ const sizeArgs = (match, invocation) => {
|
|
|
342
343
|
return ["vitest", "package-script"].includes(match.id) && targetedRun(invocation) ? ["--size", "targeted"] : [];
|
|
343
344
|
};
|
|
344
345
|
|
|
346
|
+
// Translate only known Cargo options. Unknown flags, aliases and help requests keep the original invocation.
|
|
347
|
+
// Runtime arguments after `--` belong only to `run`; a toolchain selector belongs before either verb.
|
|
348
|
+
const cargoBuildArgs = (args) => {
|
|
349
|
+
const prefix = args[0]?.startsWith("+") ? [args[0]] : [];
|
|
350
|
+
if (args[prefix.length] !== "run") {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
const build = [...prefix, "build"];
|
|
354
|
+
const values = new Set([
|
|
355
|
+
"--package", "--bin", "--example", "--features", "--profile", "--target", "--target-dir", "--manifest-path",
|
|
356
|
+
"--jobs", "--color", "--message-format", "--config", "--lockfile-path", "-p", "-F", "-j", "-Z",
|
|
357
|
+
]);
|
|
358
|
+
const switches = new Set([
|
|
359
|
+
"--release", "-r", "--all-features", "--no-default-features", "--locked", "--offline", "--frozen",
|
|
360
|
+
"--verbose", "--quiet", "-q", "--timings", "--future-incompat-report", "--ignore-rust-version", "--keep-going",
|
|
361
|
+
]);
|
|
362
|
+
for (let i = prefix.length + 1; i < args.length; i++) {
|
|
363
|
+
const arg = args[i];
|
|
364
|
+
if (arg === "--") {
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
if (switches.has(arg) || /^-v+$/u.test(arg) || /^-(p|F|j|Z).+/u.test(arg)) {
|
|
368
|
+
build.push(arg);
|
|
369
|
+
} else if (values.has(arg)) {
|
|
370
|
+
const value = args[++i];
|
|
371
|
+
if (value === undefined || value === "--" || (value.startsWith("-") && !(arg === "-j" || arg === "--jobs"))) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
build.push(arg, value);
|
|
375
|
+
} else if (arg.includes("=") && (values.has(arg.split("=")[0]) || arg.startsWith("--timings="))) {
|
|
376
|
+
build.push(arg);
|
|
377
|
+
} else {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return build;
|
|
382
|
+
};
|
|
383
|
+
|
|
345
384
|
// When a slot held this long is worth a line in the log: half of what its own rule lets it hold, never for a rule that
|
|
346
385
|
// lets it hold forever.
|
|
347
386
|
const holdWarnSeconds = (maxHold) => (maxHold > 0 ? maxHold / 2 : undefined);
|
|
@@ -363,4 +402,5 @@ module.exports = {
|
|
|
363
402
|
holdWarnSeconds,
|
|
364
403
|
ruleById,
|
|
365
404
|
heldPoolsOf,
|
|
405
|
+
cargoBuildArgs,
|
|
366
406
|
};
|
|
@@ -95,3 +95,6 @@ export function holdWarnSeconds(maxHold: number): number | undefined;
|
|
|
95
95
|
export function ruleById(config: HeavyCommands, id: string): HeavyCommandRule | undefined;
|
|
96
96
|
// The pools the programs above one already hold slots of, from INTENTIC_HEAVY_HELD and INTENTIC_QUEUE_SLOT.
|
|
97
97
|
export function heldPoolsOf(env: Readonly<Record<string, string | undefined>>, config: HeavyCommands): string[];
|
|
98
|
+
|
|
99
|
+
// The build before a cargo run, or undefined when its arguments cannot be translated safely.
|
|
100
|
+
export function cargoBuildArgs(args: readonly string[]): string[] | undefined;
|
package/src/memory-room.d.mts
CHANGED
|
@@ -5,8 +5,8 @@ export const COST_BYTES: Readonly<Record<RoomWorkload, number>>;
|
|
|
5
5
|
export const PERSON_RESERVE_BYTES: number;
|
|
6
6
|
export const TARGETED_RUN_BYTES: number;
|
|
7
7
|
// A toolchain command priced by its named shape instead of its class's cost (SIZE_BYTES): one aimed at named files,
|
|
8
|
-
// the editor's full typecheck, a changed-files Vue typecheck.
|
|
9
|
-
export type RoomSize = "targeted" | "editor-typecheck" | "scoped-typecheck";
|
|
8
|
+
// the editor's full typecheck, a changed-files Vue typecheck, a Cargo build.
|
|
9
|
+
export type RoomSize = "targeted" | "editor-typecheck" | "scoped-typecheck" | "cargo-build";
|
|
10
10
|
export const SIZE_BYTES: Readonly<Record<RoomSize, number>>;
|
|
11
11
|
export function isRoomSize(size: string): size is RoomSize;
|
|
12
12
|
export const TEST_PROCESS_BYTES: Readonly<{ fanOutWorker: number; standaloneWorker: number; typecheck: number; ceiling: number }>;
|
|
@@ -40,6 +40,18 @@ export interface MemoryReading {
|
|
|
40
40
|
readonly ioStallSustainedPercent?: number | undefined;
|
|
41
41
|
// memory.events `oom_kill`, cumulative; undefined without a cgroup.
|
|
42
42
|
readonly oomKills: number | undefined;
|
|
43
|
+
// (2026-11-02) The machine this sandbox runs on, as its agent last said, added by the daemon only while that is
|
|
44
|
+
// fresh; absent is "no host to answer to", never "no room". Work nobody waits on waits while it would take the host
|
|
45
|
+
// under `reserveBytes`.
|
|
46
|
+
readonly host?: HostRoom | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface HostRoom {
|
|
50
|
+
// The machine's name as the owner knows it (its device card), for the sentence a held command is given.
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly availableBytes: number;
|
|
53
|
+
readonly totalBytes: number;
|
|
54
|
+
readonly reserveBytes: number;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
export interface ShortMemory {
|
package/src/memory-room.mjs
CHANGED
|
@@ -34,6 +34,10 @@ export const TARGETED_RUN_BYTES = 512 * MIB;
|
|
|
34
34
|
// `size`, which queue-run passes as `--size <name>`). One name per measured shape:
|
|
35
35
|
export const SIZE_BYTES = Object.freeze({
|
|
36
36
|
targeted: TARGETED_RUN_BYTES,
|
|
37
|
+
// Shinobi resource-metrics.jsonl, 2026-11-02: `other` RSS peaked at 12.70 GiB at 19:25 with one cargo slot held,
|
|
38
|
+
// and 13.88 GiB at 23:14 with two. Rust was unclassified then, so this includes other work and unqueued runs.
|
|
39
|
+
// Admission estimate per slot: two fit the 18 GiB sandbox with headroom. Cargo's jobs still bound its fan-out.
|
|
40
|
+
"cargo-build": 7 * GIB,
|
|
37
41
|
// The editor's full typecheck, `vue-tsc` over `_editor/web`, or `_site/demo`'s, which compiles the same source:
|
|
38
42
|
// 6.8 GiB at its peak (measured 2026-10-26, cold and warm alike), under the 8 GiB heap its script gives it.
|
|
39
43
|
"editor-typecheck": 7 * GIB,
|
|
@@ -209,6 +213,11 @@ export const judge = (reading, { workload, attended, reservedBytes = 0, size })
|
|
|
209
213
|
const stallSustainedPercent = reading.stallSustainedPercent ?? 0;
|
|
210
214
|
const unreserved = freeBytesOf(reading);
|
|
211
215
|
const counted = countedBytesOf(reading);
|
|
216
|
+
// Asked before the sandbox's own figures, which may measure nothing: a VM with room says nothing of the PC under it.
|
|
217
|
+
const hostHeld = hostWait(reading, { workload, attended, reservedBytes, size }, need, unreserved);
|
|
218
|
+
if (hostHeld !== undefined) {
|
|
219
|
+
return hostHeld;
|
|
220
|
+
}
|
|
212
221
|
if (limitBytes === undefined || usedBytes === undefined || counted === undefined || unreserved === undefined) {
|
|
213
222
|
return { verdict: "run", needBytes: need, freeBytes: undefined, reservedBytes };
|
|
214
223
|
}
|
|
@@ -252,6 +261,29 @@ export const judge = (reading, { workload, attended, reservedBytes = 0, size })
|
|
|
252
261
|
return shortOf(`Sandbox memory is low: ${used}${held}`, { limitBytes, residentBytes: usedBytes - swapBytes, swapBytes });
|
|
253
262
|
};
|
|
254
263
|
|
|
264
|
+
// (2026-11-02) Why work nobody is waiting on should wait for the machine this sandbox runs on, or undefined while it need
|
|
265
|
+
// not. Everything else here reads the sandbox's own cgroup and the kernel under it, which on a Windows PC is Docker
|
|
266
|
+
// Desktop's WSL2 VM: on 2026-11-02 that VM read as roomy while Windows had 1.1 GB left and froze for 43 s. The daemon adds
|
|
267
|
+
// `reading.host` from what the host's agent last said (hosts/host-memory.ts), only while that is fresh, so a reading
|
|
268
|
+
// without it is "no host to answer to", never "no room". Work waits while starting it would take the host below its
|
|
269
|
+
// reserve, the line under which the host's agent starts giving the VM's cache back. Only the work's own cost counts here:
|
|
270
|
+
// the person's share in `needBytes` is room inside the sandbox, which the sandbox's own figures already keep.
|
|
271
|
+
const hostShortOf = (host, costBytes, reservedBytes) => {
|
|
272
|
+
if (host === undefined || host.availableBytes - host.reserveBytes - reservedBytes >= costBytes) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
const held = reservedBytes > 0 ? `, ${gib(reservedBytes)} of it held for work that just started` : "";
|
|
276
|
+
return `${host.name}, the machine this sandbox runs on, is low on memory: it has ${gib(host.availableBytes)} available of ${gib(host.totalBytes)}${held}, and heavy work waits while starting it would leave it under ${gib(host.reserveBytes)}`;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// The verdict that holds work nobody waits on for the host, or undefined while the host has room for it (hostShortOf).
|
|
280
|
+
const hostWait = (reading, { workload, attended, reservedBytes, size }, need, unreserved) => {
|
|
281
|
+
const diagnosis = attended ? undefined : hostShortOf(reading.host, costBytesOf(workload, size), reservedBytes);
|
|
282
|
+
return diagnosis === undefined
|
|
283
|
+
? undefined
|
|
284
|
+
: { verdict: "wait", needBytes: need, freeBytes: unreserved === undefined ? undefined : Math.max(0, unreserved - reservedBytes), reservedBytes, diagnosis };
|
|
285
|
+
};
|
|
286
|
+
|
|
255
287
|
// Why work nobody is waiting on should wait for the disk, or undefined while it need not (the `disk` clause).
|
|
256
288
|
const diskStalled = (reading) => {
|
|
257
289
|
const recent = reading.ioStallPercent ?? 0;
|