happy-imou-cloud 2.0.10 → 2.0.11
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/dist/BaseReasoningProcessor-5ACv9gKu.mjs +320 -0
- package/dist/BaseReasoningProcessor-COrRWyn0.cjs +323 -0
- package/dist/ProviderSelectionHandler-BljOLMQn.mjs +261 -0
- package/dist/ProviderSelectionHandler-DBGobhGZ.cjs +265 -0
- package/dist/{api-BjxmW-0W.mjs → api-BcWf5v4i.mjs} +174 -46
- package/dist/{api-DUE5TJBE.cjs → api-Ye-rPX6s.cjs} +174 -46
- package/dist/{command-Df7u5eAT.cjs → command-BK93nizl.cjs} +6 -6
- package/dist/{command-ComOeFLY.mjs → command-nOI80Mnm.mjs} +6 -6
- package/dist/{index-CzvgPwr1.mjs → index-DnsqY6I_.mjs} +189 -60
- package/dist/{index-Cuvfa15L.cjs → index-J7QKJ8lc.cjs} +194 -61
- package/dist/index.cjs +8 -8
- package/dist/index.mjs +8 -8
- package/dist/lib.cjs +2 -3
- package/dist/lib.d.cts +2 -0
- package/dist/lib.d.mts +2 -0
- package/dist/lib.mjs +2 -3
- package/dist/{persistence-BxP6Jw1f.mjs → persistence-BMa6cyw9.mjs} +2 -3
- package/dist/{persistence-D7JtnrYA.cjs → persistence-xK5CKhbn.cjs} +2 -3
- package/dist/registerKillSessionHandler-CH6yN0eG.cjs +1198 -0
- package/dist/registerKillSessionHandler-Dxwg4L4J.mjs +1179 -0
- package/dist/{runClaude-C1W_Nw0C.cjs → runClaude-BMHlBny_.cjs} +884 -398
- package/dist/{runClaude-B_fTMxc4.mjs → runClaude-cQ-UT0Ke.mjs} +882 -396
- package/dist/{runCodex-CUgOiIEz.mjs → runCodex-AnJUPIhX.mjs} +155 -578
- package/dist/{runCodex-D2VCWVEK.cjs → runCodex-roOSOWWL.cjs} +158 -583
- package/dist/{runGemini-2_FEtJYa.mjs → runGemini-BGo_0mzA.mjs} +142 -387
- package/dist/{runGemini-CiGnjflq.cjs → runGemini-Cg6Zbqlz.cjs} +145 -390
- package/package.json +9 -8
- package/scripts/e2e/fake-codex-acp-agent.mjs +139 -0
- package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -0
- package/scripts/release-smoke.mjs +23 -6
- package/dist/names-CicSgRNg.mjs +0 -625
- package/dist/names-yJNZoTv_.cjs +0 -636
- package/dist/registerKillSessionHandler-ARQrPvnT.mjs +0 -454
- package/dist/registerKillSessionHandler-DCMFiXyA.cjs +0 -459
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
import { i as initialMachineMetadata, e as projectPath, R as RuntimeShell, h as resolveCanonicalToolNameV2, f as formatDisplayMessage } from './index-DnsqY6I_.mjs';
|
|
2
|
+
import { readSettings } from './persistence-BMa6cyw9.mjs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { c as configuration, p as packageJson, l as logger } from './api-BcWf5v4i.mjs';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import 'axios';
|
|
9
|
+
import 'node:events';
|
|
10
|
+
import 'socket.io-client';
|
|
11
|
+
import 'tweetnacl';
|
|
12
|
+
import 'fs/promises';
|
|
13
|
+
import 'path';
|
|
14
|
+
import 'node:child_process';
|
|
15
|
+
import 'expo-server-sdk';
|
|
16
|
+
import 'chalk';
|
|
17
|
+
import './types-CiliQpqS.mjs';
|
|
18
|
+
|
|
19
|
+
class MissingMachineIdError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "MissingMachineIdError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function ensureManagedProviderMachine(opts) {
|
|
26
|
+
const settings = await (opts.loadSettings ?? readSettings)();
|
|
27
|
+
const machineId = settings?.machineId;
|
|
28
|
+
if (!machineId) {
|
|
29
|
+
throw new MissingMachineIdError(opts.missingMachineIdMessage);
|
|
30
|
+
}
|
|
31
|
+
await opts.api.getOrCreateMachine({
|
|
32
|
+
machineId,
|
|
33
|
+
metadata: opts.machineMetadata ?? initialMachineMetadata
|
|
34
|
+
});
|
|
35
|
+
return machineId;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createSessionMetadata(opts) {
|
|
39
|
+
const state = {
|
|
40
|
+
controlledByUser: false
|
|
41
|
+
};
|
|
42
|
+
const metadata = {
|
|
43
|
+
path: process.cwd(),
|
|
44
|
+
host: os.hostname(),
|
|
45
|
+
version: packageJson.version,
|
|
46
|
+
os: os.platform(),
|
|
47
|
+
machineId: opts.machineId,
|
|
48
|
+
homeDir: os.homedir(),
|
|
49
|
+
happyHomeDir: configuration.happyCloudHomeDir,
|
|
50
|
+
happyLibDir: projectPath(),
|
|
51
|
+
happyToolsDir: resolve(projectPath(), "tools", "unpacked"),
|
|
52
|
+
startedFromDaemon: opts.startedBy === "daemon",
|
|
53
|
+
hostPid: process.pid,
|
|
54
|
+
startedBy: opts.startedBy || "terminal",
|
|
55
|
+
lifecycleState: "running",
|
|
56
|
+
lifecycleStateSince: Date.now(),
|
|
57
|
+
flavor: opts.flavor
|
|
58
|
+
};
|
|
59
|
+
return { state, metadata };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function launchRuntimeHandleWithFactoryResult(opts) {
|
|
63
|
+
const shell = opts.shell ?? new RuntimeShell();
|
|
64
|
+
let factoryResult;
|
|
65
|
+
const session = await shell.launch({
|
|
66
|
+
provider: opts.provider,
|
|
67
|
+
cwd: opts.cwd,
|
|
68
|
+
env: opts.env,
|
|
69
|
+
createBackend: (factoryOpts) => {
|
|
70
|
+
factoryResult = opts.createBackendResult(factoryOpts);
|
|
71
|
+
return factoryResult.backend;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (factoryResult === void 0) {
|
|
75
|
+
throw new Error(`Runtime provider "${opts.provider}" did not create a backend result`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
session,
|
|
79
|
+
factoryResult
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function inferToolResultError(result) {
|
|
84
|
+
if (!result || typeof result !== "object") {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const record = result;
|
|
88
|
+
if (record.isError === true || record.is_error === true) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (typeof record.error === "string" && record.error.trim().length > 0) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (record.success === false) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const status = typeof record.status === "string" ? record.status.toLowerCase() : "";
|
|
98
|
+
return status === "failed" || status === "cancelled" || status === "error" || status === "denied" || status === "aborted";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildToolHappierMetaV2(input) {
|
|
102
|
+
return {
|
|
103
|
+
...input,
|
|
104
|
+
v: 2
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function attachToolHappierMetaV2(value, meta) {
|
|
108
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...value,
|
|
113
|
+
_happier: buildToolHappierMetaV2(meta)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getDefaultExecToolName(provider) {
|
|
118
|
+
switch (provider) {
|
|
119
|
+
case "claude":
|
|
120
|
+
return "ClaudeBash";
|
|
121
|
+
case "codex":
|
|
122
|
+
return "CodexBash";
|
|
123
|
+
case "gemini":
|
|
124
|
+
return "GeminiBash";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function getDefaultPatchToolName(provider) {
|
|
128
|
+
switch (provider) {
|
|
129
|
+
case "claude":
|
|
130
|
+
return "ClaudePatch";
|
|
131
|
+
case "codex":
|
|
132
|
+
return "CodexPatch";
|
|
133
|
+
case "gemini":
|
|
134
|
+
return "GeminiPatch";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function attachToolMeta(provider, rawToolName, value) {
|
|
138
|
+
const canonicalToolName = resolveCanonicalToolNameV2(rawToolName);
|
|
139
|
+
return attachToolHappierMetaV2(value, {
|
|
140
|
+
v: 2,
|
|
141
|
+
protocol: "acp",
|
|
142
|
+
provider,
|
|
143
|
+
rawToolName,
|
|
144
|
+
canonicalToolName
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function forwardAgentMessageToProviderSession(msg, options) {
|
|
148
|
+
const createId = options.createId ?? randomUUID;
|
|
149
|
+
const toolResultType = options.toolResultType ?? "tool-result";
|
|
150
|
+
switch (msg.type) {
|
|
151
|
+
case "tool-call": {
|
|
152
|
+
options.send({
|
|
153
|
+
type: "tool-call",
|
|
154
|
+
name: resolveCanonicalToolNameV2(msg.toolName),
|
|
155
|
+
callId: msg.callId,
|
|
156
|
+
input: attachToolMeta(options.provider, msg.toolName, msg.args),
|
|
157
|
+
id: createId()
|
|
158
|
+
});
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
case "tool-result": {
|
|
162
|
+
options.send({
|
|
163
|
+
type: toolResultType,
|
|
164
|
+
callId: msg.callId,
|
|
165
|
+
output: attachToolMeta(options.provider, msg.toolName, msg.result),
|
|
166
|
+
id: createId(),
|
|
167
|
+
isError: inferToolResultError(msg.result)
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
case "fs-edit": {
|
|
172
|
+
options.send({
|
|
173
|
+
type: "file-edit",
|
|
174
|
+
description: msg.description,
|
|
175
|
+
diff: msg.diff,
|
|
176
|
+
filePath: msg.path || "unknown",
|
|
177
|
+
id: createId()
|
|
178
|
+
});
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
case "terminal-output": {
|
|
182
|
+
options.send({
|
|
183
|
+
type: "terminal-output",
|
|
184
|
+
data: msg.data,
|
|
185
|
+
callId: msg.callId ?? createId()
|
|
186
|
+
});
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
case "permission-request": {
|
|
190
|
+
const payload = msg.payload && typeof msg.payload === "object" ? msg.payload : {};
|
|
191
|
+
options.send({
|
|
192
|
+
type: "permission-request",
|
|
193
|
+
permissionId: msg.id,
|
|
194
|
+
toolName: typeof payload.toolName === "string" ? payload.toolName : msg.reason || "unknown",
|
|
195
|
+
description: msg.reason || (typeof payload.toolName === "string" ? payload.toolName : ""),
|
|
196
|
+
options: payload
|
|
197
|
+
});
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
case "exec-approval-request": {
|
|
201
|
+
const rawToolName = options.execToolName ?? getDefaultExecToolName(options.provider);
|
|
202
|
+
const { call_id, type: _type, ...inputs } = msg;
|
|
203
|
+
options.send({
|
|
204
|
+
type: "tool-call",
|
|
205
|
+
name: resolveCanonicalToolNameV2(rawToolName),
|
|
206
|
+
callId: call_id,
|
|
207
|
+
input: attachToolMeta(options.provider, rawToolName, inputs),
|
|
208
|
+
id: createId()
|
|
209
|
+
});
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
case "patch-apply-begin": {
|
|
213
|
+
const rawToolName = options.patchToolName ?? getDefaultPatchToolName(options.provider);
|
|
214
|
+
options.send({
|
|
215
|
+
type: "tool-call",
|
|
216
|
+
name: resolveCanonicalToolNameV2(rawToolName),
|
|
217
|
+
callId: msg.call_id,
|
|
218
|
+
input: attachToolMeta(options.provider, rawToolName, {
|
|
219
|
+
auto_approved: msg.auto_approved,
|
|
220
|
+
changes: msg.changes
|
|
221
|
+
}),
|
|
222
|
+
id: createId()
|
|
223
|
+
});
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
case "patch-apply-end": {
|
|
227
|
+
const rawToolName = options.patchToolName ?? getDefaultPatchToolName(options.provider);
|
|
228
|
+
options.send({
|
|
229
|
+
type: toolResultType,
|
|
230
|
+
callId: msg.call_id,
|
|
231
|
+
output: attachToolMeta(options.provider, rawToolName, {
|
|
232
|
+
stdout: msg.stdout,
|
|
233
|
+
stderr: msg.stderr,
|
|
234
|
+
success: msg.success
|
|
235
|
+
}),
|
|
236
|
+
id: createId(),
|
|
237
|
+
isError: !msg.success
|
|
238
|
+
});
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
case "token-count": {
|
|
242
|
+
const { type: _type, ...payload } = msg;
|
|
243
|
+
options.send({
|
|
244
|
+
type: "token_count",
|
|
245
|
+
...payload,
|
|
246
|
+
id: createId()
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
default:
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function closeProviderSession(session, opts = {}) {
|
|
256
|
+
let firstError;
|
|
257
|
+
const captureError = (error) => {
|
|
258
|
+
if (firstError === void 0) {
|
|
259
|
+
firstError = error;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
if (opts.archiveReason) {
|
|
263
|
+
try {
|
|
264
|
+
session.updateMetadata((currentMetadata) => ({
|
|
265
|
+
...currentMetadata,
|
|
266
|
+
lifecycleState: "archived",
|
|
267
|
+
lifecycleStateSince: Date.now(),
|
|
268
|
+
archivedBy: opts.archivedBy ?? "cli",
|
|
269
|
+
archiveReason: opts.archiveReason
|
|
270
|
+
}));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
captureError(error);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
session.sendSessionDeath();
|
|
277
|
+
} catch (error) {
|
|
278
|
+
captureError(error);
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await session.flush();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
captureError(error);
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
await session.close();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
captureError(error);
|
|
289
|
+
}
|
|
290
|
+
if (firstError !== void 0) {
|
|
291
|
+
throw firstError;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createAbortError() {
|
|
296
|
+
const error = new Error("Operation aborted");
|
|
297
|
+
error.name = "AbortError";
|
|
298
|
+
return error;
|
|
299
|
+
}
|
|
300
|
+
async function waitForResponseCompleteWithAbort(backend, signal, timeoutMs = 12e4) {
|
|
301
|
+
if (!backend.waitForResponseComplete) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (signal.aborted) {
|
|
305
|
+
throw createAbortError();
|
|
306
|
+
}
|
|
307
|
+
await new Promise((resolve, reject) => {
|
|
308
|
+
const onAbort = () => reject(createAbortError());
|
|
309
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
310
|
+
backend.waitForResponseComplete(timeoutMs).then(resolve).catch(reject).finally(() => {
|
|
311
|
+
signal.removeEventListener("abort", onAbort);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function supportsAgentStateUpdateEvents(sessionClient) {
|
|
317
|
+
return typeof sessionClient.once === "function" && typeof sessionClient.off === "function";
|
|
318
|
+
}
|
|
319
|
+
async function syncControlledByUserState(sessionClient, controlledByUser) {
|
|
320
|
+
if (!supportsAgentStateUpdateEvents(sessionClient)) {
|
|
321
|
+
sessionClient.updateAgentState((currentState) => ({
|
|
322
|
+
...currentState,
|
|
323
|
+
controlledByUser
|
|
324
|
+
}));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
await new Promise((resolve) => {
|
|
328
|
+
let settled = false;
|
|
329
|
+
const handleUpdated = () => {
|
|
330
|
+
if (settled) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
settled = true;
|
|
334
|
+
clearTimeout(timeout);
|
|
335
|
+
sessionClient.off("agent-state-updated", handleUpdated);
|
|
336
|
+
resolve();
|
|
337
|
+
};
|
|
338
|
+
const timeout = setTimeout(() => {
|
|
339
|
+
if (settled) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
settled = true;
|
|
343
|
+
sessionClient.off("agent-state-updated", handleUpdated);
|
|
344
|
+
resolve();
|
|
345
|
+
}, 1500);
|
|
346
|
+
sessionClient.once("agent-state-updated", handleUpdated);
|
|
347
|
+
sessionClient.updateAgentState((currentState) => ({
|
|
348
|
+
...currentState,
|
|
349
|
+
controlledByUser
|
|
350
|
+
}));
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const DEFAULT_MAX_MESSAGES = 200;
|
|
355
|
+
const DEFAULT_MAX_CHARACTERS = 1e5;
|
|
356
|
+
const DEFAULT_MAX_MESSAGE_CHARACTERS = 8e3;
|
|
357
|
+
const TRUNCATION_PREFIX = "...\n";
|
|
358
|
+
class MessageBuffer {
|
|
359
|
+
messages = [];
|
|
360
|
+
listeners = [];
|
|
361
|
+
nextId = 1;
|
|
362
|
+
enabled;
|
|
363
|
+
maxMessages;
|
|
364
|
+
maxCharacters;
|
|
365
|
+
maxMessageCharacters;
|
|
366
|
+
constructor(options = {}) {
|
|
367
|
+
this.enabled = options.enabled ?? true;
|
|
368
|
+
this.maxMessages = Math.max(1, options.maxMessages ?? DEFAULT_MAX_MESSAGES);
|
|
369
|
+
this.maxCharacters = Math.max(1, options.maxCharacters ?? DEFAULT_MAX_CHARACTERS);
|
|
370
|
+
this.maxMessageCharacters = Math.max(1, options.maxMessageCharacters ?? DEFAULT_MAX_MESSAGE_CHARACTERS);
|
|
371
|
+
}
|
|
372
|
+
addMessage(content, type = "assistant") {
|
|
373
|
+
const id = `msg-${this.nextId++}`;
|
|
374
|
+
if (!this.enabled) {
|
|
375
|
+
return id;
|
|
376
|
+
}
|
|
377
|
+
const message = {
|
|
378
|
+
id,
|
|
379
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
380
|
+
content: this.normalizeContent(content),
|
|
381
|
+
type
|
|
382
|
+
};
|
|
383
|
+
this.messages.push(message);
|
|
384
|
+
this.trimMessages();
|
|
385
|
+
this.notifyListeners();
|
|
386
|
+
return message.id;
|
|
387
|
+
}
|
|
388
|
+
updateMessage(id, content, options = {}) {
|
|
389
|
+
if (!this.enabled) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
const index = this.messages.findIndex((message) => message.id === id);
|
|
393
|
+
if (index === -1) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
const normalizedContent = this.normalizeContent(content);
|
|
397
|
+
const previous = this.messages[index];
|
|
398
|
+
this.messages[index] = {
|
|
399
|
+
...previous,
|
|
400
|
+
content: this.truncateContent(options.mode === "replace" ? normalizedContent : previous.content + normalizedContent)
|
|
401
|
+
};
|
|
402
|
+
this.trimMessages();
|
|
403
|
+
this.notifyListeners();
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
removeMessage(id) {
|
|
407
|
+
if (!this.enabled) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
const index = this.messages.findIndex((message) => message.id === id);
|
|
411
|
+
if (index === -1) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
this.messages.splice(index, 1);
|
|
415
|
+
this.notifyListeners();
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Update the last message of a specific type by appending content to it
|
|
420
|
+
* Useful for streaming responses where deltas should accumulate in one message
|
|
421
|
+
*/
|
|
422
|
+
updateLastMessage(contentDelta, type = "assistant") {
|
|
423
|
+
if (!this.enabled) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const normalizedDelta = this.normalizeContent(contentDelta);
|
|
427
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
428
|
+
if (this.messages[i].type === type) {
|
|
429
|
+
const oldMessage = this.messages[i];
|
|
430
|
+
const updatedMessage = {
|
|
431
|
+
...oldMessage,
|
|
432
|
+
content: this.truncateContent(oldMessage.content + normalizedDelta)
|
|
433
|
+
};
|
|
434
|
+
this.messages[i] = updatedMessage;
|
|
435
|
+
this.trimMessages();
|
|
436
|
+
this.notifyListeners();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
this.addMessage(normalizedDelta, type);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Remove the last message of a specific type
|
|
444
|
+
* Useful for removing placeholder messages like "Thinking..." when actual response starts
|
|
445
|
+
*/
|
|
446
|
+
removeLastMessage(type) {
|
|
447
|
+
if (!this.enabled) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
451
|
+
if (this.messages[i].type === type) {
|
|
452
|
+
this.messages.splice(i, 1);
|
|
453
|
+
this.notifyListeners();
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
getMessages() {
|
|
460
|
+
return [...this.messages];
|
|
461
|
+
}
|
|
462
|
+
clear() {
|
|
463
|
+
this.messages = [];
|
|
464
|
+
this.nextId = 1;
|
|
465
|
+
if (!this.enabled) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
this.notifyListeners();
|
|
469
|
+
}
|
|
470
|
+
onUpdate(listener) {
|
|
471
|
+
if (!this.enabled) {
|
|
472
|
+
return () => {
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
this.listeners.push(listener);
|
|
476
|
+
return () => {
|
|
477
|
+
const index = this.listeners.indexOf(listener);
|
|
478
|
+
if (index > -1) {
|
|
479
|
+
this.listeners.splice(index, 1);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
normalizeContent(content) {
|
|
484
|
+
return this.truncateContent(formatDisplayMessage(content));
|
|
485
|
+
}
|
|
486
|
+
truncateContent(content) {
|
|
487
|
+
if (content.length <= this.maxMessageCharacters) {
|
|
488
|
+
return content;
|
|
489
|
+
}
|
|
490
|
+
const tailLength = Math.max(0, this.maxMessageCharacters - TRUNCATION_PREFIX.length);
|
|
491
|
+
return `${TRUNCATION_PREFIX}${content.slice(content.length - tailLength)}`;
|
|
492
|
+
}
|
|
493
|
+
trimMessages() {
|
|
494
|
+
while (this.messages.length > this.maxMessages) {
|
|
495
|
+
this.messages.shift();
|
|
496
|
+
}
|
|
497
|
+
let totalCharacters = this.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
498
|
+
while (totalCharacters > this.maxCharacters && this.messages.length > 1) {
|
|
499
|
+
const removed = this.messages.shift();
|
|
500
|
+
if (removed) {
|
|
501
|
+
totalCharacters -= removed.content.length;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
notifyListeners() {
|
|
506
|
+
const messages = this.getMessages();
|
|
507
|
+
this.listeners.forEach((listener) => listener(messages));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
let ConversationHistory$1 = class ConversationHistory {
|
|
512
|
+
messages = [];
|
|
513
|
+
maxMessages;
|
|
514
|
+
maxCharacters;
|
|
515
|
+
constructor(options = {}) {
|
|
516
|
+
this.maxMessages = options.maxMessages ?? 20;
|
|
517
|
+
this.maxCharacters = options.maxCharacters ?? 5e4;
|
|
518
|
+
}
|
|
519
|
+
isDuplicate(role, content) {
|
|
520
|
+
if (this.messages.length === 0) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
524
|
+
const message = this.messages[index];
|
|
525
|
+
if (message.role !== role) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const normalizedIncoming = content.trim().replace(/\s+/g, " ");
|
|
529
|
+
const normalizedExisting = message.content.replace(/\s+/g, " ");
|
|
530
|
+
return normalizedIncoming === normalizedExisting;
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
addUserMessage(content) {
|
|
535
|
+
this.addMessage("user", content);
|
|
536
|
+
}
|
|
537
|
+
addAssistantMessage(content) {
|
|
538
|
+
this.addMessage("assistant", content);
|
|
539
|
+
}
|
|
540
|
+
hasHistory() {
|
|
541
|
+
return this.messages.length > 0;
|
|
542
|
+
}
|
|
543
|
+
size() {
|
|
544
|
+
return this.messages.length;
|
|
545
|
+
}
|
|
546
|
+
clear() {
|
|
547
|
+
this.messages = [];
|
|
548
|
+
logger.debug("[ConversationHistory] History cleared");
|
|
549
|
+
}
|
|
550
|
+
getContextForNewSession(prefixMessage = "Continue from the prior session using the conversation below as context.") {
|
|
551
|
+
if (this.messages.length === 0) {
|
|
552
|
+
return "";
|
|
553
|
+
}
|
|
554
|
+
const formattedMessages = this.messages.map((message) => {
|
|
555
|
+
const role = message.role === "user" ? "User" : "Assistant";
|
|
556
|
+
const content = message.content.length > 2e3 ? `${message.content.slice(0, 2e3)}... [truncated]` : message.content;
|
|
557
|
+
return `${role}: ${content}`;
|
|
558
|
+
}).join("\n\n");
|
|
559
|
+
return [
|
|
560
|
+
"[PREVIOUS CONVERSATION CONTEXT]",
|
|
561
|
+
prefixMessage,
|
|
562
|
+
"",
|
|
563
|
+
formattedMessages,
|
|
564
|
+
"",
|
|
565
|
+
"[END OF PREVIOUS CONTEXT]",
|
|
566
|
+
""
|
|
567
|
+
].join("\n");
|
|
568
|
+
}
|
|
569
|
+
getSummary() {
|
|
570
|
+
const totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
571
|
+
const userCount = this.messages.filter((message) => message.role === "user").length;
|
|
572
|
+
const assistantCount = this.messages.filter((message) => message.role === "assistant").length;
|
|
573
|
+
return `${this.messages.length} messages (${userCount} user, ${assistantCount} assistant), ${totalChars} chars`;
|
|
574
|
+
}
|
|
575
|
+
addMessage(role, content) {
|
|
576
|
+
const trimmedContent = content.trim();
|
|
577
|
+
if (!trimmedContent) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (this.isDuplicate(role, trimmedContent)) {
|
|
581
|
+
logger.debug(`[ConversationHistory] Skipping duplicate ${role} message (${trimmedContent.length} chars)`);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
this.messages.push({
|
|
585
|
+
role,
|
|
586
|
+
content: trimmedContent,
|
|
587
|
+
timestamp: Date.now()
|
|
588
|
+
});
|
|
589
|
+
this.trimHistory();
|
|
590
|
+
logger.debug(`[ConversationHistory] Added ${role} message (${trimmedContent.length} chars), total: ${this.messages.length}`);
|
|
591
|
+
}
|
|
592
|
+
trimHistory() {
|
|
593
|
+
while (this.messages.length > this.maxMessages) {
|
|
594
|
+
this.messages.shift();
|
|
595
|
+
}
|
|
596
|
+
let totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
|
|
597
|
+
while (totalChars > this.maxCharacters && this.messages.length > 1) {
|
|
598
|
+
const removed = this.messages.shift();
|
|
599
|
+
if (removed) {
|
|
600
|
+
totalChars -= removed.content.length;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const INTERACTION_SUPERSEDED_ERROR = "Interaction superseded by new user message";
|
|
607
|
+
const INTERACTION_TIMED_OUT_ERROR = "Interaction timed out waiting for user response";
|
|
608
|
+
const DEFAULT_INTERACTION_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
609
|
+
function getPendingInteractionTimeoutMs() {
|
|
610
|
+
const raw = Number(process.env.HAPPY_INTERACTION_TIMEOUT_MS);
|
|
611
|
+
if (Number.isFinite(raw) && raw > 0) {
|
|
612
|
+
return raw;
|
|
613
|
+
}
|
|
614
|
+
return DEFAULT_INTERACTION_TIMEOUT_MS;
|
|
615
|
+
}
|
|
616
|
+
class BasePermissionHandler {
|
|
617
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
618
|
+
session;
|
|
619
|
+
isResetting = false;
|
|
620
|
+
constructor(session) {
|
|
621
|
+
this.session = session;
|
|
622
|
+
this.setupRpcHandler();
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Update the session reference (used after offline reconnection swaps sessions).
|
|
626
|
+
* This is critical for avoiding stale session references after onSessionSwap.
|
|
627
|
+
*/
|
|
628
|
+
updateSession(newSession) {
|
|
629
|
+
logger.debug(`${this.getLogPrefix()} Session reference updated`);
|
|
630
|
+
this.session = newSession;
|
|
631
|
+
this.setupRpcHandler();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Setup RPC handler for permission responses.
|
|
635
|
+
*/
|
|
636
|
+
setupRpcHandler() {
|
|
637
|
+
this.session.rpcHandlerManager.registerHandler(
|
|
638
|
+
"permission",
|
|
639
|
+
async (response) => {
|
|
640
|
+
const pending = this.pendingRequests.get(response.id);
|
|
641
|
+
if (!pending) {
|
|
642
|
+
logger.debug(`${this.getLogPrefix()} Permission request not found or already resolved`);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
this.pendingRequests.delete(response.id);
|
|
646
|
+
this.clearPendingRequestTimeout(pending);
|
|
647
|
+
const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
|
|
648
|
+
pending.resolve(result);
|
|
649
|
+
this.session.updateAgentState((currentState) => {
|
|
650
|
+
const request = currentState.requests?.[response.id];
|
|
651
|
+
if (!request) return currentState;
|
|
652
|
+
const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
|
|
653
|
+
let res = {
|
|
654
|
+
...currentState,
|
|
655
|
+
requests: remainingRequests,
|
|
656
|
+
completedRequests: {
|
|
657
|
+
...currentState.completedRequests,
|
|
658
|
+
[response.id]: {
|
|
659
|
+
...request,
|
|
660
|
+
completedAt: Date.now(),
|
|
661
|
+
status: response.approved ? "approved" : "denied",
|
|
662
|
+
decision: result.decision
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
return res;
|
|
667
|
+
});
|
|
668
|
+
logger.debug(`${this.getLogPrefix()} Permission ${response.approved ? "approved" : "denied"} for ${pending.toolName}`);
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Add a pending request to the agent state.
|
|
674
|
+
*/
|
|
675
|
+
addPendingRequestToState(toolCallId, toolName, input) {
|
|
676
|
+
this.session.updateAgentState((currentState) => ({
|
|
677
|
+
...currentState,
|
|
678
|
+
requests: {
|
|
679
|
+
...currentState.requests,
|
|
680
|
+
[toolCallId]: {
|
|
681
|
+
tool: toolName,
|
|
682
|
+
arguments: input,
|
|
683
|
+
createdAt: Date.now()
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}));
|
|
687
|
+
}
|
|
688
|
+
registerPendingRequest(toolCallId, toolName, input, logSuffix = "") {
|
|
689
|
+
return new Promise((resolve, reject) => {
|
|
690
|
+
const pending = {
|
|
691
|
+
resolve,
|
|
692
|
+
reject,
|
|
693
|
+
toolName,
|
|
694
|
+
input
|
|
695
|
+
};
|
|
696
|
+
pending.timeoutHandle = setTimeout(() => {
|
|
697
|
+
this.handlePendingRequestTimeout(toolCallId, pending);
|
|
698
|
+
}, getPendingInteractionTimeoutMs());
|
|
699
|
+
this.pendingRequests.set(toolCallId, pending);
|
|
700
|
+
this.addPendingRequestToState(toolCallId, toolName, input);
|
|
701
|
+
logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})${logSuffix}`);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
hasPendingRequests() {
|
|
705
|
+
return this.pendingRequests.size > 0;
|
|
706
|
+
}
|
|
707
|
+
supersedePendingRequests(reason = INTERACTION_SUPERSEDED_ERROR) {
|
|
708
|
+
const pendingSnapshot = Array.from(this.pendingRequests.entries());
|
|
709
|
+
if (pendingSnapshot.length === 0) {
|
|
710
|
+
return 0;
|
|
711
|
+
}
|
|
712
|
+
this.pendingRequests.clear();
|
|
713
|
+
const completedAt = Date.now();
|
|
714
|
+
for (const [, pending] of pendingSnapshot) {
|
|
715
|
+
this.clearPendingRequestTimeout(pending);
|
|
716
|
+
pending.resolve({ decision: "abort" });
|
|
717
|
+
}
|
|
718
|
+
this.session.updateAgentState((currentState) => {
|
|
719
|
+
const requests = { ...currentState.requests || {} };
|
|
720
|
+
const completedRequests = { ...currentState.completedRequests || {} };
|
|
721
|
+
for (const [id, request] of Object.entries(requests)) {
|
|
722
|
+
if (request.requestKind === "selection") {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
completedRequests[id] = {
|
|
726
|
+
...request,
|
|
727
|
+
completedAt,
|
|
728
|
+
status: "denied",
|
|
729
|
+
reason,
|
|
730
|
+
decision: "abort",
|
|
731
|
+
requestKind: request.requestKind || "permission"
|
|
732
|
+
};
|
|
733
|
+
delete requests[id];
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
...currentState,
|
|
737
|
+
requests,
|
|
738
|
+
completedRequests
|
|
739
|
+
};
|
|
740
|
+
});
|
|
741
|
+
logger.debug(`${this.getLogPrefix()} Superseded ${pendingSnapshot.length} pending permission request(s)`);
|
|
742
|
+
return pendingSnapshot.length;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Reset state for new sessions.
|
|
746
|
+
* This method is idempotent - safe to call multiple times.
|
|
747
|
+
*/
|
|
748
|
+
reset() {
|
|
749
|
+
if (this.isResetting) {
|
|
750
|
+
logger.debug(`${this.getLogPrefix()} Reset already in progress, skipping`);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
this.isResetting = true;
|
|
754
|
+
try {
|
|
755
|
+
const pendingSnapshot = Array.from(this.pendingRequests.entries());
|
|
756
|
+
this.pendingRequests.clear();
|
|
757
|
+
for (const [id, pending] of pendingSnapshot) {
|
|
758
|
+
try {
|
|
759
|
+
this.clearPendingRequestTimeout(pending);
|
|
760
|
+
pending.reject(new Error("Session reset"));
|
|
761
|
+
} catch (err) {
|
|
762
|
+
logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
this.session.updateAgentState((currentState) => {
|
|
766
|
+
const pendingRequests = currentState.requests || {};
|
|
767
|
+
const completedRequests = { ...currentState.completedRequests };
|
|
768
|
+
for (const [id, request] of Object.entries(pendingRequests)) {
|
|
769
|
+
completedRequests[id] = {
|
|
770
|
+
...request,
|
|
771
|
+
completedAt: Date.now(),
|
|
772
|
+
status: "canceled",
|
|
773
|
+
reason: "Session reset"
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
...currentState,
|
|
778
|
+
requests: {},
|
|
779
|
+
completedRequests
|
|
780
|
+
};
|
|
781
|
+
});
|
|
782
|
+
logger.debug(`${this.getLogPrefix()} Permission handler reset`);
|
|
783
|
+
} finally {
|
|
784
|
+
this.isResetting = false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
clearPendingRequestTimeout(pending) {
|
|
788
|
+
if (pending?.timeoutHandle) {
|
|
789
|
+
clearTimeout(pending.timeoutHandle);
|
|
790
|
+
pending.timeoutHandle = void 0;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
handlePendingRequestTimeout(toolCallId, pending) {
|
|
794
|
+
const active = this.pendingRequests.get(toolCallId);
|
|
795
|
+
if (!active || active !== pending) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
this.pendingRequests.delete(toolCallId);
|
|
799
|
+
this.clearPendingRequestTimeout(active);
|
|
800
|
+
active.resolve({ decision: "abort" });
|
|
801
|
+
this.session.updateAgentState((currentState) => {
|
|
802
|
+
const request = currentState.requests?.[toolCallId] || {
|
|
803
|
+
tool: active.toolName,
|
|
804
|
+
arguments: active.input,
|
|
805
|
+
createdAt: Date.now(),
|
|
806
|
+
requestKind: "permission"
|
|
807
|
+
};
|
|
808
|
+
const { [toolCallId]: _, ...remainingRequests } = currentState.requests || {};
|
|
809
|
+
return {
|
|
810
|
+
...currentState,
|
|
811
|
+
requests: remainingRequests,
|
|
812
|
+
completedRequests: {
|
|
813
|
+
...currentState.completedRequests,
|
|
814
|
+
[toolCallId]: {
|
|
815
|
+
...request,
|
|
816
|
+
completedAt: Date.now(),
|
|
817
|
+
status: "canceled",
|
|
818
|
+
reason: INTERACTION_TIMED_OUT_ERROR,
|
|
819
|
+
decision: "abort",
|
|
820
|
+
requestKind: request.requestKind || "permission"
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
this.session.sendSessionEvent({
|
|
826
|
+
type: "message",
|
|
827
|
+
message: "Pending interaction timed out waiting for a response. Send a new message to continue."
|
|
828
|
+
});
|
|
829
|
+
logger.debug(`${this.getLogPrefix()} Permission request timed out for ${active.toolName} (${toolCallId})`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
class MessageQueue2 {
|
|
834
|
+
queue = [];
|
|
835
|
+
// Made public for testing
|
|
836
|
+
waiter = null;
|
|
837
|
+
closed = false;
|
|
838
|
+
onMessageHandler = null;
|
|
839
|
+
modeHasher;
|
|
840
|
+
constructor(modeHasher, onMessageHandler = null) {
|
|
841
|
+
this.modeHasher = modeHasher;
|
|
842
|
+
this.onMessageHandler = onMessageHandler;
|
|
843
|
+
logger.debug(`[MessageQueue2] Initialized`);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Set a handler that will be called when a message arrives
|
|
847
|
+
*/
|
|
848
|
+
setOnMessage(handler) {
|
|
849
|
+
this.onMessageHandler = handler;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Push a message to the queue with a mode.
|
|
853
|
+
*/
|
|
854
|
+
push(message, mode) {
|
|
855
|
+
if (this.closed) {
|
|
856
|
+
throw new Error("Cannot push to closed queue");
|
|
857
|
+
}
|
|
858
|
+
const modeHash = this.modeHasher(mode);
|
|
859
|
+
logger.debug(`[MessageQueue2] push() called with mode hash: ${modeHash}`);
|
|
860
|
+
this.queue.push({
|
|
861
|
+
message,
|
|
862
|
+
mode,
|
|
863
|
+
modeHash,
|
|
864
|
+
isolate: false
|
|
865
|
+
});
|
|
866
|
+
if (this.onMessageHandler) {
|
|
867
|
+
this.onMessageHandler(message, mode);
|
|
868
|
+
}
|
|
869
|
+
if (this.waiter) {
|
|
870
|
+
logger.debug(`[MessageQueue2] Notifying waiter`);
|
|
871
|
+
const waiter = this.waiter;
|
|
872
|
+
this.waiter = null;
|
|
873
|
+
waiter(true);
|
|
874
|
+
}
|
|
875
|
+
logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Push a message immediately without batching delay.
|
|
879
|
+
* Does not clear the queue or enforce isolation.
|
|
880
|
+
*/
|
|
881
|
+
pushImmediate(message, mode) {
|
|
882
|
+
if (this.closed) {
|
|
883
|
+
throw new Error("Cannot push to closed queue");
|
|
884
|
+
}
|
|
885
|
+
const modeHash = this.modeHasher(mode);
|
|
886
|
+
logger.debug(`[MessageQueue2] pushImmediate() called with mode hash: ${modeHash}`);
|
|
887
|
+
this.queue.push({
|
|
888
|
+
message,
|
|
889
|
+
mode,
|
|
890
|
+
modeHash,
|
|
891
|
+
isolate: false
|
|
892
|
+
});
|
|
893
|
+
if (this.onMessageHandler) {
|
|
894
|
+
this.onMessageHandler(message, mode);
|
|
895
|
+
}
|
|
896
|
+
if (this.waiter) {
|
|
897
|
+
logger.debug(`[MessageQueue2] Notifying waiter for immediate message`);
|
|
898
|
+
const waiter = this.waiter;
|
|
899
|
+
this.waiter = null;
|
|
900
|
+
waiter(true);
|
|
901
|
+
}
|
|
902
|
+
logger.debug(`[MessageQueue2] pushImmediate() completed. Queue size: ${this.queue.length}`);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Push a message that must be processed in complete isolation.
|
|
906
|
+
* Clears any pending messages and ensures this message is never batched with others.
|
|
907
|
+
* Used for special commands that require dedicated processing.
|
|
908
|
+
*/
|
|
909
|
+
pushIsolateAndClear(message, mode) {
|
|
910
|
+
if (this.closed) {
|
|
911
|
+
throw new Error("Cannot push to closed queue");
|
|
912
|
+
}
|
|
913
|
+
const modeHash = this.modeHasher(mode);
|
|
914
|
+
logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
|
|
915
|
+
this.queue = [];
|
|
916
|
+
this.queue.push({
|
|
917
|
+
message,
|
|
918
|
+
mode,
|
|
919
|
+
modeHash,
|
|
920
|
+
isolate: true
|
|
921
|
+
});
|
|
922
|
+
if (this.onMessageHandler) {
|
|
923
|
+
this.onMessageHandler(message, mode);
|
|
924
|
+
}
|
|
925
|
+
if (this.waiter) {
|
|
926
|
+
logger.debug(`[MessageQueue2] Notifying waiter for isolated message`);
|
|
927
|
+
const waiter = this.waiter;
|
|
928
|
+
this.waiter = null;
|
|
929
|
+
waiter(true);
|
|
930
|
+
}
|
|
931
|
+
logger.debug(`[MessageQueue2] pushIsolateAndClear() completed. Queue size: ${this.queue.length}`);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Push a message to the beginning of the queue with a mode.
|
|
935
|
+
*/
|
|
936
|
+
unshift(message, mode) {
|
|
937
|
+
if (this.closed) {
|
|
938
|
+
throw new Error("Cannot unshift to closed queue");
|
|
939
|
+
}
|
|
940
|
+
const modeHash = this.modeHasher(mode);
|
|
941
|
+
logger.debug(`[MessageQueue2] unshift() called with mode hash: ${modeHash}`);
|
|
942
|
+
this.queue.unshift({
|
|
943
|
+
message,
|
|
944
|
+
mode,
|
|
945
|
+
modeHash,
|
|
946
|
+
isolate: false
|
|
947
|
+
});
|
|
948
|
+
if (this.onMessageHandler) {
|
|
949
|
+
this.onMessageHandler(message, mode);
|
|
950
|
+
}
|
|
951
|
+
if (this.waiter) {
|
|
952
|
+
logger.debug(`[MessageQueue2] Notifying waiter`);
|
|
953
|
+
const waiter = this.waiter;
|
|
954
|
+
this.waiter = null;
|
|
955
|
+
waiter(true);
|
|
956
|
+
}
|
|
957
|
+
logger.debug(`[MessageQueue2] unshift() completed. Queue size: ${this.queue.length}`);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Reset the queue - clears all messages and resets to empty state
|
|
961
|
+
*/
|
|
962
|
+
reset() {
|
|
963
|
+
logger.debug(`[MessageQueue2] reset() called. Clearing ${this.queue.length} messages`);
|
|
964
|
+
this.queue = [];
|
|
965
|
+
this.closed = false;
|
|
966
|
+
this.waiter = null;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Close the queue - no more messages can be pushed
|
|
970
|
+
*/
|
|
971
|
+
close() {
|
|
972
|
+
logger.debug(`[MessageQueue2] close() called`);
|
|
973
|
+
this.closed = true;
|
|
974
|
+
if (this.waiter) {
|
|
975
|
+
const waiter = this.waiter;
|
|
976
|
+
this.waiter = null;
|
|
977
|
+
waiter(false);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Check if the queue is closed
|
|
982
|
+
*/
|
|
983
|
+
isClosed() {
|
|
984
|
+
return this.closed;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Get the current queue size
|
|
988
|
+
*/
|
|
989
|
+
size() {
|
|
990
|
+
return this.queue.length;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Wait for messages and return all messages with the same mode as a single string
|
|
994
|
+
* Returns { message: string, mode: T } or null if aborted/closed
|
|
995
|
+
*/
|
|
996
|
+
async waitForMessagesAndGetAsString(abortSignal) {
|
|
997
|
+
if (this.queue.length > 0) {
|
|
998
|
+
return this.collectBatch();
|
|
999
|
+
}
|
|
1000
|
+
if (this.closed || abortSignal?.aborted) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
const hasMessages = await this.waitForMessages(abortSignal);
|
|
1004
|
+
if (!hasMessages) {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
return this.collectBatch();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Collect a batch of messages with the same mode, respecting isolation requirements
|
|
1011
|
+
*/
|
|
1012
|
+
collectBatch() {
|
|
1013
|
+
if (this.queue.length === 0) {
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
const firstItem = this.queue[0];
|
|
1017
|
+
const sameModeMessages = [];
|
|
1018
|
+
let mode = firstItem.mode;
|
|
1019
|
+
let isolate = firstItem.isolate ?? false;
|
|
1020
|
+
const targetModeHash = firstItem.modeHash;
|
|
1021
|
+
if (firstItem.isolate) {
|
|
1022
|
+
const item = this.queue.shift();
|
|
1023
|
+
sameModeMessages.push(item.message);
|
|
1024
|
+
logger.debug(`[MessageQueue2] Collected isolated message with mode hash: ${targetModeHash}`);
|
|
1025
|
+
} else {
|
|
1026
|
+
while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash && !this.queue[0].isolate) {
|
|
1027
|
+
const item = this.queue.shift();
|
|
1028
|
+
sameModeMessages.push(item.message);
|
|
1029
|
+
}
|
|
1030
|
+
logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
|
|
1031
|
+
}
|
|
1032
|
+
const combinedMessage = sameModeMessages.join("\n");
|
|
1033
|
+
return {
|
|
1034
|
+
message: combinedMessage,
|
|
1035
|
+
mode,
|
|
1036
|
+
hash: targetModeHash,
|
|
1037
|
+
isolate
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Wait for messages to arrive
|
|
1042
|
+
*/
|
|
1043
|
+
waitForMessages(abortSignal) {
|
|
1044
|
+
return new Promise((resolve) => {
|
|
1045
|
+
let abortHandler = null;
|
|
1046
|
+
if (abortSignal) {
|
|
1047
|
+
abortHandler = () => {
|
|
1048
|
+
logger.debug("[MessageQueue2] Wait aborted");
|
|
1049
|
+
if (this.waiter === waiterFunc) {
|
|
1050
|
+
this.waiter = null;
|
|
1051
|
+
}
|
|
1052
|
+
resolve(false);
|
|
1053
|
+
};
|
|
1054
|
+
abortSignal.addEventListener("abort", abortHandler);
|
|
1055
|
+
}
|
|
1056
|
+
const waiterFunc = (hasMessages) => {
|
|
1057
|
+
if (abortHandler && abortSignal) {
|
|
1058
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
1059
|
+
}
|
|
1060
|
+
resolve(hasMessages);
|
|
1061
|
+
};
|
|
1062
|
+
if (this.queue.length > 0) {
|
|
1063
|
+
if (abortHandler && abortSignal) {
|
|
1064
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
1065
|
+
}
|
|
1066
|
+
resolve(true);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (this.closed || abortSignal?.aborted) {
|
|
1070
|
+
if (abortHandler && abortSignal) {
|
|
1071
|
+
abortSignal.removeEventListener("abort", abortHandler);
|
|
1072
|
+
}
|
|
1073
|
+
resolve(false);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
this.waiter = waiterFunc;
|
|
1077
|
+
logger.debug("[MessageQueue2] Waiting for messages...");
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function deterministicStringify(obj, options = {}) {
|
|
1083
|
+
const {
|
|
1084
|
+
undefinedBehavior = "omit",
|
|
1085
|
+
sortArrays = false,
|
|
1086
|
+
replacer,
|
|
1087
|
+
includeSymbols = false
|
|
1088
|
+
} = options;
|
|
1089
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1090
|
+
function processValue(value, key) {
|
|
1091
|
+
if (replacer && key !== void 0) {
|
|
1092
|
+
value = replacer(key, value);
|
|
1093
|
+
}
|
|
1094
|
+
if (value === null) return null;
|
|
1095
|
+
if (value === void 0) {
|
|
1096
|
+
switch (undefinedBehavior) {
|
|
1097
|
+
case "omit":
|
|
1098
|
+
return void 0;
|
|
1099
|
+
case "null":
|
|
1100
|
+
return null;
|
|
1101
|
+
case "throw":
|
|
1102
|
+
throw new Error(`Undefined value at key: ${key}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
1106
|
+
return value;
|
|
1107
|
+
}
|
|
1108
|
+
if (value instanceof Date) {
|
|
1109
|
+
return value.toISOString();
|
|
1110
|
+
}
|
|
1111
|
+
if (value instanceof RegExp) {
|
|
1112
|
+
return value.toString();
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof value === "function") {
|
|
1115
|
+
return void 0;
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof value === "symbol") {
|
|
1118
|
+
return includeSymbols ? value.toString() : void 0;
|
|
1119
|
+
}
|
|
1120
|
+
if (typeof value === "bigint") {
|
|
1121
|
+
return value.toString() + "n";
|
|
1122
|
+
}
|
|
1123
|
+
if (seen.has(value)) {
|
|
1124
|
+
throw new Error("Circular reference detected");
|
|
1125
|
+
}
|
|
1126
|
+
seen.add(value);
|
|
1127
|
+
if (Array.isArray(value)) {
|
|
1128
|
+
const processed2 = value.map((item, index) => processValue(item, String(index))).filter((item) => item !== void 0);
|
|
1129
|
+
if (sortArrays) {
|
|
1130
|
+
processed2.sort((a, b) => {
|
|
1131
|
+
const aStr = JSON.stringify(processValue(a));
|
|
1132
|
+
const bStr = JSON.stringify(processValue(b));
|
|
1133
|
+
return aStr.localeCompare(bStr);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
seen.delete(value);
|
|
1137
|
+
return processed2;
|
|
1138
|
+
}
|
|
1139
|
+
if (value.constructor === Object || value.constructor === void 0) {
|
|
1140
|
+
const processed2 = {};
|
|
1141
|
+
const keys = Object.keys(value).sort();
|
|
1142
|
+
for (const k of keys) {
|
|
1143
|
+
const processedValue = processValue(value[k], k);
|
|
1144
|
+
if (processedValue !== void 0) {
|
|
1145
|
+
processed2[k] = processedValue;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
seen.delete(value);
|
|
1149
|
+
return processed2;
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
const plain = { ...value };
|
|
1153
|
+
seen.delete(value);
|
|
1154
|
+
return processValue(plain, key);
|
|
1155
|
+
} catch {
|
|
1156
|
+
seen.delete(value);
|
|
1157
|
+
return String(value);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
const processed = processValue(obj);
|
|
1161
|
+
return JSON.stringify(processed);
|
|
1162
|
+
}
|
|
1163
|
+
function hashObject(obj, options, encoding = "hex") {
|
|
1164
|
+
const jsonString = deterministicStringify(obj, options);
|
|
1165
|
+
return createHash("sha256").update(jsonString).digest(encoding);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function registerKillSessionHandler(rpcHandlerManager, killThisHappy) {
|
|
1169
|
+
rpcHandlerManager.registerHandler("killSession", async () => {
|
|
1170
|
+
logger.debug("Kill session request received");
|
|
1171
|
+
void killThisHappy();
|
|
1172
|
+
return {
|
|
1173
|
+
success: true,
|
|
1174
|
+
message: "Killing happy-cli process"
|
|
1175
|
+
};
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
export { BasePermissionHandler as B, ConversationHistory$1 as C, INTERACTION_SUPERSEDED_ERROR as I, MissingMachineIdError as M, INTERACTION_TIMED_OUT_ERROR as a, MessageQueue2 as b, createSessionMetadata as c, MessageBuffer as d, ensureManagedProviderMachine as e, closeProviderSession as f, getPendingInteractionTimeoutMs as g, hashObject as h, inferToolResultError as i, forwardAgentMessageToProviderSession as j, launchRuntimeHandleWithFactoryResult as l, registerKillSessionHandler as r, syncControlledByUserState as s, waitForResponseCompleteWithAbort as w };
|