@ouro.bot/cli 0.1.0-alpha.892 → 0.1.0-alpha.894

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,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redactSecrets = redactSecrets;
4
+ const runtime_1 = require("../../nerves/runtime");
5
+ /**
6
+ * Terminal screens and Feed tool input can show credentials. Everything the cmux sense hands to a
7
+ * model (screen text, request previews) goes through this first. It is a best-effort net for
8
+ * secret-shaped strings, not a guarantee, so callers also keep their output bounded.
9
+ */
10
+ const REDACTED = "[redacted]";
11
+ const WHOLE_MATCH = [
12
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
13
+ /\bsk-[A-Za-z0-9_-]{16,}/g,
14
+ /\bgh[pousr]_[A-Za-z0-9]{20,}/g,
15
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/g,
16
+ /\bAKIA[0-9A-Z]{16}\b/g,
17
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
18
+ /\bv1\.[A-Za-z0-9_-]{40,}\.[A-Za-z0-9_-]{40,}/g,
19
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/g,
20
+ ];
21
+ /** Keep the label (group 1), redact the value. */
22
+ const LABELLED_VALUE = [
23
+ /(\b(?:Bearer|Basic)\s+)[A-Za-z0-9._~+/=-]{6,}/gi,
24
+ /(\b(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|client[_-]?secret)\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s'"&]+)/gi,
25
+ /([?&](?:token|key|api_key|access_token|sig|signature)=)[^&\s]+/gi,
26
+ ];
27
+ function redactSecrets(text) {
28
+ let result = text;
29
+ for (const pattern of WHOLE_MATCH)
30
+ result = result.replace(pattern, REDACTED);
31
+ for (const pattern of LABELLED_VALUE)
32
+ result = result.replace(pattern, `$1${REDACTED}`);
33
+ if (result !== text) {
34
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_redacted", message: "redacted secret-shaped text from cmux output", meta: { removedChars: text.length - result.length } });
35
+ }
36
+ return result;
37
+ }
@@ -0,0 +1,287 @@
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.CMUX_WAKE_TRIGGER = void 0;
37
+ exports.cmuxStatePath = cmuxStatePath;
38
+ exports.queueEscalation = queueEscalation;
39
+ exports.wakeForEscalations = wakeForEscalations;
40
+ exports.startCmuxSenseApp = startCmuxSenseApp;
41
+ const node_crypto_1 = require("node:crypto");
42
+ const path = __importStar(require("node:path"));
43
+ const cmux_config_1 = require("../../heart/cmux-config");
44
+ const socket_client_1 = require("../../heart/daemon/socket-client");
45
+ const identity_1 = require("../../heart/identity");
46
+ const runtime_credentials_1 = require("../../heart/runtime-credentials");
47
+ const pending_1 = require("../../mind/pending");
48
+ const runtime_1 = require("../../nerves/runtime");
49
+ const attention_1 = require("./attention");
50
+ const answer_1 = require("./answer");
51
+ const client_1 = require("./client");
52
+ const RECONNECT_DELAYS_MS = [1_000, 2_000, 5_000, 10_000, 30_000];
53
+ const FEED_POLL_MS = 30_000;
54
+ const SAVE_DELAY_MS = 1_000;
55
+ function cmuxStatePath(agentName) {
56
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "cmux", "state.json");
57
+ }
58
+ /** The private-turn trigger the daemon's policy accepts for a cmux escalation wake. */
59
+ exports.CMUX_WAKE_TRIGGER = "cmux-feed";
60
+ /** cmux waits about 120 seconds; a notice the agent has not read within 30 minutes is dropped unread. */
61
+ const ESCALATION_TTL_MS = 30 * 60_000;
62
+ /** Queues the escalation in the agent's private runtime, as the mail sense queues its notices. */
63
+ function queueEscalation(agent, escalation, nowMs) {
64
+ (0, pending_1.queuePendingMessage)((0, pending_1.getPrivateRuntimePendingDir)(agent), {
65
+ from: "cmux",
66
+ friendId: "self",
67
+ channel: "cmux",
68
+ key: "feed",
69
+ content: escalation.content,
70
+ timestamp: nowMs,
71
+ expiresAt: nowMs + ESCALATION_TTL_MS,
72
+ mode: "reflect",
73
+ });
74
+ }
75
+ /** How many request ids one wake names in its origin references. */
76
+ const WAKE_REF_LIMIT = 20;
77
+ /**
78
+ * Asks the daemon for one private turn for everything a check pass queued: the turn reads every
79
+ * pending message at once, so a burst of Feed requests costs one turn, not one each. A refused or
80
+ * failed wake leaves the messages for the next private turn.
81
+ */
82
+ async function wakeForEscalations(agent, requestIds) {
83
+ const key = requestIds.length === 1 ? requestIds[0] : `${requestIds.length}:${(0, node_crypto_1.createHash)("sha256").update(requestIds.join("\n")).digest("hex").slice(0, 32)}`;
84
+ try {
85
+ const response = await (0, socket_client_1.requestPrivateWake)(agent, undefined, {
86
+ reason: requestIds.length === 1 ? "cmux Feed request" : `${requestIds.length} cmux Feed requests`,
87
+ triggerSource: exports.CMUX_WAKE_TRIGGER,
88
+ budgetClass: "interactive",
89
+ idempotencyKey: `cmux-feed:${agent}:${key}`,
90
+ originRefs: [...requestIds.slice(0, WAKE_REF_LIMIT).map((id) => ({ kind: "cmux-feed", id })), { kind: "sense", id: "cmux" }],
91
+ });
92
+ if (response && !response.ok)
93
+ throw new Error(response.error ?? "the daemon refused the wake");
94
+ }
95
+ catch (error) {
96
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_wake_error", message: "could not wake the private runtime for cmux escalations; they wait for the next private turn", meta: { agent, error: error.message } });
97
+ }
98
+ }
99
+ async function startCmuxSenseApp(options) {
100
+ const agent = options.agentName;
101
+ const nowMs = options.now ?? Date.now;
102
+ const now = () => new Date(nowMs()).toISOString();
103
+ const machine = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
104
+ const resolved = (0, cmux_config_1.resolveCmuxConnection)(agent, machine.ok ? machine.config : {});
105
+ if (!resolved.ok)
106
+ throw new Error(resolved.error);
107
+ const client = (options.createClient ?? ((connection) => (0, client_1.createCmuxClient)(connection, { agentName: agent })))(resolved.connection);
108
+ const escalate = options.escalate ?? ((escalation) => queueEscalation(agent, escalation, nowMs()));
109
+ const wake = options.wake ?? ((requestIds) => wakeForEscalations(agent, requestIds));
110
+ const schedule = options.schedule ?? ((fn, ms) => {
111
+ const timer = setTimeout(fn, ms);
112
+ return () => clearTimeout(timer);
113
+ });
114
+ const statePath = cmuxStatePath(agent);
115
+ const answers = { agentRoot: (0, identity_1.getAgentRoot)(agent), stateDir: path.dirname(statePath), client, now: nowMs, cmuxVersion: () => state.cmuxVersion };
116
+ const state = (0, attention_1.readCmuxState)(statePath, now());
117
+ state.connected = false;
118
+ let stopped = false;
119
+ let stream = null;
120
+ let attempt = 0;
121
+ let cancelReconnect = null;
122
+ let cancelPoll = null;
123
+ let cancelSave = null;
124
+ let feedChain = Promise.resolve();
125
+ let feedQueued = false;
126
+ let lastWritten = null;
127
+ /** Writes the state file only when something other than the timestamp changed. */
128
+ const save = () => {
129
+ cancelSave?.();
130
+ cancelSave = null;
131
+ const body = JSON.stringify({ ...state, updatedAt: null });
132
+ if (body === lastWritten)
133
+ return;
134
+ state.updatedAt = now();
135
+ // A failed write is retried by the next save; it never breaks the Feed check chain.
136
+ try {
137
+ (0, attention_1.writeCmuxState)(statePath, state);
138
+ lastWritten = body;
139
+ }
140
+ catch (error) {
141
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_state_write_error", message: "could not write the cmux sense state; the next save retries", meta: { agent, error: error.message } });
142
+ }
143
+ };
144
+ const saveSoon = () => {
145
+ cancelSave ??= schedule(save, SAVE_DELAY_MS);
146
+ };
147
+ /**
148
+ * One pending item: answered once when the code allows it, otherwise escalated with the reason.
149
+ * A failure while judging or answering escalates the item with the error; a failure to record the
150
+ * escalation leaves it unremembered, so the next check retries it. One item never blocks another.
151
+ */
152
+ async function handleItem(item) {
153
+ let reason;
154
+ let judgment = null;
155
+ try {
156
+ judgment = (0, answer_1.judgeFeedItem)(answers, item);
157
+ if (judgment.reply) {
158
+ const outcome = await (0, answer_1.answerOnce)(answers, item, judgment);
159
+ if (outcome === "replied_once" || outcome === "race") {
160
+ (0, attention_1.rememberEscalation)(state, item.requestId);
161
+ return null;
162
+ }
163
+ reason = outcome === "unconfirmed" ? "the sense's reply may or may not have reached cmux, and cmux does not show the request resolved by it" : "the sense's own reply did not go out";
164
+ }
165
+ else {
166
+ reason = judgment.reason;
167
+ }
168
+ }
169
+ catch (error) {
170
+ reason = `the sense could not judge or answer it: ${error.message}`;
171
+ }
172
+ const escalation = (0, attention_1.escalationMessage)(agent, item, (0, attention_1.surfaceForSession)(state, item.workstreamId), reason);
173
+ try {
174
+ await escalate(escalation);
175
+ }
176
+ catch (error) {
177
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_escalation_error", message: "could not record a cmux Feed request; the next check retries", meta: { agent, error: error.message } });
178
+ return null;
179
+ }
180
+ (0, attention_1.rememberEscalation)(state, item.requestId);
181
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_escalated", message: "brought a cmux Feed request to the agent", meta: { agent, kind: item.kind, source: item.source } });
182
+ // The escalation is queued either way; a decision log that cannot take the line is noted, not retried.
183
+ try {
184
+ if (judgment)
185
+ (0, answer_1.recordDecision)(answers, item, judgment, "escalated", reason);
186
+ }
187
+ catch (error) {
188
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_decision_log_error", message: "could not log a cmux escalation", meta: { agent, error: error.message } });
189
+ }
190
+ return escalation.requestId;
191
+ }
192
+ async function checkFeed() {
193
+ try {
194
+ const items = (0, attention_1.pendingFeedItems)(await client.call("feed.list", { pending_only: true }));
195
+ const queued = [];
196
+ for (const item of items.filter((entry) => !state.escalated.includes(entry.requestId))) {
197
+ const id = await handleItem(item);
198
+ if (id !== null)
199
+ queued.push(id);
200
+ }
201
+ // One wake per check pass: the private turn reads every queued escalation at once.
202
+ if (queued.length > 0)
203
+ await wake(queued);
204
+ }
205
+ catch (error) {
206
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_feed_check_error", message: "cmux Feed check failed; the next check retries", meta: { agent, error: error.message } });
207
+ }
208
+ save();
209
+ }
210
+ /** Bursts of waiting events share one queued `feed.list` read instead of stacking reads. */
211
+ const requestFeedCheck = () => {
212
+ if (feedQueued)
213
+ return;
214
+ feedQueued = true;
215
+ feedChain = feedChain.then(() => {
216
+ feedQueued = false;
217
+ return checkFeed();
218
+ });
219
+ };
220
+ /** Records the cmux version once per connection; observing works on any version. */
221
+ async function identify() {
222
+ try {
223
+ const result = await client.call("system.identify", {});
224
+ state.cmuxVersion = typeof result.version === "string" ? result.version : null;
225
+ }
226
+ catch {
227
+ state.cmuxVersion = null;
228
+ }
229
+ saveSoon();
230
+ }
231
+ const poll = () => {
232
+ cancelPoll = schedule(() => {
233
+ requestFeedCheck();
234
+ poll();
235
+ }, FEED_POLL_MS);
236
+ };
237
+ function connect() {
238
+ cancelReconnect = null;
239
+ stream = client.stream({ ...(state.seq !== null ? { after_seq: state.seq } : {}), categories: ["feed", "surface"] }, {
240
+ onFrame: (frame) => {
241
+ if (frame.type === "ack") {
242
+ attempt = 0;
243
+ const { reset } = (0, attention_1.applyAck)(state, frame);
244
+ state.connected = true;
245
+ state.lastError = null;
246
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_stream_connected", message: "cmux event stream connected", meta: { agent, reset } });
247
+ // The version gates answering, so the first Feed check on a connection waits for it.
248
+ void identify().then(requestFeedCheck);
249
+ return;
250
+ }
251
+ if (frame.type !== "event")
252
+ return;
253
+ if ((0, attention_1.applyEventFrame)(state, frame, now()).feedCheck)
254
+ requestFeedCheck();
255
+ saveSoon();
256
+ },
257
+ onClose: (error) => {
258
+ stream = null;
259
+ state.connected = false;
260
+ state.lastError = error?.message ?? null;
261
+ if (stopped)
262
+ return;
263
+ const delay = RECONNECT_DELAYS_MS[Math.min(attempt, RECONNECT_DELAYS_MS.length - 1)];
264
+ attempt += 1;
265
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.cmux_stream_closed", message: "cmux event stream closed; reconnecting", meta: { agent, delay, error: error?.message ?? null } });
266
+ save();
267
+ cancelReconnect = schedule(connect, delay);
268
+ },
269
+ });
270
+ }
271
+ connect();
272
+ poll();
273
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_sense_started", message: "cmux sense started", meta: { agent, auth: resolved.connection.auth.kind } });
274
+ return {
275
+ statePath,
276
+ async stop() {
277
+ stopped = true;
278
+ cancelReconnect?.();
279
+ cancelPoll?.();
280
+ stream?.close();
281
+ await feedChain;
282
+ state.connected = false;
283
+ save();
284
+ (0, runtime_1.emitNervesEvent)({ component: "senses", event: "senses.cmux_sense_stopped", message: "cmux sense stopped", meta: { agent } });
285
+ },
286
+ };
287
+ }
@@ -0,0 +1,77 @@
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
+ const agentArgIndex = process.argv.indexOf("--agent");
37
+ const agentName = agentArgIndex >= 0 ? process.argv[agentArgIndex + 1] : undefined;
38
+ if (!agentName) {
39
+ // eslint-disable-next-line no-console -- pre-boot guard: --agent check before imports
40
+ console.error("Missing required --agent <name> argument.\nUsage: node dist/senses/cmux-entry.js --agent ouroboros");
41
+ process.exit(1);
42
+ }
43
+ const runtime_logging_1 = require("../heart/daemon/runtime-logging");
44
+ const runtime_1 = require("../nerves/runtime");
45
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("cmux");
46
+ (0, runtime_1.emitNervesEvent)({
47
+ component: "senses",
48
+ event: "senses.entry_boot",
49
+ message: "booting cmux entrypoint",
50
+ meta: { entry: "cmux", agentName },
51
+ });
52
+ Promise.resolve().then(() => __importStar(require("../heart/runtime-credentials"))).then(async ({ readMachineRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, waitForRuntimeCredentialBootstrap }) => {
53
+ await waitForRuntimeCredentialBootstrap(agentName);
54
+ if (!readMachineRuntimeCredentialConfig(agentName).ok) {
55
+ const { loadOrCreateMachineIdentity } = await Promise.resolve().then(() => __importStar(require("../heart/machine-identity")));
56
+ await refreshMachineRuntimeCredentialConfig(agentName, loadOrCreateMachineIdentity().machineId, { preserveCachedOnFailure: true }).catch(() => undefined);
57
+ }
58
+ const { startCmuxSenseApp } = await Promise.resolve().then(() => __importStar(require("./cmux/sense")));
59
+ const app = await startCmuxSenseApp({ agentName: agentName });
60
+ const shutdown = () => {
61
+ void app.stop().finally(() => process.exit(0));
62
+ };
63
+ process.once("SIGTERM", shutdown);
64
+ process.once("SIGINT", shutdown);
65
+ })
66
+ .catch((error) => {
67
+ (0, runtime_1.emitNervesEvent)({
68
+ level: "error",
69
+ component: "senses",
70
+ event: "senses.entry_error",
71
+ message: "cmux entrypoint failed",
72
+ meta: { entry: "cmux", agentName, error: error instanceof Error ? error.message : String(error) },
73
+ });
74
+ // eslint-disable-next-line no-console -- fatal startup guard for sense process
75
+ console.error(error instanceof Error ? error.message : String(error));
76
+ process.exit(1);
77
+ });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.892",
3
+ "version": "0.1.0-alpha.894",
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.892",
9
+ "version": "0.1.0-alpha.894",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.892",
3
+ "version": "0.1.0-alpha.894",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },