@sentropic/h2a 0.80.0 → 0.82.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 +5 -0
- package/dist/bin-routing.d.ts +13 -0
- package/dist/bin-routing.d.ts.map +1 -0
- package/dist/bin-routing.js +46 -0
- package/dist/bin-routing.js.map +1 -0
- package/dist/bin.js +62 -1
- package/dist/bin.js.map +1 -1
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli-contract.js +22 -6
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +197 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +3 -1
- package/dist/mcp.js.map +1 -1
- package/dist/runtime/canevas/adapter.d.ts +11 -0
- package/dist/runtime/canevas/adapter.d.ts.map +1 -0
- package/dist/runtime/canevas/adapter.js +22 -0
- package/dist/runtime/canevas/adapter.js.map +1 -0
- package/dist/runtime/canevas/aggregate.d.ts +56 -0
- package/dist/runtime/canevas/aggregate.d.ts.map +1 -0
- package/dist/runtime/canevas/aggregate.js +84 -0
- package/dist/runtime/canevas/aggregate.js.map +1 -0
- package/dist/runtime/canevas/answer.d.ts +21 -0
- package/dist/runtime/canevas/answer.d.ts.map +1 -0
- package/dist/runtime/canevas/answer.js +62 -0
- package/dist/runtime/canevas/answer.js.map +1 -0
- package/dist/runtime/canevas/app.d.ts +19 -0
- package/dist/runtime/canevas/app.d.ts.map +1 -0
- package/dist/runtime/canevas/app.js +49 -0
- package/dist/runtime/canevas/app.js.map +1 -0
- package/dist/runtime/canevas/gather.d.ts +3 -0
- package/dist/runtime/canevas/gather.d.ts.map +1 -0
- package/dist/runtime/canevas/gather.js +39 -0
- package/dist/runtime/canevas/gather.js.map +1 -0
- package/dist/runtime/canevas/reply.d.ts +27 -0
- package/dist/runtime/canevas/reply.d.ts.map +1 -0
- package/dist/runtime/canevas/reply.js +31 -0
- package/dist/runtime/canevas/reply.js.map +1 -0
- package/dist/runtime/canevas/serve.d.ts +11 -0
- package/dist/runtime/canevas/serve.d.ts.map +1 -0
- package/dist/runtime/canevas/serve.js +42 -0
- package/dist/runtime/canevas/serve.js.map +1 -0
- package/dist/runtime/canevas/ui.d.ts +2 -0
- package/dist/runtime/canevas/ui.d.ts.map +1 -0
- package/dist/runtime/canevas/ui.js +124 -0
- package/dist/runtime/canevas/ui.js.map +1 -0
- package/dist/runtime/loop/engine/adapters.d.ts +91 -0
- package/dist/runtime/loop/engine/adapters.d.ts.map +1 -0
- package/dist/runtime/loop/engine/adapters.js +292 -0
- package/dist/runtime/loop/engine/adapters.js.map +1 -0
- package/dist/runtime/loop/engine/decision.d.ts +71 -0
- package/dist/runtime/loop/engine/decision.d.ts.map +1 -0
- package/dist/runtime/loop/engine/decision.js +141 -0
- package/dist/runtime/loop/engine/decision.js.map +1 -0
- package/dist/runtime/loop/engine/execute.d.ts +33 -0
- package/dist/runtime/loop/engine/execute.d.ts.map +1 -0
- package/dist/runtime/loop/engine/execute.js +69 -0
- package/dist/runtime/loop/engine/execute.js.map +1 -0
- package/dist/runtime/loop/engine/tick.d.ts +27 -0
- package/dist/runtime/loop/engine/tick.d.ts.map +1 -0
- package/dist/runtime/loop/engine/tick.js +72 -0
- package/dist/runtime/loop/engine/tick.js.map +1 -0
- package/dist/runtime/loop/index.d.ts +14 -0
- package/dist/runtime/loop/index.d.ts.map +1 -1
- package/dist/runtime/loop/index.js +23 -0
- package/dist/runtime/loop/index.js.map +1 -1
- package/dist/runtime/mcp/handlers.d.ts +12 -0
- package/dist/runtime/mcp/handlers.d.ts.map +1 -1
- package/dist/runtime/mcp/handlers.js +42 -0
- package/dist/runtime/mcp/handlers.js.map +1 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +5 -1
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/tools.d.ts.map +1 -1
- package/dist/runtime/mcp/tools.js +22 -0
- package/dist/runtime/mcp/tools.js.map +1 -1
- package/package.json +9 -1
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,12 @@ import { homedir, hostname } from "node:os";
|
|
|
43
43
|
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
44
44
|
import { createRequire } from "node:module";
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
|
+
// Static import — `@sentropic/track` is a LIGHT library (pure reader/model, no
|
|
47
|
+
// pty/aws/runtime). The golden rule (lazy-only) is scoped to
|
|
48
|
+
// `@sentropic/h2a-runtime`/node-pty/aws-sdk, which track is NOT. Importing its
|
|
49
|
+
// public `runCli` lets ④ run read-only track verbs IN-PROCESS (no spawn), on the
|
|
50
|
+
// same `.track` under the same lock as the surviving spawn facade.
|
|
51
|
+
import { runCli as runTrackCli } from "@sentropic/track";
|
|
46
52
|
import { H2A_ATTESTER_COMPREHENSION_RIGHT, H2A_COMPREHENSION_ATTESTATION_BODY_KIND, H2A_DECLARATION_INTERET_BODY_KIND, H2A_ORG_MANIFEST_FILENAME, H2A_ORG_PROPOSAL_BODY_KIND, H2A_ORG_RATIFIED_BODY_KIND, H2A_ROLES, H2A_WORK_STATUSES, auditNhiPosture, buildComprehensionAttestation, canAttestComprehension, checkEnvelopeFreshness, computeHash, createEnvelope, deriveWorkspaceId, diffOrgManifest, effectiveOrgInstances, isComprehensionAttestation, nhiAttestationEnvelope, nhiInventory, deriveConnectionConfidence, nhiTrustBundle, orgAssignmentEnvelope, parseOrgManifest, signCanonical, signEnvelope, subagentAddress, validateOrgManifest, verifyComprehensionAttestation } from "@sentropic/h2a";
|
|
47
53
|
import { H2A_CLAUDE_HOST } from "./hosts/claude.js";
|
|
48
54
|
import { H2A_CODEX_HOST } from "./hosts/codex.js";
|
|
@@ -69,6 +75,9 @@ import { appendConductorClaim } from "./runtime/governance/claims.js";
|
|
|
69
75
|
import { conductorLaunchCheck } from "./runtime/governance/launch-check.js";
|
|
70
76
|
import { lastSpawnRequestAt, recordSpawnRequest, spawnAllowed } from "./runtime/governance/spawns.js";
|
|
71
77
|
import { createObjectiveLoop, listLoopEvents, listObjectiveLoops, readObjectiveLoop } from "./runtime/loop/index.js";
|
|
78
|
+
import { runLoopWatch, runTick } from "./runtime/loop/engine/tick.js";
|
|
79
|
+
import { gatherPendingDecisions } from "./runtime/canevas/gather.js";
|
|
80
|
+
import { runCanevasServe } from "./runtime/canevas/serve.js";
|
|
72
81
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
73
82
|
// `dist/cli.js` lives in `packages/h2a-cli/dist/`; skills are at
|
|
74
83
|
// `packages/h2a-cli/skills/`. Two levels up from the dist file.
|
|
@@ -1681,16 +1690,8 @@ function cmdLoop(argv, streams) {
|
|
|
1681
1690
|
streams.stdout.write(`${JSON.stringify({ loopId, ...(agent ? { agent } : {}), events: listLoopEvents(root, loopId) }, null, 2)}\n`);
|
|
1682
1691
|
return 0;
|
|
1683
1692
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
if (!loopId || loopId.startsWith("--")) {
|
|
1687
|
-
streams.stderr.write(`h2a loop ${sub}: <loopId> is required\n`);
|
|
1688
|
-
return 1;
|
|
1689
|
-
}
|
|
1690
|
-
readObjectiveLoop(root, loopId);
|
|
1691
|
-
streams.stdout.write(`${JSON.stringify({ ok: true, loopId, action: sub, supported: false, reason: "loop orchestration is not implemented in h2a loop MVP" }, null, 2)}\n`);
|
|
1692
|
-
return 0;
|
|
1693
|
-
}
|
|
1693
|
+
// `tick` / `watch` are ASYNC (lazy runtime + periodic loop) → dispatched in
|
|
1694
|
+
// bin.ts via `runLoopEngineCli`, not here (cmdLoop is synchronous).
|
|
1694
1695
|
}
|
|
1695
1696
|
catch (error) {
|
|
1696
1697
|
streams.stderr.write(`h2a loop ${sub ?? ""}: ${error.message}\n`);
|
|
@@ -1699,6 +1700,83 @@ function cmdLoop(argv, streams) {
|
|
|
1699
1700
|
streams.stderr.write("h2a loop: subcommand required (create, list, status, agents, attach, logs, tick, watch)\n");
|
|
1700
1701
|
return 1;
|
|
1701
1702
|
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Async dispatcher for `h2a loop tick|watch` (SLICE-1: dry-run only — produces a
|
|
1705
|
+
* plan via the pure core + lazy adapters, executes nothing). Called from bin.ts
|
|
1706
|
+
* because these are async (lazy runtime import + periodic loop). `argv` is the
|
|
1707
|
+
* full bin argv: `["loop", "tick"|"watch", "<loopId>", ...flags]`.
|
|
1708
|
+
*/
|
|
1709
|
+
export async function runLoopEngineCli(argv, streams, signal) {
|
|
1710
|
+
const sub = argv[1];
|
|
1711
|
+
const loopId = argv[2];
|
|
1712
|
+
const { flags } = parseFlags(argv);
|
|
1713
|
+
const cwd = streams.cwd ?? (() => process.cwd());
|
|
1714
|
+
const root = resolveRoot(flags, cwd);
|
|
1715
|
+
if (!loopId || loopId.startsWith("--")) {
|
|
1716
|
+
streams.stderr.write(`h2a loop ${sub}: <loopId> is required\n`);
|
|
1717
|
+
return 1;
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
if (sub === "watch") {
|
|
1721
|
+
const intervalMs = flags["interval-ms"] ? Number(flags["interval-ms"]) : undefined;
|
|
1722
|
+
const max = flags.max ? Number(flags.max) : undefined;
|
|
1723
|
+
return await runLoopWatch(root, loopId, {
|
|
1724
|
+
...(intervalMs !== undefined && Number.isFinite(intervalMs) ? { intervalMs } : {}),
|
|
1725
|
+
...(max !== undefined && Number.isFinite(max) ? { max } : {}),
|
|
1726
|
+
...(signal ? { signal } : {}),
|
|
1727
|
+
stdout: streams.stdout
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
// tick — DRY-RUN by default; `--execute` runs the plan (tranche 1: close only).
|
|
1731
|
+
const execute = argv.includes("--execute");
|
|
1732
|
+
const { plan, exec } = await runTick(root, loopId, { execute });
|
|
1733
|
+
const out = exec ? { ...plan, exec } : plan;
|
|
1734
|
+
streams.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
1735
|
+
return plan.outcome === "failed" ? 1 : 0;
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1738
|
+
streams.stderr.write(`h2a loop ${sub}: ${error.message}\n`);
|
|
1739
|
+
return classifyStoreError(error.message);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
// Canevas ③ — read-only tranche-1 : agrège les décisions humaines en attente
|
|
1743
|
+
// (enveloppes `escalate` des inbox des instances live) en un JSON stable. La vue
|
|
1744
|
+
// session (capturePane runtime lazy), le serveur Hono et le pont-réponse
|
|
1745
|
+
// (reply-envelope gardé) sont des tranches ultérieures.
|
|
1746
|
+
function cmdCanevas(argv, streams) {
|
|
1747
|
+
const { command: sub, flags } = parseFlags(argv);
|
|
1748
|
+
const cwd = streams.cwd ?? (() => process.cwd());
|
|
1749
|
+
const root = resolveRoot(flags, cwd);
|
|
1750
|
+
if (sub === "list") {
|
|
1751
|
+
try {
|
|
1752
|
+
const decisions = gatherPendingDecisions(root);
|
|
1753
|
+
streams.stdout.write(`${JSON.stringify({ kind: "canevas-decisions", version: 1, decisions }, null, 2)}\n`);
|
|
1754
|
+
return 0;
|
|
1755
|
+
}
|
|
1756
|
+
catch (error) {
|
|
1757
|
+
streams.stderr.write(`h2a canevas list: ${error.message}\n`);
|
|
1758
|
+
return classifyStoreError(error.message);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
streams.stderr.write("h2a canevas: subcommand required (list, serve)\n");
|
|
1762
|
+
return 1;
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Async dispatcher for `h2a canevas serve` (read-only Hono server on 127.0.0.1).
|
|
1766
|
+
* Called from bin.ts (async + long-running). `argv` = full bin argv.
|
|
1767
|
+
*/
|
|
1768
|
+
export async function runCanevasServeCli(argv, streams, signal) {
|
|
1769
|
+
const { flags } = parseFlags(argv);
|
|
1770
|
+
const cwd = streams.cwd ?? (() => process.cwd());
|
|
1771
|
+
const root = resolveRoot(flags, cwd);
|
|
1772
|
+
const port = flags.port ? Number(flags.port) : undefined;
|
|
1773
|
+
return runCanevasServe({
|
|
1774
|
+
root,
|
|
1775
|
+
...(port !== undefined && Number.isFinite(port) ? { port } : {}),
|
|
1776
|
+
stderr: streams.stderr,
|
|
1777
|
+
...(signal ? { signal } : {})
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1702
1780
|
function cmdDrumbeat(argv, streams) {
|
|
1703
1781
|
// DEC-086 (D2): durable anti-stall registry — record stops, scan candidates,
|
|
1704
1782
|
// clear. The long-running `watch` daemon is async (dispatched from bin.ts).
|
|
@@ -4535,10 +4613,43 @@ export async function cmdKeepalive(flags, streams) {
|
|
|
4535
4613
|
// P3 (façade track) — les verbes track sont délégués à la CLI `@sentropic/track`
|
|
4536
4614
|
// par shell-out (le store `.track` est append-only single-writer : pas de 2ᵉ writer).
|
|
4537
4615
|
// Verbes spécifiques, sans namespace `track` (dissous comme host/sub).
|
|
4538
|
-
const TRACK_FACADE_VERBS = new Set([
|
|
4616
|
+
export const TRACK_FACADE_VERBS = new Set([
|
|
4539
4617
|
"decision", "report", "accept", "blocker", "item", "query",
|
|
4540
4618
|
"consolidate", "priority", "branch", "focus", "ingest", "restructure"
|
|
4541
4619
|
]);
|
|
4620
|
+
// ④ de-spawn — a strict SUBSET of the facade verbs routed IN-PROCESS via
|
|
4621
|
+
// `@sentropic/track`'s `runCli`, no child process. Every native verb is proven
|
|
4622
|
+
// SYNC in the pinned track (dispatches through runCli's synchronous switch and
|
|
4623
|
+
// returns a `number`); the ONLY async verb, `focus` (dynamic
|
|
4624
|
+
// `import('@sentropic/focus')` → Promise), is deliberately excluded and keeps
|
|
4625
|
+
// shelling out. All paths resolve the SAME `.track` (native uses
|
|
4626
|
+
// `process.cwd()`, the spawn inherits it) and share track's single O_EXCL lock —
|
|
4627
|
+
// no split-brain, no second writer.
|
|
4628
|
+
//
|
|
4629
|
+
// The native set is split READ-ONLY vs WRITE because the throw-fallback rule
|
|
4630
|
+
// differs (RISK: double-write, see `delegateToTrackNative`):
|
|
4631
|
+
// • READ-ONLY (`query`/`report`) — a native throw wrote nothing, so falling
|
|
4632
|
+
// back to the spawn facade is safe.
|
|
4633
|
+
// • WRITE (the rest) — track's `appendCommand` is ATOMIC under the O_EXCL lock
|
|
4634
|
+
// (read→validate→append→writeHead→verify): it throws either BEFORE the
|
|
4635
|
+
// append (validation → nothing written) or AFTER a landed write (the
|
|
4636
|
+
// verify receipt). A spawn fallback on that throw could DOUBLE-WRITE, so a
|
|
4637
|
+
// write NEVER falls back on throw — it propagates the error (rc≠0 + stderr).
|
|
4638
|
+
export const TRACK_NATIVE_READONLY_VERBS = new Set(["query", "report"]);
|
|
4639
|
+
// ④ tranche-2 — the SYNC write verbs, de-spawned in-process. Each dispatches
|
|
4640
|
+
// through runCli's mutating switch as a plain sync `cmd…` returning a `number`.
|
|
4641
|
+
// `focus` is absent (async); `restructure`/`ingest` are sync file-plan applies.
|
|
4642
|
+
export const TRACK_NATIVE_WRITE_VERBS = new Set([
|
|
4643
|
+
"decision", "item", "accept", "blocker",
|
|
4644
|
+
"consolidate", "priority", "branch", "ingest", "restructure"
|
|
4645
|
+
]);
|
|
4646
|
+
// Union of the in-process verbs (read-only + sync writes). `focus` (async) is
|
|
4647
|
+
// NOT here and keeps the spawn facade. Kept exported as the single "is this verb
|
|
4648
|
+
// native?" predicate used by the dispatcher and the contract tests.
|
|
4649
|
+
export const TRACK_NATIVE_VERBS = new Set([
|
|
4650
|
+
...TRACK_NATIVE_READONLY_VERBS,
|
|
4651
|
+
...TRACK_NATIVE_WRITE_VERBS
|
|
4652
|
+
]);
|
|
4542
4653
|
function resolveTrackBin() {
|
|
4543
4654
|
// Le champ `exports` de @sentropic/track bloque l'accès à ./package.json,
|
|
4544
4655
|
// donc on résout l'entrée puis on remonte jusqu'au package.json du package.
|
|
@@ -4568,12 +4679,85 @@ function delegateToTrack(argv, streams) {
|
|
|
4568
4679
|
return 1;
|
|
4569
4680
|
}
|
|
4570
4681
|
}
|
|
4682
|
+
/**
|
|
4683
|
+
* ④ — run a track verb IN-PROCESS via `@sentropic/track`'s `runCli`.
|
|
4684
|
+
*
|
|
4685
|
+
* Split-brain mitigation (RISK #1): the native call resolves `.track` from
|
|
4686
|
+
* `process.cwd()` — the SAME cwd the spawn facade inherits (`spawnSync` with no
|
|
4687
|
+
* `cwd` option) — so both paths bind the identical store, and track's own
|
|
4688
|
+
* O_EXCL lock still serialises them. We deliberately do NOT wrap the call in any
|
|
4689
|
+
* h2a file-lock: track's lock is not re-entrant, and the facade never took one.
|
|
4690
|
+
*
|
|
4691
|
+
* Output is BUFFERED, then flushed only on a synchronous result.
|
|
4692
|
+
*
|
|
4693
|
+
* Two escape hatches, and the DOUBLE-WRITE rule (RISK #2) that separates them:
|
|
4694
|
+
* • `runCli` returns a Promise — an async verb (e.g. `focus`) that has NOT yet
|
|
4695
|
+
* written synchronously. The buffer is discarded and we fall back to the
|
|
4696
|
+
* spawn facade. Safe for reads AND writes; unreachable for the shipped set
|
|
4697
|
+
* (only sync verbs are routed here), so it is a defensive belt.
|
|
4698
|
+
* • `runCli` throws — an UNEXPECTED escape from track's own try/catch. For a
|
|
4699
|
+
* `readOnly` verb nothing was written, so we fall back to the spawn facade.
|
|
4700
|
+
* For a WRITE verb we MUST NOT re-run: track's `appendCommand` is atomic
|
|
4701
|
+
* (throws BEFORE the append on validation, or AFTER a landed write on the
|
|
4702
|
+
* verify receipt), so a spawn retry could double-write. We flush the buffered
|
|
4703
|
+
* native output and propagate a non-zero rc + stderr instead.
|
|
4704
|
+
*/
|
|
4705
|
+
function delegateToTrackNative(argv, streams, readOnly) {
|
|
4706
|
+
const outBuf = [];
|
|
4707
|
+
const errBuf = [];
|
|
4708
|
+
const io = {
|
|
4709
|
+
// CRITICAL: same root as the spawn facade inherits. Do NOT swap in
|
|
4710
|
+
// `streams.cwd` here — divergence would split-brain the two paths.
|
|
4711
|
+
cwd: process.cwd(),
|
|
4712
|
+
out: (s) => {
|
|
4713
|
+
outBuf.push(s);
|
|
4714
|
+
},
|
|
4715
|
+
err: (s) => {
|
|
4716
|
+
errBuf.push(s);
|
|
4717
|
+
}
|
|
4718
|
+
};
|
|
4719
|
+
try {
|
|
4720
|
+
const rc = runTrackCli([...argv], io);
|
|
4721
|
+
// Async verb (Promise) — nothing written synchronously yet, so a spawn
|
|
4722
|
+
// fallback cannot double-write. Not representable for the shipped sync set.
|
|
4723
|
+
if (rc !== null && typeof rc === "object" && typeof rc.then === "function") {
|
|
4724
|
+
return delegateToTrack(argv, streams);
|
|
4725
|
+
}
|
|
4726
|
+
for (const s of outBuf)
|
|
4727
|
+
streams.stdout.write(s);
|
|
4728
|
+
for (const s of errBuf)
|
|
4729
|
+
streams.stderr.write(s);
|
|
4730
|
+
return rc;
|
|
4731
|
+
}
|
|
4732
|
+
catch (err) {
|
|
4733
|
+
// READ-ONLY throw: nothing persisted → the spawn facade may safely re-run.
|
|
4734
|
+
if (readOnly) {
|
|
4735
|
+
return delegateToTrack(argv, streams);
|
|
4736
|
+
}
|
|
4737
|
+
// WRITE throw (anti-double-write): DO NOT re-run. `appendCommand` is atomic,
|
|
4738
|
+
// so either nothing landed or the write already landed — a spawn retry could
|
|
4739
|
+
// append a second event. Flush what the native path emitted and fail loud.
|
|
4740
|
+
for (const s of outBuf)
|
|
4741
|
+
streams.stdout.write(s);
|
|
4742
|
+
for (const s of errBuf)
|
|
4743
|
+
streams.stderr.write(s);
|
|
4744
|
+
streams.stderr.write(`h2a ${argv[0]}: track write failed in-process (${err.message}). ` +
|
|
4745
|
+
`No spawn retry — the store append is atomic; a retry could double-write.\n`);
|
|
4746
|
+
return 1;
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4571
4749
|
export function runCli(argv = process.argv.slice(2), streams = {
|
|
4572
4750
|
stdout: process.stdout,
|
|
4573
4751
|
stderr: process.stderr
|
|
4574
4752
|
}) {
|
|
4575
4753
|
const { command, flags } = parseFlags(argv);
|
|
4576
4754
|
if (command && TRACK_FACADE_VERBS.has(command)) {
|
|
4755
|
+
// ④: read-only + sync-write verbs run in-process; `focus` (async) keeps
|
|
4756
|
+
// shelling out. Same `.track`, same lock. A native WRITE never falls back to
|
|
4757
|
+
// spawn on throw (anti-double-write) — hence the read-only flag below.
|
|
4758
|
+
if (TRACK_NATIVE_VERBS.has(command)) {
|
|
4759
|
+
return delegateToTrackNative(argv, streams, TRACK_NATIVE_READONLY_VERBS.has(command));
|
|
4760
|
+
}
|
|
4577
4761
|
return delegateToTrack(argv, streams);
|
|
4578
4762
|
}
|
|
4579
4763
|
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
@@ -4598,6 +4782,8 @@ export function runCli(argv = process.argv.slice(2), streams = {
|
|
|
4598
4782
|
return cmdSubagent(argv.slice(1), streams);
|
|
4599
4783
|
if (command === "loop")
|
|
4600
4784
|
return cmdLoop(argv.slice(1), streams);
|
|
4785
|
+
if (command === "canevas")
|
|
4786
|
+
return cmdCanevas(argv.slice(1), streams);
|
|
4601
4787
|
if (command === "drumbeat") {
|
|
4602
4788
|
const sub = argv[1];
|
|
4603
4789
|
if (sub === "watch") {
|