@senpi/runtime 3.0.148 → 3.0.150
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,184 @@
|
|
|
1
|
+
import { addressSetKey, quantError, quantRunArgs } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A start refused because the same wallet set already has a run in flight. Carries the running id
|
|
4
|
+
* so the refusal can name it and `senpi quant status <id>` — a second run would not finish the first
|
|
5
|
+
* any sooner, it would only halve both runs' share of Hyperliquid's rate limit.
|
|
6
|
+
*/
|
|
7
|
+
export class QuantInProgressError extends Error {
|
|
8
|
+
runId;
|
|
9
|
+
addresses;
|
|
10
|
+
constructor(runId, addresses) {
|
|
11
|
+
super(`quant run ${runId} is already running on ${addresses.join(", ")}`);
|
|
12
|
+
this.runId = runId;
|
|
13
|
+
this.addresses = addresses;
|
|
14
|
+
this.name = "QuantInProgressError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A start refused because the boot janitor (stamping runs a restart cut short) failed. Kept apart
|
|
19
|
+
* from other start failures so the gateway can name the recovery step: the quant state dir must be
|
|
20
|
+
* writable; the next start retries the cleanup.
|
|
21
|
+
*/
|
|
22
|
+
export class QuantJanitorError extends Error {
|
|
23
|
+
cause;
|
|
24
|
+
constructor(cause) {
|
|
25
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
26
|
+
this.cause = cause;
|
|
27
|
+
this.name = "QuantJanitorError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function errorText(err) {
|
|
31
|
+
return err instanceof Error ? err.message : String(err);
|
|
32
|
+
}
|
|
33
|
+
/** Map the runner's outcome onto the registry's finish shape. */
|
|
34
|
+
function toRunOutcome(o) {
|
|
35
|
+
return o.ok ? { status: "done", document: o.document } : { status: o.status, error: o.error };
|
|
36
|
+
}
|
|
37
|
+
/** Single-flight desk runs. See the module doc for the slot and restart invariants. */
|
|
38
|
+
export class QuantJobController {
|
|
39
|
+
opts;
|
|
40
|
+
bySet = new Map();
|
|
41
|
+
byId = new Map();
|
|
42
|
+
janitor;
|
|
43
|
+
constructor(opts) {
|
|
44
|
+
this.opts = opts;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run the registry's boot janitor once per process and answer the ids it stamped failed.
|
|
48
|
+
*
|
|
49
|
+
* Memoized: the plugin service calls it at start (to log what a restart cut short), and `start`
|
|
50
|
+
* awaits it before claiming a slot, whichever comes first. A failure is NOT memoized — the next
|
|
51
|
+
* caller retries — and `start` refuses while it fails, so this process never creates a run the
|
|
52
|
+
* janitor has not yet had a chance to run ahead of (which it would then stamp as dead).
|
|
53
|
+
*/
|
|
54
|
+
recoverInterrupted() {
|
|
55
|
+
if (!this.janitor) {
|
|
56
|
+
const attempt = this.opts.registry.markInterrupted();
|
|
57
|
+
this.janitor = attempt;
|
|
58
|
+
attempt.catch(() => {
|
|
59
|
+
if (this.janitor === attempt)
|
|
60
|
+
this.janitor = undefined;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return this.janitor;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Serve a fresh run, or claim the wallet set's slot and start the engine detached.
|
|
67
|
+
*
|
|
68
|
+
* Order: janitor → freshness (async) → check-and-claim (sync) → create record → spawn. The claim
|
|
69
|
+
* follows the last await before it, so of two simultaneous starts exactly one claims and the other
|
|
70
|
+
* sees the slot taken.
|
|
71
|
+
*
|
|
72
|
+
* @throws {QuantInProgressError} when the wallet set (case- and order-insensitive) has a run in flight.
|
|
73
|
+
* @throws {QuantJanitorError} when the boot janitor fails; nothing runs, the next start retries it.
|
|
74
|
+
* @throws When the record write fails; the slot is released and nothing runs.
|
|
75
|
+
*/
|
|
76
|
+
async start(request, options) {
|
|
77
|
+
await this.recoverInterrupted().catch((err) => {
|
|
78
|
+
throw new QuantJanitorError(err);
|
|
79
|
+
});
|
|
80
|
+
if (!options.fresh) {
|
|
81
|
+
const hit = await this.opts.registry.findFresh(request);
|
|
82
|
+
if (hit)
|
|
83
|
+
return { runId: hit.runId, status: "done", cached: true };
|
|
84
|
+
}
|
|
85
|
+
const key = addressSetKey(request.addresses);
|
|
86
|
+
const running = this.bySet.get(key);
|
|
87
|
+
if (running)
|
|
88
|
+
throw new QuantInProgressError(running.runId, running.request.addresses);
|
|
89
|
+
const live = { runId: this.opts.registry.newRunId(), key, request };
|
|
90
|
+
this.bySet.set(key, live); // claimed synchronously after the last await above
|
|
91
|
+
// Live BEFORE the record exists: `status` reads "running on disk, not live" as dead, so a read
|
|
92
|
+
// landing in the create window must already find the run live.
|
|
93
|
+
this.byId.set(live.runId, live);
|
|
94
|
+
try {
|
|
95
|
+
await this.opts.registry.create(live.runId, request, this.opts.engineVersion);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
this.bySet.delete(key);
|
|
99
|
+
this.byId.delete(live.runId);
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
live.settled = this.execute(live);
|
|
103
|
+
return { runId: live.runId, status: "running", cached: false };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A run's status; no id = the newest run. `undefined` when there is no such run (including an
|
|
107
|
+
* invalid, path-like id — the registry never resolves one). Waits for the boot janitor, so a run a
|
|
108
|
+
* restart cut short reads `failed`, never `running`. A janitor failure does not block reads.
|
|
109
|
+
*
|
|
110
|
+
* A record that says `running` while this process has no live job for it is dead — every run of
|
|
111
|
+
* this process is live from before its record is written until after its outcome is — whether the
|
|
112
|
+
* outcome could not be recorded (disk full after create) or a failing janitor left a restart's
|
|
113
|
+
* row. It is answered `failed` with the re-run command, and stamped failed best-effort (the
|
|
114
|
+
* registry keeps the first outcome, so this can never overwrite a real one).
|
|
115
|
+
*
|
|
116
|
+
* @throws {QuantRunLookupError} (from the registry) for a record of another docVersion.
|
|
117
|
+
*/
|
|
118
|
+
async status(runId) {
|
|
119
|
+
await this.recoverInterrupted().catch(() => undefined);
|
|
120
|
+
const id = runId ?? (await this.opts.registry.list())[0]?.runId;
|
|
121
|
+
if (!id)
|
|
122
|
+
return undefined;
|
|
123
|
+
const record = await this.opts.registry.get(id);
|
|
124
|
+
if (!record)
|
|
125
|
+
return undefined;
|
|
126
|
+
const live = this.byId.get(id);
|
|
127
|
+
if (!live && record.meta.status === "running") {
|
|
128
|
+
const error = quantError("E_QUANT_ENGINE_ERROR", `run ${id} is not running in this gateway: it stopped without recording an outcome.`, `Run the desk again: openclaw senpi quant run ${quantRunArgs(record.meta)}`);
|
|
129
|
+
const stored = await this.opts.registry.finish(id, { status: "failed", error }).catch((err) => {
|
|
130
|
+
this.opts.log(`quant run ${id}: could not stamp a dead run failed: ${errorText(err)}`);
|
|
131
|
+
return undefined;
|
|
132
|
+
});
|
|
133
|
+
const finishedAt = stored?.meta.finishedAt;
|
|
134
|
+
return {
|
|
135
|
+
runId: id,
|
|
136
|
+
status: stored && stored.meta.status !== "running" ? stored.meta.status : "failed",
|
|
137
|
+
startedAt: record.meta.startedAt,
|
|
138
|
+
...(finishedAt ? { finishedAt } : {}),
|
|
139
|
+
error: stored?.error ?? error,
|
|
140
|
+
addresses: record.meta.addresses,
|
|
141
|
+
days: record.meta.days,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
runId: id,
|
|
146
|
+
status: live ? "running" : record.meta.status,
|
|
147
|
+
startedAt: record.meta.startedAt,
|
|
148
|
+
...(record.meta.finishedAt ? { finishedAt: record.meta.finishedAt } : {}),
|
|
149
|
+
...(live?.progress ? { progress: live.progress } : {}),
|
|
150
|
+
...(record.error ? { error: record.error } : {}),
|
|
151
|
+
addresses: record.meta.addresses,
|
|
152
|
+
days: record.meta.days,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Resolves when a live run has been finished into the registry (immediately if not live). Tests use it. */
|
|
156
|
+
waitFor(runId) {
|
|
157
|
+
return this.byId.get(runId)?.settled ?? Promise.resolve();
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Run the engine and record its outcome. Never rejects: an engine that throws (spawn exploded) is
|
|
161
|
+
* recorded as `E_QUANT_ENGINE_ERROR`, and a failure to record even that is logged. The slot is
|
|
162
|
+
* always released, so a wallet can never stay busy after its run ended. The registry's `finish`
|
|
163
|
+
* keeps the first outcome, so a late second finisher cannot overwrite a delivered desk.
|
|
164
|
+
*/
|
|
165
|
+
async execute(live) {
|
|
166
|
+
try {
|
|
167
|
+
const outcome = await this.opts.runEngine(live.request, (line) => {
|
|
168
|
+
if (line.startsWith("[quant-desk]"))
|
|
169
|
+
live.progress = line;
|
|
170
|
+
});
|
|
171
|
+
await this.opts.registry.finish(live.runId, toRunOutcome(outcome));
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
this.opts.log(`quant run ${live.runId} failed outside the engine: ${errorText(err)}`);
|
|
175
|
+
const error = quantError("E_QUANT_ENGINE_ERROR", `the desk could not be run: ${errorText(err)}.`, "This is a fault in the runtime, not in the wallet. Quote the error; do not retry in a loop.");
|
|
176
|
+
await this.opts.registry.finish(live.runId, { status: "failed", error }).catch((e) => this.opts.log(`quant run ${live.runId}: could not record the failure: ${errorText(e)}`));
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
this.bySet.delete(live.key);
|
|
180
|
+
this.byId.delete(live.runId);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=job.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.js","sourceRoot":"","sources":["../../src/quant/job.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type RenderOutcome } from "./engine-runner.js";
|
|
2
|
+
import { QuantJobController } from "./job.js";
|
|
3
|
+
import { QuantRunRegistry } from "./registry.js";
|
|
4
|
+
import { type QuantError, type QuantRequest, type QuantSection } from "./types.js";
|
|
5
|
+
/** The engine package's parent directory: dist/quant/engine when built, src/quant/engine under vitest. */
|
|
6
|
+
export declare const QUANT_ENGINE_DIR: string;
|
|
7
|
+
/** The slice of the plugin API this module uses. */
|
|
8
|
+
export interface QuantPluginApi {
|
|
9
|
+
registerGatewayMethod(name: string, handler: (ctx: {
|
|
10
|
+
respond: (ok: boolean, value: unknown) => void;
|
|
11
|
+
params?: Record<string, unknown>;
|
|
12
|
+
}) => void | Promise<void>, opts?: {
|
|
13
|
+
scope?: "operator.write" | "operator.read";
|
|
14
|
+
}): void;
|
|
15
|
+
}
|
|
16
|
+
/** Everything the handlers need, injected (tests pass a stub renderer; production spawns render.py). */
|
|
17
|
+
export interface QuantGatewayContext {
|
|
18
|
+
controller: QuantJobController;
|
|
19
|
+
registry: QuantRunRegistry;
|
|
20
|
+
renderMarkdown(runFile: string, sections: readonly QuantSection[]): Promise<RenderOutcome>;
|
|
21
|
+
}
|
|
22
|
+
/** Validate `senpi.quant.start` params into a {@link QuantRequest}. Exported for tests. */
|
|
23
|
+
export declare function parseStartParams(params: Record<string, unknown> | undefined): {
|
|
24
|
+
ok: true;
|
|
25
|
+
request: QuantRequest;
|
|
26
|
+
fresh: boolean;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
error: QuantError;
|
|
30
|
+
};
|
|
31
|
+
/** Register the four quant transport methods with explicit scopes (never the admin fallback). */
|
|
32
|
+
export declare function registerQuantGatewayMethods(api: QuantPluginApi, ctx: QuantGatewayContext): void;
|
|
33
|
+
/** Options for {@link createQuantDesk}. `stateDir` is `<senpi-state>/quant`; `pythonBin`/`engineDir`/`now` are test seams. */
|
|
34
|
+
export interface CreateQuantDeskOptions {
|
|
35
|
+
stateDir: string;
|
|
36
|
+
senpiAuthToken?: string;
|
|
37
|
+
logger: {
|
|
38
|
+
info(line: string): void;
|
|
39
|
+
};
|
|
40
|
+
pythonBin?: string;
|
|
41
|
+
engineDir?: string;
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build and register the quant desk. Does no I/O until a method is called or the boot janitor runs.
|
|
46
|
+
* The returned registry is for in-process readers (show_widget); the controller is for the service
|
|
47
|
+
* start hook, which calls `recoverInterrupted()` to log what a restart cut short.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createQuantDesk(api: QuantPluginApi, opts: CreateQuantDeskOptions): {
|
|
50
|
+
registry: QuantRunRegistry;
|
|
51
|
+
controller: QuantJobController;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/quant/register.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC7F,OAAO,EAA2C,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAE0C,KAAK,UAAU,EAAE,KAAK,YAAY,EAAwB,KAAK,YAAY,EAC3H,MAAM,YAAY,CAAC;AAEpB,0GAA0G;AAC1G,eAAO,MAAM,gBAAgB,QAAsD,CAAC;AAEpF,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,qBAAqB,CACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC5H,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,eAAe,CAAA;KAAE,GACpD,IAAI,CAAC;CACT;AAED,wGAAwG;AACxG,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5F;AA2BD,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC1C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAkCxF;AAwJD,iGAAiG;AACjG,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAK/F;AAED,8HAA8H;AAC9H,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE;QAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,sBAAsB,GAAG;IAAE,QAAQ,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,kBAAkB,CAAA;CAAE,CAejJ"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway wiring for the quant desk: `senpi.quant.{start,status,get,list}`.
|
|
3
|
+
*
|
|
4
|
+
* Like the deploy pair, these are transport between the `senpi quant` CLI and the plugin; the CLI
|
|
5
|
+
* is the teaching surface. `createQuantDesk` builds the registry, controller and runner from an
|
|
6
|
+
* explicit context and returns the registry so index.ts can hand it to show_widget and to the boot
|
|
7
|
+
* janitor — nothing here reaches into the plugin's boot closure.
|
|
8
|
+
*
|
|
9
|
+
* Refusals lead with `[E_QUANT_*]` (the CLI exits 2 on them); internal failures carry no bracket
|
|
10
|
+
* (the CLI exits 1). Every refusal ends in a non-destructive next step (`Next: …`).
|
|
11
|
+
*
|
|
12
|
+
* The read methods wait for the controller's boot janitor (`recoverInterrupted`) like `start` does,
|
|
13
|
+
* so neither `status` nor `get` nor `list` can show a run a restart cut short as `running`.
|
|
14
|
+
*/
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { VERSION } from "../version.js";
|
|
17
|
+
import { renderQuantMarkdown, runQuantEngine } from "./engine-runner.js";
|
|
18
|
+
import { QuantInProgressError, QuantJanitorError, QuantJobController } from "./job.js";
|
|
19
|
+
import { QuantRunRegistry } from "./registry.js";
|
|
20
|
+
import { QUANT_ADDRESS_RE, QUANT_DEFAULT_DAYS, QUANT_RUN_ID_RE, QUANT_MAX_ADDRESSES, QUANT_MAX_DAYS, QUANT_SECTIONS, QuantRunLookupError, quantError, quantRunArgs, } from "./types.js";
|
|
21
|
+
/** The engine package's parent directory: dist/quant/engine when built, src/quant/engine under vitest. */
|
|
22
|
+
export const QUANT_ENGINE_DIR = fileURLToPath(new URL("./engine", import.meta.url));
|
|
23
|
+
/** The generic start command, for refusals that have no request to spell back. */
|
|
24
|
+
const RUN_USAGE = "openclaw senpi quant run <0x address>";
|
|
25
|
+
/** A malformed request: nothing was started, fix it and run again. */
|
|
26
|
+
function invalid(text) {
|
|
27
|
+
return quantError("E_QUANT_INVALID_REQUEST", `${text} Nothing was started.`, `Fix the request and run it again: ${RUN_USAGE}`);
|
|
28
|
+
}
|
|
29
|
+
/** Refusal on the wire: the bracketed message plus its next step, so the CLI's stderr carries both. */
|
|
30
|
+
function refuse(respond, error) {
|
|
31
|
+
respond(false, undefined, { code: error.code, message: `${error.message} Next: ${error.what_to_do}` });
|
|
32
|
+
}
|
|
33
|
+
/** Internal failure on the wire: no bracket anywhere, so the CLI classifies it as exit 1. */
|
|
34
|
+
function unavailable(respond, text) {
|
|
35
|
+
respond(false, undefined, { code: "UNAVAILABLE", message: text.replace(/\[[A-Z][A-Z0-9_]*\]\s*/g, "") });
|
|
36
|
+
}
|
|
37
|
+
function errorText(err) {
|
|
38
|
+
return err instanceof Error ? err.message : String(err);
|
|
39
|
+
}
|
|
40
|
+
/** Validate `senpi.quant.start` params into a {@link QuantRequest}. Exported for tests. */
|
|
41
|
+
export function parseStartParams(params) {
|
|
42
|
+
const p = params ?? {};
|
|
43
|
+
if (!Array.isArray(p.addresses) || p.addresses.length === 0) {
|
|
44
|
+
return { ok: false, error: invalid(`"addresses" must list 1 to ${QUANT_MAX_ADDRESSES} Hyperliquid addresses.`) };
|
|
45
|
+
}
|
|
46
|
+
// Too wide a book gets its own refusal: "fix the request" does not say WHICH wallets to keep,
|
|
47
|
+
// and an agent reading a user's strategy wallets needs to be told how to choose.
|
|
48
|
+
if (p.addresses.length > QUANT_MAX_ADDRESSES) {
|
|
49
|
+
return { ok: false, error: quantError("E_QUANT_INVALID_REQUEST", `a book reads at most ${QUANT_MAX_ADDRESSES} wallets; got ${p.addresses.length}. Nothing was started.`, `pass the ${QUANT_MAX_ADDRESSES} with the most recent activity (strategy_list shows lastActivity) — openclaw senpi quant run <up to ${QUANT_MAX_ADDRESSES}> --book`) };
|
|
50
|
+
}
|
|
51
|
+
const addresses = [];
|
|
52
|
+
for (const a of p.addresses) {
|
|
53
|
+
if (typeof a !== "string" || !QUANT_ADDRESS_RE.test(a.trim())) {
|
|
54
|
+
return { ok: false, error: invalid(`not a Hyperliquid address: ${JSON.stringify(a)} — expected 0x followed by 40 hex characters.`) };
|
|
55
|
+
}
|
|
56
|
+
const lower = a.trim().toLowerCase();
|
|
57
|
+
if (!addresses.includes(lower))
|
|
58
|
+
addresses.push(lower);
|
|
59
|
+
}
|
|
60
|
+
const book = p.book === true;
|
|
61
|
+
const days = p.days === undefined ? QUANT_DEFAULT_DAYS : p.days;
|
|
62
|
+
if (typeof days !== "number" || !Number.isInteger(days) || days < 1 || days > QUANT_MAX_DAYS) {
|
|
63
|
+
return { ok: false, error: invalid(`"days" must be a whole number from 1 to ${QUANT_MAX_DAYS}.`) };
|
|
64
|
+
}
|
|
65
|
+
const whose = p.whose === undefined ? "mine" : p.whose;
|
|
66
|
+
if (whose !== "mine" && whose !== "other")
|
|
67
|
+
return { ok: false, error: invalid(`"whose" must be "mine" or "other".`) };
|
|
68
|
+
// Checked after days/whose so the book hint re-asks for the SAME window and voice (quantRunArgs).
|
|
69
|
+
if (addresses.length > 1 && !book) {
|
|
70
|
+
return { ok: false, error: quantError("E_QUANT_UNSUPPORTED", "several addresses without --book is a side-by-side comparison, which the runtime does not run yet. Nothing was started.", `To read them as ONE book (a senpi user's strategy wallets): openclaw senpi quant run ${quantRunArgs({ addresses, book: true, whose, days })}`) };
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, fresh: p.fresh === true, request: { addresses, days, whose, book, force: p.force === true } };
|
|
73
|
+
}
|
|
74
|
+
/** Validate `sections` against the engine's render sections; an unknown one is refused naming them all. */
|
|
75
|
+
function parseSections(raw) {
|
|
76
|
+
if (raw === undefined)
|
|
77
|
+
return [];
|
|
78
|
+
const known = QUANT_SECTIONS;
|
|
79
|
+
if (!Array.isArray(raw) || raw.some((s) => typeof s !== "string" || !known.includes(s))) {
|
|
80
|
+
return quantError("E_QUANT_INVALID_REQUEST", `unknown section in ${JSON.stringify(raw)}.`, `Sections: ${QUANT_SECTIONS.join(", ")}.`);
|
|
81
|
+
}
|
|
82
|
+
return raw;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The NOT_FOUND refusal. Names up to five recent run ids and the list command, so an agent holding a
|
|
86
|
+
* stale or mistyped id (or a path-like one, which the registry never resolves) has a way forward.
|
|
87
|
+
*/
|
|
88
|
+
async function notFound(registry, runId) {
|
|
89
|
+
if (runId === undefined) {
|
|
90
|
+
return quantError("E_QUANT_NOT_FOUND", "no quant run has been started on this agent.", `Start one: ${RUN_USAGE}`);
|
|
91
|
+
}
|
|
92
|
+
const recent = (await registry.list()).slice(0, 5).map((r) => r.runId);
|
|
93
|
+
return quantError("E_QUANT_NOT_FOUND", `no quant run ${JSON.stringify(runId)} on this agent. ${recent.length > 0 ? `Recent runs: ${recent.join(", ")}.` : "No runs yet."}`, "List them: openclaw senpi quant list");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The refusal for a run the index knows but whose stored record cannot be used: the file is corrupt
|
|
97
|
+
* or gone, it is `done` with no document, or render.py could not read it. NOT_FOUND-style, because
|
|
98
|
+
* there is no desk to show — but unlike a plain NOT_FOUND it names the exact re-run command, so an
|
|
99
|
+
* agent does not loop between `list` (which shows the run) and `get` (which cannot read it).
|
|
100
|
+
* `--fresh` so the re-run cannot be served from anything cached for the same request.
|
|
101
|
+
*/
|
|
102
|
+
function unreadableRun(runId, why, request) {
|
|
103
|
+
return quantError("E_QUANT_NOT_FOUND", `stored run ${runId} is unreadable (${why}); there is no desk to show for it.`, `Re-run the desk: openclaw senpi quant run ${quantRunArgs(request)} --fresh`, { runId });
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The index row for a run id whose record did not read, if any — the corrupt-at-rest case. `list`
|
|
107
|
+
* shows such a run, so answering a plain "no such run" would contradict it. Undefined for ids that
|
|
108
|
+
* are not run ids (path-like) and for ids the index does not know.
|
|
109
|
+
*/
|
|
110
|
+
async function indexedRow(registry, runId) {
|
|
111
|
+
const rows = await registry.list();
|
|
112
|
+
const id = runId ?? rows[0]?.runId;
|
|
113
|
+
if (id === undefined || !QUANT_RUN_ID_RE.test(id))
|
|
114
|
+
return undefined;
|
|
115
|
+
return rows.find((r) => r.runId === id);
|
|
116
|
+
}
|
|
117
|
+
/** Wait for the boot janitor on a read path; a janitor failure never blocks a read. */
|
|
118
|
+
async function afterJanitor(ctx) {
|
|
119
|
+
await ctx.controller.recoverInterrupted().catch(() => undefined);
|
|
120
|
+
}
|
|
121
|
+
/** `senpi.quant.start`: validate, start or serve fresh, answer `{runId, status, cached, monitor}`. */
|
|
122
|
+
async function handleStart(ctx, params, respond) {
|
|
123
|
+
const parsed = parseStartParams(params);
|
|
124
|
+
if (!parsed.ok)
|
|
125
|
+
return refuse(respond, parsed.error);
|
|
126
|
+
try {
|
|
127
|
+
const started = await ctx.controller.start(parsed.request, { fresh: parsed.fresh });
|
|
128
|
+
respond(true, { ...started, monitor: `openclaw senpi quant status ${started.runId}` });
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (err instanceof QuantInProgressError) {
|
|
132
|
+
return refuse(respond, quantError("E_QUANT_IN_PROGRESS", `a desk on ${err.addresses.join(", ")} is already running as run ${err.runId}. A second run does not make the first finish — both would share Hyperliquid's rate limit.`, `Watch the one in flight: openclaw senpi quant status ${err.runId}`, { runId: err.runId }));
|
|
133
|
+
}
|
|
134
|
+
if (err instanceof QuantJanitorError) {
|
|
135
|
+
return unavailable(respond, `the quant state dir could not be cleaned up after a restart: ${errorText(err.cause)}. Nothing was started; make the quant state dir writable, and the next start retries the cleanup.`);
|
|
136
|
+
}
|
|
137
|
+
unavailable(respond, `could not start the desk: ${errorText(err)}. Nothing is running for this request; check the gateway log.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** `senpi.quant.status`: one run's status, or the newest run's when no id is given. */
|
|
141
|
+
async function handleStatus(ctx, params, respond) {
|
|
142
|
+
const runId = params?.runId;
|
|
143
|
+
if (runId !== undefined && typeof runId !== "string")
|
|
144
|
+
return refuse(respond, invalid(`"runId" must be a string.`));
|
|
145
|
+
try {
|
|
146
|
+
const status = await ctx.controller.status(runId);
|
|
147
|
+
if (!status) {
|
|
148
|
+
const row = await indexedRow(ctx.registry, runId);
|
|
149
|
+
if (row)
|
|
150
|
+
return refuse(respond, unreadableRun(row.runId, "its file is missing or not valid JSON", row));
|
|
151
|
+
return refuse(respond, await notFound(ctx.registry, runId));
|
|
152
|
+
}
|
|
153
|
+
respond(true, status);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (err instanceof QuantRunLookupError)
|
|
157
|
+
return refuse(respond, err.error);
|
|
158
|
+
unavailable(respond, `could not read the run: ${errorText(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `senpi.quant.get`: a run's stored document (`json`) or its Markdown re-rendered by render.py
|
|
163
|
+
* (`markdown`, the default) — never recomputed. A run that is not done answers its status and no
|
|
164
|
+
* document. A stored run that cannot be used — its file corrupt or gone while the index lists it, a
|
|
165
|
+
* `done` record with no document, or render.py unable to read it — is a NOT_FOUND-style refusal
|
|
166
|
+
* with the exact re-run command ({@link unreadableRun}); any other render failure is internal (no
|
|
167
|
+
* bracket).
|
|
168
|
+
*/
|
|
169
|
+
async function handleGet(ctx, params, respond) {
|
|
170
|
+
const sections = parseSections(params?.sections);
|
|
171
|
+
if (!Array.isArray(sections))
|
|
172
|
+
return refuse(respond, sections);
|
|
173
|
+
const format = params?.format ?? "markdown";
|
|
174
|
+
if (format !== "markdown" && format !== "json")
|
|
175
|
+
return refuse(respond, invalid(`"format" must be "markdown" or "json".`));
|
|
176
|
+
const runId = params?.runId;
|
|
177
|
+
if (typeof runId !== "string")
|
|
178
|
+
return refuse(respond, invalid(`"runId" is required.`));
|
|
179
|
+
await afterJanitor(ctx);
|
|
180
|
+
let record;
|
|
181
|
+
try {
|
|
182
|
+
record = await ctx.registry.get(runId);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
if (err instanceof QuantRunLookupError)
|
|
186
|
+
return refuse(respond, err.error);
|
|
187
|
+
return unavailable(respond, `could not read the run: ${errorText(err)}`);
|
|
188
|
+
}
|
|
189
|
+
if (!record) {
|
|
190
|
+
const row = await indexedRow(ctx.registry, runId);
|
|
191
|
+
if (row)
|
|
192
|
+
return refuse(respond, unreadableRun(runId, "its file is missing or not valid JSON", row));
|
|
193
|
+
return refuse(respond, await notFound(ctx.registry, runId));
|
|
194
|
+
}
|
|
195
|
+
if (record.meta.status === "done" && !record.document) {
|
|
196
|
+
return refuse(respond, unreadableRun(runId, "the stored run has no document", record.meta));
|
|
197
|
+
}
|
|
198
|
+
if (record.meta.status !== "done" || !record.document) {
|
|
199
|
+
return respond(true, { runId, docVersion: record.docVersion, status: record.meta.status, ...(record.error ? { error: record.error } : {}) });
|
|
200
|
+
}
|
|
201
|
+
if (format === "json")
|
|
202
|
+
return respond(true, { runId, docVersion: record.docVersion, status: "done", document: record.document });
|
|
203
|
+
const rendered = await ctx.renderMarkdown(ctx.registry.runPath(runId), sections);
|
|
204
|
+
if (!rendered.ok && rendered.storedRunUnreadable !== undefined) {
|
|
205
|
+
return refuse(respond, unreadableRun(runId, rendered.storedRunUnreadable, record.meta));
|
|
206
|
+
}
|
|
207
|
+
if (!rendered.ok)
|
|
208
|
+
return unavailable(respond, `the stored desk could not be rendered: ${rendered.error.message}`);
|
|
209
|
+
respond(true, { runId, docVersion: record.docVersion, status: "done", markdown: rendered.markdown });
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `senpi.quant.list`: the whole index, newest first. Rows carry `runId, addresses, days, whose,
|
|
213
|
+
* book, status, startedAt, finishedAt`. The index's freshness `key` is internal and stripped here,
|
|
214
|
+
* so the wire shape is exactly the documented one.
|
|
215
|
+
*/
|
|
216
|
+
async function handleList(ctx, respond) {
|
|
217
|
+
await afterJanitor(ctx);
|
|
218
|
+
try {
|
|
219
|
+
respond(true, { runs: (await ctx.registry.list()).map(({ key: _key, ...row }) => row) });
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
unavailable(respond, `could not list quant runs: ${errorText(err)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Register the four quant transport methods with explicit scopes (never the admin fallback). */
|
|
226
|
+
export function registerQuantGatewayMethods(api, ctx) {
|
|
227
|
+
api.registerGatewayMethod("senpi.quant.start", async (g) => handleStart(ctx, g.params, g.respond), { scope: "operator.write" });
|
|
228
|
+
api.registerGatewayMethod("senpi.quant.status", async (g) => handleStatus(ctx, g.params, g.respond), { scope: "operator.read" });
|
|
229
|
+
api.registerGatewayMethod("senpi.quant.get", async (g) => handleGet(ctx, g.params, g.respond), { scope: "operator.read" });
|
|
230
|
+
api.registerGatewayMethod("senpi.quant.list", async (g) => handleList(ctx, g.respond), { scope: "operator.read" });
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build and register the quant desk. Does no I/O until a method is called or the boot janitor runs.
|
|
234
|
+
* The returned registry is for in-process readers (show_widget); the controller is for the service
|
|
235
|
+
* start hook, which calls `recoverInterrupted()` to log what a restart cut short.
|
|
236
|
+
*/
|
|
237
|
+
export function createQuantDesk(api, opts) {
|
|
238
|
+
const registry = new QuantRunRegistry({ dir: opts.stateDir, now: opts.now ?? (() => Date.now()) });
|
|
239
|
+
const spawnOpts = {
|
|
240
|
+
engineDir: opts.engineDir ?? QUANT_ENGINE_DIR,
|
|
241
|
+
...(opts.pythonBin ? { pythonBin: opts.pythonBin } : {}),
|
|
242
|
+
...(opts.senpiAuthToken ? { senpiAuthToken: opts.senpiAuthToken } : {}),
|
|
243
|
+
};
|
|
244
|
+
const controller = new QuantJobController({
|
|
245
|
+
registry,
|
|
246
|
+
engineVersion: VERSION,
|
|
247
|
+
log: (line) => opts.logger.info(`[senpi-quant] ${line}`),
|
|
248
|
+
runEngine: (request, onProgress) => runQuantEngine(request, { ...spawnOpts, stateDir: opts.stateDir, onStderrLine: onProgress }),
|
|
249
|
+
});
|
|
250
|
+
registerQuantGatewayMethods(api, { controller, registry, renderMarkdown: (file, sections) => renderQuantMarkdown(file, sections, spawnOpts) });
|
|
251
|
+
return { registry, controller };
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/quant/register.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type QuantDocument, type QuantError, type QuantRequest, type QuantRunReader, type QuantRunRecord, type QuantRunSummary } from "./types.js";
|
|
2
|
+
/** Runs retained; older ones are deleted with their files. Running runs are never pruned. */
|
|
3
|
+
export declare const QUANT_KEEP_RUNS = 20;
|
|
4
|
+
/** A done run for the same request younger than this is served instead of recomputed. */
|
|
5
|
+
export declare const QUANT_FRESH_MS: number;
|
|
6
|
+
/** How a run ended, as the job controller reports it. */
|
|
7
|
+
export type QuantRunOutcome = {
|
|
8
|
+
readonly status: "done";
|
|
9
|
+
readonly document: QuantDocument;
|
|
10
|
+
} | {
|
|
11
|
+
readonly status: "failed" | "refused";
|
|
12
|
+
readonly error: QuantError;
|
|
13
|
+
};
|
|
14
|
+
/** Construction options; `now` is injected so freshness is testable without timers. */
|
|
15
|
+
export interface QuantRunRegistryOptions {
|
|
16
|
+
dir: string;
|
|
17
|
+
now(): number;
|
|
18
|
+
keep?: number;
|
|
19
|
+
freshMs?: number;
|
|
20
|
+
}
|
|
21
|
+
/** File-backed registry of quant runs. See the module doc for the layout and invariants. */
|
|
22
|
+
export declare class QuantRunRegistry implements QuantRunReader {
|
|
23
|
+
private readonly opts;
|
|
24
|
+
private readonly mutex;
|
|
25
|
+
private readonly keep;
|
|
26
|
+
private readonly freshMs;
|
|
27
|
+
constructor(opts: QuantRunRegistryOptions);
|
|
28
|
+
/** A fresh run id (`qd-` + 8 hex). */
|
|
29
|
+
newRunId(): string;
|
|
30
|
+
/** The record path for a VALID run id. Throws on anything else, so no caller can build a traversal. */
|
|
31
|
+
runPath(runId: string): string;
|
|
32
|
+
/** Record a new `running` run and put it at the head of the index. */
|
|
33
|
+
create(runId: string, request: QuantRequest, engineVersion: string): Promise<QuantRunRecord>;
|
|
34
|
+
/**
|
|
35
|
+
* Close a run with its outcome. The FIRST outcome wins: a record that is already terminal
|
|
36
|
+
* (done/failed/refused) is never overwritten, and the call answers the stored record instead.
|
|
37
|
+
*
|
|
38
|
+
* Why first-wins: two finishers can race on one run — the engine's exit landing just as its
|
|
39
|
+
* watchdog fires, or the boot janitor meeting a run whose file was written done a moment before a
|
|
40
|
+
* crash cut off the index update. Overwriting would turn a delivered desk into a failure. The
|
|
41
|
+
* check-then-write is serialized under a per-run lock (`run:<id>`, never the index lock; nothing
|
|
42
|
+
* nests on it), so the second finisher always sees the first one's file.
|
|
43
|
+
*
|
|
44
|
+
* @returns The stored record after the call — the new one, or the earlier terminal one untouched.
|
|
45
|
+
* @throws When the run has no record (it was never created, or its file is gone or unreadable).
|
|
46
|
+
*/
|
|
47
|
+
finish(runId: string, outcome: QuantRunOutcome): Promise<QuantRunRecord>;
|
|
48
|
+
/**
|
|
49
|
+
* The body of {@link finish}, reporting whether it wrote. `undefined` = no readable record.
|
|
50
|
+
*
|
|
51
|
+
* When the record is already terminal it re-syncs that run's index row from the file rather than
|
|
52
|
+
* writing: the file is the truth, and an index row still saying `running` (the crash window) would
|
|
53
|
+
* otherwise make `status` report a dead run as live. For a double finish that already updated the
|
|
54
|
+
* index, the re-sync rewrites the same row — harmless.
|
|
55
|
+
*/
|
|
56
|
+
private settle;
|
|
57
|
+
/**
|
|
58
|
+
* Read a run. `undefined` for an invalid id, a missing file or an unreadable one — all three are
|
|
59
|
+
* "no such run" to a caller. Throws {@link QuantRunLookupError} for a record of another docVersion.
|
|
60
|
+
*/
|
|
61
|
+
get(runId: string): Promise<QuantRunRecord | undefined>;
|
|
62
|
+
/**
|
|
63
|
+
* Newest-first summaries. The index is already bounded by {@link updateIndex}'s pruning: at most
|
|
64
|
+
* `keep` rows plus any `running` rows past the cap. It is deliberately NOT sliced to `keep` here —
|
|
65
|
+
* pruning spares a running run precisely so it stays visible, and a slice would hide a live run
|
|
66
|
+
* from `senpi quant list` while its record still exists.
|
|
67
|
+
*/
|
|
68
|
+
list(): Promise<QuantRunSummary[]>;
|
|
69
|
+
/** The newest done run for this exact request finished within the freshness window, if any. */
|
|
70
|
+
findFresh(request: Pick<QuantRequest, "addresses" | "days" | "whose" | "book">): Promise<QuantRunRecord | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Boot janitor: every run still `running` in the index belongs to a process that no longer exists
|
|
73
|
+
* (the slot is in-memory only). Each such row is resolved from its file:
|
|
74
|
+
* - file still `running` → stamped `failed` with the exact re-run command, so `senpi quant status`
|
|
75
|
+
* never reports a dead run as live;
|
|
76
|
+
* - file already terminal (the process died between writing the record and the index) → the
|
|
77
|
+
* index row is re-synced from the file; the delivered outcome is kept, never stamped failed;
|
|
78
|
+
* - no readable file → the row is dropped (there is nothing to report or re-read).
|
|
79
|
+
*
|
|
80
|
+
* Must run before this process starts any run of its own: a run it created would be `running`
|
|
81
|
+
* too and would be stamped. The job controller gates `start` on it for that reason.
|
|
82
|
+
*
|
|
83
|
+
* @returns Only the ids it STAMPED failed — not re-synced or dropped rows — so the boot log names
|
|
84
|
+
* exactly the runs a restart cut short.
|
|
85
|
+
*/
|
|
86
|
+
markInterrupted(): Promise<string[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Parse `runs/<runId>.json` without any version check. Missing, unparseable and invalid-id (runPath
|
|
89
|
+
* throws) all collapse to `undefined` — the caller decides what "no record" means.
|
|
90
|
+
*/
|
|
91
|
+
private readRecord;
|
|
92
|
+
/**
|
|
93
|
+
* Read `index.json`; a missing or corrupt index reads as empty rather than failing, so a damaged
|
|
94
|
+
* index costs history, never the ability to start a run (the next write rewrites it).
|
|
95
|
+
*/
|
|
96
|
+
private readIndex;
|
|
97
|
+
/**
|
|
98
|
+
* Serialized read-modify-write of the index; prunes past `keep` (never a running run) and deletes
|
|
99
|
+
* dropped files. A dropped row whose id is not a run id (a hand-edited index) loses its row but
|
|
100
|
+
* no file: `runPath` would throw on it, and one bad row must not break every later write — nor
|
|
101
|
+
* may it name a path outside `runs/`.
|
|
102
|
+
*/
|
|
103
|
+
private updateIndex;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/quant/registry.ts"],"names":[],"mappings":"AAeA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,6FAA6F;AAC7F,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,yFAAyF;AACzF,eAAO,MAAM,cAAc,QAAc,CAAC;AAc1C,yDAAyD;AACzD,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC;AAE1E,uFAAuF;AACvF,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,IAAI,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4BD,4FAA4F;AAC5F,qBAAa,gBAAiB,YAAW,cAAc;IAKzC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAEJ,IAAI,EAAE,uBAAuB;IAK1D,sCAAsC;IACtC,QAAQ,IAAI,MAAM;IAIlB,uGAAuG;IACvG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAK9B,sEAAsE;IAChE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAkBlG;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAM9E;;;;;;;OAOG;YACW,MAAM;IAoBpB;;;OAGG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAiB7D;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAIxC,+FAA+F;IACzF,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAY1H;;;;;;;;;;;;;;OAcG;IACG,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAgB1C;;;OAGG;YACW,UAAU;IAQxB;;;OAGG;YACW,SAAS;IASvB;;;;;OAKG;YACW,WAAW;CAc1B"}
|