@senpi/runtime 3.0.59 → 3.0.63
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 +73 -2
- package/dist/actions/decision-engine.d.ts +0 -2
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +1 -79
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/cli/deploy-render.d.ts +91 -0
- package/dist/cli/deploy-render.d.ts.map +1 -0
- package/dist/cli/deploy-render.js +216 -0
- package/dist/cli/deploy-render.js.map +1 -0
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +78 -6
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts +6 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +244 -8
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/cli/validate-command.d.ts.map +1 -1
- package/dist/cli/validate-command.js +32 -1
- package/dist/cli/validate-command.js.map +1 -1
- package/dist/deploy/deadlines.d.ts +47 -0
- package/dist/deploy/deadlines.d.ts.map +1 -0
- package/dist/deploy/deadlines.js +56 -0
- package/dist/deploy/deadlines.js.map +1 -0
- package/dist/deploy/error-text.d.ts +25 -0
- package/dist/deploy/error-text.d.ts.map +1 -0
- package/dist/deploy/error-text.js +42 -0
- package/dist/deploy/error-text.js.map +1 -0
- package/dist/deploy/funding.d.ts +87 -0
- package/dist/deploy/funding.d.ts.map +1 -0
- package/dist/deploy/funding.js +229 -0
- package/dist/deploy/funding.js.map +1 -0
- package/dist/deploy/install-gate-runner.d.ts +12 -0
- package/dist/deploy/install-gate-runner.d.ts.map +1 -0
- package/dist/deploy/install-gate-runner.js +17 -0
- package/dist/deploy/install-gate-runner.js.map +1 -0
- package/dist/deploy/job.d.ts +199 -0
- package/dist/deploy/job.d.ts.map +1 -0
- package/dist/deploy/job.js +463 -0
- package/dist/deploy/job.js.map +1 -0
- package/dist/deploy/min-budget.d.ts +68 -0
- package/dist/deploy/min-budget.d.ts.map +1 -0
- package/dist/deploy/min-budget.js +297 -0
- package/dist/deploy/min-budget.js.map +1 -0
- package/dist/deploy/orchestrator.d.ts +305 -0
- package/dist/deploy/orchestrator.d.ts.map +1 -0
- package/dist/deploy/orchestrator.js +2910 -0
- package/dist/deploy/orchestrator.js.map +1 -0
- package/dist/deploy/package.d.ts +130 -0
- package/dist/deploy/package.d.ts.map +1 -0
- package/dist/deploy/package.js +351 -0
- package/dist/deploy/package.js.map +1 -0
- package/dist/deploy/proof-gate.d.ts +82 -0
- package/dist/deploy/proof-gate.d.ts.map +1 -0
- package/dist/deploy/proof-gate.js +269 -0
- package/dist/deploy/proof-gate.js.map +1 -0
- package/dist/deploy/python-float.d.ts +34 -0
- package/dist/deploy/python-float.d.ts.map +1 -0
- package/dist/deploy/python-float.js +46 -0
- package/dist/deploy/python-float.js.map +1 -0
- package/dist/deploy/recheck-commands.d.ts +50 -0
- package/dist/deploy/recheck-commands.d.ts.map +1 -0
- package/dist/deploy/recheck-commands.js +59 -0
- package/dist/deploy/recheck-commands.js.map +1 -0
- package/dist/deploy/register.d.ts +99 -0
- package/dist/deploy/register.d.ts.map +1 -0
- package/dist/deploy/register.js +689 -0
- package/dist/deploy/register.js.map +1 -0
- package/dist/deploy/taxonomy.d.ts +24 -0
- package/dist/deploy/taxonomy.d.ts.map +1 -0
- package/dist/deploy/taxonomy.js +31 -0
- package/dist/deploy/taxonomy.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +244 -132
- package/dist/index.js.map +1 -1
- package/dist/runtime/strategy-registry.d.ts +8 -0
- package/dist/runtime/strategy-registry.d.ts.map +1 -1
- package/dist/runtime/strategy-registry.js +14 -1
- package/dist/runtime/strategy-registry.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +7 -4
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/scanners/implementations/market-regime.d.ts +1 -1
- package/dist/senpi/client.d.ts +58 -4
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +192 -15
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/strategy-status.d.ts +32 -0
- package/dist/senpi/strategy-status.d.ts.map +1 -0
- package/dist/senpi/strategy-status.js +70 -0
- package/dist/senpi/strategy-status.js.map +1 -0
- package/dist/senpi/types.d.ts +47 -3
- package/dist/senpi/types.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -1
- package/dist/telemetry/metrics.d.ts +23 -0
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +65 -0
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/types/strategy.d.ts +9 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/universe/package-universe.d.ts +97 -0
- package/dist/universe/package-universe.d.ts.map +1 -0
- package/dist/universe/package-universe.js +275 -0
- package/dist/universe/package-universe.js.map +1 -0
- package/dist/validate/codes.d.ts +20 -0
- package/dist/validate/codes.d.ts.map +1 -1
- package/dist/validate/codes.js +36 -0
- package/dist/validate/codes.js.map +1 -1
- package/dist/validate/import-stage.js +5 -5
- package/dist/validate/import-stage.js.map +1 -1
- package/dist/validate/index.d.ts +1 -1
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/install-gate.d.ts.map +1 -1
- package/dist/validate/install-gate.js +10 -2
- package/dist/validate/install-gate.js.map +1 -1
- package/dist/validate/live-instruments.d.ts +44 -0
- package/dist/validate/live-instruments.d.ts.map +1 -0
- package/dist/validate/live-instruments.js +71 -0
- package/dist/validate/live-instruments.js.map +1 -0
- package/dist/validate/live-stage.js +5 -5
- package/dist/validate/live-stage.js.map +1 -1
- package/dist/validate/proof.d.ts +11 -1
- package/dist/validate/proof.d.ts.map +1 -1
- package/dist/validate/proof.js +7 -6
- package/dist/validate/proof.js.map +1 -1
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +6 -4
- package/dist/validate/recipe-checks.js.map +1 -1
- package/dist/validate/run.d.ts +5 -0
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +10 -1
- package/dist/validate/run.js.map +1 -1
- package/dist/validate/target.d.ts +41 -0
- package/dist/validate/target.d.ts.map +1 -1
- package/dist/validate/target.js +192 -16
- package/dist/validate/target.js.map +1 -1
- package/dist/validate/universe-stage.d.ts +71 -0
- package/dist/validate/universe-stage.d.ts.map +1 -0
- package/dist/validate/universe-stage.js +183 -0
- package/dist/validate/universe-stage.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/runtime/managed-wallet-guard.d.ts +0 -47
- package/dist/runtime/managed-wallet-guard.d.ts.map +0 -1
- package/dist/runtime/managed-wallet-guard.js +0 -106
- package/dist/runtime/managed-wallet-guard.js.map +0 -1
- package/dist/runtime/protection-nudge.d.ts +0 -67
- package/dist/runtime/protection-nudge.d.ts.map +0 -1
- package/dist/runtime/protection-nudge.js +0 -244
- package/dist/runtime/protection-nudge.js.map +0 -1
|
@@ -24,6 +24,7 @@ Quick commands:
|
|
|
24
24
|
senpi guide actions Action types and decision modes
|
|
25
25
|
senpi guide dsl DSL exit: phases, lock modes, tiers, time-based cuts
|
|
26
26
|
senpi validate <pkg> Prove a strategy runs before funding it (static → import → live)
|
|
27
|
+
senpi deploy -p <pkg> Take a proven package live: create+fund → install → verified tick
|
|
27
28
|
senpi scanner Per-scanner health: runs, errors, liveness, "(no signals yet)" flag
|
|
28
29
|
senpi events Local domain-event log — the trade narrative
|
|
29
30
|
senpi explain <asset> Stitch one asset's position lifecycle from the event log
|
|
@@ -478,11 +479,75 @@ senpi config — Manage CLI preferences (~/.openclaw/senpi-cli.json)
|
|
|
478
479
|
config unset <key> Remove a key
|
|
479
480
|
config reset Clear all preferences
|
|
480
481
|
|
|
482
|
+
senpi deploy — Take a strategy package live, end to end (the strategy lifecycle)
|
|
483
|
+
openclaw senpi deploy -p <package-dir> --budget <usd> # start (detached): preflight → create+fund → install → verified tick
|
|
484
|
+
openclaw senpi deploy status # watch until terminal; quotes the verified report
|
|
485
|
+
-p, --path <dir> Strategy package directory (contains strategy.yaml)
|
|
486
|
+
--budget <usd> Total budget across the package's wallets (min $10/wallet)
|
|
487
|
+
--decision-model <model> Required if the package uses decision_mode: llm
|
|
488
|
+
--tick-wait <seconds> Wait for one verified scanner tick (default 120, 0 skips)
|
|
489
|
+
--max-wait <seconds> Wait for wallets to reach ACTIVE (default 150)
|
|
490
|
+
--json Raw JSON output
|
|
491
|
+
deploy returns in ~1s; the job runs detached. Poll
|
|
492
|
+
'deploy status' until it is terminal. Exit codes:
|
|
493
|
+
0 live · 2 refused · 3 failed · 4 installed-unobserved ·
|
|
494
|
+
5 interrupted · 6 pending/running · 1 error. An
|
|
495
|
+
unobserved tick reports as "unobserved", never as success.
|
|
496
|
+
'deploy' itself uses the same classes at START: 0 job
|
|
497
|
+
started · 2 refused (the message leads with its code, e.g.
|
|
498
|
+
[INVALID_REQUEST]) · 1 internal/transport. So 2 always
|
|
499
|
+
means obey the message; 1 always means the deploy's state
|
|
500
|
+
is unknown — never retry a 2.
|
|
501
|
+
A package no 'senpi validate' has ever proven — or one whose
|
|
502
|
+
files have changed since, or whose proof predates this
|
|
503
|
+
runtime — is refused BEFORE any money moves. The refusal
|
|
504
|
+
leads with [E_VALIDATE_NO_PROOF] /
|
|
505
|
+
[E_VALIDATE_CONTENT_CHANGED] /
|
|
506
|
+
[E_VALIDATE_RUNTIME_VERSION_CHANGED] and names the exact
|
|
507
|
+
validate command for the instance that failed. Two instances
|
|
508
|
+
sharing one runtime directory are refused outright
|
|
509
|
+
([INVALID_REQUEST]): a proof lives beside its recipe, one
|
|
510
|
+
file per directory, so it cannot speak for both.
|
|
511
|
+
Budget is two-tier: BELOW the $10/wallet floor it refuses
|
|
512
|
+
[E_FUNDS_BELOW_FLOOR]; and when the split leaves a wallet
|
|
513
|
+
under its OWN sizing it DEPLOYS and warns
|
|
514
|
+
[W_BUDGET_BELOW_STRATEGY_MIN] (that sleeve runs degraded —
|
|
515
|
+
fewer slots than authored). [W_BUDGET_UNRESOLVED] = the
|
|
516
|
+
minimum could not be computed and may be understated.
|
|
517
|
+
Neither warn stops a deploy; both judge each wallet against
|
|
518
|
+
its OWN allocation, not the budget against the package
|
|
519
|
+
total (those differ once a sleeve is adopted). To run the
|
|
520
|
+
authored design you must close and redeploy — a re-run at
|
|
521
|
+
a bigger --budget only adopts the wallet it already made.
|
|
522
|
+
Follow the warn's own close command verbatim; it is scoped
|
|
523
|
+
to the short sleeves so adopted wallets are left alone.
|
|
524
|
+
Some reports deliberately carry NO close command (a funded
|
|
525
|
+
wallet with no runtime installed; a funding_share: 0
|
|
526
|
+
sleeve; or when [E_ROLLBACK_INCOMPLETE] owns the cleanup)
|
|
527
|
+
— that is the correct answer there, not a gap to fill:
|
|
528
|
+
never substitute a package-wide close.
|
|
529
|
+
Both of those judge the PLAN. After the wallets exist the
|
|
530
|
+
report also checks the OUTCOME: when the backend lands under
|
|
531
|
+
90% of what a wallet was asked to be funded, it warns
|
|
532
|
+
[W_BUDGET_PARTIAL_FUND] with both numbers ("funded $60.00 of
|
|
533
|
+
requested $500.00 (12%, short $440.00)"). The strategy is
|
|
534
|
+
still live and the exit code does not move — do NOT close it
|
|
535
|
+
over the shortfall. Quote the FUNDED figure, never the
|
|
536
|
+
requested one. Those figures are AT DEPLOY TIME and the note
|
|
537
|
+
re-renders on every later status read, so re-read the
|
|
538
|
+
current funding (status.py) before moving money, then top up
|
|
539
|
+
via the senpi-deposit-withdraw-transfer skill;
|
|
540
|
+
close-and-redeploy is the destructive fallback. The warn is
|
|
541
|
+
silent wherever a D-6 rollback was attempted — if the create
|
|
542
|
+
step carries [E_ROLLBACK_INCOMPLETE], THAT line owns the
|
|
543
|
+
remediation (reclaim), and it is the one to follow.
|
|
544
|
+
When the backend reports no readable funded amount at all,
|
|
545
|
+
you get [W_BUDGET_FUNDED_UNREADABLE] instead: no percentage,
|
|
546
|
+
no shortfall, no top-up. The funded amount is UNKNOWN, never
|
|
547
|
+
$0.00 — ask the USER to verify what actually landed before
|
|
548
|
+
any money action
|
|
549
|
+
|
|
481
550
|
senpi runtime — Manage runtimes
|
|
482
|
-
runtime create Add a runtime from a YAML file
|
|
483
|
-
-p, --path <path> Path to strategy YAML file
|
|
484
|
-
-c, --content <yaml> Inline YAML content
|
|
485
|
-
--runtime-id <id> Override auto-derived ID
|
|
486
551
|
runtime delete [id] Remove a runtime
|
|
487
552
|
--id <runtime_id> Delete by runtime ID
|
|
488
553
|
--address <wallet> Delete by wallet address
|
|
@@ -510,6 +575,12 @@ senpi validate — Prove a strategy can execute BEFORE it is funded
|
|
|
510
575
|
command, not the code
|
|
511
576
|
A full unscoped PASS at live depth writes .senpi-proof.json beside the recipe
|
|
512
577
|
(content-addressed, no expiry); the deploy gate verifies it via verifyProof().
|
|
578
|
+
That gate REFUSES, before any money moves: [E_VALIDATE_NO_PROOF] nothing here
|
|
579
|
+
was ever observed to run · [E_VALIDATE_CONTENT_CHANGED] the files moved since
|
|
580
|
+
the proof, and it names which · [E_VALIDATE_RUNTIME_VERSION_CHANGED] the proof
|
|
581
|
+
predates the running runtime (the package needs no edit — just re-prove it).
|
|
582
|
+
A proof attests that this content RAN and passed, not that every check at that
|
|
583
|
+
depth produced a verdict — deploy re-checks the live instrument universe itself.
|
|
513
584
|
|
|
514
585
|
senpi status — Lightweight runtime health digest
|
|
515
586
|
status Overall health + scanners (with degraded count),
|
|
@@ -623,8 +694,9 @@ senpi.installRuntime
|
|
|
623
694
|
Success: { id, name, wallet }
|
|
624
695
|
Errors: INVALID_REQUEST (missing YAML, duplicate ID/wallet, or the install gate: a recipe that
|
|
625
696
|
cannot trade — no margin source, no risk guardrails, missing scanner entrypoint, or an
|
|
626
|
-
unresolvable scanner path — is refused with what/why/fix per problem
|
|
627
|
-
|
|
697
|
+
unresolvable scanner path — is refused with what/why/fix per problem, the message
|
|
698
|
+
leading with the bracketed code of the first blocking finding, e.g.
|
|
699
|
+
[E_VALIDATE_UNGATED]), UNAVAILABLE (bad YAML, start failure)
|
|
628
700
|
|
|
629
701
|
senpi.listRuntimes
|
|
630
702
|
Params: (none)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guide-commands.js","sourceRoot":"","sources":["../../src/cli/guide-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,aAAa,GACjB,0EAA0E,CAAC;AAE7E,+EAA+E;AAE/E,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC;qBACtB,OAAO
|
|
1
|
+
{"version":3,"file":"guide-commands.js","sourceRoot":"","sources":["../../src/cli/guide-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,12 @@ export declare function renderEventsDigest(entries: StoredEvent[]): string;
|
|
|
24
24
|
export declare function renderExplain(asset: string, entries: StoredEvent[]): string;
|
|
25
25
|
/** Minimal Commander command type for subcommands and options. */
|
|
26
26
|
interface CommanderCommand {
|
|
27
|
-
|
|
27
|
+
/** Commander 14 accepts either a description string or an options object (e.g. `{ hidden: true }`). */
|
|
28
|
+
command(name: string, descOrOpts?: string | {
|
|
29
|
+
hidden?: boolean;
|
|
30
|
+
}, opts?: {
|
|
31
|
+
hidden?: boolean;
|
|
32
|
+
}): CommanderCommand;
|
|
28
33
|
description(desc: string): CommanderCommand;
|
|
29
34
|
argument(name: string, description?: string): CommanderCommand;
|
|
30
35
|
option(flags: string, description?: string): CommanderCommand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"senpi-commands.d.ts","sourceRoot":"","sources":["../../src/cli/senpi-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"senpi-commands.d.ts","sourceRoot":"","sources":["../../src/cli/senpi-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAI/D;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,CAAA;CAAE,GAChF,IAAI,CAmxCN;AAuaD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CAWvE;AAmDD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CASjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAW3E;AA8HD,kEAAkE;AAClE,UAAU,gBAAgB;IACxB,uGAAuG;IACvG,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,EAC1C,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC1B,gBAAgB,CAAC;IACpB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC/D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC9D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACtE,MAAM,CACJ,EAAE,EACE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzD,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzE,gBAAgB,CAAC;CACrB"}
|
|
@@ -11,6 +11,8 @@ import { spawnSync } from "../utils/safe-spawn.js";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { registerGuideCommands, CHEATSHEET_TEXT } from "./guide-commands.js";
|
|
13
13
|
import { registerSenpiConfigCommands } from "./senpi-config-commands.js";
|
|
14
|
+
import { exitCodeForDeploy, renderDeploySnapshot } from "./deploy-render.js";
|
|
15
|
+
import { isRefusalMessage } from "../deploy/taxonomy.js";
|
|
14
16
|
import { registerValidateCommand } from "./validate-command.js";
|
|
15
17
|
import { VERSION } from "../version.js";
|
|
16
18
|
/**
|
|
@@ -27,14 +29,18 @@ export function registerSenpiCli(program) {
|
|
|
27
29
|
});
|
|
28
30
|
registerSenpiConfigCommands(senpi);
|
|
29
31
|
registerValidateCommand(senpi);
|
|
32
|
+
registerDeployCommands(senpi);
|
|
30
33
|
const runtime = senpi.command("runtime");
|
|
31
34
|
runtime.description("Manage runtimes");
|
|
32
35
|
runtime
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
// Demoted, not deleted: `senpi deploy` is the supported path (funds preflight + attribution +
|
|
37
|
+
// verified tick). Hidden keeps it out of help/routing while deploy.py keeps working.
|
|
38
|
+
.command("create", { hidden: true })
|
|
39
|
+
.description("Internal — prefer `senpi deploy` (funds preflight + attribution + verified tick). Direct create skips those gates.")
|
|
35
40
|
.option("-p, --path <path>", "Path to your strategy YAML file")
|
|
36
41
|
.option("-c, --content <content>", "Paste YAML content directly")
|
|
37
42
|
.option("--runtime-id <id>", "Name for this runtime (default: from file name or content)")
|
|
43
|
+
.option("--runtime-yaml-dir <dir>", "Directory to resolve relative scanner paths against (content installs only)")
|
|
38
44
|
.action(async (opts) => {
|
|
39
45
|
const hasPath = typeof opts.path === "string" && opts.path.length > 0;
|
|
40
46
|
const hasContent = typeof opts.content === "string";
|
|
@@ -75,7 +81,13 @@ export function registerSenpiCli(program) {
|
|
|
75
81
|
// scanner's relative `path: ./` and the scaffold can import scan.py. We
|
|
76
82
|
// send content (not path) so remote gateways work, but that drops the
|
|
77
83
|
// directory; this hint restores it for FS-sharing (local/box) setups.
|
|
78
|
-
|
|
84
|
+
// An explicit --runtime-yaml-dir wins: it is the only way a content install can name
|
|
85
|
+
// the directory its relative scanner paths resolve against (otherwise refused).
|
|
86
|
+
...(typeof opts.runtimeYamlDir === "string" && opts.runtimeYamlDir.length > 0
|
|
87
|
+
? { runtimeYamlDir: path.resolve(process.cwd(), opts.runtimeYamlDir) }
|
|
88
|
+
: hasPath
|
|
89
|
+
? { runtimeYamlDir: path.dirname(path.resolve(process.cwd(), opts.path)) }
|
|
90
|
+
: {}),
|
|
79
91
|
...(typeof opts.runtimeId === "string" && opts.runtimeId.length > 0 ? { id: opts.runtimeId } : {}),
|
|
80
92
|
};
|
|
81
93
|
// Runtime create is a SLOW synchronous call: the handler starts a long-lived
|
|
@@ -600,14 +612,19 @@ export function registerSenpiCli(program) {
|
|
|
600
612
|
const params = {};
|
|
601
613
|
if (typeof opts.runtime === "string" && opts.runtime.length > 0)
|
|
602
614
|
params.runtimeId = opts.runtime;
|
|
603
|
-
|
|
615
|
+
// Same class as the deploy verbs' null-data path: a health surface that exits 0 after
|
|
616
|
+
// reading no health is the one thing it exists not to do.
|
|
617
|
+
const data = callGatewayJson("senpi.getHealthStatus", params, (cause) => reportUnreadableGateway("senpi status", cause, " No health was read."));
|
|
604
618
|
if (!data) {
|
|
605
619
|
return;
|
|
606
620
|
}
|
|
607
621
|
if (data.ok === false) {
|
|
608
622
|
const err = data.error;
|
|
609
|
-
|
|
610
|
-
|
|
623
|
+
const message = err?.message ?? "Request failed.";
|
|
624
|
+
console.error(message);
|
|
625
|
+
// Same reading as the deploy verbs, and the same reason: a coded refusal is something to
|
|
626
|
+
// obey, not to retry. Kept here so this branch and the subprocess-level one below agree.
|
|
627
|
+
process.exitCode = isRefusalMessage(message) ? DEPLOY_REFUSED_EXIT : 1;
|
|
611
628
|
return;
|
|
612
629
|
}
|
|
613
630
|
const payload = (data.payload ?? data);
|
|
@@ -1101,11 +1118,225 @@ function fmtMetricNum(n) {
|
|
|
1101
1118
|
return String(n);
|
|
1102
1119
|
return n.toFixed(2);
|
|
1103
1120
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1121
|
+
/**
|
|
1122
|
+
* The `senpi deploy` verb pair — the agent-facing surface for the whole go-live
|
|
1123
|
+
* path. There is no `cancel` (D-4): undeploying a strategy is closing it, and a
|
|
1124
|
+
* wedged job frees its own slot at the deadline. `deploy` starts a DETACHED job and returns in ~1s; monitoring is explicit
|
|
1125
|
+
* polling of `deploy status`. The `senpi.deploy.*` gateway methods behind these
|
|
1126
|
+
* verbs are internal transport and are deliberately not documented anywhere an
|
|
1127
|
+
* agent reads.
|
|
1128
|
+
*/
|
|
1129
|
+
function registerDeployCommands(senpi) {
|
|
1130
|
+
const deploy = senpi
|
|
1131
|
+
.command("deploy")
|
|
1132
|
+
.description("Deploy a strategy package end-to-end (detached): funds preflight, wallet create+fund, install, one observed tick. " +
|
|
1133
|
+
"Starts the job and returns — watch with `senpi deploy status`. " +
|
|
1134
|
+
"Start exit codes: 0 job started · 2 refused (the message leads with the code) · 1 internal/transport error")
|
|
1135
|
+
// NOT `requiredOption`: Commander checks mandatory options by walking the ancestor chain from
|
|
1136
|
+
// the leaf command, so a mandatory `-p` on this parent makes `deploy status` fail before
|
|
1137
|
+
// dispatch ("required option '-p, --path <dir>' not specified"). The start action carries its
|
|
1138
|
+
// own explicit guard below, so nothing is lost.
|
|
1139
|
+
.option("-p, --path <dir>", "Strategy package directory (contains strategy.yaml)")
|
|
1140
|
+
.option("--budget <usd>", "Total budget in USD across the package's wallets (min $10/wallet)")
|
|
1141
|
+
.option("--decision-model <model>", "Model for llm-decision actions (required if the package uses decision_mode: llm)")
|
|
1142
|
+
.option("--tick-wait <seconds>", "How long the job waits to observe one verified scanner tick (default 120, 0 skips)")
|
|
1143
|
+
.option("--max-wait <seconds>", "How long the job waits for wallets to reach ACTIVE (default 150)")
|
|
1144
|
+
.option("--json", "Print the raw start response JSON")
|
|
1145
|
+
.action(async (opts) => {
|
|
1146
|
+
// Every numeric flag is validated the same way. A silently-dropped NaN would run the job
|
|
1147
|
+
// with the default window while the agent believes its own value took effect.
|
|
1148
|
+
// These local guards are REFUSALS, not usage errors: nothing was started, and re-running
|
|
1149
|
+
// the same command can only refuse again. They carry the taxonomy code and exit 2 like
|
|
1150
|
+
// every other start-time refusal — reported as 1 the newly-taught start map reads them as
|
|
1151
|
+
// "transport error, state unknown", whose natural handling is to poll `deploy status`.
|
|
1152
|
+
const numeric = (flag, raw, example) => {
|
|
1153
|
+
if (raw === undefined)
|
|
1154
|
+
return undefined;
|
|
1155
|
+
const value = Number(raw);
|
|
1156
|
+
if (!Number.isFinite(value)) {
|
|
1157
|
+
console.error(`[INVALID_REQUEST] ${flag} must be a number, e.g. ${flag} ${example}. Nothing was started.`);
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
return value;
|
|
1161
|
+
};
|
|
1162
|
+
const budget = numeric("--budget", opts.budget, "500");
|
|
1163
|
+
const tickWaitSec = numeric("--tick-wait", opts.tickWait, "120");
|
|
1164
|
+
const maxWaitSec = numeric("--max-wait", opts.maxWait, "150");
|
|
1165
|
+
if (budget === null || tickWaitSec === null || maxWaitSec === null) {
|
|
1166
|
+
process.exitCode = DEPLOY_REFUSED_EXIT;
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (!opts.path) {
|
|
1170
|
+
// This guard fires BEFORE the gateway is called, so the gateway's own coded
|
|
1171
|
+
// missing-package-dir refusal is unreachable from the CLI — this surface has to carry
|
|
1172
|
+
// it verbatim rather than degrade it to a codeless usage error.
|
|
1173
|
+
console.error("[INVALID_REQUEST] A strategy package directory is required. Nothing was started. " +
|
|
1174
|
+
"Run: senpi deploy -p <package-dir> --budget <usd>");
|
|
1175
|
+
process.exitCode = DEPLOY_REFUSED_EXIT;
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const params = {
|
|
1179
|
+
packageDir: path.resolve(process.cwd(), opts.path),
|
|
1180
|
+
...(budget !== undefined ? { budget } : {}),
|
|
1181
|
+
...(opts.decisionModel ? { decisionModel: opts.decisionModel } : {}),
|
|
1182
|
+
...(tickWaitSec !== undefined ? { tickWaitSec } : {}),
|
|
1183
|
+
...(maxWaitSec !== undefined ? { maxWaitSec } : {}),
|
|
1184
|
+
};
|
|
1185
|
+
const data = callGatewayJson("senpi.deploy.start", params, (cause) => reportUnreadableGateway("senpi deploy", cause, DEPLOY_STATE_UNKNOWN_HINT));
|
|
1186
|
+
if (!data)
|
|
1187
|
+
return;
|
|
1188
|
+
if (data.ok === false) {
|
|
1189
|
+
const err = data.error;
|
|
1190
|
+
const message = err?.message ?? "Deploy start failed.";
|
|
1191
|
+
console.error(message);
|
|
1192
|
+
// D-12 all the way down: `2 = refused`, `1 = internal/transport`. Every start-time gate
|
|
1193
|
+
// (the [INVALID_REQUEST] family — no DSL exit, missing decision model/env, scanner
|
|
1194
|
+
// `enabled` key, non-positive budget, missing/unreadable package — plus
|
|
1195
|
+
// [E_DEPLOY_IN_PROGRESS]) is a refusal, and the taught surfaces tell agents to branch on
|
|
1196
|
+
// the code. Reported as 1 they read as "internal/transport error", whose natural
|
|
1197
|
+
// handling is retry rather than obey — a retried no-stop-loss refusal is the whole bug.
|
|
1198
|
+
process.exitCode = isRefusalMessage(message) ? DEPLOY_REFUSED_EXIT : 1;
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const started = (data.payload ?? data);
|
|
1202
|
+
if (opts.json) {
|
|
1203
|
+
console.log(JSON.stringify(started, null, 2));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
console.log(`deploy ${started.deployId} started — phase: ${started.phase}`);
|
|
1207
|
+
console.log(" watch: openclaw senpi deploy status");
|
|
1208
|
+
});
|
|
1209
|
+
deploy
|
|
1210
|
+
.command("status [deployId]")
|
|
1211
|
+
.description("Show the deploy job: phase while running; the full verified report once terminal. " +
|
|
1212
|
+
"Exit codes: 0 live · 2 refused · 3 failed · 4 installed-unobserved · 5 interrupted · " +
|
|
1213
|
+
"6 pending/still running · 1 error")
|
|
1214
|
+
.option("--json", "Print the raw snapshot JSON")
|
|
1215
|
+
.action(async (deployId, opts) => {
|
|
1216
|
+
const data = callGatewayJson("senpi.deploy.status", deployId ? { deployId } : {}, (cause) => reportUnreadableGateway("senpi deploy status", cause, DEPLOY_STATUS_STATE_UNKNOWN_HINT));
|
|
1217
|
+
if (!data)
|
|
1218
|
+
return;
|
|
1219
|
+
if (data.ok === false) {
|
|
1220
|
+
const message = data.error?.message ?? "Status failed.";
|
|
1221
|
+
console.error(message);
|
|
1222
|
+
// Read the same way the start verb reads its own gateway error, and for the same reason:
|
|
1223
|
+
// `[NOT_FOUND] No deploy has been started on this agent` is a fact, not a fault. Exit 1
|
|
1224
|
+
// would tell an agent to retry a read whose answer cannot change until a deploy is started.
|
|
1225
|
+
process.exitCode = isRefusalMessage(message) ? DEPLOY_REFUSED_EXIT : 1;
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const snap = (data.payload ?? data);
|
|
1229
|
+
// D-12: the exit code applies on the --json path too, so a JSON consumer gets the code AND
|
|
1230
|
+
// the payload rather than having to choose.
|
|
1231
|
+
process.exitCode = exitCodeForDeploy(snap);
|
|
1232
|
+
if (opts?.json) {
|
|
1233
|
+
console.log(JSON.stringify(snap, null, 2));
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
renderDeploySnapshot(snap);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* D-12: `2 = refused`. Named so the start action's guards, its gateway branch, and the one place
|
|
1241
|
+
* every verb's gateway call is classified (`callGatewayJson`) cannot drift. `2` means the same thing
|
|
1242
|
+
* wherever it comes from — a deliberate no, to obey rather than retry — so `senpi status` reports a
|
|
1243
|
+
* coded refusal with it too, even though only the deploy verbs publish the full map.
|
|
1244
|
+
*/
|
|
1245
|
+
const DEPLOY_REFUSED_EXIT = 2;
|
|
1246
|
+
/** Appended to a deploy verb's unreadable-gateway message: the one surface that can still answer. */
|
|
1247
|
+
const DEPLOY_STATE_UNKNOWN_HINT = " This says nothing about the deploy itself — if the gateway is up, ask `senpi deploy status` " +
|
|
1248
|
+
"for the job's real state, and check the gateway log if that keeps failing.";
|
|
1249
|
+
/**
|
|
1250
|
+
* The `deploy status` variant of the same teaching.
|
|
1251
|
+
*
|
|
1252
|
+
* `DEPLOY_STATE_UNKNOWN_HINT` names `senpi deploy status` as the surface that can still answer,
|
|
1253
|
+
* which is a loop when the verb the reader just ran IS status. Same content — the deploy is
|
|
1254
|
+
* untouched by this failure, and the job may be running right now — routed to the thing that
|
|
1255
|
+
* actually has to change first.
|
|
1256
|
+
*/
|
|
1257
|
+
const DEPLOY_STATUS_STATE_UNKNOWN_HINT = " This says nothing about the deploy itself — a started job may be running right now. Start the " +
|
|
1258
|
+
"gateway if it is down (`openclaw gateway run`), check the gateway log, then re-read.";
|
|
1259
|
+
/**
|
|
1260
|
+
* Did the gateway ANSWER this failed `gateway call`, with a refusal?
|
|
1261
|
+
*
|
|
1262
|
+
* `openclaw gateway call` collapses two different worlds into one process status: it catches
|
|
1263
|
+
* everything its RPC throws, prints `Gateway call failed: <error>` to stderr, and exits **1** —
|
|
1264
|
+
* whether the gateway was never reachable or the gateway answered `respond(false, undefined, error)`
|
|
1265
|
+
* and the client rethrew that answer as a `GatewayClientRequestError`. There is no second signal to
|
|
1266
|
+
* read: stdout carries the payload only on success, and the exit code is a constant.
|
|
1267
|
+
*
|
|
1268
|
+
* What DOES separate them is this plugin's own message taxonomy. Every refusal these gateway methods
|
|
1269
|
+
* produce leads with its bracketed code — `[INVALID_REQUEST]`, `[E_UNIVERSE_NOT_LIVE]`,
|
|
1270
|
+
* `[E_NO_DSL_EXIT]`, `[NOT_FOUND]` — and no transport failure does: those read `gateway closed
|
|
1271
|
+
* (1006 …)`, `gateway timeout after 10000ms`, `connect ECONNREFUSED …`, `gateway … requires
|
|
1272
|
+
* credentials`. So the classifier is the taxonomy, not openclaw's error class names (which are its
|
|
1273
|
+
* internals and free to change), and the reader is the ONE reader — `isRefusalMessage`, which is
|
|
1274
|
+
* built on `extractTaxonomyCode` and already knows the advisory `W_` family is not a refusal.
|
|
1275
|
+
*
|
|
1276
|
+
* Every bracket is tried, not just the first: the code sits behind a `Gateway call failed: ` label
|
|
1277
|
+
* and an error name, and a colourised or otherwise decorated stderr can put other brackets ahead
|
|
1278
|
+
* of it.
|
|
1279
|
+
*/
|
|
1280
|
+
function gatewayAnsweredRefusal(stderr) {
|
|
1281
|
+
for (let i = stderr.indexOf("["); i >= 0; i = stderr.indexOf("[", i + 1)) {
|
|
1282
|
+
if (isRefusalMessage(stderr.slice(i)))
|
|
1283
|
+
return true;
|
|
1284
|
+
}
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* The answer for a `gateway call` that produced nothing this CLI could read.
|
|
1289
|
+
*
|
|
1290
|
+
* Always 1 (internal/transport), never 0 — under D-12 a 0 from a deploy verb means `live`, and this
|
|
1291
|
+
* path learned nothing about the job. Deliberately overrides whatever exit code the subprocess
|
|
1292
|
+
* carried, so a `gateway call` that happened to exit 3 cannot masquerade as D-12 `failed`. It never
|
|
1293
|
+
* claims the deploy failed either: a started job may be running right now.
|
|
1294
|
+
*/
|
|
1295
|
+
function reportUnreadableGateway(verb, cause, followUp = "") {
|
|
1296
|
+
process.exitCode = 1;
|
|
1297
|
+
console.error((cause === "dispatch"
|
|
1298
|
+
? `${verb}: the \`gateway call\` subprocess failed, so nothing was read back — exit 1 (transport/internal).`
|
|
1299
|
+
: `${verb}: the gateway answered but its response could not be read — exit 1 (transport/internal).`) +
|
|
1300
|
+
followUp);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* `onFailure` fires on either null path, naming which one. Callers that pass it own the exit code
|
|
1304
|
+
* and the message; callers that omit it keep the historical behaviour (the subprocess's own code
|
|
1305
|
+
* plus the gateway-not-running tip, and a silent return on unparseable stdout).
|
|
1306
|
+
*
|
|
1307
|
+
* A gateway-answered REFUSAL takes neither path: it is classified and exited here, so no caller can
|
|
1308
|
+
* print an unreadable-gateway message over an answer the user has already read.
|
|
1309
|
+
*/
|
|
1310
|
+
function callGatewayJson(method, params, onFailure) {
|
|
1311
|
+
const result = spawnSync(process.argv[0], [process.argv[1], "gateway", "call", method, "--json", "--params", JSON.stringify(params)],
|
|
1312
|
+
// stderr is PIPED, where it used to be inherited, for one reason: a gateway REFUSAL arrives
|
|
1313
|
+
// only there, and a stderr this process never sees is a stderr it can contradict. The child
|
|
1314
|
+
// still owns every character of it — see the verbatim re-emit directly below.
|
|
1315
|
+
{ stdio: ["inherit", "pipe", "pipe"], cwd: process.cwd(), encoding: "utf8" });
|
|
1316
|
+
const stderr = typeof result.stderr === "string" ? result.stderr : "";
|
|
1317
|
+
// FIRST, before anything this CLI prints, which is exactly where an inherited stderr put it —
|
|
1318
|
+
// the child has already exited, so its whole output is ordered ahead of ours. Unmodified: no
|
|
1319
|
+
// reformatting, no truncation, no prefix. The only edit is a terminating newline when the child
|
|
1320
|
+
// did not write one, so the next line cannot be glued onto the last word of a refusal.
|
|
1321
|
+
if (stderr.length > 0) {
|
|
1322
|
+
process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
|
|
1323
|
+
}
|
|
1106
1324
|
if (result.status !== 0) {
|
|
1325
|
+
if (gatewayAnsweredRefusal(stderr)) {
|
|
1326
|
+
// The gateway answered, and it refused. D-12: `2 = refused` — the code agents branch on to
|
|
1327
|
+
// OBEY rather than retry. Reported as 1 (internal/transport) these read as "try again", and
|
|
1328
|
+
// a delisted instrument, a package with no DSL exit, or a wallet another package's stamp
|
|
1329
|
+
// claims will refuse identically forever.
|
|
1330
|
+
//
|
|
1331
|
+
// Nothing further is printed. The refusal above is the answer; the unreadable-gateway text
|
|
1332
|
+
// ("nothing was read back", "if the gateway is not running") would contradict it one line
|
|
1333
|
+
// later, which is the defect this branch exists to end.
|
|
1334
|
+
process.exitCode = DEPLOY_REFUSED_EXIT;
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1107
1337
|
process.exitCode = result.status ?? 1;
|
|
1108
1338
|
console.error("\nTip: If the gateway is not running, start it with: openclaw gateway run");
|
|
1339
|
+
onFailure?.("dispatch");
|
|
1109
1340
|
return null;
|
|
1110
1341
|
}
|
|
1111
1342
|
try {
|
|
@@ -1113,6 +1344,11 @@ function callGatewayJson(method, params) {
|
|
|
1113
1344
|
}
|
|
1114
1345
|
catch {
|
|
1115
1346
|
process.stdout.write(result.stdout ?? "");
|
|
1347
|
+
// Unparseable stdout means this command did NOT do what its output implies, so it must never
|
|
1348
|
+
// exit 0. Set here rather than per-verb so the swallow cannot re-seed itself on the next
|
|
1349
|
+
// caller; verbs that pass `onFailure` refine the message, not the code.
|
|
1350
|
+
process.exitCode = 1;
|
|
1351
|
+
onFailure?.("unreadable");
|
|
1116
1352
|
return null;
|
|
1117
1353
|
}
|
|
1118
1354
|
}
|