@cruxy/cli 0.24.0 → 0.26.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/dist/agent/session.d.ts +13 -0
- package/dist/agent/session.js +6 -0
- package/dist/approval/prompt.d.ts +7 -1
- package/dist/approval/prompt.js +52 -17
- package/dist/checkpoint/gate-hook.d.ts +28 -0
- package/dist/checkpoint/gate-hook.js +98 -0
- package/dist/checkpoint/gate.d.ts +7 -1
- package/dist/checkpoint/gate.js +8 -2
- package/dist/checkpoint/index.d.ts +1 -0
- package/dist/checkpoint/index.js +1 -0
- package/dist/cli/commands/rollback.d.ts +4 -1
- package/dist/cli/commands/rollback.js +16 -9
- package/dist/cli/commands/run.js +12 -0
- package/dist/cli/commands/skills.js +10 -2
- package/dist/cli/repl.d.ts +1 -1
- package/dist/cli/repl.js +113 -1
- package/dist/cli/session-factory.d.ts +4 -12
- package/dist/cli/session-factory.js +50 -96
- package/dist/components/frame.d.ts +6 -3
- package/dist/components/frame.js +21 -23
- package/dist/components/fuzzy.js +5 -1
- package/dist/components/select.js +4 -1
- package/dist/config/schema.d.ts +86 -0
- package/dist/config/schema.js +41 -0
- package/dist/errors/constructors.d.ts +18 -0
- package/dist/errors/constructors.js +49 -0
- package/dist/errors/types.d.ts +13 -0
- package/dist/errors/types.js +21 -0
- package/dist/jobs/approval-queue.d.ts +85 -0
- package/dist/jobs/approval-queue.js +96 -0
- package/dist/jobs/dispatch-tool.d.ts +34 -0
- package/dist/jobs/dispatch-tool.js +96 -0
- package/dist/jobs/index.d.ts +6 -0
- package/dist/jobs/index.js +6 -0
- package/dist/jobs/log-buffer.d.ts +31 -0
- package/dist/jobs/log-buffer.js +30 -0
- package/dist/jobs/log-renderer.d.ts +32 -0
- package/dist/jobs/log-renderer.js +70 -0
- package/dist/jobs/manager.d.ts +139 -0
- package/dist/jobs/manager.js +397 -0
- package/dist/jobs/types.d.ts +81 -0
- package/dist/jobs/types.js +10 -0
- package/dist/render/capabilities.d.ts +11 -0
- package/dist/render/capabilities.js +19 -3
- package/dist/render/diff.d.ts +1 -1
- package/dist/render/diff.js +23 -7
- package/dist/render/index.d.ts +4 -2
- package/dist/render/index.js +8 -2
- package/dist/render/layout.d.ts +59 -0
- package/dist/render/layout.js +158 -0
- package/dist/render/resize.d.ts +36 -0
- package/dist/render/resize.js +45 -0
- package/dist/render/state.d.ts +13 -0
- package/dist/render/state.js +38 -0
- package/dist/render/tty-renderer.d.ts +8 -0
- package/dist/render/tty-renderer.js +36 -11
- package/dist/render/types.d.ts +15 -1
- package/dist/subagent/orchestrator.d.ts +9 -0
- package/dist/subagent/orchestrator.js +6 -1
- package/dist/subagent/semaphore.d.ts +40 -11
- package/dist/subagent/semaphore.js +23 -26
- package/package.json +1 -1
package/dist/render/types.d.ts
CHANGED
|
@@ -39,8 +39,22 @@ export interface RenderCapabilities {
|
|
|
39
39
|
/** Unicode glyphs are safe (U.1) — false under `TERM=dumb` / `CRUXY_ASCII`;
|
|
40
40
|
* independent of `color`. Drives the theme's glyph table, not its stylers. */
|
|
41
41
|
unicode: boolean;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Terminal columns (U.12) — the ONE width source every surface reads; 80 when
|
|
44
|
+
* unknown (non-TTY / no `columns`). Honors `COLUMNS` when set. Mutated in
|
|
45
|
+
* place on resize (see {@link onResize}), so a surface reading it after a
|
|
46
|
+
* SIGWINCH sees the new width without re-probing anything.
|
|
47
|
+
*/
|
|
43
48
|
width: number;
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to width changes (U.12): the SIGWINCH push signal for the live
|
|
51
|
+
* region / transient frames to reflow at the new width. The listener fires
|
|
52
|
+
* with the new width AFTER {@link width} has been updated; the returned
|
|
53
|
+
* function unsubscribes. Absent when the stream cannot resize (non-TTY / no
|
|
54
|
+
* `columns`) — those surfaces just read the static {@link width}. Committed
|
|
55
|
+
* output is never re-rendered from here; only live surfaces subscribe.
|
|
56
|
+
*/
|
|
57
|
+
onResize?(listener: (width: number) => void): () => void;
|
|
44
58
|
}
|
|
45
59
|
/** Accumulated token usage the loop already tracks (U.4) — never fabricated. */
|
|
46
60
|
export interface TokenUsage {
|
|
@@ -6,6 +6,7 @@ import type { Router } from "../routing/index.js";
|
|
|
6
6
|
import type { ApproveAction, ToolContext, ToolRegistry } from "../tools/index.js";
|
|
7
7
|
import type { SandboxService } from "../sandbox/index.js";
|
|
8
8
|
import { Workspace } from "../workspace/index.js";
|
|
9
|
+
import { Semaphore } from "./semaphore.js";
|
|
9
10
|
import type { SubagentResult, SubagentSpec } from "./types.js";
|
|
10
11
|
/**
|
|
11
12
|
* Everything a spawn needs from the surrounding session, injected by the
|
|
@@ -60,6 +61,14 @@ export interface SubagentOrchestratorDeps {
|
|
|
60
61
|
* same shared checkpoint gate, those writes join the run's one set (⚖︎JC-δ).
|
|
61
62
|
*/
|
|
62
63
|
checkpointsActive?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The ONE shared execution semaphore (C.28). When set, parallel fan-out draws
|
|
66
|
+
* permits from THIS instance — the same one background jobs use — so the
|
|
67
|
+
* `subagent.maxConcurrency` cap bounds subagents AND jobs COMBINED, never one
|
|
68
|
+
* cap each. Omitted → the orchestrator constructs its own (C.33 behaviour,
|
|
69
|
+
* used by tests that exercise the orchestrator in isolation).
|
|
70
|
+
*/
|
|
71
|
+
executionSemaphore?: Semaphore;
|
|
63
72
|
}
|
|
64
73
|
/**
|
|
65
74
|
* Spawns subagents (C.14): the existing agent loop re-driven over isolated
|
|
@@ -32,7 +32,12 @@ export class SubagentOrchestrator {
|
|
|
32
32
|
sem;
|
|
33
33
|
constructor(deps) {
|
|
34
34
|
this.deps = deps;
|
|
35
|
-
|
|
35
|
+
// Share the session's ONE execution semaphore when injected (C.28) so
|
|
36
|
+
// subagents and background jobs contend for the same permits; fall back to a
|
|
37
|
+
// private one at the same cap when running the orchestrator in isolation.
|
|
38
|
+
this.sem =
|
|
39
|
+
deps.executionSemaphore ??
|
|
40
|
+
new Semaphore(deps.config.subagent.maxConcurrency);
|
|
36
41
|
}
|
|
37
42
|
/** Live/queued fan-out slots (inspection/tests): proves the global cap holds. */
|
|
38
43
|
get concurrency() {
|
|
@@ -1,27 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A counting semaphore (C.33): bounds how many
|
|
2
|
+
* A counting semaphore (C.33): bounds how many agent runs execute at once.
|
|
3
3
|
* FIFO — waiters are served in arrival order, so a fan-out's results stay
|
|
4
4
|
* dispatch-order-fair — and the permit is handed directly from a releaser to
|
|
5
5
|
* the next waiter, so the live count never transiently exceeds the cap.
|
|
6
6
|
*
|
|
7
|
-
* Used as the ONE shared bound on
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* Used as the ONE shared bound on all concurrent execution: parallel subagent
|
|
8
|
+
* fan-out (C.33) AND background jobs (C.28) draw permits from the SAME instance,
|
|
9
|
+
* so `subagent.maxConcurrency` caps their COMBINED live count — never one cap
|
|
10
|
+
* per subsystem. Parallel dispatch happens at depth 0 only (the `spawn_subagents`
|
|
11
|
+
* tool is never granted to a child), and a permit is held for a child's whole
|
|
12
|
+
* lifetime — including any *sequential* nested spawn beneath it, which is
|
|
13
|
+
* deliberately un-permitted. Because no permit holder ever blocks trying to
|
|
14
|
+
* acquire a second permit, the semaphore cannot be part of a wait cycle: it is
|
|
15
|
+
* deadlock-free by construction (see the C.33 design doc's deadlock argument).
|
|
16
|
+
*
|
|
17
|
+
* C.28 adds a PRIORITY lane to {@link acquire}. A background job that pauses to
|
|
18
|
+
* await a human approval RELEASES its permit (JC-D: a job blocked on a human is
|
|
19
|
+
* not using compute, and holding the slot would deadlock the cap if N jobs all
|
|
20
|
+
* paused). When the human approves, the job re-acquires with `priority: true` so
|
|
21
|
+
* it jumps ahead of newly-dispatched work — a resumed job never starves behind a
|
|
22
|
+
* fresh fan-out that filled the queue while it waited. Normal (non-priority)
|
|
23
|
+
* waiters keep strict FIFO among themselves, so C.33's dispatch-order fairness is
|
|
24
|
+
* unchanged for everything that does not pause.
|
|
14
25
|
*/
|
|
26
|
+
export interface AcquireOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Serve this waiter ahead of all non-priority waiters (still FIFO among
|
|
29
|
+
* priority waiters). Used by a resumed background job re-acquiring its slot
|
|
30
|
+
* after an approval, so it is not starved by work dispatched while it paused.
|
|
31
|
+
*/
|
|
32
|
+
priority?: boolean;
|
|
33
|
+
}
|
|
15
34
|
export declare class Semaphore {
|
|
16
35
|
private permits;
|
|
17
|
-
|
|
36
|
+
/** Waiters that take the next permit ahead of {@link normal} (FIFO within). */
|
|
37
|
+
private readonly priority;
|
|
38
|
+
/** Ordinary waiters, served strictly FIFO after any priority waiters. */
|
|
39
|
+
private readonly normal;
|
|
18
40
|
constructor(permits: number);
|
|
19
41
|
/** Run `fn` while holding one permit; the permit is released even if it throws. */
|
|
20
42
|
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Take one permit, waiting if none is free. Resolves immediately when a permit
|
|
45
|
+
* is available; otherwise enqueues onto the priority or normal lane and resolves
|
|
46
|
+
* when {@link release} hands it a permit. Callers that acquire directly (a
|
|
47
|
+
* paused/resumed job) MUST call {@link release} exactly once per acquire.
|
|
48
|
+
*/
|
|
49
|
+
acquire(opts?: AcquireOptions): Promise<void>;
|
|
50
|
+
/** Return one permit — handed straight to the next waiter (priority first). */
|
|
51
|
+
release(): void;
|
|
21
52
|
/** Permits currently available (inspection/tests). */
|
|
22
53
|
get available(): number;
|
|
23
54
|
/** Callers currently blocked waiting for a permit (inspection/tests). */
|
|
24
55
|
get waiting(): number;
|
|
25
|
-
private acquire;
|
|
26
|
-
private release;
|
|
27
56
|
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A counting semaphore (C.33): bounds how many subagent runs execute at once.
|
|
3
|
-
* FIFO — waiters are served in arrival order, so a fan-out's results stay
|
|
4
|
-
* dispatch-order-fair — and the permit is handed directly from a releaser to
|
|
5
|
-
* the next waiter, so the live count never transiently exceeds the cap.
|
|
6
|
-
*
|
|
7
|
-
* Used as the ONE shared bound on parallel fan-out. Parallel dispatch happens at
|
|
8
|
-
* depth 0 only (the `spawn_subagents` tool is never granted to a child), and a
|
|
9
|
-
* permit is held for a child's whole lifetime — including any *sequential*
|
|
10
|
-
* nested spawn beneath it, which is deliberately un-permitted. Because no permit
|
|
11
|
-
* holder ever blocks trying to acquire a second permit, the semaphore cannot be
|
|
12
|
-
* part of a wait cycle: it is deadlock-free by construction (see the C.33 design
|
|
13
|
-
* doc's deadlock argument).
|
|
14
|
-
*/
|
|
15
1
|
export class Semaphore {
|
|
16
2
|
permits;
|
|
17
|
-
|
|
3
|
+
/** Waiters that take the next permit ahead of {@link normal} (FIFO within). */
|
|
4
|
+
priority = [];
|
|
5
|
+
/** Ordinary waiters, served strictly FIFO after any priority waiters. */
|
|
6
|
+
normal = [];
|
|
18
7
|
constructor(permits) {
|
|
19
8
|
// A non-positive cap would wedge every run; clamp to at least 1.
|
|
20
9
|
this.permits = Math.max(1, Math.floor(permits));
|
|
@@ -29,23 +18,23 @@ export class Semaphore {
|
|
|
29
18
|
this.release();
|
|
30
19
|
}
|
|
31
20
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
acquire() {
|
|
21
|
+
/**
|
|
22
|
+
* Take one permit, waiting if none is free. Resolves immediately when a permit
|
|
23
|
+
* is available; otherwise enqueues onto the priority or normal lane and resolves
|
|
24
|
+
* when {@link release} hands it a permit. Callers that acquire directly (a
|
|
25
|
+
* paused/resumed job) MUST call {@link release} exactly once per acquire.
|
|
26
|
+
*/
|
|
27
|
+
acquire(opts = {}) {
|
|
41
28
|
if (this.permits > 0) {
|
|
42
29
|
this.permits--;
|
|
43
30
|
return Promise.resolve();
|
|
44
31
|
}
|
|
45
|
-
|
|
32
|
+
const lane = opts.priority ? this.priority : this.normal;
|
|
33
|
+
return new Promise((resolve) => lane.push(resolve));
|
|
46
34
|
}
|
|
35
|
+
/** Return one permit — handed straight to the next waiter (priority first). */
|
|
47
36
|
release() {
|
|
48
|
-
const next = this.
|
|
37
|
+
const next = this.priority.shift() ?? this.normal.shift();
|
|
49
38
|
// Hand the permit straight to the next waiter (never bump the count above
|
|
50
39
|
// the cap); only when nobody waits does the count grow back.
|
|
51
40
|
if (next)
|
|
@@ -53,4 +42,12 @@ export class Semaphore {
|
|
|
53
42
|
else
|
|
54
43
|
this.permits++;
|
|
55
44
|
}
|
|
45
|
+
/** Permits currently available (inspection/tests). */
|
|
46
|
+
get available() {
|
|
47
|
+
return this.permits;
|
|
48
|
+
}
|
|
49
|
+
/** Callers currently blocked waiting for a permit (inspection/tests). */
|
|
50
|
+
get waiting() {
|
|
51
|
+
return this.priority.length + this.normal.length;
|
|
52
|
+
}
|
|
56
53
|
}
|