@ouro.bot/cli 0.1.0-alpha.733 → 0.1.0-alpha.734
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 +25 -0
- package/deploy/unraid/Dockerfile +27 -0
- package/deploy/unraid/README.txt +1368 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +705 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1777 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +502 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
- package/deploy/unraid/sanctuary.xml +25 -0
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +168 -9
- package/dist/heart/auth/auth-flow.js +17 -1
- package/dist/heart/autonomy-budget.js +44 -1
- package/dist/heart/awaiting/await-scheduler.js +6 -2
- package/dist/heart/config.js +8 -0
- package/dist/heart/core.js +117 -132
- package/dist/heart/daemon/cli-exec.js +73 -1
- package/dist/heart/daemon/cli-help.js +5 -0
- package/dist/heart/daemon/cli-parse.js +6 -4
- package/dist/heart/daemon/container-credential-bootstrap.js +185 -0
- package/dist/heart/daemon/container-healthcheck.js +94 -0
- package/dist/heart/daemon/container-runtime.js +84 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
- package/dist/heart/daemon/container-spec-auditor.js +156 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
- package/dist/heart/daemon/daemon-entry.js +116 -28
- package/dist/heart/daemon/daemon-health.js +10 -0
- package/dist/heart/daemon/daemon.js +92 -5
- package/dist/heart/daemon/runtime-logging.js +6 -0
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
- package/dist/heart/daemon/sense-manager.js +25 -6
- package/dist/heart/daemon/supercronic-supervisor.js +267 -0
- package/dist/heart/habits/habit-scheduler.js +7 -3
- package/dist/heart/identity.js +13 -3
- package/dist/heart/provider-credentials.js +9 -1
- package/dist/heart/provider-models.js +8 -0
- package/dist/heart/provider-ping.js +5 -2
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/runtime-credentials.js +217 -8
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/tool-approval.js +1 -0
- package/dist/heart/turn-context.js +8 -0
- package/dist/mind/prompt.js +9 -0
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/index.js +75 -9
- package/dist/nerves/runtime.js +12 -0
- package/dist/repertoire/tool-arguments.js +39 -0
- package/dist/repertoire/tools-unraid.js +353 -0
- package/dist/repertoire/tools.js +19 -1
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +130 -0
- package/dist/senses/private-runtime-worker.js +3 -2
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
- package/dist/senses/sanctuary-health-runner.js +146 -0
- package/dist/senses/sanctuary-health.js +447 -0
- package/dist/senses/sanctuary-interactive-control.js +306 -0
- package/dist/senses/sanctuary-runtime.js +206 -0
- package/dist/senses/shared-turn.js +11 -3
- package/dist/senses/telegram-approval-runtime.js +526 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-client.js +1471 -0
- package/dist/senses/telegram-entry.js +80 -0
- package/dist/senses/telegram.js +1232 -0
- package/npm-shrinkwrap.json +6558 -0
- package/package.json +4 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"profiles": {
|
|
4
|
+
"sanctuary-telegram": [
|
|
5
|
+
"unraid_list_containers",
|
|
6
|
+
"unraid_get_container_logs",
|
|
7
|
+
"unraid_get_storage",
|
|
8
|
+
"unraid_get_disks",
|
|
9
|
+
"unraid_get_notifications",
|
|
10
|
+
"unraid_get_system",
|
|
11
|
+
"unraid_restart_container",
|
|
12
|
+
"ponder",
|
|
13
|
+
"settle",
|
|
14
|
+
"speak"
|
|
15
|
+
],
|
|
16
|
+
"sanctuary-health-private": [
|
|
17
|
+
"send_message",
|
|
18
|
+
"rest"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<Container version="2">
|
|
3
|
+
<Name>ouro-butler</Name>
|
|
4
|
+
<Repository>sha256:REPLACE_WITH_EXACT_LOCAL_IMAGE_ID</Repository>
|
|
5
|
+
<Registry/>
|
|
6
|
+
<Network>host</Network>
|
|
7
|
+
<Shell>sh</Shell>
|
|
8
|
+
<Privileged>false</Privileged>
|
|
9
|
+
<Support/>
|
|
10
|
+
<Project/>
|
|
11
|
+
<Overview>Mendelow Cloud Butler</Overview>
|
|
12
|
+
<Category>Tools:</Category>
|
|
13
|
+
<WebUI/>
|
|
14
|
+
<TemplateURL/>
|
|
15
|
+
<Icon/>
|
|
16
|
+
<ExtraParams>--restart=unless-stopped --user=10001:10001</ExtraParams>
|
|
17
|
+
<PostArgs/>
|
|
18
|
+
<CPUset/>
|
|
19
|
+
<DateInstalled>0</DateInstalled>
|
|
20
|
+
<DonateText/>
|
|
21
|
+
<DonateLink/>
|
|
22
|
+
<Requires/>
|
|
23
|
+
<Config Name="Runtime state" Target="/home/ouro/.ouro-cli" Default="/mnt/user/appdata/ouro-butler/runtime/.ouro-cli" Mode="rw" Description="Machine-scoped runtime" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/runtime/.ouro-cli</Config>
|
|
24
|
+
<Config Name="Agent bundle" Target="/home/ouro/AgentBundles/sanctuary.ouro" Default="/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro" Mode="rw" Description="Sanctuary bundle" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro</Config>
|
|
25
|
+
</Container>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileApprovalTokenStore = exports.FileApprovalCheckpointStore = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_crypto_1 = require("node:crypto");
|
|
40
|
+
const tool_approval_1 = require("./tool-approval");
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
function readObject(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
const value = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
46
|
+
throw new Error("state is not an object");
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
51
|
+
return {};
|
|
52
|
+
throw new Error(`approval state is corrupt: ${path.basename(filePath)}`, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function atomicWrite(filePath, value) {
|
|
56
|
+
const directory = path.dirname(filePath);
|
|
57
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
58
|
+
const temporary = `${filePath}.tmp-${process.pid}-${(0, node_crypto_1.randomUUID)()}`;
|
|
59
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
60
|
+
const temporaryHandle = fs.openSync(temporary, "r");
|
|
61
|
+
try {
|
|
62
|
+
fs.fsyncSync(temporaryHandle);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
fs.closeSync(temporaryHandle);
|
|
66
|
+
}
|
|
67
|
+
fs.renameSync(temporary, filePath);
|
|
68
|
+
fs.chmodSync(filePath, 0o600);
|
|
69
|
+
const directoryHandle = fs.openSync(directory, "r");
|
|
70
|
+
try {
|
|
71
|
+
fs.fsyncSync(directoryHandle);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
fs.closeSync(directoryHandle);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
class FileApprovalCheckpointStore {
|
|
78
|
+
filePath;
|
|
79
|
+
constructor(filePath) {
|
|
80
|
+
this.filePath = filePath;
|
|
81
|
+
}
|
|
82
|
+
write(draft) {
|
|
83
|
+
const checkpoint = {
|
|
84
|
+
...structuredClone(draft),
|
|
85
|
+
checkpointDigest: (0, tool_approval_1.digestApprovalSuspensionCheckpointPayload)(draft),
|
|
86
|
+
suspendedSessionRevision: draft.baseSessionRevision,
|
|
87
|
+
};
|
|
88
|
+
const records = readObject(this.filePath);
|
|
89
|
+
records[checkpoint.approvalId] = checkpoint;
|
|
90
|
+
atomicWrite(this.filePath, records);
|
|
91
|
+
(0, runtime_1.emitNervesEvent)({ component: "engine", event: "engine.approval_checkpoint_written", message: "approval checkpoint written", meta: { approvalId: checkpoint.approvalId } });
|
|
92
|
+
return { checkpointDigest: checkpoint.checkpointDigest, suspendedSessionRevision: checkpoint.suspendedSessionRevision };
|
|
93
|
+
}
|
|
94
|
+
read(approvalId) { return structuredClone(readObject(this.filePath)[approvalId] ?? null); }
|
|
95
|
+
list() { return Object.values(readObject(this.filePath)).map((record) => structuredClone(record)); }
|
|
96
|
+
remove(approvalId) {
|
|
97
|
+
const records = readObject(this.filePath);
|
|
98
|
+
delete records[approvalId];
|
|
99
|
+
atomicWrite(this.filePath, records);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.FileApprovalCheckpointStore = FileApprovalCheckpointStore;
|
|
103
|
+
class FileApprovalTokenStore {
|
|
104
|
+
filePath;
|
|
105
|
+
constructor(filePath) {
|
|
106
|
+
this.filePath = filePath;
|
|
107
|
+
}
|
|
108
|
+
put(approvalId, token) { const records = readObject(this.filePath); records[approvalId] = token; atomicWrite(this.filePath, records); }
|
|
109
|
+
has(approvalId) { return approvalId in readObject(this.filePath); }
|
|
110
|
+
get(approvalId) { return readObject(this.filePath)[approvalId] ?? null; }
|
|
111
|
+
remove(approvalId) { const records = readObject(this.filePath); delete records[approvalId]; atomicWrite(this.filePath, records); }
|
|
112
|
+
}
|
|
113
|
+
exports.FileApprovalTokenStore = FileApprovalTokenStore;
|
|
@@ -40,6 +40,7 @@ exports.ApprovalStoreError = void 0;
|
|
|
40
40
|
exports.canonicalApprovalArguments = canonicalApprovalArguments;
|
|
41
41
|
exports.parseApprovalRecord = parseApprovalRecord;
|
|
42
42
|
exports.openApprovalStore = openApprovalStore;
|
|
43
|
+
exports.readApprovalsByScenarioHandleDigest = readApprovalsByScenarioHandleDigest;
|
|
43
44
|
const node_crypto_1 = require("node:crypto");
|
|
44
45
|
const fs = __importStar(require("node:fs"));
|
|
45
46
|
const path = __importStar(require("node:path"));
|
|
@@ -162,8 +163,10 @@ function validStateShape(record) {
|
|
|
162
163
|
if (record.state === "abandoned_before_attempt") {
|
|
163
164
|
const validPreparingRecovery = !hasOwner && record.epoch === 0
|
|
164
165
|
&& record.suspendedSessionRevision === null && record.transportMessageId === null;
|
|
166
|
+
const validPromptBindingRecovery = !hasOwner && record.epoch === 0
|
|
167
|
+
&& hasSuspension && record.transportMessageId === null;
|
|
165
168
|
const validClaimRecovery = hasOwner && record.epoch > 0 && hasSuspension && hasPromptBinding;
|
|
166
|
-
return (validPreparingRecovery || validClaimRecovery)
|
|
169
|
+
return (validPreparingRecovery || validPromptBindingRecovery || validClaimRecovery)
|
|
167
170
|
&& !hasAttempt && record.result === null && isNonEmpty(record.reason);
|
|
168
171
|
}
|
|
169
172
|
if (record.state === "drifted") {
|
|
@@ -483,6 +486,15 @@ function openApprovalStore(options) {
|
|
|
483
486
|
attempted_at TEXT,
|
|
484
487
|
continued_at TEXT
|
|
485
488
|
)
|
|
489
|
+
;
|
|
490
|
+
CREATE TABLE IF NOT EXISTS approval_acceptance_scenarios (
|
|
491
|
+
approval_id TEXT PRIMARY KEY,
|
|
492
|
+
scenario_handle_digest TEXT NOT NULL,
|
|
493
|
+
FOREIGN KEY (approval_id) REFERENCES approval_actions(approval_id)
|
|
494
|
+
)
|
|
495
|
+
;
|
|
496
|
+
CREATE INDEX IF NOT EXISTS approval_acceptance_scenario_digest
|
|
497
|
+
ON approval_acceptance_scenarios (scenario_handle_digest)
|
|
486
498
|
`);
|
|
487
499
|
const now = options.now ?? (() => new Date());
|
|
488
500
|
const randomUUID = options.randomUUID ?? node_crypto_1.randomUUID;
|
|
@@ -501,9 +513,22 @@ function openApprovalStore(options) {
|
|
|
501
513
|
fail("corrupt_record");
|
|
502
514
|
}
|
|
503
515
|
};
|
|
504
|
-
const insert = (record) => {
|
|
505
|
-
|
|
506
|
-
.
|
|
516
|
+
const insert = (record, scenarioHandleDigest) => {
|
|
517
|
+
const transaction = database.transaction(() => {
|
|
518
|
+
database.prepare("INSERT INTO approval_actions (approval_id, state, epoch, record_json) VALUES (?, ?, ?, ?)")
|
|
519
|
+
.run(record.approvalId, record.state, record.epoch, JSON.stringify(record));
|
|
520
|
+
if (scenarioHandleDigest)
|
|
521
|
+
database.prepare("INSERT INTO approval_acceptance_scenarios (approval_id, scenario_handle_digest) VALUES (?, ?)")
|
|
522
|
+
.run(record.approvalId, scenarioHandleDigest);
|
|
523
|
+
});
|
|
524
|
+
transaction();
|
|
525
|
+
if (scenarioHandleDigest)
|
|
526
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "approval.acceptance_transition", message: "acceptance-bound approval transitioned", meta: { approvalId: record.approvalId, scenarioHandleDigest, toolName: record.toolName, argumentDigest: record.argumentDigest, state: record.state } });
|
|
527
|
+
};
|
|
528
|
+
const acceptanceDigest = (approvalId) => {
|
|
529
|
+
const row = database.prepare("SELECT scenario_handle_digest FROM approval_acceptance_scenarios WHERE approval_id = ?")
|
|
530
|
+
.get(approvalId);
|
|
531
|
+
return row?.scenario_handle_digest ?? null;
|
|
507
532
|
};
|
|
508
533
|
const cas = (previous, next) => {
|
|
509
534
|
const changed = database.prepare(`
|
|
@@ -512,6 +537,9 @@ function openApprovalStore(options) {
|
|
|
512
537
|
`).run(next.state, next.epoch, JSON.stringify(next), previous.approvalId, previous.state, previous.epoch, JSON.stringify(previous));
|
|
513
538
|
if (changed.changes !== 1)
|
|
514
539
|
fail("transition_conflict");
|
|
540
|
+
const scenarioHandleDigest = acceptanceDigest(next.approvalId);
|
|
541
|
+
if (scenarioHandleDigest)
|
|
542
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "approval.acceptance_transition", message: "acceptance-bound approval transitioned", meta: { approvalId: next.approvalId, scenarioHandleDigest, toolName: next.toolName, argumentDigest: next.argumentDigest, state: next.state } });
|
|
515
543
|
return structuredClone(next);
|
|
516
544
|
};
|
|
517
545
|
const requireRecord = (approvalId) => rawRead(approvalId) ?? fail("approval_not_found");
|
|
@@ -539,6 +567,9 @@ function openApprovalStore(options) {
|
|
|
539
567
|
`).run(to, timestamp(), input.approvalId, input.ownerId, input.epoch, from);
|
|
540
568
|
if (changed.changes !== 1)
|
|
541
569
|
fail(`continuation_${to}_not_eligible`);
|
|
570
|
+
const scenarioHandleDigest = acceptanceDigest(input.approvalId);
|
|
571
|
+
if (scenarioHandleDigest)
|
|
572
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "approval.acceptance_continuation_transition", message: "acceptance-bound approval continuation transitioned", meta: { approvalId: approval.approvalId, scenarioHandleDigest, toolName: approval.toolName, argumentDigest: approval.argumentDigest, state: to } });
|
|
542
573
|
return combineContinuation(approval, readContinuationRow(input.approvalId));
|
|
543
574
|
};
|
|
544
575
|
return {
|
|
@@ -585,7 +616,9 @@ function openApprovalStore(options) {
|
|
|
585
616
|
reason: null,
|
|
586
617
|
frozenAssistantMessage: structuredClone(input.frozenAssistantMessage),
|
|
587
618
|
};
|
|
588
|
-
|
|
619
|
+
if (input.scenarioHandleDigest !== undefined)
|
|
620
|
+
assertHash(input.scenarioHandleDigest, "invalid_proposal");
|
|
621
|
+
insert(parseApprovalRecord(record), input.scenarioHandleDigest);
|
|
589
622
|
return { record: structuredClone(record), decisionToken };
|
|
590
623
|
});
|
|
591
624
|
},
|
|
@@ -602,21 +635,41 @@ function openApprovalStore(options) {
|
|
|
602
635
|
return observeBindPrompt(() => {
|
|
603
636
|
const previous = requireRecord(input.approvalId);
|
|
604
637
|
if (previous.state !== "awaiting_prompt_binding" || input.transport !== previous.transport
|
|
605
|
-
|| input.transportChatId !== previous.transportChatId || !isNonEmpty(input.transportMessageId)
|
|
638
|
+
|| input.transportChatId !== previous.transportChatId || !isNonEmpty(input.transportMessageId)
|
|
639
|
+
|| (input.expiresAt !== undefined && !isTimestamp(input.expiresAt)))
|
|
606
640
|
fail("invalid_prompt_binding");
|
|
607
|
-
return cas(previous, { ...previous, state: "proposed", transportMessageId: input.transportMessageId, updatedAt: timestamp() });
|
|
641
|
+
return cas(previous, { ...previous, state: "proposed", transportMessageId: input.transportMessageId, expiresAt: input.expiresAt ?? previous.expiresAt, updatedAt: timestamp() });
|
|
642
|
+
});
|
|
643
|
+
},
|
|
644
|
+
abandonPromptBinding(input) {
|
|
645
|
+
return observeAbandonBeforeAttempt(() => {
|
|
646
|
+
const previous = requireRecord(input.approvalId);
|
|
647
|
+
if (previous.state === "abandoned_before_attempt" && previous.ownerId === null
|
|
648
|
+
&& previous.epoch === 0 && previous.reason === input.reason)
|
|
649
|
+
return previous;
|
|
650
|
+
if (previous.state !== "awaiting_prompt_binding" || !isNonEmpty(input.reason))
|
|
651
|
+
fail("prompt_abandon_not_eligible");
|
|
652
|
+
return cas(previous, {
|
|
653
|
+
...previous,
|
|
654
|
+
state: "abandoned_before_attempt",
|
|
655
|
+
reason: input.reason,
|
|
656
|
+
updatedAt: timestamp(),
|
|
657
|
+
});
|
|
608
658
|
});
|
|
609
659
|
},
|
|
610
660
|
decide(input) {
|
|
611
661
|
return observeDecide(() => {
|
|
612
662
|
const previous = requireRecord(input.approvalId);
|
|
663
|
+
const decisionAt = input.decisionAt ?? now().getTime();
|
|
664
|
+
if (!Number.isSafeInteger(decisionAt))
|
|
665
|
+
fail("invalid_decision_time");
|
|
613
666
|
if (!sameDecisionBinding(previous, input))
|
|
614
667
|
fail("decision_binding_mismatch");
|
|
615
668
|
if (previous.state === "denied" && input.decision === "deny")
|
|
616
669
|
return previous;
|
|
617
670
|
if (previous.state !== "proposed")
|
|
618
671
|
fail("decision_not_eligible");
|
|
619
|
-
if (
|
|
672
|
+
if (decisionAt >= Date.parse(previous.expiresAt)) {
|
|
620
673
|
return cas(previous, { ...previous, state: "expired", updatedAt: timestamp() });
|
|
621
674
|
}
|
|
622
675
|
if (input.decision === "deny") {
|
|
@@ -625,7 +678,7 @@ function openApprovalStore(options) {
|
|
|
625
678
|
if (!isNonEmpty(input.ownerId))
|
|
626
679
|
fail("invalid_owner");
|
|
627
680
|
options.hooks?.beforeClaimCas?.();
|
|
628
|
-
if (now().getTime() >= Date.parse(previous.expiresAt)) {
|
|
681
|
+
if ((input.decisionAt ?? now().getTime()) >= Date.parse(previous.expiresAt)) {
|
|
629
682
|
return cas(previous, { ...previous, state: "expired", updatedAt: timestamp() });
|
|
630
683
|
}
|
|
631
684
|
return cas(previous, { ...previous, state: "claimed", ownerId: input.ownerId, epoch: previous.epoch + 1, updatedAt: timestamp() });
|
|
@@ -753,6 +806,9 @@ function openApprovalStore(options) {
|
|
|
753
806
|
interruptedAfterAttempt = terminalized.changes === 1;
|
|
754
807
|
row = readContinuationRow(input.approvalId);
|
|
755
808
|
}
|
|
809
|
+
const scenarioHandleDigest = acceptanceDigest(input.approvalId);
|
|
810
|
+
if (scenarioHandleDigest)
|
|
811
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "approval.acceptance_continuation_transition", message: "acceptance-bound approval continuation transitioned", meta: { approvalId: approval.approvalId, scenarioHandleDigest, toolName: approval.toolName, argumentDigest: approval.argumentDigest, state: row.state } });
|
|
756
812
|
return {
|
|
757
813
|
claimed,
|
|
758
814
|
interruptedAfterAttempt,
|
|
@@ -777,10 +833,87 @@ function openApprovalStore(options) {
|
|
|
777
833
|
`).run(completedAt, input.approvalId, input.ownerId, input.epoch);
|
|
778
834
|
if (changed.changes !== 1)
|
|
779
835
|
fail("continuation_completion_not_eligible");
|
|
836
|
+
const scenarioHandleDigest = acceptanceDigest(input.approvalId);
|
|
837
|
+
if (scenarioHandleDigest)
|
|
838
|
+
(0, runtime_1.emitNervesEvent)({ component: "heart", event: "approval.acceptance_continuation_transition", message: "acceptance-bound approval continuation transitioned", meta: { approvalId: approval.approvalId, scenarioHandleDigest, toolName: approval.toolName, argumentDigest: approval.argumentDigest, state: "completed" } });
|
|
780
839
|
return combineContinuation(approval, readContinuationRow(input.approvalId));
|
|
781
840
|
});
|
|
782
841
|
},
|
|
783
842
|
read(approvalId) { return observeRead(() => rawRead(approvalId)); },
|
|
843
|
+
readByScenarioHandleDigest(scenarioHandleDigest) {
|
|
844
|
+
assertHash(scenarioHandleDigest, "invalid_scenario_handle_digest");
|
|
845
|
+
const rows = database.prepare(`
|
|
846
|
+
SELECT a.record_json
|
|
847
|
+
FROM approval_actions a
|
|
848
|
+
INNER JOIN approval_acceptance_scenarios s ON s.approval_id = a.approval_id
|
|
849
|
+
WHERE s.scenario_handle_digest = ?
|
|
850
|
+
ORDER BY a.approval_id
|
|
851
|
+
`).all(scenarioHandleDigest);
|
|
852
|
+
return rows.map((row) => parseApprovalRecord(JSON.parse(row.record_json)));
|
|
853
|
+
},
|
|
854
|
+
listTelegramIdentitySubjects() {
|
|
855
|
+
const subjects = new Set();
|
|
856
|
+
const add = (value) => {
|
|
857
|
+
if (/^tg_[A-Za-z0-9_-]{43}$/u.test(value))
|
|
858
|
+
subjects.add(value);
|
|
859
|
+
};
|
|
860
|
+
const rows = database.prepare("SELECT record_json FROM approval_actions ORDER BY approval_id")
|
|
861
|
+
.all();
|
|
862
|
+
for (const row of rows) {
|
|
863
|
+
const record = parseApprovalRecord(JSON.parse(row.record_json));
|
|
864
|
+
if (record.transport !== "telegram")
|
|
865
|
+
continue;
|
|
866
|
+
add(record.requesterId);
|
|
867
|
+
add(record.transportUserId);
|
|
868
|
+
add(record.transportChatId);
|
|
869
|
+
if (record.sessionKey.startsWith("telegram:"))
|
|
870
|
+
add(record.sessionKey.slice("telegram:".length));
|
|
871
|
+
for (const match of record.sessionPath.matchAll(/tg_[A-Za-z0-9_-]{43}/gu))
|
|
872
|
+
add(match[0]);
|
|
873
|
+
}
|
|
874
|
+
return [...subjects].sort();
|
|
875
|
+
},
|
|
876
|
+
migrateTelegramIdentity(input) {
|
|
877
|
+
if (!isNonEmpty(input.legacyUserId) || !isNonEmpty(input.legacyChatId) || !/^tg_[A-Za-z0-9_-]{43}$/u.test(input.subject)) {
|
|
878
|
+
fail("invalid_telegram_identity_migration");
|
|
879
|
+
}
|
|
880
|
+
const rows = database.prepare("SELECT approval_id, record_json FROM approval_actions ORDER BY approval_id")
|
|
881
|
+
.all();
|
|
882
|
+
const migrate = database.transaction(() => {
|
|
883
|
+
let migrated = 0;
|
|
884
|
+
for (const row of rows) {
|
|
885
|
+
const previous = parseApprovalRecord(JSON.parse(row.record_json));
|
|
886
|
+
if (previous.transport !== "telegram")
|
|
887
|
+
continue;
|
|
888
|
+
const next = {
|
|
889
|
+
...previous,
|
|
890
|
+
sessionKey: previous.sessionKey === `telegram:${input.legacyChatId}` ? `telegram:${input.subject}` : previous.sessionKey,
|
|
891
|
+
sessionPath: previous.sessionPath
|
|
892
|
+
.replace(`telegram-user:${input.legacyUserId}`, `telegram-user:${input.subject}`)
|
|
893
|
+
.replace(`telegram_${input.legacyChatId}.json`, `telegram_${input.subject}.json`),
|
|
894
|
+
requesterId: previous.requesterId === input.legacyUserId ? input.subject : previous.requesterId,
|
|
895
|
+
transportUserId: previous.transportUserId === input.legacyUserId ? input.subject : previous.transportUserId,
|
|
896
|
+
transportChatId: previous.transportChatId === input.legacyChatId ? input.subject : previous.transportChatId,
|
|
897
|
+
transportMessageId: previous.transportMessageId && !previous.transportMessageId.startsWith("tgm_")
|
|
898
|
+
? `tgm_${(0, node_crypto_1.createHmac)("sha256", input.subject).update(`message:${previous.transportMessageId}`, "utf8").digest("base64url")}`
|
|
899
|
+
: previous.transportMessageId,
|
|
900
|
+
};
|
|
901
|
+
const nextJson = JSON.stringify(parseApprovalRecord(next));
|
|
902
|
+
if (nextJson === row.record_json)
|
|
903
|
+
continue;
|
|
904
|
+
const changed = database.prepare("UPDATE approval_actions SET record_json = ? WHERE approval_id = ? AND record_json = ?")
|
|
905
|
+
.run(nextJson, row.approval_id, row.record_json);
|
|
906
|
+
if (changed.changes !== 1)
|
|
907
|
+
fail("telegram_identity_migration_conflict");
|
|
908
|
+
migrated += 1;
|
|
909
|
+
}
|
|
910
|
+
return migrated;
|
|
911
|
+
});
|
|
912
|
+
const migrated = migrate();
|
|
913
|
+
if (migrated > 0)
|
|
914
|
+
database.exec("VACUUM");
|
|
915
|
+
return migrated;
|
|
916
|
+
},
|
|
784
917
|
close() {
|
|
785
918
|
return observeClose(() => {
|
|
786
919
|
options.hooks?.beforeClose?.();
|
|
@@ -789,3 +922,29 @@ function openApprovalStore(options) {
|
|
|
789
922
|
},
|
|
790
923
|
};
|
|
791
924
|
}
|
|
925
|
+
function readApprovalsByScenarioHandleDigest(databasePath, scenarioHandleDigest) {
|
|
926
|
+
assertHash(scenarioHandleDigest, "invalid_scenario_handle_digest");
|
|
927
|
+
const database = new better_sqlite3_1.default(databasePath, { readonly: true, fileMustExist: true });
|
|
928
|
+
try {
|
|
929
|
+
const rows = database.prepare(`
|
|
930
|
+
SELECT a.record_json, c.owner_id, c.owner_pid, c.epoch AS continuation_epoch, c.state AS continuation_state,
|
|
931
|
+
c.claimed_at, c.materialized_at, c.attempted_at AS continuation_attempted_at, c.continued_at
|
|
932
|
+
FROM approval_actions a
|
|
933
|
+
INNER JOIN approval_acceptance_scenarios s ON s.approval_id = a.approval_id
|
|
934
|
+
LEFT JOIN approval_continuations c ON c.approval_id = a.approval_id
|
|
935
|
+
WHERE s.scenario_handle_digest = ?
|
|
936
|
+
ORDER BY a.approval_id
|
|
937
|
+
`).all(scenarioHandleDigest);
|
|
938
|
+
return rows.map((row) => ({
|
|
939
|
+
approval: parseApprovalRecord(JSON.parse(row.record_json)),
|
|
940
|
+
continuation: row.continuation_state === null ? null : {
|
|
941
|
+
continuationOwnerId: row.owner_id, continuationOwnerPid: row.owner_pid, continuationEpoch: row.continuation_epoch,
|
|
942
|
+
continuationState: row.continuation_state, continuationClaimedAt: row.claimed_at, continuationMaterializedAt: row.materialized_at,
|
|
943
|
+
continuationAttemptedAt: row.continuation_attempted_at, continuedAt: row.continued_at,
|
|
944
|
+
},
|
|
945
|
+
}));
|
|
946
|
+
}
|
|
947
|
+
finally {
|
|
948
|
+
database.close();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
@@ -92,7 +92,9 @@ function readAgentConfigForAgent(agentName, bundlesRoot = (0, identity_1.getAgen
|
|
|
92
92
|
provider !== "anthropic" &&
|
|
93
93
|
provider !== "minimax" &&
|
|
94
94
|
provider !== "openai-codex" &&
|
|
95
|
-
provider !== "github-copilot"
|
|
95
|
+
provider !== "github-copilot" &&
|
|
96
|
+
provider !== "openai-compatible" &&
|
|
97
|
+
provider !== "openai-compatible-gemini") {
|
|
96
98
|
throw new Error(`agent.json at ${configPath} has unsupported provider '${String(provider)}'`);
|
|
97
99
|
}
|
|
98
100
|
if (!agentFacing || typeof agentFacing !== "object") {
|
|
@@ -106,6 +108,7 @@ function readAgentConfigForAgent(agentName, bundlesRoot = (0, identity_1.getAgen
|
|
|
106
108
|
const config = {
|
|
107
109
|
...parsed,
|
|
108
110
|
senses: (0, identity_1.normalizeSenses)(parsed.senses, configPath),
|
|
111
|
+
habitPaidTurnsPerDay: (0, identity_1.normalizeHabitPaidTurnsPerDay)(agentName, parsed.habitPaidTurnsPerDay, configPath),
|
|
109
112
|
};
|
|
110
113
|
return {
|
|
111
114
|
configPath,
|
|
@@ -272,6 +275,19 @@ async function collectRuntimeAuthCredentials(input, deps) {
|
|
|
272
275
|
const spawnSync = deps.spawnSync ?? child_process_1.spawnSync;
|
|
273
276
|
const homeDir = deps.homeDir ?? os.homedir();
|
|
274
277
|
const now = deps.now ?? (() => new Date());
|
|
278
|
+
if (input.provider === "openai-compatible" || input.provider === "openai-compatible-gemini") {
|
|
279
|
+
if (!input.promptSecret)
|
|
280
|
+
throw new Error(`${input.provider} API key entry requires an interactive terminal so the secret can be hidden.`);
|
|
281
|
+
const apiKey = (await input.promptSecret(`${input.provider === "openai-compatible" ? "Z.ai" : "Gemini"} API key: `)).trim();
|
|
282
|
+
if (!apiKey)
|
|
283
|
+
throw new Error(`${input.provider} API key is required.`);
|
|
284
|
+
return {
|
|
285
|
+
apiKey,
|
|
286
|
+
baseUrl: input.provider === "openai-compatible"
|
|
287
|
+
? "https://api.z.ai/api/paas/v4/"
|
|
288
|
+
: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
275
291
|
if (input.provider === "github-copilot") {
|
|
276
292
|
let token = process.env.GH_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim() || "";
|
|
277
293
|
if (!token) {
|
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AUTONOMY_BUDGET_DEFAULT_POLICY = void 0;
|
|
36
|
+
exports.DEFAULT_HABIT_PAID_TURNS_PER_DAY = exports.AUTONOMY_BUDGET_DEFAULT_POLICY = void 0;
|
|
37
|
+
exports.normalizeHabitPaidTurnsPerDay = normalizeHabitPaidTurnsPerDay;
|
|
38
|
+
exports.resolveAutonomyBudgetPolicy = resolveAutonomyBudgetPolicy;
|
|
37
39
|
exports.autonomyBudgetStatePath = autonomyBudgetStatePath;
|
|
38
40
|
exports.autonomyReceiptsDir = autonomyReceiptsDir;
|
|
39
41
|
exports.readAutonomyBudgetState = readAutonomyBudgetState;
|
|
@@ -56,6 +58,47 @@ exports.AUTONOMY_BUDGET_DEFAULT_POLICY = {
|
|
|
56
58
|
stormFailureWindowMs: 10 * 60 * 1000,
|
|
57
59
|
stormBlockMs: 30 * 60 * 1000,
|
|
58
60
|
};
|
|
61
|
+
exports.DEFAULT_HABIT_PAID_TURNS_PER_DAY = 4;
|
|
62
|
+
function normalizeHabitPaidTurnsPerDay(agentName, value, configFile) {
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
if (agentName === "sanctuary") {
|
|
65
|
+
throw new Error(`agent.json at ${configFile} must explicitly set habitPaidTurnsPerDay for Sanctuary.`);
|
|
66
|
+
}
|
|
67
|
+
return exports.DEFAULT_HABIT_PAID_TURNS_PER_DAY;
|
|
68
|
+
}
|
|
69
|
+
if (!Number.isInteger(value) || value < 0 || value > 96) {
|
|
70
|
+
(0, runtime_1.emitNervesEvent)({
|
|
71
|
+
level: "error",
|
|
72
|
+
event: "config_identity.error",
|
|
73
|
+
component: "config/identity",
|
|
74
|
+
message: "agent config has invalid habit paid-turn budget",
|
|
75
|
+
meta: { path: configFile, value },
|
|
76
|
+
});
|
|
77
|
+
throw new Error(`agent.json at ${configFile} habitPaidTurnsPerDay must be an integer from 0 through 96.`);
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function resolveAutonomyBudgetPolicy(agentRoot, agentName, basePolicy = exports.AUTONOMY_BUDGET_DEFAULT_POLICY) {
|
|
82
|
+
const configPath = path.join(agentRoot, "agent.json");
|
|
83
|
+
let configured;
|
|
84
|
+
if (fs.existsSync(configPath)) {
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new Error(`failed to read autonomy budget from ${configPath}: ${String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
93
|
+
throw new Error(`agent.json at ${configPath} must be an object`);
|
|
94
|
+
}
|
|
95
|
+
configured = parsed.habitPaidTurnsPerDay;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
...basePolicy,
|
|
99
|
+
habitPaidTurnsPerDay: normalizeHabitPaidTurnsPerDay(agentName, configured, configPath),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
59
102
|
function autonomyRoot(agentRoot) {
|
|
60
103
|
return path.join(agentRoot, "state", "autonomy");
|
|
61
104
|
}
|
|
@@ -50,6 +50,7 @@ class AwaitScheduler {
|
|
|
50
50
|
onAwaitExpire;
|
|
51
51
|
deps;
|
|
52
52
|
execForVerify;
|
|
53
|
+
verifyJobs;
|
|
53
54
|
platform;
|
|
54
55
|
watcher = null;
|
|
55
56
|
debounceTimer = null;
|
|
@@ -65,6 +66,7 @@ class AwaitScheduler {
|
|
|
65
66
|
this.onAwaitExpire = options.onAwaitExpire;
|
|
66
67
|
this.deps = options.deps;
|
|
67
68
|
this.execForVerify = options.execForVerify;
|
|
69
|
+
this.verifyJobs = options.verifyJobs;
|
|
68
70
|
this.platform = options.platform ?? process.platform;
|
|
69
71
|
}
|
|
70
72
|
start() {
|
|
@@ -242,9 +244,11 @@ class AwaitScheduler {
|
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
verifyCronAndCreateFallbacks(jobs) {
|
|
245
|
-
if (!this.execForVerify)
|
|
247
|
+
if (!this.execForVerify && !this.verifyJobs)
|
|
246
248
|
return;
|
|
247
|
-
const verifiedLabels = this.
|
|
249
|
+
const verifiedLabels = this.verifyJobs?.(jobs)
|
|
250
|
+
? new Set(jobs.map((job) => this.platform === "darwin" ? `bot.ouro.${job.agent}.${job.taskId}` : job.taskId.replace(/^await\./u, "")))
|
|
251
|
+
: this.execForVerify ? this.verifyCronEntries() : new Set();
|
|
248
252
|
for (const job of jobs) {
|
|
249
253
|
// job.taskId is already namespaced as "await.<name>". The bare name is
|
|
250
254
|
// what we pass to `--await` and what shows up in the crontab regex
|
package/dist/heart/config.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.getMinimaxConfig = getMinimaxConfig;
|
|
|
43
43
|
exports.getAnthropicConfig = getAnthropicConfig;
|
|
44
44
|
exports.getOpenAICodexConfig = getOpenAICodexConfig;
|
|
45
45
|
exports.getGithubCopilotConfig = getGithubCopilotConfig;
|
|
46
|
+
exports.getOpenAICompatibleConfig = getOpenAICompatibleConfig;
|
|
46
47
|
exports.getTeamsConfig = getTeamsConfig;
|
|
47
48
|
exports.getTeamsSecondaryConfig = getTeamsSecondaryConfig;
|
|
48
49
|
exports.getContextConfig = getContextConfig;
|
|
@@ -248,6 +249,13 @@ function getGithubCopilotConfig() {
|
|
|
248
249
|
baseUrl: typeof raw.baseUrl === "string" ? raw.baseUrl : "",
|
|
249
250
|
};
|
|
250
251
|
}
|
|
252
|
+
function getOpenAICompatibleConfig(provider) {
|
|
253
|
+
const raw = readProviderConfig(provider);
|
|
254
|
+
return {
|
|
255
|
+
apiKey: typeof raw.apiKey === "string" ? raw.apiKey : "",
|
|
256
|
+
baseUrl: typeof raw.baseUrl === "string" ? raw.baseUrl : "",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
251
259
|
function getTeamsConfig() {
|
|
252
260
|
const config = loadConfig();
|
|
253
261
|
return { ...config.teams };
|