@senpi/runtime 3.0.148 → 3.0.149
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/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +55 -0
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/quant-command.d.ts +128 -0
- package/dist/cli/quant-command.d.ts.map +1 -0
- package/dist/cli/quant-command.js +345 -0
- package/dist/cli/quant-command.js.map +1 -0
- package/dist/cli/senpi-commands.d.ts +11 -0
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +49 -4
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/quant/engine/quantdesk/__init__.py +19 -0
- package/dist/quant/engine/quantdesk/__main__.py +281 -0
- package/dist/quant/engine/quantdesk/_version.py +24 -0
- package/dist/quant/engine/quantdesk/benchmark.py +90 -0
- package/dist/quant/engine/quantdesk/book.py +197 -0
- package/dist/quant/engine/quantdesk/deep.py +197 -0
- package/dist/quant/engine/quantdesk/desk.py +878 -0
- package/dist/quant/engine/quantdesk/dsl.py +224 -0
- package/dist/quant/engine/quantdesk/followups.py +117 -0
- package/dist/quant/engine/quantdesk/hl_api.py +473 -0
- package/dist/quant/engine/quantdesk/market.py +290 -0
- package/dist/quant/engine/quantdesk/mcp_client.py +158 -0
- package/dist/quant/engine/quantdesk/metrics.py +409 -0
- package/dist/quant/engine/quantdesk/opportunities.py +88 -0
- package/dist/quant/engine/quantdesk/render.py +787 -0
- package/dist/quant/engine/quantdesk/roundtrips.py +124 -0
- package/dist/quant/engine/quantdesk/score.py +817 -0
- package/dist/quant/engine/quantdesk/senpi_history.py +160 -0
- package/dist/quant/engine/quantdesk/smart_money.py +354 -0
- package/dist/quant/engine/quantdesk/strategy_read.py +292 -0
- package/dist/quant/engine/quantdesk/taxonomy.py +55 -0
- package/dist/quant/engine/quantdesk/timing.py +164 -0
- package/dist/quant/engine/quantdesk/voice.py +42 -0
- package/dist/quant/engine-runner.d.ts +133 -0
- package/dist/quant/engine-runner.d.ts.map +1 -0
- package/dist/quant/engine-runner.js +306 -0
- package/dist/quant/engine-runner.js.map +1 -0
- package/dist/quant/job.d.ts +125 -0
- package/dist/quant/job.d.ts.map +1 -0
- package/dist/quant/job.js +184 -0
- package/dist/quant/job.js.map +1 -0
- package/dist/quant/register.d.ts +53 -0
- package/dist/quant/register.d.ts.map +1 -0
- package/dist/quant/register.js +253 -0
- package/dist/quant/register.js.map +1 -0
- package/dist/quant/registry.d.ts +105 -0
- package/dist/quant/registry.d.ts.map +1 -0
- package/dist/quant/registry.js +248 -0
- package/dist/quant/registry.js.map +1 -0
- package/dist/quant/types.d.ts +216 -0
- package/dist/quant/types.d.ts.map +1 -0
- package/dist/quant/types.js +121 -0
- package/dist/quant/types.js.map +1 -0
- package/dist/skills-manager/manifest.js +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -1
- package/dist/widgets/index.d.ts +8 -1
- package/dist/widgets/index.d.ts.map +1 -1
- package/dist/widgets/index.js +1 -0
- package/dist/widgets/index.js.map +1 -1
- package/dist/widgets/registry.d.ts +8 -0
- package/dist/widgets/registry.d.ts.map +1 -1
- package/dist/widgets/registry.js +26 -1
- package/dist/widgets/registry.js.map +1 -1
- package/dist/widgets/tool.d.ts +14 -4
- package/dist/widgets/tool.d.ts.map +1 -1
- package/dist/widgets/tool.js +36 -23
- package/dist/widgets/tool.js.map +1 -1
- package/dist/widgets/turn-cap.d.ts +33 -0
- package/dist/widgets/turn-cap.d.ts.map +1 -0
- package/dist/widgets/turn-cap.js +119 -0
- package/dist/widgets/turn-cap.js.map +1 -0
- package/dist/widgets/types.d.ts +33 -5
- package/dist/widgets/types.d.ts.map +1 -1
- package/dist/widgets/types.js +4 -3
- package/dist/widgets/types.js.map +1 -1
- package/dist/widgets/widgets/fund-user-wallet.d.ts.map +1 -1
- package/dist/widgets/widgets/fund-user-wallet.js +1 -0
- package/dist/widgets/widgets/fund-user-wallet.js.map +1 -1
- package/dist/widgets/widgets/quant-desk-recommendations.d.ts +52 -0
- package/dist/widgets/widgets/quant-desk-recommendations.d.ts.map +1 -0
- package/dist/widgets/widgets/quant-desk-recommendations.js +278 -0
- package/dist/widgets/widgets/quant-desk-recommendations.js.map +1 -0
- package/openclaw.plugin.json +12 -2
- package/package.json +4 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { type QuantDocument, type QuantError, type QuantRequest, type QuantSection } from "./types.js";
|
|
2
|
+
/** Outer bound on one desk run (spec §4.4). Past it the child is SIGKILLed → `E_QUANT_TIMEOUT`. */
|
|
3
|
+
export declare const QUANT_ENGINE_TIMEOUT_MS = 240000;
|
|
4
|
+
/** Outer bound on re-rendering a stored run (no network, so far shorter than a run). */
|
|
5
|
+
export declare const QUANT_RENDER_TIMEOUT_MS = 30000;
|
|
6
|
+
/** How to reach and run the engine. `engineDir` is the directory CONTAINING the `quantdesk` package. */
|
|
7
|
+
export interface EngineSpawnOptions {
|
|
8
|
+
/** Directory holding the `quantdesk` package; prepended to the child's PYTHONPATH. */
|
|
9
|
+
engineDir: string;
|
|
10
|
+
/** Interpreter to run. Default `$PYTHON`, else `python3` on PATH. */
|
|
11
|
+
pythonBin?: string;
|
|
12
|
+
/** Outer bound before SIGKILL. Default {@link QUANT_ENGINE_TIMEOUT_MS}. */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/** Base environment (default process.env). */
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
/** Senpi MCP bearer; wins over the base env's SENPI_AUTH_TOKEN / SENPI_API_KEY. */
|
|
17
|
+
senpiAuthToken?: string;
|
|
18
|
+
/** Senpi MCP URL; wins over the base env's SENPI_MCP_URL. */
|
|
19
|
+
senpiMcpUrl?: string;
|
|
20
|
+
/** Called once per complete stderr line as it arrives (the `[quant-desk] [n/8]` progress). */
|
|
21
|
+
onStderrLine?: (line: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/** A desk run: where the engine keeps its cache/scratch, and (tests only) a recorded fixture. */
|
|
24
|
+
export interface RunEngineOptions extends EngineSpawnOptions {
|
|
25
|
+
stateDir: string;
|
|
26
|
+
fixture?: string;
|
|
27
|
+
}
|
|
28
|
+
/** What the process did, before interpretation. `out` is the parsed result file, if any. */
|
|
29
|
+
export interface EngineProcessResult {
|
|
30
|
+
/** The process exit code; null when it was killed by a signal or never started. */
|
|
31
|
+
exitCode: number | null;
|
|
32
|
+
/** True when the outer timeout fired and SIGKILLed the child. */
|
|
33
|
+
timedOut: boolean;
|
|
34
|
+
/** Set when the interpreter could not be started at all (e.g. ENOENT). */
|
|
35
|
+
spawnError?: string;
|
|
36
|
+
/** The last {@link STDERR_TAIL_LINES} non-empty stderr lines. */
|
|
37
|
+
stderrTail: string[];
|
|
38
|
+
/** The parsed result file; undefined when absent or not JSON. */
|
|
39
|
+
out?: unknown;
|
|
40
|
+
}
|
|
41
|
+
/** A desk run's interpreted outcome. `refused` = a deterministic "no"; `failed` = could not answer. */
|
|
42
|
+
export type EngineOutcome = {
|
|
43
|
+
readonly ok: true;
|
|
44
|
+
readonly document: QuantDocument;
|
|
45
|
+
} | {
|
|
46
|
+
readonly ok: false;
|
|
47
|
+
readonly status: "failed" | "refused";
|
|
48
|
+
readonly error: QuantError;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A re-render's outcome.
|
|
52
|
+
*
|
|
53
|
+
* `storedRunUnreadable` is set (to the engine's reason) when render.py exited 2 WITH a result file:
|
|
54
|
+
* that is render_doc saying the run file could not be parsed or holds no document — a fact about
|
|
55
|
+
* the STORED RUN, not an engine fault. The gateway turns it into a NOT_FOUND-style answer with the
|
|
56
|
+
* re-run command rather than "quote the error, do not retry". An exit 2 with no file (the argument
|
|
57
|
+
* parser) never sets it.
|
|
58
|
+
*/
|
|
59
|
+
export type RenderOutcome = {
|
|
60
|
+
readonly ok: true;
|
|
61
|
+
readonly markdown: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly ok: false;
|
|
64
|
+
readonly error: QuantError;
|
|
65
|
+
readonly storedRunUnreadable?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The child's environment. PYTHONPATH gets the engine dir first. MCP credentials are injected only
|
|
69
|
+
* when present — never as "" — mirroring the scanner launcher, so the engine's own "no token → no
|
|
70
|
+
* senpi client" path stays truthful.
|
|
71
|
+
*
|
|
72
|
+
* Two interpreter settings are forced, whatever the base env says:
|
|
73
|
+
* - `PYTHONHASHSEED=0`: the engine still has sites that iterate a `set` of strings (coin names,
|
|
74
|
+
* wallets) where the order can reach the output. String hashing is randomised per process, so
|
|
75
|
+
* those sites would make the same wallet produce different documents run to run (one such site
|
|
76
|
+
* already made the oracle reproduce only two runs in three before it was fixed). The recorded
|
|
77
|
+
* fixture does not exercise the remaining ones, so tests cannot catch them; a fixed seed makes
|
|
78
|
+
* every run deterministic until they are all fixed.
|
|
79
|
+
* - `PYTHONDONTWRITEBYTECODE=1`: the engine runs from the installed `dist/quant/engine`; without
|
|
80
|
+
* this every first import would drop `__pycache__` into the shipped package directory.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildEngineEnv(opts: EngineSpawnOptions): NodeJS.ProcessEnv;
|
|
83
|
+
/**
|
|
84
|
+
* Run `python -m quantdesk <args>` once; resolve when it exits (or is killed), never reject.
|
|
85
|
+
*
|
|
86
|
+
* @param args The interface arguments after `-m quantdesk` (`run …` or `render …`).
|
|
87
|
+
* @param outPath The `--out` path inside `args`; read after exit. A missing or non-JSON file leaves
|
|
88
|
+
* `out` undefined — the caller decides what that means from the exit code.
|
|
89
|
+
* @returns The raw process facts. Side effect: SIGKILLs the child if it outlives `timeoutMs`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function spawnEngine(args: readonly string[], outPath: string, opts: EngineSpawnOptions): Promise<EngineProcessResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Interpret a finished engine process (spec §4.4 exit map). Pure — exported for tests.
|
|
94
|
+
* 0 done · 1 E_QUANT_UPSTREAM · 2 E_QUANT_INVALID_REQUEST · 3 E_QUANT_NO_ACTIVITY ·
|
|
95
|
+
* 4 E_QUANT_NOT_A_TRADER · anything else (the engine's crash code 70, the retired lock's 5, any
|
|
96
|
+
* code this runtime does not know) E_QUANT_ENGINE_ERROR. An exit 2 with NO result file is the
|
|
97
|
+
* engine's argument parser, mapped to E_QUANT_INVALID_REQUEST like any other exit 2; any other exit
|
|
98
|
+
* with no result file is an engine fault.
|
|
99
|
+
*
|
|
100
|
+
* Only exit 4 names `--force`: it is the one refusal a reader can knowingly override (read a vault
|
|
101
|
+
* as a trader). Every other next step is non-destructive and never suggests forcing.
|
|
102
|
+
*
|
|
103
|
+
* Exit 3's next step follows the request's voice: the engine's own-wallet guidance for `mine`,
|
|
104
|
+
* "a longer --days or another address" for `other` (someone else's wallet has no strategy wallets
|
|
105
|
+
* of the reader's to resolve).
|
|
106
|
+
*
|
|
107
|
+
* @param res The process facts from {@link spawnEngine}.
|
|
108
|
+
* @param request The request, to spell the exact re-run command in teaching text.
|
|
109
|
+
* @param timeoutMs The bound that was applied, for the timeout message.
|
|
110
|
+
* @param nowMs "Now" for the index-coverage figure's staleness (exact count vs "over 25,000").
|
|
111
|
+
*/
|
|
112
|
+
export declare function mapEngineResult(res: EngineProcessResult, request: QuantRequest, timeoutMs: number, nowMs?: number): EngineOutcome;
|
|
113
|
+
/**
|
|
114
|
+
* Run one desk. Writes the config into a private temp dir, reads the result file, removes the dir.
|
|
115
|
+
*
|
|
116
|
+
* @param request The validated request (addresses lowercased, days bounded) — the runtime's job.
|
|
117
|
+
* @param opts Where the engine lives and where it keeps its cache/scratch (`stateDir`), plus
|
|
118
|
+
* `fixture` for tests (the engine then replays a recording with no network).
|
|
119
|
+
* @returns The document, or a refused/failed outcome whose error leads with `[E_QUANT_*]`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function runQuantEngine(request: QuantRequest, opts: RunEngineOptions): Promise<EngineOutcome>;
|
|
122
|
+
/**
|
|
123
|
+
* Re-render sections of a stored run through render.py, so the engine stays the one producer of
|
|
124
|
+
* the desk's Markdown (a TypeScript renderer would drift). No network, no recompute.
|
|
125
|
+
*
|
|
126
|
+
* @param runFile A stored run record (`runs/<id>.json`) or a bare document.
|
|
127
|
+
* @param sections Sections to render, in the order given; empty renders the whole desk.
|
|
128
|
+
* @returns The Markdown, or an `[E_QUANT_*]` error: TIMEOUT, INVALID_REQUEST (the engine's parser
|
|
129
|
+
* refused, e.g. an unknown section) or ENGINE_ERROR (anything else, with the stderr tail;
|
|
130
|
+
* `storedRunUnreadable` marks the exit-2-with-a-file case, see {@link RenderOutcome}).
|
|
131
|
+
*/
|
|
132
|
+
export declare function renderQuantMarkdown(runFile: string, sections: readonly QuantSection[], opts: EngineSpawnOptions): Promise<RenderOutcome>;
|
|
133
|
+
//# sourceMappingURL=engine-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-runner.d.ts","sourceRoot":"","sources":["../../src/quant/engine-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawns the Python desk engine (`python3 -m quantdesk`) and turns its result file into an outcome.
|
|
3
|
+
*
|
|
4
|
+
* - Spawned through utils/safe-spawn.ts: OpenClaw's install scanner blocks any plugin file holding
|
|
5
|
+
* both a spawn call and the standard-library process module's name, so that name appears nowhere
|
|
6
|
+
* in this file — not even here (pinned by the runner test's "install-scanner hygiene" case).
|
|
7
|
+
* - The result travels by FILE (spec D11, the live-stage precedent): the engine's stdout is drained
|
|
8
|
+
* and ignored, so nothing it prints can corrupt the document.
|
|
9
|
+
* - stderr lines are forwarded as they arrive (the `[quant-desk] [n/8]` progress lines) and the
|
|
10
|
+
* last 20 are kept for failure detail.
|
|
11
|
+
* - Outer timeout then SIGKILL. The engine has no timeout of its own that bounds a stalled socket.
|
|
12
|
+
* - The result file is strict JSON: non-finite figures arrive as the STRINGS "Infinity",
|
|
13
|
+
* "-Infinity", "NaN". They parse as ordinary strings and are passed through untouched — they are
|
|
14
|
+
* data, not a sign of a broken engine.
|
|
15
|
+
*/
|
|
16
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { spawn } from "../utils/safe-spawn.js";
|
|
20
|
+
import { QUANT_DOC_VERSION, QUANT_SECTIONS, SENPI_INDEX_COVERAGE, indexCoverageFigure, quantError, quantRunArgs, } from "./types.js";
|
|
21
|
+
/** Outer bound on one desk run (spec §4.4). Past it the child is SIGKILLed → `E_QUANT_TIMEOUT`. */
|
|
22
|
+
export const QUANT_ENGINE_TIMEOUT_MS = 240_000;
|
|
23
|
+
/** Outer bound on re-rendering a stored run (no network, so far shorter than a run). */
|
|
24
|
+
export const QUANT_RENDER_TIMEOUT_MS = 30_000;
|
|
25
|
+
/** How many trailing stderr lines a failure carries — enough for a Python traceback's last frames. */
|
|
26
|
+
const STDERR_TAIL_LINES = 20;
|
|
27
|
+
/**
|
|
28
|
+
* argparse's exit status. `python -m quantdesk` itself exits 2 WITHOUT writing a result file when
|
|
29
|
+
* its own arguments are wrong (a bad `--section`, a missing `--out`): the parse fails before the
|
|
30
|
+
* interface has anywhere to write. Everything past parsing writes a file stating `exit_code`.
|
|
31
|
+
*/
|
|
32
|
+
const ARGPARSE_EXIT = 2;
|
|
33
|
+
/** Narrow to a plain JSON object (not an array), else undefined. */
|
|
34
|
+
function asRecord(x) {
|
|
35
|
+
return x !== null && typeof x === "object" && !Array.isArray(x) ? x : undefined;
|
|
36
|
+
}
|
|
37
|
+
/** A non-empty string, else undefined — so `??` fallbacks also cover "". */
|
|
38
|
+
function str(x) {
|
|
39
|
+
return typeof x === "string" && x.length > 0 ? x : undefined;
|
|
40
|
+
}
|
|
41
|
+
/** The listed keys of `body` that are present, for a refusal's `details`. */
|
|
42
|
+
function pick(body, keys) {
|
|
43
|
+
return Object.fromEntries(keys.filter((k) => body[k] !== undefined).map((k) => [k, body[k]]));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The child's environment. PYTHONPATH gets the engine dir first. MCP credentials are injected only
|
|
47
|
+
* when present — never as "" — mirroring the scanner launcher, so the engine's own "no token → no
|
|
48
|
+
* senpi client" path stays truthful.
|
|
49
|
+
*
|
|
50
|
+
* Two interpreter settings are forced, whatever the base env says:
|
|
51
|
+
* - `PYTHONHASHSEED=0`: the engine still has sites that iterate a `set` of strings (coin names,
|
|
52
|
+
* wallets) where the order can reach the output. String hashing is randomised per process, so
|
|
53
|
+
* those sites would make the same wallet produce different documents run to run (one such site
|
|
54
|
+
* already made the oracle reproduce only two runs in three before it was fixed). The recorded
|
|
55
|
+
* fixture does not exercise the remaining ones, so tests cannot catch them; a fixed seed makes
|
|
56
|
+
* every run deterministic until they are all fixed.
|
|
57
|
+
* - `PYTHONDONTWRITEBYTECODE=1`: the engine runs from the installed `dist/quant/engine`; without
|
|
58
|
+
* this every first import would drop `__pycache__` into the shipped package directory.
|
|
59
|
+
*/
|
|
60
|
+
export function buildEngineEnv(opts) {
|
|
61
|
+
const base = opts.env ?? process.env;
|
|
62
|
+
const env = { ...base };
|
|
63
|
+
env.PYTHONPATH = base.PYTHONPATH ? `${opts.engineDir}:${base.PYTHONPATH}` : opts.engineDir;
|
|
64
|
+
delete env.SENPI_AUTH_TOKEN;
|
|
65
|
+
delete env.SENPI_MCP_URL;
|
|
66
|
+
const token = opts.senpiAuthToken || base.SENPI_AUTH_TOKEN || base.SENPI_API_KEY || undefined;
|
|
67
|
+
const mcpUrl = opts.senpiMcpUrl || base.SENPI_MCP_URL || undefined;
|
|
68
|
+
if (token)
|
|
69
|
+
env.SENPI_AUTH_TOKEN = token;
|
|
70
|
+
if (mcpUrl)
|
|
71
|
+
env.SENPI_MCP_URL = mcpUrl;
|
|
72
|
+
env.PYTHONHASHSEED = "0";
|
|
73
|
+
env.PYTHONDONTWRITEBYTECODE = "1";
|
|
74
|
+
return env;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run `python -m quantdesk <args>` once; resolve when it exits (or is killed), never reject.
|
|
78
|
+
*
|
|
79
|
+
* @param args The interface arguments after `-m quantdesk` (`run …` or `render …`).
|
|
80
|
+
* @param outPath The `--out` path inside `args`; read after exit. A missing or non-JSON file leaves
|
|
81
|
+
* `out` undefined — the caller decides what that means from the exit code.
|
|
82
|
+
* @returns The raw process facts. Side effect: SIGKILLs the child if it outlives `timeoutMs`.
|
|
83
|
+
*/
|
|
84
|
+
export function spawnEngine(args, outPath, opts) {
|
|
85
|
+
const python = opts.pythonBin ?? process.env.PYTHON ?? "python3";
|
|
86
|
+
const timeoutMs = opts.timeoutMs ?? QUANT_ENGINE_TIMEOUT_MS;
|
|
87
|
+
return new Promise((done) => {
|
|
88
|
+
const tail = [];
|
|
89
|
+
let pending = "";
|
|
90
|
+
let settled = false;
|
|
91
|
+
let timedOut = false;
|
|
92
|
+
const pushLine = (line) => {
|
|
93
|
+
if (line.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
tail.push(line);
|
|
96
|
+
if (tail.length > STDERR_TAIL_LINES)
|
|
97
|
+
tail.shift();
|
|
98
|
+
opts.onStderrLine?.(line);
|
|
99
|
+
};
|
|
100
|
+
const child = spawn(python, ["-m", "quantdesk", ...args], { env: buildEngineEnv(opts), stdio: ["ignore", "pipe", "pipe"] });
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
timedOut = true;
|
|
103
|
+
child.kill("SIGKILL");
|
|
104
|
+
}, timeoutMs);
|
|
105
|
+
const finish = (r) => {
|
|
106
|
+
if (settled)
|
|
107
|
+
return;
|
|
108
|
+
settled = true;
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
if (pending)
|
|
111
|
+
pushLine(pending);
|
|
112
|
+
done({ ...r, timedOut, stderrTail: [...tail] });
|
|
113
|
+
};
|
|
114
|
+
child.stdout?.on("data", () => undefined); // drained, never read: results travel by file
|
|
115
|
+
// Decode as a stream: the progress lines carry "…" and "—", and a chunk boundary can fall inside
|
|
116
|
+
// a multibyte character — String(buffer) per chunk would print replacement characters.
|
|
117
|
+
child.stderr?.setEncoding("utf8");
|
|
118
|
+
child.stderr?.on("data", (chunk) => {
|
|
119
|
+
pending += String(chunk);
|
|
120
|
+
const lines = pending.split("\n");
|
|
121
|
+
pending = lines.pop() ?? "";
|
|
122
|
+
for (const l of lines)
|
|
123
|
+
pushLine(l.replace(/\r$/, ""));
|
|
124
|
+
});
|
|
125
|
+
child.on("error", (err) => finish({ exitCode: null, spawnError: err.message }));
|
|
126
|
+
child.on("close", (code) => {
|
|
127
|
+
let out;
|
|
128
|
+
try {
|
|
129
|
+
out = JSON.parse(readFileSync(outPath, "utf8"));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
out = undefined;
|
|
133
|
+
}
|
|
134
|
+
finish({ exitCode: code, out });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** An engine fault: never the wallet's doing, so the next step is to quote it, not to retry. */
|
|
139
|
+
function engineError(text, stderrTail) {
|
|
140
|
+
return quantError("E_QUANT_ENGINE_ERROR", text, "This is a fault in the desk engine, not in the wallet. Quote the error; do not retry in a loop.", stderrTail.length > 0 ? { stderrTail: [...stderrTail] } : undefined);
|
|
141
|
+
}
|
|
142
|
+
/** The last stderr line carrying an argparse-style `error:` — the one sentence naming the cause. */
|
|
143
|
+
function lastErrorLine(stderrTail) {
|
|
144
|
+
return [...stderrTail].reverse().find((l) => /error:/.test(l));
|
|
145
|
+
}
|
|
146
|
+
/** The interpreter never started: the fix is on the agent (python3), not in the request. */
|
|
147
|
+
function spawnFailure(spawnError) {
|
|
148
|
+
return quantError("E_QUANT_ENGINE_ERROR", `the desk engine could not be started: ${spawnError}.`, "This agent needs python3 on its PATH (or PYTHON set to an interpreter). Nothing was computed.");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The engine's own argument parser refused (exit 2, no result file). The runtime builds these
|
|
152
|
+
* arguments, so this is a request the runtime should not have sent — a bad section name is the
|
|
153
|
+
* realistic case. The parser's last line ("…: error: argument --section: invalid choice …") is the
|
|
154
|
+
* one sentence worth showing, and it leads the message.
|
|
155
|
+
*/
|
|
156
|
+
function argparseRefusal(stderrTail) {
|
|
157
|
+
const why = lastErrorLine(stderrTail) ?? stderrTail[stderrTail.length - 1];
|
|
158
|
+
return quantError("E_QUANT_INVALID_REQUEST", `the desk engine refused its arguments${why ? `: ${why}` : "."}`, `Nothing was computed. Check the request: sections are ${QUANT_SECTIONS.join(", ")}; addresses are 0x followed by 40 hex characters.`, stderrTail.length > 0 ? { stderrTail: [...stderrTail] } : undefined);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Interpret a finished engine process (spec §4.4 exit map). Pure — exported for tests.
|
|
162
|
+
* 0 done · 1 E_QUANT_UPSTREAM · 2 E_QUANT_INVALID_REQUEST · 3 E_QUANT_NO_ACTIVITY ·
|
|
163
|
+
* 4 E_QUANT_NOT_A_TRADER · anything else (the engine's crash code 70, the retired lock's 5, any
|
|
164
|
+
* code this runtime does not know) E_QUANT_ENGINE_ERROR. An exit 2 with NO result file is the
|
|
165
|
+
* engine's argument parser, mapped to E_QUANT_INVALID_REQUEST like any other exit 2; any other exit
|
|
166
|
+
* with no result file is an engine fault.
|
|
167
|
+
*
|
|
168
|
+
* Only exit 4 names `--force`: it is the one refusal a reader can knowingly override (read a vault
|
|
169
|
+
* as a trader). Every other next step is non-destructive and never suggests forcing.
|
|
170
|
+
*
|
|
171
|
+
* Exit 3's next step follows the request's voice: the engine's own-wallet guidance for `mine`,
|
|
172
|
+
* "a longer --days or another address" for `other` (someone else's wallet has no strategy wallets
|
|
173
|
+
* of the reader's to resolve).
|
|
174
|
+
*
|
|
175
|
+
* @param res The process facts from {@link spawnEngine}.
|
|
176
|
+
* @param request The request, to spell the exact re-run command in teaching text.
|
|
177
|
+
* @param timeoutMs The bound that was applied, for the timeout message.
|
|
178
|
+
* @param nowMs "Now" for the index-coverage figure's staleness (exact count vs "over 25,000").
|
|
179
|
+
*/
|
|
180
|
+
export function mapEngineResult(res, request, timeoutMs, nowMs = Date.now()) {
|
|
181
|
+
const again = `openclaw senpi quant run ${quantRunArgs(request)}`;
|
|
182
|
+
const failed = (error) => ({ ok: false, status: "failed", error });
|
|
183
|
+
const refused = (error) => ({ ok: false, status: "refused", error });
|
|
184
|
+
if (res.timedOut) {
|
|
185
|
+
return failed(quantError("E_QUANT_TIMEOUT", `the desk did not finish within ${Math.round(timeoutMs / 1000)}s and was stopped.`, `Run it once more — reads cached by the first attempt make the second faster: ${again}. Never start several at once.`, res.stderrTail.length > 0 ? { stderrTail: res.stderrTail } : undefined));
|
|
186
|
+
}
|
|
187
|
+
if (res.spawnError)
|
|
188
|
+
return failed(spawnFailure(res.spawnError));
|
|
189
|
+
const out = asRecord(res.out);
|
|
190
|
+
if (!out && res.exitCode === ARGPARSE_EXIT)
|
|
191
|
+
return refused(argparseRefusal(res.stderrTail));
|
|
192
|
+
const code = typeof out?.exit_code === "number" ? out.exit_code : undefined;
|
|
193
|
+
if (!out || code === undefined) {
|
|
194
|
+
return failed(engineError(`the desk engine exited ${res.exitCode ?? "abnormally"} without a result.`, res.stderrTail));
|
|
195
|
+
}
|
|
196
|
+
const body = asRecord(out.refusal) ?? {};
|
|
197
|
+
switch (code) {
|
|
198
|
+
case 0: {
|
|
199
|
+
const doc = asRecord(out.document);
|
|
200
|
+
if (!doc || doc.doc_version !== QUANT_DOC_VERSION || !Array.isArray(doc.cards)) {
|
|
201
|
+
return failed(engineError(`the engine returned a document this runtime cannot read (doc_version ${String(doc?.doc_version)}; expected ${QUANT_DOC_VERSION}).`, res.stderrTail));
|
|
202
|
+
}
|
|
203
|
+
return { ok: true, document: doc };
|
|
204
|
+
}
|
|
205
|
+
case 1:
|
|
206
|
+
return failed(quantError("E_QUANT_UPSTREAM", str(body.error) ?? "a Hyperliquid read failed.", str(body.what_to_do) ?? `One retry is worth it: ${again}`, pick(body, ["rate_limited"])));
|
|
207
|
+
case 2: {
|
|
208
|
+
// Exit 2 WITH a file: the engine rejected the config the runtime built (malformed config), or
|
|
209
|
+
// desk.py's own parser rejected the argv run() derived from it — then the body is only
|
|
210
|
+
// "no detail" and the real `error:` line is on stderr. Addresses were validated before the
|
|
211
|
+
// spawn, so this is a runtime defect, never something the reader can fix by retyping.
|
|
212
|
+
const bodyError = str(body.error);
|
|
213
|
+
const cause = bodyError && bodyError !== "no detail" ? bodyError : lastErrorLine(res.stderrTail) ?? bodyError ?? "no detail";
|
|
214
|
+
return refused(quantError("E_QUANT_INVALID_REQUEST", `the desk engine rejected the request the runtime built for it: ${cause}`, "Nothing was computed. The addresses passed validation, so this is a runtime defect the reader cannot fix: quote the error above and report it; do not retry with other input.", res.stderrTail.length > 0 ? { stderrTail: [...res.stderrTail] } : undefined));
|
|
215
|
+
}
|
|
216
|
+
case 3: {
|
|
217
|
+
// The engine says only WHETHER Senpi's index has the wallet; the count is the runtime's
|
|
218
|
+
// (SENPI_INDEX_COVERAGE), printed exact while fresh and as the round floor once stale.
|
|
219
|
+
const indexed = body.indexed === true ? true : body.indexed === false ? false : null;
|
|
220
|
+
const figure = indexCoverageFigure(nowMs);
|
|
221
|
+
const indexLine = indexed === false
|
|
222
|
+
? `Senpi's index does not have this wallet yet: it covers ${figure} wallets with closed positions (as of ${SENPI_INDEX_COVERAGE.asOf}); wallets are added in waves — never promise a date.`
|
|
223
|
+
: indexed === true
|
|
224
|
+
? "Senpi's index has this wallet."
|
|
225
|
+
: `Whether Senpi's index (${figure} wallets with closed positions, as of ${SENPI_INDEX_COVERAGE.asOf}) has this wallet cannot be told: nothing closed in the window either way.`;
|
|
226
|
+
// The engine's own-wallet guidance (resolve your strategy wallets with strategy_list) is for
|
|
227
|
+
// the reader's OWN book. An --other read is someone else's wallet: pointing the reader at
|
|
228
|
+
// their strategy wallets there answers a question they did not ask, so the analyst voice
|
|
229
|
+
// gets the one next step that can change the answer.
|
|
230
|
+
const own = request.whose === "other" ? undefined : str(body.if_this_is_your_own_wallet);
|
|
231
|
+
return refused(quantError("E_QUANT_NO_ACTIVITY", `${str(body.error) ?? "no perp activity in the window."} ${indexLine}${own ? ` If this is your own wallet: ${own}` : ""}`, request.whose === "other"
|
|
232
|
+
? "This wallet had no perp activity in the window; try a longer --days or another address."
|
|
233
|
+
: own ?? str(body.what_this_usually_means) ?? "Try a wallet with perp trades in the window, or a longer --days.", { indexed, indexedWallets: SENPI_INDEX_COVERAGE.indexedWallets, indexAsOf: SENPI_INDEX_COVERAGE.asOf,
|
|
234
|
+
...pick(body, ["if_this_is_your_own_wallet", "what_this_usually_means", "perp_fills_in_window", "open_perp_positions"]) }));
|
|
235
|
+
}
|
|
236
|
+
case 4: {
|
|
237
|
+
const say = str(body.say_to_the_reader);
|
|
238
|
+
return refused(quantError("E_QUANT_NOT_A_TRADER", str(body.error) ?? "this address is not a trader's book.", `${say ? `Tell the reader: ${say} ` : ""}Only if they still want it read as a trader: ${again} --force`, pick(body, ["not_a_trader", "say_to_the_reader", "if_you_meant_it", "name", "coins", "tape_cap"])));
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
return failed(engineError(str(asRecord(out.crash)?.error) ?? `the desk engine exited ${code}.`, res.stderrTail));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Run one desk. Writes the config into a private temp dir, reads the result file, removes the dir.
|
|
246
|
+
*
|
|
247
|
+
* @param request The validated request (addresses lowercased, days bounded) — the runtime's job.
|
|
248
|
+
* @param opts Where the engine lives and where it keeps its cache/scratch (`stateDir`), plus
|
|
249
|
+
* `fixture` for tests (the engine then replays a recording with no network).
|
|
250
|
+
* @returns The document, or a refused/failed outcome whose error leads with `[E_QUANT_*]`.
|
|
251
|
+
*/
|
|
252
|
+
export async function runQuantEngine(request, opts) {
|
|
253
|
+
const dir = mkdtempSync(join(tmpdir(), "senpi-quant-"));
|
|
254
|
+
try {
|
|
255
|
+
const configPath = join(dir, "config.json");
|
|
256
|
+
const outPath = join(dir, "out.json");
|
|
257
|
+
writeFileSync(configPath, JSON.stringify({
|
|
258
|
+
addresses: request.addresses, days: request.days, whose: request.whose, book: request.book, force: request.force,
|
|
259
|
+
state_dir: opts.stateDir, ...(opts.fixture ? { fixture: opts.fixture } : {}),
|
|
260
|
+
}), "utf8");
|
|
261
|
+
const res = await spawnEngine(["run", "--config", configPath, "--out", outPath], outPath, opts);
|
|
262
|
+
return mapEngineResult(res, request, opts.timeoutMs ?? QUANT_ENGINE_TIMEOUT_MS);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
rmSync(dir, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Re-render sections of a stored run through render.py, so the engine stays the one producer of
|
|
270
|
+
* the desk's Markdown (a TypeScript renderer would drift). No network, no recompute.
|
|
271
|
+
*
|
|
272
|
+
* @param runFile A stored run record (`runs/<id>.json`) or a bare document.
|
|
273
|
+
* @param sections Sections to render, in the order given; empty renders the whole desk.
|
|
274
|
+
* @returns The Markdown, or an `[E_QUANT_*]` error: TIMEOUT, INVALID_REQUEST (the engine's parser
|
|
275
|
+
* refused, e.g. an unknown section) or ENGINE_ERROR (anything else, with the stderr tail;
|
|
276
|
+
* `storedRunUnreadable` marks the exit-2-with-a-file case, see {@link RenderOutcome}).
|
|
277
|
+
*/
|
|
278
|
+
export async function renderQuantMarkdown(runFile, sections, opts) {
|
|
279
|
+
const dir = mkdtempSync(join(tmpdir(), "senpi-quant-render-"));
|
|
280
|
+
try {
|
|
281
|
+
const outPath = join(dir, "out.json");
|
|
282
|
+
const args = ["render", "--doc", runFile, ...sections.flatMap((s) => ["--section", s]), "--out", outPath];
|
|
283
|
+
const timeoutMs = opts.timeoutMs ?? QUANT_RENDER_TIMEOUT_MS;
|
|
284
|
+
const res = await spawnEngine(args, outPath, { ...opts, timeoutMs });
|
|
285
|
+
const out = asRecord(res.out);
|
|
286
|
+
if (out?.exit_code === 0 && typeof out.markdown === "string")
|
|
287
|
+
return { ok: true, markdown: out.markdown };
|
|
288
|
+
if (res.timedOut) {
|
|
289
|
+
return { ok: false, error: quantError("E_QUANT_TIMEOUT", `rendering the stored desk did not finish within ${Math.round(timeoutMs / 1000)}s and was stopped.`, "Rendering reads no network; read the stored run's document instead of retrying in a loop.", res.stderrTail.length > 0 ? { stderrTail: [...res.stderrTail] } : undefined) };
|
|
290
|
+
}
|
|
291
|
+
if (res.spawnError)
|
|
292
|
+
return { ok: false, error: spawnFailure(res.spawnError) };
|
|
293
|
+
if (!out && res.exitCode === ARGPARSE_EXIT)
|
|
294
|
+
return { ok: false, error: argparseRefusal(res.stderrTail) };
|
|
295
|
+
const why = str(asRecord(out?.refusal)?.error) ?? str(asRecord(out?.crash)?.error);
|
|
296
|
+
return {
|
|
297
|
+
ok: false,
|
|
298
|
+
error: engineError(`rendering the stored desk failed (exit ${String(out?.exit_code ?? res.exitCode)})${why ? `: ${why}` : "."}`, res.stderrTail),
|
|
299
|
+
...(out?.exit_code === ARGPARSE_EXIT ? { storedRunUnreadable: why ?? "the stored run could not be read" } : {}),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
rmSync(dir, { recursive: true, force: true });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=engine-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-runner.js","sourceRoot":"","sources":["../../src/quant/engine-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quant job controller: one in-flight desk per wallet set, run detached, finished into the
|
|
3
|
+
* registry.
|
|
4
|
+
*
|
|
5
|
+
* The slot is IN-MEMORY ONLY (the deploy/job.ts idiom): a persisted slot would wedge a wallet after
|
|
6
|
+
* a crash. The freshness lookup is async, so the slot is checked and claimed AFTER it, with no await
|
|
7
|
+
* between the check and the claim — two simultaneous starts cannot both pass, and a cached answer
|
|
8
|
+
* never takes the slot. The engine runner's own outer timeout is the watchdog: it always settles.
|
|
9
|
+
*
|
|
10
|
+
* Restart safety: a `running` row left in the index by a dead process must never read as live. The
|
|
11
|
+
* boot janitor ({@link QuantRunRegistry.markInterrupted}) stamps those rows, and it has to run
|
|
12
|
+
* BEFORE this process creates a run of its own — otherwise it would stamp a live run. The gateway
|
|
13
|
+
* methods are registered (and callable) before the plugin service's `start()`, so waiting for the
|
|
14
|
+
* service is not enough: every `start` awaits {@link QuantJobController.recoverInterrupted} first,
|
|
15
|
+
* and so do the read paths, so no caller can observe a dead run as running.
|
|
16
|
+
*/
|
|
17
|
+
import type { EngineOutcome } from "./engine-runner.js";
|
|
18
|
+
import type { QuantRunRegistry } from "./registry.js";
|
|
19
|
+
import { type QuantError, type QuantRequest, type QuantRunStatus } from "./types.js";
|
|
20
|
+
/**
|
|
21
|
+
* A start refused because the same wallet set already has a run in flight. Carries the running id
|
|
22
|
+
* so the refusal can name it and `senpi quant status <id>` — a second run would not finish the first
|
|
23
|
+
* any sooner, it would only halve both runs' share of Hyperliquid's rate limit.
|
|
24
|
+
*/
|
|
25
|
+
export declare class QuantInProgressError extends Error {
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly addresses: readonly string[];
|
|
28
|
+
constructor(runId: string, addresses: readonly string[]);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A start refused because the boot janitor (stamping runs a restart cut short) failed. Kept apart
|
|
32
|
+
* from other start failures so the gateway can name the recovery step: the quant state dir must be
|
|
33
|
+
* writable; the next start retries the cleanup.
|
|
34
|
+
*/
|
|
35
|
+
export declare class QuantJanitorError extends Error {
|
|
36
|
+
readonly cause: unknown;
|
|
37
|
+
constructor(cause: unknown);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What `start` answers: a new running run (`cached: false`), or a done run for the same request
|
|
41
|
+
* finished inside the freshness window, served from the registry (`status: "done"`, `cached: true`).
|
|
42
|
+
*/
|
|
43
|
+
export interface QuantStartResult {
|
|
44
|
+
runId: string;
|
|
45
|
+
status: "running" | "done";
|
|
46
|
+
cached: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One run's status as the gateway reports it. `progress` is the engine's latest `[quant-desk]`
|
|
50
|
+
* stderr line and exists only while this process runs the desk; `error` only on failed/refused.
|
|
51
|
+
*/
|
|
52
|
+
export interface QuantStatus {
|
|
53
|
+
runId: string;
|
|
54
|
+
status: QuantRunStatus;
|
|
55
|
+
startedAt: string;
|
|
56
|
+
finishedAt?: string;
|
|
57
|
+
progress?: string;
|
|
58
|
+
error?: QuantError;
|
|
59
|
+
addresses: readonly string[];
|
|
60
|
+
days: number;
|
|
61
|
+
}
|
|
62
|
+
/** Collaborators; `runEngine` is injected so the controller is testable without Python. */
|
|
63
|
+
export interface QuantJobControllerOptions {
|
|
64
|
+
registry: QuantRunRegistry;
|
|
65
|
+
/** Run one desk. Must always settle (the real runner has its own SIGKILL timeout). */
|
|
66
|
+
runEngine(request: QuantRequest, onProgress: (line: string) => void): Promise<EngineOutcome>;
|
|
67
|
+
/** Stamped into each run's meta (the runtime package version). */
|
|
68
|
+
engineVersion: string;
|
|
69
|
+
log(line: string): void;
|
|
70
|
+
}
|
|
71
|
+
/** Single-flight desk runs. See the module doc for the slot and restart invariants. */
|
|
72
|
+
export declare class QuantJobController {
|
|
73
|
+
private readonly opts;
|
|
74
|
+
private readonly bySet;
|
|
75
|
+
private readonly byId;
|
|
76
|
+
private janitor?;
|
|
77
|
+
constructor(opts: QuantJobControllerOptions);
|
|
78
|
+
/**
|
|
79
|
+
* Run the registry's boot janitor once per process and answer the ids it stamped failed.
|
|
80
|
+
*
|
|
81
|
+
* Memoized: the plugin service calls it at start (to log what a restart cut short), and `start`
|
|
82
|
+
* awaits it before claiming a slot, whichever comes first. A failure is NOT memoized — the next
|
|
83
|
+
* caller retries — and `start` refuses while it fails, so this process never creates a run the
|
|
84
|
+
* janitor has not yet had a chance to run ahead of (which it would then stamp as dead).
|
|
85
|
+
*/
|
|
86
|
+
recoverInterrupted(): Promise<string[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Serve a fresh run, or claim the wallet set's slot and start the engine detached.
|
|
89
|
+
*
|
|
90
|
+
* Order: janitor → freshness (async) → check-and-claim (sync) → create record → spawn. The claim
|
|
91
|
+
* follows the last await before it, so of two simultaneous starts exactly one claims and the other
|
|
92
|
+
* sees the slot taken.
|
|
93
|
+
*
|
|
94
|
+
* @throws {QuantInProgressError} when the wallet set (case- and order-insensitive) has a run in flight.
|
|
95
|
+
* @throws {QuantJanitorError} when the boot janitor fails; nothing runs, the next start retries it.
|
|
96
|
+
* @throws When the record write fails; the slot is released and nothing runs.
|
|
97
|
+
*/
|
|
98
|
+
start(request: QuantRequest, options: {
|
|
99
|
+
fresh: boolean;
|
|
100
|
+
}): Promise<QuantStartResult>;
|
|
101
|
+
/**
|
|
102
|
+
* A run's status; no id = the newest run. `undefined` when there is no such run (including an
|
|
103
|
+
* invalid, path-like id — the registry never resolves one). Waits for the boot janitor, so a run a
|
|
104
|
+
* restart cut short reads `failed`, never `running`. A janitor failure does not block reads.
|
|
105
|
+
*
|
|
106
|
+
* A record that says `running` while this process has no live job for it is dead — every run of
|
|
107
|
+
* this process is live from before its record is written until after its outcome is — whether the
|
|
108
|
+
* outcome could not be recorded (disk full after create) or a failing janitor left a restart's
|
|
109
|
+
* row. It is answered `failed` with the re-run command, and stamped failed best-effort (the
|
|
110
|
+
* registry keeps the first outcome, so this can never overwrite a real one).
|
|
111
|
+
*
|
|
112
|
+
* @throws {QuantRunLookupError} (from the registry) for a record of another docVersion.
|
|
113
|
+
*/
|
|
114
|
+
status(runId?: string): Promise<QuantStatus | undefined>;
|
|
115
|
+
/** Resolves when a live run has been finished into the registry (immediately if not live). Tests use it. */
|
|
116
|
+
waitFor(runId: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Run the engine and record its outcome. Never rejects: an engine that throws (spawn exploded) is
|
|
119
|
+
* recorded as `E_QUANT_ENGINE_ERROR`, and a failure to record even that is logged. The slot is
|
|
120
|
+
* always released, so a wallet can never stay busy after its run ended. The registry's `finish`
|
|
121
|
+
* keeps the first outcome, so a late second finisher cannot overwrite a delivered desk.
|
|
122
|
+
*/
|
|
123
|
+
private execute;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../../src/quant/job.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAmB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAA2C,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAE9H;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM;IAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE;gBAApD,KAAK,EAAE,MAAM,EAAW,SAAS,EAAE,SAAS,MAAM,EAAE;CAI1E;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO;gBAAd,KAAK,EAAE,OAAO;CAIpC;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2FAA2F;AAC3F,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,sFAAsF;IACtF,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7F,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAuBD,uFAAuF;AACvF,qBAAa,kBAAkB;IAKjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,OAAO,CAAC,CAAoB;gBAEP,IAAI,EAAE,yBAAyB;IAE5D;;;;;;;OAOG;IACH,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAWvC;;;;;;;;;;OAUG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2B1F;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAwC9D,4GAA4G;IAC5G,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;;OAKG;YACW,OAAO;CAiBtB"}
|