@ouro.bot/cli 0.1.0-alpha.804 → 0.1.0-alpha.806

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,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FrontendSessionService = exports.FrontendSessionConflictError = exports.FrontendTurnConflictError = void 0;
4
+ const shared_turn_1 = require("../senses/shared-turn");
5
+ const runtime_1 = require("../nerves/runtime");
6
+ class FrontendTurnConflictError extends Error {
7
+ constructor(turnId) {
8
+ super(`frontend turn already exists: ${turnId}`);
9
+ this.name = "FrontendTurnConflictError";
10
+ }
11
+ }
12
+ exports.FrontendTurnConflictError = FrontendTurnConflictError;
13
+ class FrontendSessionConflictError extends Error {
14
+ constructor(sessionKey) {
15
+ super(`frontend session already has an active turn: ${sessionKey}`);
16
+ this.name = "FrontendSessionConflictError";
17
+ }
18
+ }
19
+ exports.FrontendSessionConflictError = FrontendSessionConflictError;
20
+ const JOURNALED_FRONTEND_EVENTS = new Set([
21
+ "assistant_delivery",
22
+ "tool_started",
23
+ "tool_completed",
24
+ "structured_output",
25
+ "error",
26
+ ]);
27
+ function required(value, field) {
28
+ const trimmed = value.trim();
29
+ if (!trimmed)
30
+ throw new Error(`${field} must be a non-empty string`);
31
+ return trimmed;
32
+ }
33
+ class FrontendSessionService {
34
+ activeTurns = new Map();
35
+ activeSessions = new Map();
36
+ listeners = new Set();
37
+ runner;
38
+ journal;
39
+ authority;
40
+ closed = false;
41
+ constructor(options = {}) {
42
+ this.runner = options.runner ?? shared_turn_1.runSenseTurn;
43
+ this.journal = options.journal ?? null;
44
+ this.authority = options.authority ?? null;
45
+ }
46
+ hasTurn(turnId) {
47
+ return this.activeTurns.has(turnId);
48
+ }
49
+ cancelTurn(turnId) {
50
+ const controller = this.activeTurns.get(turnId);
51
+ if (!controller || controller.signal.aborted)
52
+ return false;
53
+ controller.abort();
54
+ this.authority?.cancelTurn(turnId);
55
+ return true;
56
+ }
57
+ cancelAllTurns() {
58
+ let cancelled = 0;
59
+ for (const controller of this.activeTurns.values()) {
60
+ if (controller.signal.aborted)
61
+ continue;
62
+ controller.abort();
63
+ cancelled += 1;
64
+ }
65
+ return cancelled;
66
+ }
67
+ subscribe(listener) {
68
+ this.listeners.add(listener);
69
+ return () => this.listeners.delete(listener);
70
+ }
71
+ resolvePermission(requestId, optionId) {
72
+ return this.authority?.resolvePermission(required(requestId, "requestId"), required(optionId, "optionId")) ?? false;
73
+ }
74
+ close() {
75
+ if (this.closed)
76
+ return;
77
+ this.closed = true;
78
+ this.authority?.close();
79
+ }
80
+ loadSession(ref, options = {}) {
81
+ if (!this.journal)
82
+ throw new Error("frontend journal is unavailable");
83
+ const normalized = {
84
+ agent: required(ref.agent, "agent"),
85
+ friendId: required(ref.friendId, "friendId"),
86
+ sessionId: required(ref.sessionKey, "sessionKey"),
87
+ };
88
+ return {
89
+ ...this.journal.replay(normalized, options),
90
+ pendingPermissions: this.authority?.pendingPermissions({
91
+ agent: normalized.agent,
92
+ friendId: normalized.friendId,
93
+ sessionKey: normalized.sessionId,
94
+ }) ?? [],
95
+ };
96
+ }
97
+ prepareTurn(request) {
98
+ const turnId = required(request.turnId, "turnId");
99
+ const normalized = {
100
+ ...request,
101
+ turnId,
102
+ agent: required(request.agent, "agent"),
103
+ friendId: required(request.friendId, "friendId"),
104
+ sessionKey: required(request.sessionKey, "sessionKey"),
105
+ message: required(request.message, "message"),
106
+ };
107
+ if (this.activeTurns.has(turnId))
108
+ throw new FrontendTurnConflictError(turnId);
109
+ const sessionIdentity = [normalized.agent, normalized.friendId, normalized.channel, normalized.sessionKey].join("\0");
110
+ if (this.activeSessions.has(sessionIdentity))
111
+ throw new FrontendSessionConflictError(normalized.sessionKey);
112
+ const controller = new AbortController();
113
+ this.activeTurns.set(turnId, controller);
114
+ this.activeSessions.set(sessionIdentity, turnId);
115
+ return { request: normalized, sessionIdentity, controller, started: false };
116
+ }
117
+ async runTurn(request) {
118
+ return this.runPreparedTurn(this.prepareTurn(request));
119
+ }
120
+ async runPreparedTurn(prepared) {
121
+ if (prepared.started)
122
+ throw new Error(`frontend turn already started: ${prepared.request.turnId}`);
123
+ prepared.started = true;
124
+ const normalized = prepared.request;
125
+ const turnId = normalized.turnId;
126
+ const sessionIdentity = prepared.sessionIdentity;
127
+ const controller = prepared.controller;
128
+ try {
129
+ this.publish(normalized, "user_message", { text: normalized.message }, "user_message");
130
+ this.publish(normalized, "turn_started", {}, "turn_started");
131
+ const frontendEventSink = {
132
+ onEvent: (event) => this.publishFrontendEvent(normalized, event),
133
+ };
134
+ const approvalCoordinatorFactory = normalized.ephemeral
135
+ ? undefined
136
+ : this.authority?.approvalCoordinatorFactory({
137
+ request: normalized,
138
+ publish: (type, data, journalType) => this.publish(normalized, type, data, journalType),
139
+ });
140
+ let result = await this.runner({
141
+ agentName: normalized.agent,
142
+ friendId: normalized.friendId,
143
+ channel: normalized.channel,
144
+ sessionKey: normalized.sessionKey,
145
+ userMessage: normalized.message,
146
+ signal: controller.signal,
147
+ latencyMode: "live",
148
+ frontendEventSink,
149
+ ...(normalized.disableTools ? { disableTools: true } : {}),
150
+ ...(normalized.ephemeral ? { disablePersistence: true } : {}),
151
+ ...(approvalCoordinatorFactory ? { approvalCoordinatorFactory } : {}),
152
+ ...(normalized.runtimeMcpServers ? { runtimeMcpServers: normalized.runtimeMcpServers } : {}),
153
+ });
154
+ let suspensionRounds = 0;
155
+ while (result.turnOutcome === "suspended") {
156
+ if (!result.suspension)
157
+ throw new Error(`frontend turn ${turnId} omitted its approval suspension`);
158
+ if (!this.authority)
159
+ throw new Error(`frontend turn ${turnId} suspended without an authority runtime`);
160
+ suspensionRounds += 1;
161
+ if (suspensionRounds > 8)
162
+ throw new Error(`frontend turn ${turnId} exceeded its approval suspension limit`);
163
+ result = await this.authority.resumeApproval({
164
+ request: normalized,
165
+ suspension: result.suspension,
166
+ signal: controller.signal,
167
+ frontendEventSink,
168
+ });
169
+ }
170
+ if (!result.turnOutcome)
171
+ throw new Error(`frontend turn ${turnId} omitted its outcome`);
172
+ const frontendResult = {
173
+ turnId,
174
+ outcome: result.turnOutcome,
175
+ response: result.response,
176
+ sessionPath: result.sessionPath ?? null,
177
+ };
178
+ const terminalType = result.turnOutcome === "aborted"
179
+ ? "turn_cancelled"
180
+ : result.turnOutcome === "errored"
181
+ ? "turn_failed"
182
+ : "turn_completed";
183
+ this.publish(normalized, terminalType, { result: frontendResult }, terminalType);
184
+ return frontendResult;
185
+ }
186
+ catch (error) {
187
+ this.publish(normalized, "turn_failed", {
188
+ error: error instanceof Error ? error.message : String(error),
189
+ }, "turn_failed");
190
+ throw error;
191
+ }
192
+ finally {
193
+ this.activeTurns.delete(turnId);
194
+ this.activeSessions.delete(sessionIdentity);
195
+ }
196
+ }
197
+ publishFrontendEvent(request, event) {
198
+ const journalType = JOURNALED_FRONTEND_EVENTS.has(event.type)
199
+ ? event.type
200
+ : undefined;
201
+ this.publish(request, event.type, event.data, journalType);
202
+ }
203
+ publish(request, type, data, journalType) {
204
+ const journalRef = {
205
+ agent: request.agent,
206
+ friendId: request.friendId,
207
+ sessionId: request.sessionKey,
208
+ };
209
+ const journalSequence = !request.ephemeral && this.journal && journalType
210
+ ? this.journal.append(journalRef, { turnId: request.turnId, type: journalType, data }).sequence
211
+ : null;
212
+ const event = {
213
+ ...journalRef,
214
+ sessionKey: journalRef.sessionId,
215
+ turnId: request.turnId,
216
+ type,
217
+ data,
218
+ journalSequence,
219
+ ephemeral: request.ephemeral === true,
220
+ };
221
+ for (const listener of this.listeners) {
222
+ try {
223
+ listener(event);
224
+ }
225
+ catch (error) {
226
+ (0, runtime_1.emitNervesEvent)({
227
+ level: "warn",
228
+ component: "daemon",
229
+ event: "daemon.frontend_listener_error",
230
+ message: "frontend session listener failed",
231
+ meta: { type, error: error instanceof Error ? error.message : String(error) },
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ exports.FrontendSessionService = FrontendSessionService;
@@ -0,0 +1,154 @@
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.SocketFrontendClient = void 0;
37
+ const net = __importStar(require("node:net"));
38
+ const runtime_1 = require("../nerves/runtime");
39
+ class SocketFrontendClient {
40
+ socketPath;
41
+ pending = new Map();
42
+ listeners = new Set();
43
+ closeListeners = new Set();
44
+ socket = null;
45
+ connecting = null;
46
+ nextId = 1;
47
+ buffer = "";
48
+ constructor(socketPath) {
49
+ this.socketPath = socketPath;
50
+ }
51
+ async request(method, params) {
52
+ await this.connect();
53
+ const id = String(this.nextId++);
54
+ return new Promise((resolve, reject) => {
55
+ this.pending.set(id, { resolve, reject });
56
+ this.socket.write(`${JSON.stringify({ protocolVersion: 1, id, method, params })}\n`);
57
+ });
58
+ }
59
+ onEvent(listener) {
60
+ this.listeners.add(listener);
61
+ return () => this.listeners.delete(listener);
62
+ }
63
+ onClose(listener) {
64
+ this.closeListeners.add(listener);
65
+ return () => this.closeListeners.delete(listener);
66
+ }
67
+ close() {
68
+ this.failPending(new Error("frontend socket closed"));
69
+ this.socket?.destroy();
70
+ this.socket = null;
71
+ }
72
+ connect() {
73
+ if (this.socket && !this.socket.destroyed)
74
+ return Promise.resolve();
75
+ if (this.connecting)
76
+ return this.connecting;
77
+ this.connecting = new Promise((resolve, reject) => {
78
+ const socket = net.createConnection(this.socketPath);
79
+ const fail = (error) => {
80
+ this.connecting = null;
81
+ reject(error);
82
+ };
83
+ socket.once("error", fail);
84
+ socket.once("connect", () => {
85
+ socket.removeListener("error", fail);
86
+ socket.on("error", (error) => {
87
+ this.failPending(error);
88
+ for (const listener of this.closeListeners)
89
+ listener(error);
90
+ });
91
+ socket.on("data", (chunk) => this.handleData(chunk.toString("utf8")));
92
+ socket.on("close", () => {
93
+ const error = new Error("frontend socket closed");
94
+ this.failPending(error);
95
+ for (const listener of this.closeListeners)
96
+ listener(error);
97
+ if (this.socket === socket)
98
+ this.socket = null;
99
+ });
100
+ this.socket = socket;
101
+ this.connecting = null;
102
+ (0, runtime_1.emitNervesEvent)({
103
+ component: "heart",
104
+ event: "heart.frontend_socket_client_connected",
105
+ message: "frontend socket client connected",
106
+ });
107
+ resolve();
108
+ });
109
+ });
110
+ return this.connecting;
111
+ }
112
+ handleData(chunk) {
113
+ this.buffer += chunk;
114
+ for (;;) {
115
+ const newline = this.buffer.indexOf("\n");
116
+ if (newline < 0)
117
+ return;
118
+ const line = this.buffer.slice(0, newline).trim();
119
+ this.buffer = this.buffer.slice(newline + 1);
120
+ if (!line)
121
+ continue;
122
+ let frame;
123
+ try {
124
+ frame = JSON.parse(line);
125
+ }
126
+ catch {
127
+ this.failPending(new Error("invalid frontend socket response"));
128
+ continue;
129
+ }
130
+ if (typeof frame.id === "string") {
131
+ const pending = this.pending.get(frame.id);
132
+ if (!pending)
133
+ continue;
134
+ this.pending.delete(frame.id);
135
+ if (frame.ok === false) {
136
+ pending.reject(new Error(String(frame.error?.message ?? "frontend request failed")));
137
+ }
138
+ else {
139
+ pending.resolve(frame.result);
140
+ }
141
+ }
142
+ else if (typeof frame.event === "string") {
143
+ for (const listener of this.listeners)
144
+ listener(frame);
145
+ }
146
+ }
147
+ }
148
+ failPending(error) {
149
+ for (const pending of this.pending.values())
150
+ pending.reject(error);
151
+ this.pending.clear();
152
+ }
153
+ }
154
+ exports.SocketFrontendClient = SocketFrontendClient;