@senpi/runtime 3.0.173 → 3.0.181
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/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +48 -3
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/llm-decision/openclaw-llm-decision.d.ts +8 -0
- package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -1
- package/dist/actions/llm-decision/openclaw-llm-decision.js +56 -1
- package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +42 -6
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/open-position/resolve-margin.d.ts +1 -1
- package/dist/actions/open-position/resolve-margin.d.ts.map +1 -1
- package/dist/actions/open-position/resolve-margin.js +7 -0
- package/dist/actions/open-position/resolve-margin.js.map +1 -1
- package/dist/approvals/approval-store.d.ts +37 -7
- package/dist/approvals/approval-store.d.ts.map +1 -1
- package/dist/approvals/approval-store.js +127 -23
- package/dist/approvals/approval-store.js.map +1 -1
- package/dist/approvals/manifest.d.ts.map +1 -1
- package/dist/approvals/manifest.js +0 -5
- package/dist/approvals/manifest.js.map +1 -1
- package/dist/approvals/telemetry.d.ts +27 -3
- package/dist/approvals/telemetry.d.ts.map +1 -1
- package/dist/approvals/telemetry.js +8 -4
- package/dist/approvals/telemetry.js.map +1 -1
- package/dist/approvals/tool-approval-gate.d.ts +79 -0
- package/dist/approvals/tool-approval-gate.d.ts.map +1 -1
- package/dist/approvals/tool-approval-gate.js +184 -21
- package/dist/approvals/tool-approval-gate.js.map +1 -1
- package/dist/approvals/tool-approvals-manifest.json +0 -2
- package/dist/approvals/turn-context.d.ts +271 -0
- package/dist/approvals/turn-context.d.ts.map +1 -0
- package/dist/approvals/turn-context.js +551 -0
- package/dist/approvals/turn-context.js.map +1 -0
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +43 -8
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +91 -12
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +2 -0
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +2 -0
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/constants/order-types.d.ts.map +1 -1
- package/dist/constants/order-types.js +4 -1
- package/dist/constants/order-types.js.map +1 -1
- package/dist/deploy/job.d.ts +32 -0
- package/dist/deploy/job.d.ts.map +1 -1
- package/dist/deploy/job.js +72 -7
- package/dist/deploy/job.js.map +1 -1
- package/dist/deploy/messages.d.ts +15 -1
- package/dist/deploy/messages.d.ts.map +1 -1
- package/dist/deploy/messages.js +78 -0
- package/dist/deploy/messages.js.map +1 -1
- package/dist/deploy/orchestrator.d.ts +11 -1
- package/dist/deploy/orchestrator.d.ts.map +1 -1
- package/dist/deploy/orchestrator.js +111 -15
- package/dist/deploy/orchestrator.js.map +1 -1
- package/dist/deploy/register.d.ts +18 -0
- package/dist/deploy/register.d.ts.map +1 -1
- package/dist/deploy/register.js +93 -2
- package/dist/deploy/register.js.map +1 -1
- package/dist/dsl/engine/exchange-stop.d.ts +21 -1
- package/dist/dsl/engine/exchange-stop.d.ts.map +1 -1
- package/dist/dsl/engine/exchange-stop.js +27 -1
- package/dist/dsl/engine/exchange-stop.js.map +1 -1
- package/dist/dsl/engine/floors.d.ts +2 -1
- package/dist/dsl/engine/floors.d.ts.map +1 -1
- package/dist/dsl/engine/floors.js +2 -1
- package/dist/dsl/engine/floors.js.map +1 -1
- package/dist/dsl/engine/index.d.ts +1 -1
- package/dist/dsl/engine/index.d.ts.map +1 -1
- package/dist/dsl/engine/index.js +1 -1
- package/dist/dsl/engine/index.js.map +1 -1
- package/dist/dsl/engine/tick.d.ts.map +1 -1
- package/dist/dsl/engine/tick.js +4 -3
- package/dist/dsl/engine/tick.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +7 -3
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +10 -4
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/events/initial-state.d.ts.map +1 -1
- package/dist/dsl/events/initial-state.js +2 -0
- package/dist/dsl/events/initial-state.js.map +1 -1
- package/dist/dsl/monitor/exchange-sync.d.ts +1 -0
- package/dist/dsl/monitor/exchange-sync.d.ts.map +1 -1
- package/dist/dsl/monitor/exchange-sync.js +7 -14
- package/dist/dsl/monitor/exchange-sync.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +108 -17
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/notifications/formatter.d.ts +2 -1
- package/dist/dsl/notifications/formatter.d.ts.map +1 -1
- package/dist/dsl/notifications/formatter.js +18 -1
- package/dist/dsl/notifications/formatter.js.map +1 -1
- package/dist/health/types.d.ts +6 -1
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js.map +1 -1
- package/dist/host/openclaw-host.d.ts +138 -0
- package/dist/host/openclaw-host.d.ts.map +1 -0
- package/dist/host/openclaw-host.js +177 -0
- package/dist/host/openclaw-host.js.map +1 -0
- package/dist/index.d.ts +46 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +318 -38
- package/dist/index.js.map +1 -1
- package/dist/leverage/max-leverage-cache.d.ts +25 -0
- package/dist/leverage/max-leverage-cache.d.ts.map +1 -1
- package/dist/leverage/max-leverage-cache.js +77 -0
- package/dist/leverage/max-leverage-cache.js.map +1 -1
- package/dist/runtime/auto-update/coordinator.d.ts +203 -0
- package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
- package/dist/runtime/auto-update/coordinator.js +621 -12
- package/dist/runtime/auto-update/coordinator.js.map +1 -1
- package/dist/runtime/auto-update/gateway-restart.d.ts +60 -1
- package/dist/runtime/auto-update/gateway-restart.d.ts.map +1 -1
- package/dist/runtime/auto-update/gateway-restart.js +95 -21
- package/dist/runtime/auto-update/gateway-restart.js.map +1 -1
- package/dist/runtime/auto-update/idle-gate.d.ts +139 -0
- package/dist/runtime/auto-update/idle-gate.d.ts.map +1 -0
- package/dist/runtime/auto-update/idle-gate.js +217 -0
- package/dist/runtime/auto-update/idle-gate.js.map +1 -0
- package/dist/runtime/auto-update/install-results.d.ts +130 -0
- package/dist/runtime/auto-update/install-results.d.ts.map +1 -0
- package/dist/runtime/auto-update/install-results.js +264 -0
- package/dist/runtime/auto-update/install-results.js.map +1 -0
- package/dist/runtime/auto-update/money-call-tracker.d.ts +36 -0
- package/dist/runtime/auto-update/money-call-tracker.d.ts.map +1 -0
- package/dist/runtime/auto-update/money-call-tracker.js +129 -0
- package/dist/runtime/auto-update/money-call-tracker.js.map +1 -0
- package/dist/runtime/auto-update/register-idle-gate.d.ts +70 -0
- package/dist/runtime/auto-update/register-idle-gate.d.ts.map +1 -0
- package/dist/runtime/auto-update/register-idle-gate.js +123 -0
- package/dist/runtime/auto-update/register-idle-gate.js.map +1 -0
- package/dist/runtime/auto-update/run-update-command.d.ts +13 -0
- package/dist/runtime/auto-update/run-update-command.d.ts.map +1 -1
- package/dist/runtime/auto-update/run-update-command.js +18 -0
- package/dist/runtime/auto-update/run-update-command.js.map +1 -1
- package/dist/runtime/auto-update/state-store.d.ts +14 -1
- package/dist/runtime/auto-update/state-store.d.ts.map +1 -1
- package/dist/runtime/auto-update/state-store.js +37 -3
- package/dist/runtime/auto-update/state-store.js.map +1 -1
- package/dist/runtime/auto-update/types.d.ts +51 -0
- package/dist/runtime/auto-update/types.d.ts.map +1 -1
- package/dist/runtime/auto-update/types.js.map +1 -1
- package/dist/runtime/auto-update/update-marker.d.ts +95 -0
- package/dist/runtime/auto-update/update-marker.d.ts.map +1 -0
- package/dist/runtime/auto-update/update-marker.js +185 -0
- package/dist/runtime/auto-update/update-marker.js.map +1 -0
- package/dist/runtime/auto-update/update-schedule.d.ts +57 -0
- package/dist/runtime/auto-update/update-schedule.d.ts.map +1 -0
- package/dist/runtime/auto-update/update-schedule.js +75 -0
- package/dist/runtime/auto-update/update-schedule.js.map +1 -0
- package/dist/runtime/create-scanner-module.d.ts +1 -1
- package/dist/runtime/create-scanner-module.d.ts.map +1 -1
- package/dist/runtime/create-scanner-module.js +2 -1
- package/dist/runtime/create-scanner-module.js.map +1 -1
- package/dist/runtime/decision-errors.d.ts +7 -0
- package/dist/runtime/decision-errors.d.ts.map +1 -1
- package/dist/runtime/decision-errors.js +15 -0
- package/dist/runtime/decision-errors.js.map +1 -1
- package/dist/runtime/lifecycle-events.d.ts +1 -1
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +10 -1
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/runtime-descriptor.d.ts +11 -2
- package/dist/runtime/runtime-descriptor.d.ts.map +1 -1
- package/dist/runtime/runtime-descriptor.js +11 -2
- package/dist/runtime/runtime-descriptor.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +4 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/strategy-lifecycle.js +1 -1
- package/dist/runtime/strategy-lifecycle.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.d.ts +17 -3
- package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.js +57 -32
- package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
- package/dist/runtime-api/intake/intake-events.d.ts +6 -5
- package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-events.js +9 -3
- package/dist/runtime-api/intake/intake-events.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +34 -31
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scaffold-error-router.d.ts +8 -0
- package/dist/runtime-api/intake/scaffold-error-router.d.ts.map +1 -1
- package/dist/runtime-api/intake/scaffold-error-router.js +9 -0
- package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +9 -0
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/intake/types.js +5 -1
- package/dist/runtime-api/intake/types.js.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +25 -1
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +2 -0
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +4 -0
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/engine/data-providers.d.ts +6 -0
- package/dist/scanners/engine/data-providers.d.ts.map +1 -1
- package/dist/scanners/engine/data-providers.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +9 -0
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +29 -6
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/external/scaffold/ctx.py +55 -1
- package/dist/scanners/external/scaffold/delivery.py +92 -28
- package/dist/scanners/external/scaffold/preflight.py +1 -1
- package/dist/scanners/external/scaffold/scaffold.py +75 -2
- package/dist/scanners/runtime-module.d.ts +2 -0
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +14 -0
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/senpi/client.d.ts +36 -1
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +100 -8
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/types.d.ts +54 -2
- package/dist/senpi/types.d.ts.map +1 -1
- package/dist/telemetry/metrics.d.ts +15 -1
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +25 -1
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/otel-sdk.d.ts +12 -0
- package/dist/telemetry/otel-sdk.d.ts.map +1 -1
- package/dist/telemetry/otel-sdk.js +47 -1
- package/dist/telemetry/otel-sdk.js.map +1 -1
- package/dist/telemetry/spans/span-hooks.d.ts +18 -1
- package/dist/telemetry/spans/span-hooks.d.ts.map +1 -1
- package/dist/telemetry/spans/span-hooks.js +306 -96
- package/dist/telemetry/spans/span-hooks.js.map +1 -1
- package/dist/telemetry/tool-plane.d.ts +2 -0
- package/dist/telemetry/tool-plane.d.ts.map +1 -1
- package/dist/telemetry/tool-plane.js +1 -0
- package/dist/telemetry/tool-plane.js.map +1 -1
- package/dist/tool-surface/crib-cache.d.ts +21 -0
- package/dist/tool-surface/crib-cache.d.ts.map +1 -0
- package/dist/tool-surface/crib-cache.js +54 -0
- package/dist/tool-surface/crib-cache.js.map +1 -0
- package/dist/tool-surface/crib-hook.d.ts +78 -0
- package/dist/tool-surface/crib-hook.d.ts.map +1 -0
- package/dist/tool-surface/crib-hook.js +217 -0
- package/dist/tool-surface/crib-hook.js.map +1 -0
- package/dist/tool-surface/crib-sheet.d.ts +56 -0
- package/dist/tool-surface/crib-sheet.d.ts.map +1 -0
- package/dist/tool-surface/crib-sheet.js +129 -0
- package/dist/tool-surface/crib-sheet.js.map +1 -0
- package/dist/tool-surface/mcp-tool-source.d.ts +64 -0
- package/dist/tool-surface/mcp-tool-source.d.ts.map +1 -0
- package/dist/tool-surface/mcp-tool-source.js +189 -0
- package/dist/tool-surface/mcp-tool-source.js.map +1 -0
- package/dist/types/dsl/index.d.ts +4 -0
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/utils/event-catalog.d.ts +141 -10
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +23 -1
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/perp-coin.d.ts.map +1 -1
- package/dist/utils/perp-coin.js +2 -1
- package/dist/utils/perp-coin.js.map +1 -1
- package/dist/utils/resolve-order-type.d.ts.map +1 -1
- package/dist/utils/resolve-order-type.js +5 -1
- package/dist/utils/resolve-order-type.js.map +1 -1
- package/dist/validate/codes.d.ts +2 -0
- package/dist/validate/codes.d.ts.map +1 -1
- package/dist/validate/codes.js +6 -0
- package/dist/validate/codes.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 +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/recipe-checks.d.ts +7 -0
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +18 -0
- package/dist/validate/recipe-checks.js.map +1 -1
- package/dist/validate/recipe.d.ts.map +1 -1
- package/dist/validate/recipe.js +2 -1
- package/dist/validate/recipe.js.map +1 -1
- package/dist/widgets/tool.d.ts +9 -0
- package/dist/widgets/tool.d.ts.map +1 -1
- package/dist/widgets/tool.js +1 -0
- package/dist/widgets/tool.js.map +1 -1
- package/examples/strategies/README.md +2 -1
- package/examples/strategies/iguana/README.md +3 -3
- package/examples/strategies/raptor/README.md +3 -3
- package/openclaw.plugin.json +11 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { parse as parseYaml } from "yaml";
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
|
-
import { SenpiClient } from "./senpi/client.js";
|
|
17
|
+
import { isMoneyCallTool, SenpiClient, senpiOrderCallsInFlight } from "./senpi/client.js";
|
|
18
|
+
import { MoneyCallTracker, registerMoneyCallHooks } from "./runtime/auto-update/money-call-tracker.js";
|
|
18
19
|
import { buildWalletStatusHint } from "./senpi/strategy-status.js";
|
|
19
20
|
import { bootSkippedStatusLabel, partitionByWalletStatus, readBootWalletStatuses, trackableWalletKeys, } from "./runtime/boot-skip.js";
|
|
20
21
|
import { DeployJobController } from "./deploy/job.js";
|
|
21
|
-
import { registerDeployGatewayMethods } from "./deploy/register.js";
|
|
22
|
+
import { DEPLOY_CAPABILITIES, registerDeployGatewayMethods } from "./deploy/register.js";
|
|
22
23
|
import { createQuantDesk } from "./quant/register.js";
|
|
23
24
|
import { registerUpdateGatewayMethod } from "./runtime/update-register.js";
|
|
24
25
|
import { createStrategiesPublisher } from "./runtime/strategies-telemetry.js";
|
|
@@ -28,6 +29,7 @@ import { checkProofCoversRecipe, makeApplyToRuntime, makeWriteRegistry, runtimeC
|
|
|
28
29
|
import { INSTALL_DEADLINE_MS, WALLET_INSTALL_IN_FLIGHT_CODE } from "./deploy/deadlines.js";
|
|
29
30
|
import { AuditQueryError } from "./runtime/audit-query.types.js";
|
|
30
31
|
import { buildRuntimeStateDir, archiveStateDir } from "./runtime/state-layout.js";
|
|
32
|
+
import { KeyedMutex } from "./utils/keyed-mutex.js";
|
|
31
33
|
import { describeRuntimeEntry } from "./runtime/runtime-descriptor.js";
|
|
32
34
|
import { registerSenpiCli } from "./cli/senpi-commands.js";
|
|
33
35
|
import { readSenpiCliStore, resolveSenpiApiKeyFromStore, resolveSenpiBaseStateDir, } from "./cli/senpi-cli-config.js";
|
|
@@ -39,6 +41,7 @@ import { resolveAutoUpdateConfig } from "./runtime/auto-update/types.js";
|
|
|
39
41
|
import { SkillsCoordinatorUnavailableError, SkillsUpdateCoordinator, resolveSkillsManagerConfig, } from "./skills-manager/index.js";
|
|
40
42
|
import { createScannerProvidersForRun } from "./runtime/create-scanner-providers-for-run.js";
|
|
41
43
|
import { initDsl } from "./dsl/index.js";
|
|
44
|
+
import { exchangeStopClass } from "./dsl/engine/index.js";
|
|
42
45
|
import { initHealth } from "./health/index.js";
|
|
43
46
|
import { initRisk } from "./risk/index.js";
|
|
44
47
|
import { readRiskAuditEntries } from "./risk/risk-audit-log.js";
|
|
@@ -46,15 +49,21 @@ import { readEvents } from "./telemetry/event-store.js";
|
|
|
46
49
|
import { VERSION } from "./version.js";
|
|
47
50
|
import { resolvePackageName } from "./runtime/auto-update/resolve-package-name.js";
|
|
48
51
|
import { runOpenClawPluginsUpdate } from "./runtime/auto-update/run-update-command.js";
|
|
52
|
+
import { UpdateIdleGate } from "./runtime/auto-update/idle-gate.js";
|
|
53
|
+
import { registerIdleGateHooks, resolveUpdateMid } from "./runtime/auto-update/register-idle-gate.js";
|
|
54
|
+
import { resolveUpdateMarkerPath } from "./runtime/auto-update/update-marker.js";
|
|
55
|
+
import { INSTALL_RESULTS_OTEL_FLUSH_MS, INSTALL_RESULTS_OTEL_READY_MS, resolveInstallResultsPath, } from "./runtime/auto-update/install-results.js";
|
|
49
56
|
import { registerSpanHooks } from "./telemetry/spans/index.js";
|
|
50
57
|
import { registerSessionContextSeedHook } from "./prompt/session-context-seed-hook.js";
|
|
51
58
|
import { registerSkillRouterHook } from "./prompt/skill-router-hook.js";
|
|
59
|
+
import { registerCribSheet } from "./tool-surface/crib-hook.js";
|
|
52
60
|
import { getDefaultPiiRedactor, initDefaultPiiRedactor } from "./telemetry/spans/redact-pii.js";
|
|
53
|
-
import { initOtelSdk, shutdownOtelSdk } from "./telemetry/otel-sdk.js";
|
|
61
|
+
import { flushOtelLogs, initOtelSdk, shutdownOtelSdk, waitForOtelInit } from "./telemetry/otel-sdk.js";
|
|
54
62
|
import { getLogLevel, getLogLevelSource, parseLogLevel, setLogLevel, } from "./utils/logger.js";
|
|
55
63
|
import { ExternalScannerIngestError, } from "./scanners/external-scanner-receiver.js";
|
|
56
64
|
import { createLogger } from "./utils/logger.js";
|
|
57
|
-
import { registerToolApprovalGate, resolveToolApprovalsMode, } from "./approvals/tool-approval-gate.js";
|
|
65
|
+
import { PendingApprovalTracker, registerToolApprovalGate, resolveToolApprovalsMode, } from "./approvals/tool-approval-gate.js";
|
|
66
|
+
import { registerTurnContextHooks, turnClassificationAvailable, TurnContextTracker, } from "./approvals/turn-context.js";
|
|
58
67
|
import { registerShowWidgetTool } from "./widgets/index.js";
|
|
59
68
|
import { registerQuantWidgetTurnCap } from "./widgets/turn-cap.js";
|
|
60
69
|
import { EVENT_NAME, SENPI_ERROR_CODE, eventAttrs, scannerUnwiredErrorCode } from "./utils/event-catalog.js";
|
|
@@ -72,6 +81,8 @@ import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ing
|
|
|
72
81
|
import { buildScaffoldErrorRouter } from "./runtime-api/intake/scaffold-error-router.js";
|
|
73
82
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
74
83
|
import { errorText } from "./deploy/error-text.js";
|
|
84
|
+
import { initOpenClawHost } from "./host/openclaw-host.js";
|
|
85
|
+
import { hostname } from "node:os";
|
|
75
86
|
import { BOOT_FACTS_CALL_TIMEOUT_MS, planBootFacts, syncBootFacts, } from "./strategy/facts-sync.js";
|
|
76
87
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
77
88
|
export function initCore(bus) {
|
|
@@ -184,6 +195,45 @@ const WALLET_INSTALL_IN_FLIGHT = `[${WALLET_INSTALL_IN_FLIGHT_CODE}]`;
|
|
|
184
195
|
* anyone's money is being claimed, which is precisely why it must not answer with the wallet code.
|
|
185
196
|
*/
|
|
186
197
|
const RUNTIME_ID_TAKEN = "[E_RUNTIME_ID_TAKEN]";
|
|
198
|
+
/**
|
|
199
|
+
* One StrategyFacts send per runtime at a time, keyed on runtime id, across the whole process: a
|
|
200
|
+
* second `register()` in the same process shares it. Separate from `runtimeChangeLock`. Delete does
|
|
201
|
+
* not take it; an apply queues its send here and does not wait.
|
|
202
|
+
*/
|
|
203
|
+
const factsSendLock = new KeyedMutex();
|
|
204
|
+
/**
|
|
205
|
+
* Whether this process has already logged the 5.x "cannot classify runs" notice.
|
|
206
|
+
*
|
|
207
|
+
* Module-scoped so a gateway reload (which re-registers the plugin and restarts its service in the
|
|
208
|
+
* same process) does not repeat it. The 5.x case is a host-version limit with no config fix, so
|
|
209
|
+
* one info line per process is the whole message; the fixable 10.x case stays an ERROR at every
|
|
210
|
+
* registration and does not read this latch.
|
|
211
|
+
*/
|
|
212
|
+
let legacyTurnClassificationNoticeLogged = false;
|
|
213
|
+
/** Clears {@link legacyTurnClassificationNoticeLogged}. Tests only. */
|
|
214
|
+
export function resetLegacyTurnClassificationNoticeForTests() {
|
|
215
|
+
legacyTurnClassificationNoticeLogged = false;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The whole of `register()` in the `"cli-metadata"` pass: the `senpi` CLI root, under the same
|
|
219
|
+
* preconditions and with the same metadata as the full pass, and nothing else.
|
|
220
|
+
*
|
|
221
|
+
* Reads no `api.runtime`: on 10.x that pass's runtime throws on any read, and the host prints the
|
|
222
|
+
* throw as `runtime failed during register` on every `openclaw senpi …` call (RT566-04). Logs
|
|
223
|
+
* nothing: a skipped precondition is reported by the full pass, which follows in the same process.
|
|
224
|
+
*/
|
|
225
|
+
function registerSenpiCliMetadata(api) {
|
|
226
|
+
const config = api.pluginConfig;
|
|
227
|
+
if (!config)
|
|
228
|
+
return;
|
|
229
|
+
const mcpTransport = api.mcpTransport;
|
|
230
|
+
const apiKey = resolveSenpiApiKeyFromStore(readSenpiCliStore(), typeof config.apiKey === "string" && config.apiKey.length > 0 ? config.apiKey : undefined);
|
|
231
|
+
if (!mcpTransport && !apiKey)
|
|
232
|
+
return;
|
|
233
|
+
api.registerCli((ctx) => {
|
|
234
|
+
registerSenpiCli(ctx.program);
|
|
235
|
+
}, { commands: ["senpi"] });
|
|
236
|
+
}
|
|
187
237
|
/**
|
|
188
238
|
* OpenClaw plugin entry.
|
|
189
239
|
* Boots from internal registry (installed_runtimes.json) in stateDir; does not read runtime list from config.
|
|
@@ -223,7 +273,16 @@ const senpiRuntimePlugin = {
|
|
|
223
273
|
warn: (...args) => api.logger.warn(...args),
|
|
224
274
|
error: (message, meta) => api.logger.error(message, meta),
|
|
225
275
|
};
|
|
276
|
+
// The cli-metadata pass only collects CLI command roots. 10.x hands it a runtime that throws on
|
|
277
|
+
// any read (RT566-04), so register the `senpi` root and nothing else — and read no api.runtime.
|
|
278
|
+
if (api.registrationMode === "cli-metadata") {
|
|
279
|
+
registerSenpiCliMetadata(api);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
226
282
|
pluginLogger.info("[senpi-runtime] register(api) invoked");
|
|
283
|
+
// Host facts first: every version-gated registration below reads them.
|
|
284
|
+
const openClawHost = initOpenClawHost(api);
|
|
285
|
+
pluginLogger.info(`[senpi-runtime] host OpenClaw ${openClawHost.version ?? "unknown"} (${openClawHost.modern ? "modern" : "legacy"})`);
|
|
227
286
|
const config = api.pluginConfig;
|
|
228
287
|
if (!config) {
|
|
229
288
|
pluginLogger.warn("[senpi-runtime] No plugin config found — skipping registration");
|
|
@@ -286,8 +345,8 @@ const senpiRuntimePlugin = {
|
|
|
286
345
|
// Agent telemetry — gateway hooks emit gen_ai spans (llm/tool/subagent) through
|
|
287
346
|
// the shared tracer; on-site MCP calls emit their own spans from SenpiClient.
|
|
288
347
|
try {
|
|
289
|
-
registerSpanHooks({ api });
|
|
290
|
-
pluginLogger.info(
|
|
348
|
+
const spanHookCount = registerSpanHooks({ api, modernHost: openClawHost.modern });
|
|
349
|
+
pluginLogger.info(`[senpi-runtime] gen_ai span hooks registered (${spanHookCount} hooks)`);
|
|
291
350
|
}
|
|
292
351
|
catch (err) {
|
|
293
352
|
pluginLogger.warn("[senpi-runtime] failed to register span hooks", {
|
|
@@ -326,14 +385,93 @@ const senpiRuntimePlugin = {
|
|
|
326
385
|
err: err instanceof Error ? err.message : String(err),
|
|
327
386
|
});
|
|
328
387
|
}
|
|
388
|
+
// senpi__ crib sheet (OpenClaw 10.x tool-search directory mode only): the
|
|
389
|
+
// directory folds every MCP tool into one sentence, so the runtime puts
|
|
390
|
+
// the names back as prependSystemContext. Registers nothing on 5.x.
|
|
391
|
+
// Its `before_prompt_build` hook needs the same host grant as the approval
|
|
392
|
+
// turn classification below; without it 10.x drops the hook, so the line
|
|
393
|
+
// says the crib is inactive and why instead of "registered".
|
|
394
|
+
try {
|
|
395
|
+
if (registerCribSheet({
|
|
396
|
+
api: api,
|
|
397
|
+
baseStateDir,
|
|
398
|
+
pluginConfig: config,
|
|
399
|
+
logger: pluginLogger,
|
|
400
|
+
})) {
|
|
401
|
+
// Registered, so the host is modern; only the hook grant is left to check.
|
|
402
|
+
const cribHook = turnClassificationAvailable({ modernHost: true, config: api.config, pluginId: api.id });
|
|
403
|
+
pluginLogger.info(cribHook.available
|
|
404
|
+
? "[senpi-runtime] senpi__ crib sheet registered (directory mode only)"
|
|
405
|
+
: `[senpi-runtime] senpi__ crib inactive: ${cribHook.reason}` +
|
|
406
|
+
(cribHook.configFix ? `; fix: ${cribHook.configFix}` : ""));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
pluginLogger.warn("[senpi-runtime] failed to register the senpi__ crib sheet", {
|
|
411
|
+
err: err instanceof Error ? err.message : String(err),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
329
414
|
// MCP tool approval gate — state-changing senpi MCP tools require an
|
|
330
415
|
// interactive user approval before executing. Prompts reach senpi-web via
|
|
331
416
|
// the bridge's operator.approvals-scoped gateway connection; allow-always
|
|
332
417
|
// decisions persist in <stateDir>/tool-approvals.json.
|
|
333
418
|
// See docs/mcp-tool-approvals-design.md.
|
|
419
|
+
// Open trade-approval prompts, read by the self-update idle gate (R2).
|
|
420
|
+
const pendingApprovalTracker = new PendingApprovalTracker();
|
|
421
|
+
// The turn kind (chat / cron / heartbeat) and session key of each gated
|
|
422
|
+
// call, for approval telemetry and the 10.x scheduled-turn block (R6, D13).
|
|
423
|
+
const turnContextTracker = new TurnContextTracker();
|
|
424
|
+
// One boot-time fact (D15): can this host classify runs at all? Only then
|
|
425
|
+
// does the gate stop prompts outside a direct chat turn. Without it every
|
|
426
|
+
// run reads unknown and keeps the pre-10.x prompt, so a box missing the
|
|
427
|
+
// setting never blocks chat trades; it is reported loudly instead.
|
|
428
|
+
let turnClassification = turnClassificationAvailable({
|
|
429
|
+
modernHost: openClawHost.modern,
|
|
430
|
+
config: api.config,
|
|
431
|
+
pluginId: api.id,
|
|
432
|
+
});
|
|
433
|
+
try {
|
|
434
|
+
registerTurnContextHooks({ api, tracker: turnContextTracker });
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
turnClassification = { available: false, reason: "the turn-context hooks failed to register" };
|
|
438
|
+
pluginLogger.warn("[senpi-runtime] failed to register the approval turn-context hooks", {
|
|
439
|
+
err: err instanceof Error ? err.message : String(err),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
// Only the logging and the status flag differ by host; the gate below reads the same boolean
|
|
443
|
+
// either way. On 10.x it is an ERROR at every registration (a box config to fix, or hooks that
|
|
444
|
+
// failed to register). On 5.x it is a host limit no config can fix, so it is one info line per
|
|
445
|
+
// process, logged from the service start below: that runs in the gateway only, so the
|
|
446
|
+
// `openclaw senpi …` CLI children (deploy.py, close.py, an agent exec) register without
|
|
447
|
+
// printing it.
|
|
448
|
+
const turnClassificationHostLimit = !turnClassification.available && !openClawHost.modern;
|
|
449
|
+
const turnClassificationNotice = turnClassification.available
|
|
450
|
+
? undefined
|
|
451
|
+
: `[senpi-tool-approvals] cannot tell chat turns from scheduled or background runs: ${turnClassification.reason}. ` +
|
|
452
|
+
"Trade approvals are requested in every run, scheduled ones included, " +
|
|
453
|
+
(turnClassification.configFix
|
|
454
|
+
? `until the box config is fixed (${turnClassification.configFix}).`
|
|
455
|
+
: turnClassificationHostLimit
|
|
456
|
+
? "on this OpenClaw version; a 10.x host lifts this."
|
|
457
|
+
: "until the runtime registers its turn-context hooks.");
|
|
458
|
+
const legacyTurnClassificationNotice = turnClassificationHostLimit ? turnClassificationNotice : undefined;
|
|
459
|
+
if (turnClassificationNotice !== undefined && !turnClassificationHostLimit) {
|
|
460
|
+
pluginLogger.error(turnClassificationNotice);
|
|
461
|
+
}
|
|
462
|
+
const approvalsHostStatus = turnClassification.available
|
|
463
|
+
? { turnClassification: "available" }
|
|
464
|
+
: {
|
|
465
|
+
turnClassification: "unavailable",
|
|
466
|
+
reason: turnClassification.reason,
|
|
467
|
+
...(turnClassificationHostLimit ? { hostLimit: true } : {}),
|
|
468
|
+
};
|
|
334
469
|
try {
|
|
335
470
|
registerToolApprovalGate({
|
|
336
471
|
api,
|
|
472
|
+
pendingApprovals: pendingApprovalTracker,
|
|
473
|
+
turnContext: turnContextTracker,
|
|
474
|
+
turnClassification: turnClassification.available,
|
|
337
475
|
mode: resolveToolApprovalsMode({
|
|
338
476
|
pluginConfig: config,
|
|
339
477
|
env: process.env,
|
|
@@ -487,7 +625,7 @@ const senpiRuntimePlugin = {
|
|
|
487
625
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
488
626
|
// Logger contract used by `runtime-api/` includes it.
|
|
489
627
|
const apiLogger = createLogger("[senpi-core][api]");
|
|
490
|
-
/** One line per instance for
|
|
628
|
+
/** One line per instance for each StrategyFacts send; see `queueFactsSend`. */
|
|
491
629
|
const factsLogger = createLogger("[senpi-core][strategy-facts]");
|
|
492
630
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
493
631
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
@@ -525,24 +663,92 @@ const senpiRuntimePlugin = {
|
|
|
525
663
|
});
|
|
526
664
|
const autoUpdateLogger = withEventLogger(pluginLogger, "[senpi-core][auto-update]");
|
|
527
665
|
const autoUpdateConfig = resolveAutoUpdateConfig(config?.autoUpdate);
|
|
666
|
+
// OpenClaw 10.x (>= 2026.8.1): restart-to-apply (R2). The runtime cannot
|
|
667
|
+
// install itself there, so it waits for its spread slot and an idle box,
|
|
668
|
+
// writes the install marker, and restarts through mutateConfigFile; the
|
|
669
|
+
// senpi-agent wrapper installs before the next gateway spawn. On 5.7 and
|
|
670
|
+
// 5.12 the wiring is the in-process install + config-bump restart.
|
|
671
|
+
const modernHost = openClawHost.modern;
|
|
672
|
+
let idleGate;
|
|
673
|
+
if (modernHost) {
|
|
674
|
+
// Money calls on every route: the runtime's own in-process MCP calls, plus
|
|
675
|
+
// the agent's senpi__ calls (direct or through the dispatcher), seen at the
|
|
676
|
+
// tool hooks. A deploy job is read from the controller (this process) and
|
|
677
|
+
// its journals (any process).
|
|
678
|
+
const moneyCalls = new MoneyCallTracker();
|
|
679
|
+
try {
|
|
680
|
+
registerMoneyCallHooks({
|
|
681
|
+
api: api,
|
|
682
|
+
tracker: moneyCalls,
|
|
683
|
+
isMoneyTool: isMoneyCallTool,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
pluginLogger.warn("[senpi-runtime] failed to register the self-update money-call hooks", {
|
|
688
|
+
err: err instanceof Error ? err.message : String(err),
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
idleGate = new UpdateIdleGate({
|
|
692
|
+
pendingApprovals: () => pendingApprovalTracker.count(),
|
|
693
|
+
ordersInFlight: () => senpiOrderCallsInFlight() + moneyCalls.count(Date.now()),
|
|
694
|
+
// Read only from ticks, after register() has constructed the controller below.
|
|
695
|
+
deploysInFlight: () => deployController.inFlight(Date.now()),
|
|
696
|
+
logger: pluginLogger,
|
|
697
|
+
});
|
|
698
|
+
try {
|
|
699
|
+
registerIdleGateHooks({ api: api, gate: idleGate, logger: pluginLogger });
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
pluginLogger.warn("[senpi-runtime] failed to register the self-update idle gate hooks", {
|
|
703
|
+
err: err instanceof Error ? err.message : String(err),
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const openclawStateDir = typeof api.runtime?.state?.resolveStateDir === "function" ? api.runtime.state.resolveStateDir() : undefined;
|
|
528
708
|
const autoUpdateCoordinator = new AutoUpdateCoordinator({
|
|
529
709
|
enabled: true,
|
|
530
710
|
config: autoUpdateConfig,
|
|
531
711
|
packageName: PLUGIN_PACKAGE_NAME,
|
|
532
712
|
currentVersion: VERSION,
|
|
713
|
+
// The version this process loaded; "applied" is decided from it alone.
|
|
714
|
+
loadedVersion: VERSION,
|
|
533
715
|
stateDir: join(baseStateDir, "auto-update"),
|
|
534
716
|
logger: autoUpdateLogger,
|
|
535
717
|
// Use the resolved npm package name (not a hardcoded plugin id) so
|
|
536
718
|
// the update follows the same npm channel as the version check —
|
|
537
|
-
// see runtime/auto-update/run-update-command.ts.
|
|
538
|
-
|
|
719
|
+
// see runtime/auto-update/run-update-command.ts. Not on 10.x, which
|
|
720
|
+
// refuses plugins update while the gateway runs.
|
|
721
|
+
runUpdateCommand: modernHost ? undefined : runOpenClawPluginsUpdate,
|
|
539
722
|
configFromApi: api.config,
|
|
540
|
-
loadConfig: api.runtime?.config
|
|
723
|
+
loadConfig: !modernHost && api.runtime?.config
|
|
541
724
|
? () => api.runtime.config.loadConfig()
|
|
542
725
|
: undefined,
|
|
543
|
-
writeConfigFile: api.runtime?.config
|
|
726
|
+
writeConfigFile: !modernHost && api.runtime?.config
|
|
544
727
|
? (c) => api.runtime.config.writeConfigFile(c)
|
|
545
728
|
: undefined,
|
|
729
|
+
modernHost,
|
|
730
|
+
mutateConfigFile: modernHost && typeof api.runtime?.config?.mutateConfigFile === "function"
|
|
731
|
+
? (p) => api.runtime.config.mutateConfigFile(p)
|
|
732
|
+
: undefined,
|
|
733
|
+
readLiveConfig: modernHost ? openClawHost.currentConfig : undefined,
|
|
734
|
+
idleGate,
|
|
735
|
+
mid: modernHost
|
|
736
|
+
? resolveUpdateMid({
|
|
737
|
+
token: apiKey ?? process.env.SENPI_AUTH_TOKEN ?? process.env.SENPI_API_KEY,
|
|
738
|
+
env: process.env,
|
|
739
|
+
hostname,
|
|
740
|
+
})
|
|
741
|
+
: undefined,
|
|
742
|
+
updateMarkerPath: modernHost && openclawStateDir ? resolveUpdateMarkerPath(openclawStateDir) : undefined,
|
|
743
|
+
// The wrapper's install outcomes, reported once per coordinator start (gateway only),
|
|
744
|
+
// with auto-update on or off. The report runs off the start path: it waits for the OTel
|
|
745
|
+
// init register() started (else the events go to the no-op logger and the file is gone)
|
|
746
|
+
// and flushes the logs before deleting the file.
|
|
747
|
+
installResultsPath: modernHost && openclawStateDir ? resolveInstallResultsPath(openclawStateDir) : undefined,
|
|
748
|
+
installResultsTelemetry: {
|
|
749
|
+
ready: () => waitForOtelInit(INSTALL_RESULTS_OTEL_READY_MS),
|
|
750
|
+
flush: () => flushOtelLogs(INSTALL_RESULTS_OTEL_FLUSH_MS),
|
|
751
|
+
},
|
|
546
752
|
pluginId: api.id,
|
|
547
753
|
// Drive skills-manager off the auto-update tick. Both success and
|
|
548
754
|
// failure branches of auto-update's tick fire this hook so skills
|
|
@@ -622,6 +828,11 @@ const senpiRuntimePlugin = {
|
|
|
622
828
|
api.registerService({
|
|
623
829
|
id: "senpi-runtime",
|
|
624
830
|
async start() {
|
|
831
|
+
// The 5.x classification notice (see the approval gate above), once per process.
|
|
832
|
+
if (legacyTurnClassificationNotice !== undefined && !legacyTurnClassificationNoticeLogged) {
|
|
833
|
+
legacyTurnClassificationNoticeLogged = true;
|
|
834
|
+
pluginLogger.info(legacyTurnClassificationNotice);
|
|
835
|
+
}
|
|
625
836
|
// Deploy-journal janitor: stamp anything a restart left unterminated as `interrupted`.
|
|
626
837
|
// It never re-enters a deploy state machine — discovery is pull-based via
|
|
627
838
|
// `senpi deploy status`.
|
|
@@ -759,26 +970,16 @@ const senpiRuntimePlugin = {
|
|
|
759
970
|
emitEvent(apiLogger, () => buildRuntimeBootSkippedEvent({ runtimeId: entry.id, address: entry.wallet ?? "", status }));
|
|
760
971
|
}
|
|
761
972
|
const started = [];
|
|
762
|
-
|
|
973
|
+
// Started runtimes with a facts plan, in boot order; their send runs detached after boot, below.
|
|
974
|
+
const factsRuntimeIds = [];
|
|
763
975
|
try {
|
|
764
976
|
for (const entry of entriesToBoot) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
if (hasPath && hasContent)
|
|
768
|
-
continue;
|
|
769
|
-
if (!hasPath && !hasContent)
|
|
977
|
+
const inputs = factsPlanInputs(entry);
|
|
978
|
+
if (!inputs)
|
|
770
979
|
continue;
|
|
771
|
-
const pathOrContent
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const factsPlan = await planInstanceFacts({
|
|
775
|
-
runtimeId: entry.id,
|
|
776
|
-
wallet: entry.wallet,
|
|
777
|
-
stateDir: entryStateDir,
|
|
778
|
-
pathOrContent,
|
|
779
|
-
fromPath: hasPath,
|
|
780
|
-
runtimeYamlDir: hasPath ? dirname(pathOrContent) : entry.runtimeYamlDir,
|
|
781
|
-
});
|
|
980
|
+
const { pathOrContent, fromPath: hasPath, stateDir: entryStateDir } = inputs;
|
|
981
|
+
// Local only (files, no network). Read here for the `factsSent` stamp on this start.
|
|
982
|
+
const factsPlan = await planInstanceFacts(inputs);
|
|
782
983
|
const handle = await run(pathOrContent, {
|
|
783
984
|
fromPath: hasPath,
|
|
784
985
|
stateDir: entryStateDir,
|
|
@@ -792,7 +993,7 @@ const senpiRuntimePlugin = {
|
|
|
792
993
|
runtimeHandles.set(entry.id, handle);
|
|
793
994
|
started.push(entry.id);
|
|
794
995
|
if (factsPlan)
|
|
795
|
-
|
|
996
|
+
factsRuntimeIds.push(entry.id);
|
|
796
997
|
// ── supervised scanner mount (additive; guarded) ──────────────────
|
|
797
998
|
// Boot path: used for runtimes already present in the registry at
|
|
798
999
|
// startup. A runtime installed live, after the API is listening,
|
|
@@ -885,8 +1086,12 @@ const senpiRuntimePlugin = {
|
|
|
885
1086
|
throw err;
|
|
886
1087
|
}
|
|
887
1088
|
pluginLogger.info(`[senpi-runtime] Started ${started.length} runtime${started.length === 1 ? "" : "s"}`);
|
|
888
|
-
// Detached: the facts send never holds boot or trading. One line per instance
|
|
889
|
-
|
|
1089
|
+
// Detached: the facts send never holds boot or trading. One line per instance, one instance at
|
|
1090
|
+
// a time, each planned again when its turn comes under the per-runtime facts lock.
|
|
1091
|
+
void (async () => {
|
|
1092
|
+
for (const runtimeId of factsRuntimeIds)
|
|
1093
|
+
await queueFactsSend(runtimeId, bootStrategyIds);
|
|
1094
|
+
})().catch((err) => {
|
|
890
1095
|
factsLogger.warn("strategy facts send crashed", err);
|
|
891
1096
|
});
|
|
892
1097
|
// ── HTTP API ──────────────────────────────────────────────────────
|
|
@@ -1628,8 +1833,27 @@ const senpiRuntimePlugin = {
|
|
|
1628
1833
|
else
|
|
1629
1834
|
respond(false, undefined, result.error);
|
|
1630
1835
|
}, { scope: "operator.write" });
|
|
1631
|
-
// ── StrategyFacts
|
|
1632
|
-
/**
|
|
1836
|
+
// ── StrategyFacts send ─────────────────────────────────────────────────
|
|
1837
|
+
/**
|
|
1838
|
+
* The facts plan inputs for one registry entry, which are also the recipe and state dir boot
|
|
1839
|
+
* starts it with. Null when the entry holds both or neither of a recipe path and recipe content.
|
|
1840
|
+
*/
|
|
1841
|
+
function factsPlanInputs(entry) {
|
|
1842
|
+
const hasPath = typeof entry.runtimeYamlPath === "string" && entry.runtimeYamlPath.length > 0;
|
|
1843
|
+
const hasContent = typeof entry.runtimeYamlContent === "string";
|
|
1844
|
+
if (hasPath === hasContent)
|
|
1845
|
+
return null;
|
|
1846
|
+
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
1847
|
+
return {
|
|
1848
|
+
runtimeId: entry.id,
|
|
1849
|
+
wallet: entry.wallet,
|
|
1850
|
+
stateDir: buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? ""),
|
|
1851
|
+
pathOrContent,
|
|
1852
|
+
fromPath: hasPath,
|
|
1853
|
+
runtimeYamlDir: hasPath ? dirname(pathOrContent) : entry.runtimeYamlDir,
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
/** The facts plan for one instance, or null (logged once as `failed`) when it cannot be made. */
|
|
1633
1857
|
async function planInstanceFacts(input) {
|
|
1634
1858
|
try {
|
|
1635
1859
|
const config = await loadRuntimeConfig(input.pathOrContent, { fromPath: input.fromPath });
|
|
@@ -1696,6 +1920,43 @@ const senpiRuntimePlugin = {
|
|
|
1696
1920
|
await session.client.disconnect().catch(() => { });
|
|
1697
1921
|
}
|
|
1698
1922
|
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Send one runtime's facts under `factsSendLock`. The plan is made from the registry entry and
|
|
1925
|
+
* the state file as they are when the send runs, so the last send for a runtime carries its
|
|
1926
|
+
* current recipe. A runtime no longer in the registry, or whose entry cannot be planned, gets
|
|
1927
|
+
* no send. An unchanged plan makes no call.
|
|
1928
|
+
*
|
|
1929
|
+
* `strategyIds` is the boot's one batched status read (null when that read failed). Without it,
|
|
1930
|
+
* the strategy id is read for this one wallet; a failed read logs its reason and sends nothing.
|
|
1931
|
+
*/
|
|
1932
|
+
function queueFactsSend(runtimeId, strategyIds) {
|
|
1933
|
+
return factsSendLock.withKeyLock(runtimeId, async () => {
|
|
1934
|
+
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1935
|
+
const entry = (await readRegistry(baseStateDir)).runtimes.find((e) => e.id === runtimeId);
|
|
1936
|
+
const inputs = entry ? factsPlanInputs(entry) : null;
|
|
1937
|
+
if (!inputs)
|
|
1938
|
+
return;
|
|
1939
|
+
const plan = await planInstanceFacts(inputs);
|
|
1940
|
+
if (!plan)
|
|
1941
|
+
return;
|
|
1942
|
+
let ids = strategyIds ?? null;
|
|
1943
|
+
if (strategyIds === undefined && !plan.unchanged) {
|
|
1944
|
+
const read = await readBootWalletStatuses([plan.wallet.trim().toLowerCase()], (wallets, signal) => withSenpiClient((client) => client.listStrategies({ strategyAddresses: wallets }, { signal })));
|
|
1945
|
+
if (!read.ok) {
|
|
1946
|
+
factsLogger.warn("strategy facts failed: could not read the strategy id; retrying next boot", undefined, {
|
|
1947
|
+
runtimeId: plan.runtimeId,
|
|
1948
|
+
wallet: plan.wallet,
|
|
1949
|
+
hash: plan.hash.slice(0, 12),
|
|
1950
|
+
reason: read.reason,
|
|
1951
|
+
detail: read.message,
|
|
1952
|
+
});
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
ids = read.strategyIds;
|
|
1956
|
+
}
|
|
1957
|
+
await sendBootFacts([plan], ids);
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1699
1960
|
/** One connected SenpiClient per job; always disconnected, even on failure. */
|
|
1700
1961
|
async function withSenpiClient(fn) {
|
|
1701
1962
|
const client = mcpTransport ? new SenpiClient(mcpTransport) : new SenpiClient({ apiKey });
|
|
@@ -1908,6 +2169,13 @@ const senpiRuntimePlugin = {
|
|
|
1908
2169
|
...(info.slots !== undefined ? { slots: info.slots } : {}),
|
|
1909
2170
|
});
|
|
1910
2171
|
});
|
|
2172
|
+
// The apply swaps the recipe with no boot, so this send moves the backend's facts (what the
|
|
2173
|
+
// app shows) to the new recipe before the next boot. Detached: the apply has landed and must
|
|
2174
|
+
// not wait on, or fail over, the send. The send plans from the registry entry the apply has
|
|
2175
|
+
// written, and takes the wallet from that entry or its recipe.
|
|
2176
|
+
void queueFactsSend(info.runtimeId).catch((err) => {
|
|
2177
|
+
factsLogger.warn("strategy facts send crashed", err);
|
|
2178
|
+
});
|
|
1911
2179
|
},
|
|
1912
2180
|
});
|
|
1913
2181
|
registerDeployGatewayMethods(api, {
|
|
@@ -1930,7 +2198,10 @@ const senpiRuntimePlugin = {
|
|
|
1930
2198
|
// same clean line over a runtime that cannot tick.
|
|
1931
2199
|
runtimeIsLive: async (id) => runtimeHandles.has(id) && !unwiredRuntimes.has(id),
|
|
1932
2200
|
installPendingOnWallet: async (wallet) => installsInFlight.has(wallet.trim().toLowerCase()),
|
|
1933
|
-
logger: {
|
|
2201
|
+
logger: {
|
|
2202
|
+
info: (line) => api.logger.info(`[senpi-runtime] ${line}`),
|
|
2203
|
+
warn: (line) => api.logger.warn(`[senpi-runtime] ${line}`),
|
|
2204
|
+
},
|
|
1934
2205
|
});
|
|
1935
2206
|
/**
|
|
1936
2207
|
* Gateway shim for `senpi.ingestExternalScannerData`.
|
|
@@ -2055,6 +2326,7 @@ const senpiRuntimePlugin = {
|
|
|
2055
2326
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2056
2327
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
2057
2328
|
pluginLogger.info("[senpi-runtime] senpi.getDslPositionState", { asset, runtimeId, address });
|
|
2329
|
+
// `exchangeStop` is derived from the state on each response and is not persisted.
|
|
2058
2330
|
const matches = [];
|
|
2059
2331
|
for (const [id, handle] of runtimeHandles) {
|
|
2060
2332
|
if (runtimeId && id !== runtimeId)
|
|
@@ -2065,12 +2337,18 @@ const senpiRuntimePlugin = {
|
|
|
2065
2337
|
if (address) {
|
|
2066
2338
|
const state = handle.dsl.getPositionState(address, asset);
|
|
2067
2339
|
if (state)
|
|
2068
|
-
matches.push({ runtimeId: id, address, state });
|
|
2340
|
+
matches.push({ runtimeId: id, address, state, exchangeStop: exchangeStopClass(state) });
|
|
2069
2341
|
}
|
|
2070
2342
|
else {
|
|
2071
2343
|
const found = handle.dsl.findPositionByAsset(asset);
|
|
2072
|
-
if (found)
|
|
2073
|
-
matches.push({
|
|
2344
|
+
if (found) {
|
|
2345
|
+
matches.push({
|
|
2346
|
+
runtimeId: id,
|
|
2347
|
+
address: found.address,
|
|
2348
|
+
state: found.state,
|
|
2349
|
+
exchangeStop: exchangeStopClass(found.state),
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2074
2352
|
}
|
|
2075
2353
|
}
|
|
2076
2354
|
catch (err) {
|
|
@@ -2126,6 +2404,8 @@ const senpiRuntimePlugin = {
|
|
|
2126
2404
|
respond(true, {
|
|
2127
2405
|
status: status ? { ...status, runtimeId } : null,
|
|
2128
2406
|
updater,
|
|
2407
|
+
approvals: approvalsHostStatus,
|
|
2408
|
+
deploy: { capabilities: DEPLOY_CAPABILITIES },
|
|
2129
2409
|
});
|
|
2130
2410
|
}
|
|
2131
2411
|
catch (err) {
|
|
@@ -2138,7 +2418,7 @@ const senpiRuntimePlugin = {
|
|
|
2138
2418
|
return;
|
|
2139
2419
|
}
|
|
2140
2420
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
2141
|
-
respond(true, { statuses, updater });
|
|
2421
|
+
respond(true, { statuses, updater, approvals: approvalsHostStatus, deploy: { capabilities: DEPLOY_CAPABILITIES } });
|
|
2142
2422
|
}, { scope: "operator.read" });
|
|
2143
2423
|
api.registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
2144
2424
|
const respond = ctx.respond;
|