@ouro.bot/cli 0.1.0-alpha.729 → 0.1.0-alpha.730

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.
@@ -0,0 +1,379 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.SessionTransactionError = exports.SessionTurnBusyError = void 0;
40
+ exports.acquireSessionTurnLease = acquireSessionTurnLease;
41
+ exports.withSessionTurnLease = withSessionTurnLease;
42
+ exports.withImmediateSessionTurnLease = withImmediateSessionTurnLease;
43
+ exports.currentSessionTurnLease = currentSessionTurnLease;
44
+ exports.assertSessionTurnLease = assertSessionTurnLease;
45
+ exports.readSessionTransaction = readSessionTransaction;
46
+ exports.writeSessionTransaction = writeSessionTransaction;
47
+ exports.deleteSessionTransaction = deleteSessionTransaction;
48
+ const node_async_hooks_1 = require("node:async_hooks");
49
+ const node_crypto_1 = require("node:crypto");
50
+ const fs = __importStar(require("node:fs"));
51
+ const path = __importStar(require("node:path"));
52
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
53
+ const runtime_1 = require("../nerves/runtime");
54
+ class SessionTurnBusyError extends Error {
55
+ retryable = true;
56
+ constructor(message = "session turn is busy") {
57
+ super(message);
58
+ this.name = "SessionTurnBusyError";
59
+ }
60
+ }
61
+ exports.SessionTurnBusyError = SessionTurnBusyError;
62
+ class SessionTransactionError extends Error {
63
+ constructor(message) {
64
+ super(message);
65
+ this.name = "SessionTransactionError";
66
+ }
67
+ }
68
+ exports.SessionTransactionError = SessionTransactionError;
69
+ const heldLeases = new Map();
70
+ const leaseContext = new node_async_hooks_1.AsyncLocalStorage();
71
+ function canonicalSessionPath(sessionPath) {
72
+ return path.resolve(sessionPath);
73
+ }
74
+ function revision(bytes) {
75
+ return (0, node_crypto_1.createHash)("sha256").update(bytes, "utf8").digest("hex");
76
+ }
77
+ function processAlive(pid) {
78
+ try {
79
+ process.kill(pid, 0);
80
+ return true;
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
86
+ function sleep(ms) {
87
+ return new Promise((resolve) => setTimeout(resolve, ms));
88
+ }
89
+ function isSqliteBusy(error) {
90
+ return error instanceof Error && "code" in error
91
+ && error.code === "SQLITE_BUSY";
92
+ }
93
+ function openLeaseDatabase(lockPath, busyTimeoutMs = 0) {
94
+ const database = new better_sqlite3_1.default(lockPath);
95
+ database.pragma(`busy_timeout = ${busyTimeoutMs}`);
96
+ database.exec(`
97
+ CREATE TABLE IF NOT EXISTS session_turn_lease (
98
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
99
+ pid INTEGER NOT NULL,
100
+ owner_id TEXT NOT NULL,
101
+ owner_token TEXT NOT NULL
102
+ )
103
+ `);
104
+ return database;
105
+ }
106
+ function claimLeaseRecord(lockPath, record, isProcessAlive) {
107
+ const database = openLeaseDatabase(lockPath);
108
+ try {
109
+ return database.transaction(() => {
110
+ const current = database.prepare(`
111
+ SELECT pid, owner_id, owner_token FROM session_turn_lease WHERE singleton = 1
112
+ `).get();
113
+ if (!current) {
114
+ database.prepare(`
115
+ INSERT INTO session_turn_lease (singleton, pid, owner_id, owner_token) VALUES (1, ?, ?, ?)
116
+ `).run(record.pid, record.ownerId, record.ownerToken);
117
+ return { acquired: true, stale: null };
118
+ }
119
+ if (!Number.isSafeInteger(current.pid) || current.pid <= 0
120
+ || typeof current.owner_id !== "string" || current.owner_id.length === 0
121
+ || typeof current.owner_token !== "string" || current.owner_token.length === 0) {
122
+ throw new SessionTransactionError("invalid session lease record");
123
+ }
124
+ const observed = { pid: current.pid, ownerId: current.owner_id, ownerToken: current.owner_token };
125
+ if (isProcessAlive(observed.pid))
126
+ return { acquired: false, stale: null };
127
+ database.prepare(`
128
+ UPDATE session_turn_lease SET pid = ?, owner_id = ?, owner_token = ?
129
+ WHERE singleton = 1 AND pid = ? AND owner_id = ? AND owner_token = ?
130
+ `).run(record.pid, record.ownerId, record.ownerToken, observed.pid, observed.ownerId, observed.ownerToken);
131
+ return { acquired: true, stale: observed };
132
+ })();
133
+ }
134
+ finally {
135
+ database.close();
136
+ }
137
+ }
138
+ function releaseLeaseRecord(lockPath, record) {
139
+ const database = openLeaseDatabase(lockPath, 5_000);
140
+ try {
141
+ return database.prepare(`
142
+ DELETE FROM session_turn_lease WHERE singleton = 1 AND pid = ? AND owner_id = ? AND owner_token = ?
143
+ `).run(record.pid, record.ownerId, record.ownerToken).changes === 1;
144
+ }
145
+ finally {
146
+ database.close();
147
+ }
148
+ }
149
+ function makeLease(held) {
150
+ let localReleased = false;
151
+ return {
152
+ sessionPath: held.sessionPath,
153
+ ownerId: held.ownerId,
154
+ ownerToken: held.ownerToken,
155
+ release: async () => {
156
+ if (localReleased)
157
+ return;
158
+ localReleased = true;
159
+ held.depth -= 1;
160
+ if (held.depth > 0 || held.released)
161
+ return;
162
+ held.released = true;
163
+ releaseLeaseRecord(held.lockPath, held);
164
+ heldLeases.delete(held.sessionPath);
165
+ (0, runtime_1.emitNervesEvent)({
166
+ component: "mind",
167
+ event: "mind.session_turn_lease_released",
168
+ message: "released session turn lease",
169
+ meta: { sessionPath: held.sessionPath, ownerId: held.ownerId },
170
+ });
171
+ },
172
+ };
173
+ }
174
+ async function acquireSessionTurnLease(sessionPath, options = {}) {
175
+ const canonical = canonicalSessionPath(sessionPath);
176
+ const existing = heldLeases.get(canonical);
177
+ if (existing && !existing.released) {
178
+ if (options.ownerId === existing.ownerId && options.ownerToken === existing.ownerToken) {
179
+ existing.depth += 1;
180
+ return makeLease(existing);
181
+ }
182
+ }
183
+ const ownerId = options.ownerId ?? (0, node_crypto_1.randomUUID)();
184
+ const ownerToken = options.ownerToken ?? (0, node_crypto_1.randomUUID)();
185
+ const pid = options.pid ?? process.pid;
186
+ const timeoutMs = options.timeoutMs ?? 5_000;
187
+ const pollIntervalMs = options.pollIntervalMs ?? 10;
188
+ const isProcessAlive = options.isProcessAlive ?? processAlive;
189
+ const lockPath = `${canonical}.turn.lock`;
190
+ const started = Date.now();
191
+ fs.mkdirSync(path.dirname(canonical), { recursive: true });
192
+ for (;;) {
193
+ let claim = null;
194
+ try {
195
+ claim = claimLeaseRecord(lockPath, { pid, ownerId, ownerToken }, isProcessAlive);
196
+ }
197
+ catch (error) {
198
+ if (!isSqliteBusy(error))
199
+ throw error;
200
+ }
201
+ if (claim?.acquired) {
202
+ const held = { sessionPath: canonical, lockPath, pid, ownerId, ownerToken, depth: 1, released: false };
203
+ heldLeases.set(canonical, held);
204
+ (0, runtime_1.emitNervesEvent)({
205
+ component: "mind",
206
+ event: "mind.session_turn_lease_acquired",
207
+ message: "acquired session turn lease",
208
+ meta: { sessionPath: canonical, ownerId },
209
+ });
210
+ if (claim.stale) {
211
+ options.onStaleLease?.(claim.stale);
212
+ (0, runtime_1.emitNervesEvent)({
213
+ level: "warn",
214
+ component: "mind",
215
+ event: "mind.session_turn_lease_stolen",
216
+ message: "stole stale session turn lease",
217
+ meta: { sessionPath: canonical, stalePid: claim.stale.pid, staleOwnerId: claim.stale.ownerId },
218
+ });
219
+ }
220
+ return makeLease(held);
221
+ }
222
+ if (Date.now() - started >= timeoutMs) {
223
+ (0, runtime_1.emitNervesEvent)({
224
+ level: "warn",
225
+ component: "mind",
226
+ event: "mind.session_turn_lease_busy",
227
+ message: "session turn lease acquisition timed out",
228
+ meta: { sessionPath: canonical, ownerId, timeoutMs },
229
+ });
230
+ throw new SessionTurnBusyError(`session turn busy: ${canonical}`);
231
+ }
232
+ await sleep(pollIntervalMs);
233
+ }
234
+ }
235
+ async function withSessionTurnLease(sessionPath, work, options = {}) {
236
+ const lease = await acquireSessionTurnLease(sessionPath, options);
237
+ try {
238
+ return await leaseContext.run(lease, () => work(lease));
239
+ }
240
+ finally {
241
+ await lease.release();
242
+ }
243
+ }
244
+ function withImmediateSessionTurnLease(sessionPath, work, options = {}) {
245
+ const canonical = canonicalSessionPath(sessionPath);
246
+ const contextual = currentSessionTurnLease(canonical);
247
+ if (contextual)
248
+ return work(contextual);
249
+ const ownerId = options.ownerId ?? (0, node_crypto_1.randomUUID)();
250
+ const ownerToken = options.ownerToken ?? (0, node_crypto_1.randomUUID)();
251
+ const pid = options.pid ?? process.pid;
252
+ const lockPath = `${canonical}.turn.lock`;
253
+ fs.mkdirSync(path.dirname(canonical), { recursive: true });
254
+ const tryAcquire = () => {
255
+ let claim;
256
+ try {
257
+ claim = claimLeaseRecord(lockPath, { pid, ownerId, ownerToken }, options.isProcessAlive ?? processAlive);
258
+ }
259
+ catch (error) {
260
+ if (isSqliteBusy(error))
261
+ return null;
262
+ throw error;
263
+ }
264
+ if (claim.acquired) {
265
+ const held = { sessionPath: canonical, lockPath, pid, ownerId, ownerToken, depth: 1, released: false };
266
+ heldLeases.set(canonical, held);
267
+ if (claim.stale) {
268
+ options.onStaleLease?.(claim.stale);
269
+ (0, runtime_1.emitNervesEvent)({
270
+ level: "warn",
271
+ component: "mind",
272
+ event: "mind.session_turn_lease_stolen",
273
+ message: "stole stale session turn lease",
274
+ meta: { sessionPath: canonical, stalePid: claim.stale.pid, staleOwnerId: claim.stale.ownerId },
275
+ });
276
+ }
277
+ return held;
278
+ }
279
+ return null;
280
+ };
281
+ const held = tryAcquire();
282
+ if (!held)
283
+ throw new SessionTurnBusyError(`session turn busy: ${canonical}`);
284
+ const lease = makeLease(held);
285
+ try {
286
+ return leaseContext.run(lease, () => work(lease));
287
+ }
288
+ finally {
289
+ void lease.release();
290
+ }
291
+ }
292
+ function currentSessionTurnLease(sessionPath) {
293
+ const lease = leaseContext.getStore();
294
+ return lease && lease.sessionPath === canonicalSessionPath(sessionPath) ? lease : null;
295
+ }
296
+ function assertSessionTurnLease(sessionPath, lease) {
297
+ const canonical = canonicalSessionPath(sessionPath);
298
+ if (lease.sessionPath !== canonical)
299
+ throw new SessionTransactionError("leased session path mismatch");
300
+ const held = heldLeases.get(canonical);
301
+ if (!held || held.released || held.ownerId !== lease.ownerId || held.ownerToken !== lease.ownerToken) {
302
+ throw new SessionTransactionError("session lease owner token mismatch");
303
+ }
304
+ }
305
+ function readSessionTransaction(sessionPath, lease) {
306
+ assertSessionTurnLease(sessionPath, lease);
307
+ let bytes = "";
308
+ try {
309
+ bytes = fs.readFileSync(canonicalSessionPath(sessionPath), "utf8");
310
+ }
311
+ catch (error) {
312
+ if (!(error instanceof Error) || error.code !== "ENOENT")
313
+ throw error;
314
+ }
315
+ return { bytes, value: bytes ? JSON.parse(bytes) : null, revision: revision(bytes) };
316
+ }
317
+ function writeSessionTransaction(sessionPath, value, options) {
318
+ assertSessionTurnLease(sessionPath, options.lease);
319
+ const canonical = canonicalSessionPath(sessionPath);
320
+ const current = readSessionTransaction(canonical, options.lease);
321
+ if (current.revision !== options.expectedRevision)
322
+ throw new SessionTransactionError("session revision changed");
323
+ fs.mkdirSync(path.dirname(canonical), { recursive: true });
324
+ const bytes = JSON.stringify(value, null, 2);
325
+ const tempPath = path.join(path.dirname(canonical), `.${path.basename(canonical)}.tmp-${process.pid}-${(0, node_crypto_1.randomUUID)()}`);
326
+ let fd = null;
327
+ try {
328
+ fd = fs.openSync(tempPath, "wx", 0o600);
329
+ fs.writeFileSync(fd, bytes, "utf8");
330
+ fs.fsyncSync(fd);
331
+ fs.closeSync(fd);
332
+ fd = null;
333
+ options.hooks?.beforeRename?.();
334
+ fs.renameSync(tempPath, canonical);
335
+ const directoryFd = fs.openSync(path.dirname(canonical), "r");
336
+ try {
337
+ fs.fsyncSync(directoryFd);
338
+ }
339
+ finally {
340
+ fs.closeSync(directoryFd);
341
+ }
342
+ }
343
+ catch (error) {
344
+ /* v8 ignore next -- fd-close failure after a prior node:fs failure is best-effort cleanup @preserve */
345
+ if (fd !== null)
346
+ try {
347
+ fs.closeSync(fd);
348
+ }
349
+ catch { /* best effort */ }
350
+ try {
351
+ fs.unlinkSync(tempPath);
352
+ }
353
+ catch { /* best effort */ }
354
+ throw error;
355
+ }
356
+ (0, runtime_1.emitNervesEvent)({
357
+ component: "mind",
358
+ event: "mind.session_transaction_written",
359
+ message: "durably replaced session envelope",
360
+ meta: { sessionPath: canonical, ownerId: options.lease.ownerId },
361
+ });
362
+ return revision(bytes);
363
+ }
364
+ function deleteSessionTransaction(sessionPath, lease) {
365
+ assertSessionTurnLease(sessionPath, lease);
366
+ try {
367
+ fs.unlinkSync(canonicalSessionPath(sessionPath));
368
+ }
369
+ catch (error) {
370
+ if (!(error instanceof Error) || error.code !== "ENOENT")
371
+ throw error;
372
+ }
373
+ (0, runtime_1.emitNervesEvent)({
374
+ component: "mind",
375
+ event: "mind.session_transaction_deleted",
376
+ message: "deleted session under turn lease",
377
+ meta: { sessionPath: canonicalSessionPath(sessionPath), ownerId: lease.ownerId },
378
+ });
379
+ }
@@ -32,8 +32,8 @@ function toCardInfo(card) {
32
32
  async function createStripeClient() {
33
33
  const store = (0, credential_access_1.getCredentialStore)();
34
34
  const apiKey = await store.getRawSecret("stripe.com", "restrictedKey");
35
- // StripeConstructor is a callable (not a class), cast the result
36
- const stripe = (0, stripe_1.default)(apiKey);
35
+ const StripeFactory = stripe_1.default;
36
+ const stripe = new StripeFactory(apiKey);
37
37
  return {
38
38
  async createVirtualCard(opts) {
39
39
  (0, runtime_1.emitNervesEvent)({
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.digestJson = digestJson;
7
+ exports.validateAdvertisedToolArguments = validateAdvertisedToolArguments;
8
+ const node_crypto_1 = require("node:crypto");
9
+ const ajv_1 = __importDefault(require("ajv"));
10
+ const runtime_1 = require("../nerves/runtime");
11
+ const ajv = new ajv_1.default({
12
+ strict: true,
13
+ allErrors: true,
14
+ coerceTypes: false,
15
+ removeAdditional: false,
16
+ useDefaults: false,
17
+ });
18
+ const validators = new WeakMap();
19
+ function canonicalize(value) {
20
+ if (value === null || typeof value !== "object")
21
+ return JSON.stringify(value);
22
+ if (Array.isArray(value))
23
+ return `[${value.map(canonicalize).join(",")}]`;
24
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(",")}}`;
25
+ }
26
+ function digest(value) {
27
+ return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
28
+ }
29
+ function renderErrors(errors) {
30
+ return errors.map((error) => `${error.instancePath || "/"} ${error.message}`).join("; ");
31
+ }
32
+ function digestJson(value) {
33
+ return digest(canonicalize(value));
34
+ }
35
+ function validateAdvertisedToolArguments(rawArguments, schema) {
36
+ let parsed;
37
+ try {
38
+ parsed = JSON.parse(rawArguments);
39
+ }
40
+ catch {
41
+ (0, runtime_1.emitNervesEvent)({
42
+ level: "warn",
43
+ component: "repertoire",
44
+ event: "repertoire.tool_arguments_invalid_json",
45
+ message: "tool arguments were not valid JSON",
46
+ meta: { rawLength: rawArguments.length },
47
+ });
48
+ return { ok: false, reason: "malformed JSON" };
49
+ }
50
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
51
+ (0, runtime_1.emitNervesEvent)({
52
+ level: "warn",
53
+ component: "repertoire",
54
+ event: "repertoire.tool_arguments_non_object",
55
+ message: "tool arguments were not a JSON object",
56
+ meta: { parsedType: parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed },
57
+ });
58
+ return { ok: false, reason: "arguments must be a JSON object" };
59
+ }
60
+ let validator = validators.get(schema);
61
+ try {
62
+ validator ??= ajv.compile(schema);
63
+ validators.set(schema, validator);
64
+ }
65
+ catch (error) {
66
+ const reason = String(error);
67
+ (0, runtime_1.emitNervesEvent)({
68
+ level: "error",
69
+ component: "repertoire",
70
+ event: "repertoire.tool_schema_invalid",
71
+ message: "advertised tool schema could not be compiled",
72
+ meta: { reason },
73
+ });
74
+ return { ok: false, reason: `advertised schema is invalid: ${reason}` };
75
+ }
76
+ if (!validator(parsed)) {
77
+ const reason = renderErrors(validator.errors);
78
+ (0, runtime_1.emitNervesEvent)({
79
+ level: "warn",
80
+ component: "repertoire",
81
+ event: "repertoire.tool_arguments_schema_rejected",
82
+ message: "tool arguments failed advertised schema validation",
83
+ meta: { reason },
84
+ });
85
+ return { ok: false, reason };
86
+ }
87
+ const argumentsValue = structuredClone(parsed);
88
+ const canonicalArguments = canonicalize(argumentsValue);
89
+ (0, runtime_1.emitNervesEvent)({
90
+ component: "repertoire",
91
+ event: "repertoire.tool_arguments_validated",
92
+ message: "tool arguments passed advertised schema validation",
93
+ meta: { argumentDigest: digest(canonicalArguments) },
94
+ });
95
+ return {
96
+ ok: true,
97
+ value: {
98
+ arguments: argumentsValue,
99
+ canonicalArguments,
100
+ argumentDigest: digest(canonicalArguments),
101
+ schemaDigest: digestJson(schema),
102
+ },
103
+ };
104
+ }
@@ -26,9 +26,22 @@ exports.shellToolDefinitions = [
26
26
  },
27
27
  },
28
28
  required: ["command"],
29
+ additionalProperties: false,
29
30
  },
30
31
  },
31
32
  },
33
+ approvalPolicy: (args) => {
34
+ const command = typeof args.command === "string" ? args.command.trim().split(/\s+/) : [];
35
+ if (command[0] === "docker" && command[1] === "restart" && command.length > 2) {
36
+ return {
37
+ kind: "required",
38
+ policyId: "shell.docker-lifecycle.v1",
39
+ actionClass: "service-control",
40
+ requiresSoleCall: true,
41
+ };
42
+ }
43
+ return { kind: "not_required" };
44
+ },
32
45
  handler: (a) => {
33
46
  // Destructive pattern detection (friction, not a block)
34
47
  const destructivePatterns = (0, shell_sessions_1.detectDestructivePatterns)(a.command);
@@ -4,6 +4,7 @@ exports.surfaceToolDef = exports.speakTool = exports.restTool = exports.ponderTo
4
4
  exports.resetMcpDefinitions = resetMcpDefinitions;
5
5
  exports.getToolsForChannel = getToolsForChannel;
6
6
  exports.resolveToolDefinition = resolveToolDefinition;
7
+ exports.approvalPolicyForToolName = approvalPolicyForToolName;
7
8
  exports.shellRiskProfile = shellRiskProfile;
8
9
  exports.riskProfileForTool = riskProfileForTool;
9
10
  exports.riskProfileForToolName = riskProfileForToolName;
@@ -134,6 +135,9 @@ function resolveToolDefinition(toolName) {
134
135
  ?? allDefinitions.find((d) => d.tool.function.name === toolName)
135
136
  ?? mcpDefinitions.find((d) => d.tool.function.name === toolName);
136
137
  }
138
+ function approvalPolicyForToolName(name, args) {
139
+ return resolveToolDefinition(name)?.approvalPolicy?.(args) ?? { kind: "not_required" };
140
+ }
137
141
  const findDefinition = resolveToolDefinition;
138
142
  function normalizeGuardArgs(_name, args) {
139
143
  return args;