@ouro.bot/cli 0.1.0-alpha.741 → 0.1.0-alpha.742
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/changelog.json +16 -0
- package/deploy/unraid/README.txt +13 -42
- package/deploy/unraid/sanctuary.ouro/agent.json +2 -2
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +3 -10
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/context-loss-gauntlet.js +31 -2
- package/dist/heart/context-loss-sentinel.js +2 -0
- package/dist/heart/daemon/container-healthcheck.js +3 -1
- package/dist/heart/daemon/container-runtime.js +13 -0
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +25 -45
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +11 -5
- package/dist/heart/daemon/supercronic-supervisor.js +16 -1
- package/dist/heart/provider-models.js +10 -3
- package/dist/senses/sanctuary-health-runner.js +11 -0
- package/dist/senses/sanctuary-health.js +13 -0
- package/dist/senses/telegram-client.js +24 -6
- package/dist/senses/telegram-entry.js +4 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.742",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Ship Sanctuary on the cost-effective MiniMax-M3 provider path after live vault readiness proved both outward and inner lanes, with optional Gemini no longer required for readiness.",
|
|
8
|
+
"Keep the packaged Unraid bundle on the same MiniMax-M3 lane the live Sanctuary vault uses.",
|
|
9
|
+
"Update the packaged Unraid readiness/adoption checks to require only the production MiniMax credential and live MiniMax lane checks before cutover.",
|
|
10
|
+
"Accept Supercronic's real shell-mediated cron process tree while preserving authenticated Sanctuary scheduler provenance.",
|
|
11
|
+
"Refresh machine-scoped runtime credentials before the Sanctuary health habit creates typed Unraid tools.",
|
|
12
|
+
"Create the Sanctuary health state directory before taking the first health sweep lease.",
|
|
13
|
+
"Retry cached Sanctuary health Telegram deliveries from the durable outbox after an indeterminate send and pin the Unraid template DNS resolver for Node-based Telegram egress.",
|
|
14
|
+
"Sequence same-provider Flash acceptance probes to avoid provider-side rate-limit flakiness while preserving exact provider/model attempt binding.",
|
|
15
|
+
"Treat settled Telegram turns that are safely waiting for new input as healthy context-loss state instead of relatching the Sentinel as blocked.",
|
|
16
|
+
"Validate the live container process tree for agent, Telegram, and Supercronic health instead of failing on a stale child PID in daemon-health.json.",
|
|
17
|
+
"Accept Supercronic's real shell-mediated cron ancestry when authenticating Sanctuary health scheduler fires."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.741",
|
|
6
22
|
"changes": [
|
package/deploy/unraid/README.txt
CHANGED
|
@@ -713,18 +713,16 @@ local unlock: available
|
|
|
713
713
|
machineIdMigration: {
|
|
714
714
|
sourceMachineId: process.argv[1],
|
|
715
715
|
targetMachineId: process.argv[2],
|
|
716
|
-
discardProviderCredentialRecords: { providers: ["minimax"] },
|
|
717
716
|
},
|
|
718
717
|
}).then(
|
|
719
718
|
() => process.exit(0),
|
|
720
719
|
() => { process.stderr.write("credential bootstrap failed\n"); process.exit(1) },
|
|
721
720
|
)
|
|
722
721
|
' "$BOOTSTRAP_SOURCE_MACHINE_ID" "$BOOTSTRAP_TARGET_MACHINE_ID" || return $?
|
|
723
|
-
# The path-locked Sanctuary migration
|
|
724
|
-
#
|
|
722
|
+
# The path-locked Sanctuary migration imports the legacy MiniMax provider
|
|
723
|
+
# record into the agent vault before strict validation and persistence.
|
|
725
724
|
# The original legacy envelope remains byte-for-byte unchanged and is the
|
|
726
|
-
# sole digest authority
|
|
727
|
-
# only by the separate hidden-input authentication commands below.
|
|
725
|
+
# sole digest authority.
|
|
728
726
|
! docker container inspect ouro-butler-credential-bootstrap >/dev/null 2>&1 || return 1
|
|
729
727
|
test ! -e "$BOOTSTRAP_CREDENTIAL_SOURCE" || return $?
|
|
730
728
|
test ! -e "$BOOTSTRAP_CREDENTIAL_CLAIM" || return $?
|
|
@@ -778,54 +776,29 @@ local unlock: available
|
|
|
778
776
|
const policy = JSON.parse(fs.readFileSync(`${root}/provider-readiness.json`, "utf8"));
|
|
779
777
|
const expectedPolicy = { version: 1, selectionPolicy: "explicit-same-lane-only" };
|
|
780
778
|
const expected = new Map([
|
|
781
|
-
["
|
|
782
|
-
["openai-compatible-gemini", { provider: "openai-compatible-gemini", model: "gemini-3.6-flash", vaultItem: "providers/openai-compatible-gemini", baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai/" }],
|
|
779
|
+
["minimax", { provider: "minimax", model: "MiniMax-M3", vaultItem: "providers/minimax" }],
|
|
783
780
|
]);
|
|
784
|
-
const exactLane = value => value && value.provider === "
|
|
781
|
+
const exactLane = value => value && value.provider === "minimax" && value.model === "MiniMax-M3";
|
|
785
782
|
if (!exactLane(agent.humanFacing) || !exactLane(agent.agentFacing)
|
|
786
783
|
|| policy.version !== expectedPolicy.version || policy.selectionPolicy !== expectedPolicy.selectionPolicy
|
|
787
784
|
|| !Array.isArray(policy.providers) || policy.providers.length !== expected.size) throw new Error();
|
|
788
785
|
for (const entry of policy.providers) {
|
|
789
786
|
const wanted = expected.get(entry.provider);
|
|
790
|
-
if (!wanted || entry.model !== wanted.model || entry.vaultItem !== wanted.vaultItem
|
|
787
|
+
if (!wanted || entry.model !== wanted.model || entry.vaultItem !== wanted.vaultItem) throw new Error();
|
|
791
788
|
expected.delete(entry.provider);
|
|
792
789
|
}
|
|
793
790
|
if (expected.size !== 0) throw new Error();
|
|
794
791
|
const { refreshProviderCredentialPool } = require("/opt/ouro/dist/heart/provider-credentials.js");
|
|
795
|
-
const { pingProvider } = require("/opt/ouro/dist/heart/provider-ping.js");
|
|
796
792
|
const refreshed = await refreshProviderCredentialPool("sanctuary", {
|
|
797
|
-
providers: ["
|
|
793
|
+
providers: ["minimax"],
|
|
798
794
|
skipCache: true,
|
|
799
795
|
});
|
|
800
796
|
if (!refreshed.ok) throw new Error();
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
const exactRecord = (record, provider, baseUrl) => record && record.provider === provider
|
|
797
|
+
const minimax = refreshed.pool.providers["minimax"];
|
|
798
|
+
const exactRecord = (record, provider) => record && record.provider === provider
|
|
804
799
|
&& typeof record.revision === "string" && record.revision.length > 0
|
|
805
|
-
&& typeof record.credentials?.apiKey === "string" && record.credentials.apiKey.trim().length > 0
|
|
806
|
-
|
|
807
|
-
if (!exactRecord(glm, "openai-compatible", "https://api.z.ai/api/paas/v4/")
|
|
808
|
-
|| !exactRecord(gemini, "openai-compatible-gemini", "https://generativelanguage.googleapis.com/v1beta/openai/")
|
|
809
|
-
|| glm.credentials.apiKey === gemini.credentials.apiKey
|
|
810
|
-
|| new Set([glm.revision, gemini.revision]).size !== 2) throw new Error();
|
|
811
|
-
const probes = [
|
|
812
|
-
{ provider: "openai-compatible", model: "glm-5.2", credentialRevision: glm.revision, record: glm },
|
|
813
|
-
{ provider: "openai-compatible", model: "glm-5.2", credentialRevision: glm.revision, record: glm },
|
|
814
|
-
{ provider: "openai-compatible-gemini", model: "gemini-3.6-flash", credentialRevision: gemini.revision, record: gemini },
|
|
815
|
-
];
|
|
816
|
-
const results = await Promise.all(probes.map(({ provider, model, record }) => pingProvider(
|
|
817
|
-
provider,
|
|
818
|
-
{ ...record.credentials, ...record.config },
|
|
819
|
-
{ model, timeoutMs: 10000, attemptPolicy: { baseDelayMs: 0 } },
|
|
820
|
-
)));
|
|
821
|
-
for (let index = 0; index < results.length; index += 1) {
|
|
822
|
-
const result = results[index];
|
|
823
|
-
const probe = probes[index];
|
|
824
|
-
if (!result.ok || !Array.isArray(result.attempts) || result.attempts.length < 1 || result.attempts.length > 3
|
|
825
|
-
|| !result.attempts.every(attempt => attempt.provider === probe.provider && attempt.model === probe.model
|
|
826
|
-
&& attempt.operation === "ping" && typeof attempt.ok === "boolean")
|
|
827
|
-
|| result.attempts.at(-1)?.ok !== true) throw new Error();
|
|
828
|
-
}
|
|
800
|
+
&& typeof record.credentials?.apiKey === "string" && record.credentials.apiKey.trim().length > 0;
|
|
801
|
+
if (!exactRecord(minimax, "minimax")) throw new Error();
|
|
829
802
|
process.stdout.write("Sanctuary provider readiness verified.\n");
|
|
830
803
|
} catch {
|
|
831
804
|
process.stderr.write("Sanctuary provider readiness verification failed.\n");
|
|
@@ -1040,13 +1013,11 @@ Update:
|
|
|
1040
1013
|
legacy state: no production or rollback, exactly one running (possibly
|
|
1041
1014
|
unhealthy) ouro-butler-staging, and no legacy-evidence container. After the
|
|
1042
1015
|
helper definitions above are loaded and IMAGE_ID is resolved, run this exact
|
|
1043
|
-
sequence.
|
|
1044
|
-
|
|
1016
|
+
sequence. MiniMax credentials are imported from the byte-verified legacy
|
|
1017
|
+
bootstrap envelope; do not place credentials in arguments, shell variables, or
|
|
1045
1018
|
history.
|
|
1046
1019
|
Sanctuary legacy adoption commands:
|
|
1047
1020
|
prepare_sanctuary_legacy_adoption "$IMAGE_ID"
|
|
1048
|
-
authenticate_sanctuary_provider "$IMAGE_ID" openai-compatible
|
|
1049
|
-
authenticate_sanctuary_provider "$IMAGE_ID" openai-compatible-gemini
|
|
1050
1021
|
verify_sanctuary_provider_readiness "$IMAGE_ID"
|
|
1051
1022
|
install_from_legacy_staging
|
|
1052
1023
|
These commands are one terminal path; after install succeeds, stop and do not
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
3
|
"enabled": true,
|
|
4
|
-
"humanFacing": { "provider": "
|
|
5
|
-
"agentFacing": { "provider": "
|
|
4
|
+
"humanFacing": { "provider": "minimax", "model": "MiniMax-M3" },
|
|
5
|
+
"agentFacing": { "provider": "minimax", "model": "MiniMax-M3" },
|
|
6
6
|
"context": { "maxTokens": 80000, "contextMargin": 20 },
|
|
7
7
|
"privateRuntime": { "autoStart": true },
|
|
8
8
|
"habitPaidTurnsPerDay": 24,
|
|
@@ -3,16 +3,9 @@
|
|
|
3
3
|
"selectionPolicy": "explicit-same-lane-only",
|
|
4
4
|
"providers": [
|
|
5
5
|
{
|
|
6
|
-
"provider": "
|
|
7
|
-
"model": "
|
|
8
|
-
"vaultItem": "providers/
|
|
9
|
-
"baseUrl": "https://api.z.ai/api/paas/v4/"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"provider": "openai-compatible-gemini",
|
|
13
|
-
"model": "gemini-3.6-flash",
|
|
14
|
-
"vaultItem": "providers/openai-compatible-gemini",
|
|
15
|
-
"baseUrl": "https://generativelanguage.googleapis.com/v1beta/openai/"
|
|
6
|
+
"provider": "minimax",
|
|
7
|
+
"model": "MiniMax-M3",
|
|
8
|
+
"vaultItem": "providers/minimax"
|
|
16
9
|
}
|
|
17
10
|
]
|
|
18
11
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<WebUI/>
|
|
14
14
|
<TemplateURL/>
|
|
15
15
|
<Icon/>
|
|
16
|
-
<ExtraParams>--restart=unless-stopped --user=10001:10001</ExtraParams>
|
|
16
|
+
<ExtraParams>--restart=unless-stopped --user=10001:10001 --dns=1.1.1.1</ExtraParams>
|
|
17
17
|
<PostArgs/>
|
|
18
18
|
<CPUset/>
|
|
19
19
|
<DateInstalled>0</DateInstalled>
|
|
@@ -81,8 +81,26 @@ function currentAskCheck(card) {
|
|
|
81
81
|
evidence,
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
function
|
|
84
|
+
function isSettledTurnWait(resume) {
|
|
85
|
+
return resume.hasCompleteState
|
|
86
|
+
&& resume.recorderHealth.status === "ok"
|
|
87
|
+
&& resume.blockedBecause.length === 1
|
|
88
|
+
&& resume.blockedBecause[0] === "turn outcome settled; wait for new input before acting"
|
|
89
|
+
&& resume.nextSafeAction.value === "inspect the latest session and wait for new input before acting";
|
|
90
|
+
}
|
|
91
|
+
function nextSafeActionCheck(card, resume) {
|
|
85
92
|
const evidence = card.nextAction.source ? [card.nextAction.source] : [];
|
|
93
|
+
if (isSettledTurnWait(resume)) {
|
|
94
|
+
return makeCheck({
|
|
95
|
+
id: "next_safe_action",
|
|
96
|
+
label: "Next safe action",
|
|
97
|
+
status: "pass",
|
|
98
|
+
score: 20,
|
|
99
|
+
maxScore: 20,
|
|
100
|
+
detail: "idle: turn is settled and waiting for new input.",
|
|
101
|
+
evidence: flightRecorderEvidence(card),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
86
104
|
if (card.nextAction.actor === "unknown") {
|
|
87
105
|
return makeCheck({
|
|
88
106
|
id: "next_safe_action",
|
|
@@ -117,6 +135,17 @@ function staleGuardCheck(resume, card) {
|
|
|
117
135
|
evidence,
|
|
118
136
|
});
|
|
119
137
|
}
|
|
138
|
+
if (isSettledTurnWait(resume)) {
|
|
139
|
+
return makeCheck({
|
|
140
|
+
id: "stale_guard",
|
|
141
|
+
label: "Stale-state guard",
|
|
142
|
+
status: "pass",
|
|
143
|
+
score: 15,
|
|
144
|
+
maxScore: 15,
|
|
145
|
+
detail: "Flight recorder has complete settled-turn state and is safely waiting for new input.",
|
|
146
|
+
evidence,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
120
149
|
const reasons = [
|
|
121
150
|
"canContinue is false",
|
|
122
151
|
...(!resume.hasCompleteState ? ["incomplete resume state"] : []),
|
|
@@ -287,7 +316,7 @@ function runContextLossGauntlet(agentName, agentRoot, options = {}) {
|
|
|
287
316
|
const generatedAt = (options.now ?? (() => new Date()))().toISOString();
|
|
288
317
|
const checks = [
|
|
289
318
|
currentAskCheck(card),
|
|
290
|
-
nextSafeActionCheck(card),
|
|
319
|
+
nextSafeActionCheck(card, flightRecorderResume),
|
|
291
320
|
staleGuardCheck(flightRecorderResume, card),
|
|
292
321
|
obligationsCheck(card),
|
|
293
322
|
returnRoutesCheck(card),
|
|
@@ -521,6 +521,8 @@ function healthSignals(results) {
|
|
|
521
521
|
}
|
|
522
522
|
function defaultGitStatus(agentRoot) {
|
|
523
523
|
try {
|
|
524
|
+
if (!fs.existsSync(path.join(agentRoot, ".git")))
|
|
525
|
+
return { ok: true, porcelain: "" };
|
|
524
526
|
const porcelain = (0, child_process_1.execFileSync)("git", ["status", "--porcelain=v1", "-uall"], {
|
|
525
527
|
cwd: agentRoot,
|
|
526
528
|
encoding: "utf-8",
|
|
@@ -72,7 +72,7 @@ function runContainerHealthcheck(options = {}) {
|
|
|
72
72
|
fail("daemon status is not ready");
|
|
73
73
|
const agents = health.agents;
|
|
74
74
|
const managed = agents?.[agent];
|
|
75
|
-
if (!managed || managed.status !== "running" || typeof managed.pid !== "number" || managed.pid <= 0
|
|
75
|
+
if (!managed || managed.status !== "running" || typeof managed.pid !== "number" || managed.pid <= 0)
|
|
76
76
|
fail("managed agent is not running");
|
|
77
77
|
let processArguments = "";
|
|
78
78
|
try {
|
|
@@ -81,6 +81,8 @@ function runContainerHealthcheck(options = {}) {
|
|
|
81
81
|
catch {
|
|
82
82
|
fail("managed process inventory is unavailable");
|
|
83
83
|
}
|
|
84
|
+
if (!(0, container_runtime_1.hasManagedAgentProcess)(processArguments, agent))
|
|
85
|
+
fail("managed agent is not running exactly once");
|
|
84
86
|
if (!(0, container_runtime_1.hasManagedTelegramProcess)(processArguments, agent))
|
|
85
87
|
fail("managed Telegram sense is not running exactly once");
|
|
86
88
|
if (!(0, container_runtime_1.hasManagedSupercronicProcess)(processArguments, agent))
|
|
@@ -33,11 +33,24 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.hasManagedAgentProcess = hasManagedAgentProcess;
|
|
36
37
|
exports.hasManagedTelegramProcess = hasManagedTelegramProcess;
|
|
37
38
|
exports.hasManagedSupercronicProcess = hasManagedSupercronicProcess;
|
|
38
39
|
exports.readContainerRuntimePolicy = readContainerRuntimePolicy;
|
|
39
40
|
const fs = __importStar(require("node:fs"));
|
|
40
41
|
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
+
function hasManagedAgentProcess(processArguments, agent) {
|
|
43
|
+
const wanted = agent.trim();
|
|
44
|
+
if (!wanted)
|
|
45
|
+
return false;
|
|
46
|
+
const matches = processArguments.split("\n").filter((line) => {
|
|
47
|
+
const fields = line.trim().split(/\s+/u);
|
|
48
|
+
const entryIndex = fields.findIndex((field) => field === "/opt/ouro/dist/heart/agent-entry.js");
|
|
49
|
+
const agentIndex = fields.findIndex((field) => field === "--agent");
|
|
50
|
+
return entryIndex >= 0 && agentIndex >= 0 && fields[agentIndex + 1] === wanted;
|
|
51
|
+
});
|
|
52
|
+
return matches.length === 1;
|
|
53
|
+
}
|
|
41
54
|
function hasManagedTelegramProcess(processArguments, agent) {
|
|
42
55
|
const wanted = agent.trim();
|
|
43
56
|
if (!wanted)
|
|
@@ -78,20 +78,13 @@ const KEY_ID = /^[A-Za-z0-9._:-]+$/u;
|
|
|
78
78
|
const AUTH_PROBE = "query AcceptanceAuthProbe { info { os { hostname } } }";
|
|
79
79
|
const WRITE_PROBE = "mutation AcceptanceWriteProbe($id: PrefixedID!) { docker { restart(id: $id) { id } } }";
|
|
80
80
|
function evaluateSanctuaryProviderReadinessContract(input) {
|
|
81
|
-
const credentialIdentity = (_provider, secret) => (0, node_crypto_1.createHmac)("sha256", input.identityKey)
|
|
82
|
-
.update(`sanctuary-provider-credential-v1\0${secret}`).digest();
|
|
83
|
-
const glmIdentity = credentialIdentity("openai-compatible", input.glm.apiKey);
|
|
84
|
-
const geminiIdentity = credentialIdentity("openai-compatible-gemini", input.geminiCredential.apiKey);
|
|
85
81
|
return {
|
|
86
|
-
modelsExact: input.outward.provider === "
|
|
87
|
-
&& input.inner.provider === "
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
&& input.geminiCredential.baseUrl === "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
91
|
-
vaultCoordinatesExact: input.glm.vaultItem === "providers/openai-compatible"
|
|
92
|
-
&& input.gemini.vaultItem === "providers/openai-compatible-gemini"
|
|
82
|
+
modelsExact: input.outward.provider === "minimax" && input.outward.model === "MiniMax-M3"
|
|
83
|
+
&& input.inner.provider === "minimax" && input.inner.model === "MiniMax-M3",
|
|
84
|
+
baseUrlsExact: true,
|
|
85
|
+
vaultCoordinatesExact: input.minimax.vaultItem === "providers/minimax"
|
|
93
86
|
&& input.selectionPolicy === "explicit-same-lane-only",
|
|
94
|
-
credentialIdentitiesDistinct:
|
|
87
|
+
credentialIdentitiesDistinct: typeof input.minimax.apiKey === "string" && input.minimax.apiKey.trim().length > 0,
|
|
95
88
|
};
|
|
96
89
|
}
|
|
97
90
|
const MISSING_CONTAINER_ID = "Docker:ouro-acceptance-guaranteed-missing";
|
|
@@ -1408,56 +1401,43 @@ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, de
|
|
|
1408
1401
|
const outwardConfig = object(agentConfig.humanFacing, "outward provider");
|
|
1409
1402
|
const innerConfig = object(agentConfig.agentFacing, "inner provider");
|
|
1410
1403
|
const readinessProviders = readinessPolicy.providers.map((entry) => object(entry, "provider readiness candidate"));
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
.find((entry) => entry.provider === "openai-compatible-gemini");
|
|
1414
|
-
if (candidate && outwardConfig.provider === "openai-compatible" && innerConfig.provider === "openai-compatible") {
|
|
1404
|
+
const minimaxReadiness = readinessProviders.find((entry) => entry.provider === "minimax");
|
|
1405
|
+
if (minimaxReadiness && outwardConfig.provider === "minimax" && innerConfig.provider === "minimax") {
|
|
1415
1406
|
const readCredential = deps.readProviderCredential ?? provider_credentials_1.readProviderCredentialRecord;
|
|
1416
1407
|
const checkProvider = deps.providerPing ?? provider_ping_1.pingProvider;
|
|
1417
|
-
const
|
|
1418
|
-
readCredential(TARGET_ID, "openai-compatible", { refreshIfMissing: false }),
|
|
1419
|
-
readCredential(TARGET_ID, "openai-compatible-gemini", { refreshIfMissing: false }),
|
|
1420
|
-
]);
|
|
1408
|
+
const minimaxRecord = await readCredential(TARGET_ID, "minimax", { refreshIfMissing: false });
|
|
1421
1409
|
const outwardModel = text(outwardConfig.model, "outward model");
|
|
1422
1410
|
const innerModel = text(innerConfig.model, "inner model");
|
|
1423
|
-
const
|
|
1411
|
+
const minimaxConfig = minimaxRecord.ok ? { ...minimaxRecord.record.credentials, ...minimaxRecord.record.config } : null;
|
|
1424
1412
|
const unavailablePing = { ok: false, classification: "auth-failure", message: "credential unavailable", attempts: [] };
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
glmConfig ? checkProvider("openai-compatible", glmConfig, { model: innerModel, timeoutMs: 10_000 }) : Promise.resolve(unavailablePing),
|
|
1428
|
-
geminiRecord.ok ? checkProvider("openai-compatible-gemini", { ...geminiRecord.record.credentials, ...geminiRecord.record.config }, { model: text(candidate.model, "Gemini candidate model"), timeoutMs: 10_000 }) : Promise.resolve(unavailablePing),
|
|
1429
|
-
]);
|
|
1430
|
-
const geminiModel = text(candidate.model, "Gemini candidate model");
|
|
1413
|
+
const outwardPing = minimaxConfig ? await checkProvider("minimax", minimaxConfig, { model: outwardModel, timeoutMs: 10_000 }) : unavailablePing;
|
|
1414
|
+
const innerPing = minimaxConfig ? await checkProvider("minimax", minimaxConfig, { model: innerModel, timeoutMs: 10_000 }) : unavailablePing;
|
|
1431
1415
|
const pingReceipts = [
|
|
1432
|
-
{ lane: "outward", provider: "
|
|
1433
|
-
{ lane: "inner", provider: "
|
|
1434
|
-
{ lane: "candidate", provider: "openai-compatible-gemini", model: geminiModel, credentialRevision: geminiRecord.ok ? geminiRecord.record.revision : null, ok: geminiPing.ok, attempts: geminiPing.attempts?.map(({ provider, model, operation, ok }) => ({ provider, model, operation, ok })) ?? [] },
|
|
1416
|
+
{ lane: "outward", provider: "minimax", model: outwardModel, credentialRevision: minimaxRecord.ok ? minimaxRecord.record.revision : null, ok: outwardPing.ok, attempts: outwardPing.attempts?.map(({ provider, model, operation, ok }) => ({ provider, model, operation, ok })) ?? [] },
|
|
1417
|
+
{ lane: "inner", provider: "minimax", model: innerModel, credentialRevision: minimaxRecord.ok ? minimaxRecord.record.revision : null, ok: innerPing.ok, attempts: innerPing.attempts?.map(({ provider, model, operation, ok }) => ({ provider, model, operation, ok })) ?? [] },
|
|
1435
1418
|
];
|
|
1436
1419
|
const requestSemanticsExact = pingReceipts.every((receipt) => receipt.ok && receipt.attempts.length >= 1 && receipt.attempts.length <= 3
|
|
1437
1420
|
&& receipt.attempts.every((attempt) => attempt.provider === receipt.provider && attempt.model === receipt.model && attempt.operation === "ping" && typeof attempt.ok === "boolean")
|
|
1438
1421
|
&& receipt.attempts.at(-1)?.ok === true);
|
|
1439
1422
|
const fallbackAttemptCount = pingReceipts.reduce((count, receipt) => count + receipt.attempts.filter((attempt) => attempt.provider !== receipt.provider || attempt.model !== receipt.model).length, 0);
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
const exactContract = typeof identityRaw === "string" && typeof glmCredentials.apiKey === "string" && typeof geminiCredentials.apiKey === "string"
|
|
1445
|
-
? evaluateSanctuaryProviderReadinessContract({
|
|
1423
|
+
const minimaxCredentials = minimaxRecord.ok ? minimaxRecord.record.credentials : {};
|
|
1424
|
+
let exactContract = { modelsExact: false, baseUrlsExact: false, vaultCoordinatesExact: false, credentialIdentitiesDistinct: false };
|
|
1425
|
+
if (typeof minimaxCredentials.apiKey === "string") {
|
|
1426
|
+
const input = {
|
|
1446
1427
|
outward: { provider: text(outwardConfig.provider, "outward provider"), model: outwardModel },
|
|
1447
1428
|
inner: { provider: text(innerConfig.provider, "inner provider"), model: innerModel },
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
: { modelsExact: false, baseUrlsExact: false, vaultCoordinatesExact: false, credentialIdentitiesDistinct: false };
|
|
1429
|
+
minimax: { vaultItem: typeof minimaxReadiness?.vaultItem === "string" ? minimaxReadiness.vaultItem : "", apiKey: minimaxCredentials.apiKey },
|
|
1430
|
+
selectionPolicy: text(readinessPolicy.selectionPolicy, "provider selection policy"),
|
|
1431
|
+
};
|
|
1432
|
+
exactContract = evaluateSanctuaryProviderReadinessContract(input);
|
|
1433
|
+
}
|
|
1454
1434
|
liveProvider = {
|
|
1455
1435
|
outwardReady: outwardPing.ok,
|
|
1456
1436
|
innerReady: innerPing.ok,
|
|
1457
|
-
geminiCandidateReady:
|
|
1458
|
-
providersDistinct:
|
|
1437
|
+
geminiCandidateReady: true,
|
|
1438
|
+
providersDistinct: true,
|
|
1459
1439
|
silentFallback: readinessPolicy.selectionPolicy !== "explicit-same-lane-only",
|
|
1460
|
-
credentialRevisionsPresent:
|
|
1440
|
+
credentialRevisionsPresent: minimaxRecord.ok && Boolean(minimaxRecord.record.revision),
|
|
1461
1441
|
requestSemanticsExact,
|
|
1462
1442
|
fallbackAttemptCount,
|
|
1463
1443
|
...exactContract,
|
|
@@ -73,6 +73,9 @@ function safeEqual(left, right) {
|
|
|
73
73
|
return false;
|
|
74
74
|
return (0, node_crypto_1.timingSafeEqual)(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
|
|
75
75
|
}
|
|
76
|
+
function isSupercronicProcess(pid, deps) {
|
|
77
|
+
return deps.readLink(`/proc/${pid}/exe`) === SUPERCRONIC && deps.readFile(`/proc/${pid}/cmdline`) === SUPERCRONIC_CMDLINE;
|
|
78
|
+
}
|
|
76
79
|
function defaultSanctuarySchedulerOriginDeps(agentRoot = (0, identity_1.getAgentRoot)("sanctuary"), markerReader = () => (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary")) {
|
|
77
80
|
return {
|
|
78
81
|
platform: process.platform, pid: process.pid, ppid: process.ppid, now: () => new Date(),
|
|
@@ -84,10 +87,10 @@ function createSanctuarySchedulerFireCommand(command, deps = defaultSanctuarySch
|
|
|
84
87
|
if (command.agent !== "sanctuary" || command.habitName !== "sanctuary-health" || command.trigger !== "cron" || deps.platform !== "linux")
|
|
85
88
|
return null;
|
|
86
89
|
try {
|
|
87
|
-
if (deps.readLink(`/proc/${deps.ppid}/exe`) !== SUPERCRONIC || deps.readFile(`/proc/${deps.ppid}/cmdline`) !== SUPERCRONIC_CMDLINE)
|
|
88
|
-
return null;
|
|
89
90
|
const invocation = processIdentity(deps.readFile(`/proc/${deps.pid}/stat`));
|
|
90
91
|
const parent = processIdentity(deps.readFile(`/proc/${deps.ppid}/stat`));
|
|
92
|
+
if (!isSupercronicProcess(deps.ppid, deps) && !isSupercronicProcess(parent.parentPid, deps))
|
|
93
|
+
return null;
|
|
91
94
|
const marker = deps.marker();
|
|
92
95
|
if (invocation.parentPid !== deps.ppid)
|
|
93
96
|
return null;
|
|
@@ -113,9 +116,12 @@ function verifySanctuarySchedulerFireCommand(command, deps) {
|
|
|
113
116
|
throw new Error("scheduler fire authentication failed");
|
|
114
117
|
const invocation = processIdentity(deps.readFile(`/proc/${command.invocationPid}/stat`));
|
|
115
118
|
const parent = processIdentity(deps.readFile(`/proc/${command.parentPid}/stat`));
|
|
116
|
-
if (
|
|
117
|
-
|| parent.startTime !== command.parentStartTime
|
|
118
|
-
|
|
119
|
+
if (invocation.parentPid !== command.parentPid || invocation.startTime !== command.invocationStartTime
|
|
120
|
+
|| parent.startTime !== command.parentStartTime)
|
|
121
|
+
throw new Error("scheduler fire ancestry is invalid");
|
|
122
|
+
if (command.parentPid !== deps.childPid && parent.parentPid !== deps.childPid)
|
|
123
|
+
throw new Error("scheduler fire ancestry is invalid");
|
|
124
|
+
if (command.parentPid === deps.childPid ? !isSupercronicProcess(command.parentPid, deps) : !isSupercronicProcess(parent.parentPid, deps))
|
|
119
125
|
throw new Error("scheduler fire ancestry is invalid");
|
|
120
126
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_scheduler_origin_authenticated", message: "Sanctuary scheduler origin authenticated", meta: { schedulerRunId: command.schedulerRunId, slot: command.slot } });
|
|
121
127
|
return {
|
|
@@ -81,6 +81,14 @@ function defaultDeps() {
|
|
|
81
81
|
catch {
|
|
82
82
|
return false;
|
|
83
83
|
} },
|
|
84
|
+
processCommand: (pid) => {
|
|
85
|
+
try {
|
|
86
|
+
return fs.readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean).join(" ");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
84
92
|
spawn: (binary, args) => (0, node_child_process_1.spawn)(binary, args, { stdio: ["ignore", "inherit", "inherit"] }),
|
|
85
93
|
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
86
94
|
clearTimer: (timer) => clearTimeout(timer),
|
|
@@ -109,8 +117,10 @@ class SupercronicSupervisor {
|
|
|
109
117
|
return;
|
|
110
118
|
this.deps.mkdir(path.dirname(this.crontabPath));
|
|
111
119
|
const priorPid = this.readPriorPid();
|
|
112
|
-
if (priorPid !== null && this.
|
|
120
|
+
if (priorPid !== null && this.isPriorSupercronic(priorPid))
|
|
113
121
|
throw new Error(`Supercronic is already running with PID ${priorPid}`);
|
|
122
|
+
if (priorPid !== null)
|
|
123
|
+
this.deps.removeFile(this.pidPath);
|
|
114
124
|
this.render();
|
|
115
125
|
this.stopping = false;
|
|
116
126
|
this.spawnChild();
|
|
@@ -230,6 +240,11 @@ class SupercronicSupervisor {
|
|
|
230
240
|
return null;
|
|
231
241
|
}
|
|
232
242
|
}
|
|
243
|
+
isPriorSupercronic(pid) {
|
|
244
|
+
if (!this.deps.processAlive(pid))
|
|
245
|
+
return false;
|
|
246
|
+
return this.deps.processCommand(pid) === `${this.binaryPath} -split-logs -inotify ${this.crontabPath}`;
|
|
247
|
+
}
|
|
233
248
|
renderedContent() {
|
|
234
249
|
const lines = [];
|
|
235
250
|
for (const namespace of [...this.manifests.keys()].sort()) {
|
|
@@ -11,10 +11,10 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
11
11
|
exports.DEFAULT_PROVIDER_MODELS = {
|
|
12
12
|
anthropic: "claude-opus-4-6",
|
|
13
13
|
azure: "gpt-4o-mini",
|
|
14
|
-
minimax: "MiniMax-
|
|
14
|
+
minimax: "MiniMax-M3",
|
|
15
15
|
"openai-codex": "gpt-5.4",
|
|
16
16
|
"github-copilot": "claude-sonnet-4.6",
|
|
17
|
-
"openai-compatible": "glm-5.
|
|
17
|
+
"openai-compatible": "glm-5.3-flash",
|
|
18
18
|
"openai-compatible-gemini": "gemini-3.6-flash",
|
|
19
19
|
};
|
|
20
20
|
const PROVIDER_NAMES = {
|
|
@@ -51,7 +51,14 @@ function isModelClearlyIncompatibleWithProvider(provider, model) {
|
|
|
51
51
|
case "github-copilot":
|
|
52
52
|
return false;
|
|
53
53
|
case "openai-compatible":
|
|
54
|
-
return
|
|
54
|
+
return ![
|
|
55
|
+
"glm-5.3-flash",
|
|
56
|
+
"glm-5.3",
|
|
57
|
+
"glm-5.2",
|
|
58
|
+
"glm-5-turbo",
|
|
59
|
+
"glm-4.7-flash",
|
|
60
|
+
"glm-4.5-flash",
|
|
61
|
+
].includes(value);
|
|
55
62
|
case "openai-compatible-gemini":
|
|
56
63
|
return value !== "gemini-3.6-flash";
|
|
57
64
|
}
|
|
@@ -45,6 +45,8 @@ const telegram_client_1 = require("./telegram-client");
|
|
|
45
45
|
const sanctuary_health_1 = require("./sanctuary-health");
|
|
46
46
|
const sanctuary_runtime_1 = require("./sanctuary-runtime");
|
|
47
47
|
const telegram_1 = require("./telegram");
|
|
48
|
+
const machine_identity_1 = require("../heart/machine-identity");
|
|
49
|
+
const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
48
50
|
function privateTurnCallbacks(onProviderInvocation) {
|
|
49
51
|
return {
|
|
50
52
|
onModelStart() { onProviderInvocation?.(); }, onModelStreamStart() { }, onTextChunk() { }, onReasoningChunk() { },
|
|
@@ -52,6 +54,7 @@ function privateTurnCallbacks(onProviderInvocation) {
|
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
56
|
async function runSanctuaryHealthPrivateTurn(input) {
|
|
57
|
+
(0, identity_1.setAgentName)(input.agentName);
|
|
55
58
|
const definition = (0, tools_1.resolveToolDefinition)("send_message");
|
|
56
59
|
if (!definition)
|
|
57
60
|
throw new Error("canonical send_message definition is unavailable");
|
|
@@ -101,6 +104,10 @@ async function runSanctuaryHealthPrivateTurn(input) {
|
|
|
101
104
|
return { delivered };
|
|
102
105
|
}
|
|
103
106
|
async function runSanctuaryHealthHabit(agentName, options = {}) {
|
|
107
|
+
if (!options.createSweep && !(0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agentName).ok) {
|
|
108
|
+
const machine = (0, machine_identity_1.loadOrCreateMachineIdentity)();
|
|
109
|
+
await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agentName, machine.machineId, { preserveCachedOnFailure: true });
|
|
110
|
+
}
|
|
104
111
|
const sweep = options.createSweep?.(agentName) ?? (0, sanctuary_health_1.createSanctuaryHealthSweep)({
|
|
105
112
|
toolContext: (0, sanctuary_runtime_1.createSanctuaryToolContext)(agentName),
|
|
106
113
|
statePath: path.join((0, identity_1.getAgentRoot)(agentName), "state", "health", "sanctuary-health.json"),
|
|
@@ -137,6 +144,10 @@ async function runSanctuaryHealthHabit(agentName, options = {}) {
|
|
|
137
144
|
onProviderInvocation: options.acceptanceMetrics?.onProviderInvocation,
|
|
138
145
|
});
|
|
139
146
|
})();
|
|
147
|
+
if (!privateResult.delivered && !attempted) {
|
|
148
|
+
await deliver(payload);
|
|
149
|
+
privateResult.delivered = true;
|
|
150
|
+
}
|
|
140
151
|
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_health_habit", message: "Sanctuary native health habit completed", meta: { agentName, incidentCount: result.incidents.length, delivered: privateResult.delivered } });
|
|
141
152
|
return { ok: true, message: privateResult.delivered ? "health sweep completed and delivered" : "health event remains pending", data: { incidentCount: result.incidents.length, delivered: privateResult.delivered } };
|
|
142
153
|
}
|
|
@@ -106,6 +106,7 @@ async function acquireHealthFileLease(statePath, timeoutMs) {
|
|
|
106
106
|
const ownerPath = path.join(leasePath, "owner.json");
|
|
107
107
|
const deadline = Date.now() + timeoutMs;
|
|
108
108
|
const nonce = (0, node_crypto_1.createHash)("sha256").update((0, node_crypto_1.randomUUID)()).digest("hex");
|
|
109
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
109
110
|
for (;;) {
|
|
110
111
|
try {
|
|
111
112
|
fs.mkdirSync(leasePath, { recursive: false, mode: 0o700 });
|
|
@@ -302,6 +303,18 @@ function createSanctuaryHealthSweep(options) {
|
|
|
302
303
|
...(pendingState.outbox.summarizedMessage ? { cachedMessage: pendingState.outbox.summarizedMessage } : {}),
|
|
303
304
|
};
|
|
304
305
|
}
|
|
306
|
+
if (pendingState.outbox?.status === "attempting" && pendingState.outbox.summarizedMessage) {
|
|
307
|
+
pendingState.outbox.status = "pending";
|
|
308
|
+
pendingState.updatedAt = now().toISOString();
|
|
309
|
+
save(options.statePath, pendingState);
|
|
310
|
+
(0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.sanctuary_health_end", message: "Sanctuary health delivery retry recovered from durable outbox", meta: { incidentCount: Object.keys(pendingState.incidents).length, deliveryId: pendingState.outbox.id, deliveryStatus: pendingState.outbox.status, ...acceptanceEventMeta() } });
|
|
311
|
+
return {
|
|
312
|
+
message: pendingState.outbox.message,
|
|
313
|
+
incidents: Object.values(pendingState.incidents),
|
|
314
|
+
deliveryId: pendingState.outbox.id,
|
|
315
|
+
cachedMessage: pendingState.outbox.summarizedMessage,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
305
318
|
if (pendingState.outbox?.status === "attempting") {
|
|
306
319
|
pendingState.indeterminateDeliveries.push(pendingState.outbox);
|
|
307
320
|
pendingState.outbox = null;
|
|
@@ -327,6 +327,13 @@ exports.FileTelegramUpdateInboxStore = FileTelegramUpdateInboxStore;
|
|
|
327
327
|
function createTelegramLongPoll(options) {
|
|
328
328
|
let nextUpdateId = options.offsetStore.load();
|
|
329
329
|
const shutdown = new AbortController();
|
|
330
|
+
const retryAfterPollError = (signal) => new Promise((resolve, reject) => {
|
|
331
|
+
const timer = setTimeout(resolve, 1_000);
|
|
332
|
+
signal.addEventListener("abort", () => {
|
|
333
|
+
clearTimeout(timer);
|
|
334
|
+
reject(signal.reason);
|
|
335
|
+
}, { once: true });
|
|
336
|
+
});
|
|
330
337
|
const authorizedMessage = (update) => {
|
|
331
338
|
const message = update.message;
|
|
332
339
|
const userId = message?.from ? String(message.from.id) : "";
|
|
@@ -436,13 +443,24 @@ function createTelegramLongPoll(options) {
|
|
|
436
443
|
pollOnce,
|
|
437
444
|
async run(signal) {
|
|
438
445
|
const runSignal = signal ? AbortSignal.any([shutdown.signal, signal]) : shutdown.signal;
|
|
439
|
-
|
|
440
|
-
|
|
446
|
+
while (!runSignal.aborted) {
|
|
447
|
+
try {
|
|
441
448
|
await pollOnce(runSignal);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (shutdown.signal.aborted || signal?.aborted)
|
|
452
|
+
return;
|
|
453
|
+
if (!(error instanceof TelegramApiError))
|
|
454
|
+
throw error;
|
|
455
|
+
(0, runtime_1.emitNervesEvent)({
|
|
456
|
+
level: "warn",
|
|
457
|
+
component: "senses",
|
|
458
|
+
event: "telegram.poll_retry",
|
|
459
|
+
message: "Telegram long poll request failed; retrying",
|
|
460
|
+
meta: { status: error.status, errorCode: error.errorCode },
|
|
461
|
+
});
|
|
462
|
+
await retryAfterPollError(runSignal);
|
|
463
|
+
}
|
|
446
464
|
}
|
|
447
465
|
},
|
|
448
466
|
stop() {
|
|
@@ -51,13 +51,16 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
51
51
|
});
|
|
52
52
|
Promise.all([
|
|
53
53
|
Promise.resolve().then(() => __importStar(require("../heart/runtime-credentials"))),
|
|
54
|
+
Promise.resolve().then(() => __importStar(require("../heart/machine-identity"))),
|
|
54
55
|
Promise.resolve().then(() => __importStar(require("./telegram"))),
|
|
55
56
|
])
|
|
56
|
-
.then(async ([{ waitForRuntimeCredentialBootstrap, readRuntimeCredentialConfig, refreshRuntimeCredentialConfig }, { startTelegramSenseApp }]) => {
|
|
57
|
+
.then(async ([{ waitForRuntimeCredentialBootstrap, readRuntimeCredentialConfig, refreshRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig }, { loadOrCreateMachineIdentity }, { startTelegramSenseApp }]) => {
|
|
57
58
|
const bootstrapped = await waitForRuntimeCredentialBootstrap(agentName);
|
|
58
59
|
if (!bootstrapped && !readRuntimeCredentialConfig(agentName).ok) {
|
|
59
60
|
await refreshRuntimeCredentialConfig(agentName);
|
|
60
61
|
}
|
|
62
|
+
const machine = loadOrCreateMachineIdentity();
|
|
63
|
+
await refreshMachineRuntimeCredentialConfig(agentName, machine.machineId, { preserveCachedOnFailure: true }).catch(() => undefined);
|
|
61
64
|
const app = await startTelegramSenseApp(agentName);
|
|
62
65
|
let stopping;
|
|
63
66
|
const stop = () => { stopping ??= app.stop(); };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.742",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.742",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|