happy-imou-cloud 1.1.7 → 2.0.1

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.
Files changed (38) hide show
  1. package/dist/{setupOfflineReconnection-obypStdD.cjs → BaseReasoningProcessor-01KqA3Kz.cjs} +221 -90
  2. package/dist/{setupOfflineReconnection-ndObLZk0.mjs → BaseReasoningProcessor-DQE2l7Xu.mjs} +218 -90
  3. package/dist/{types-BXyraW9R.mjs → api-B5Ui8Fw0.mjs} +263 -173
  4. package/dist/{types-BSTmyv9d.cjs → api-B8v4tczT.cjs} +287 -195
  5. package/dist/command-BfIuJmeo.mjs +51 -0
  6. package/dist/command-D8yNlaDo.cjs +54 -0
  7. package/dist/future-Dq4Ha1Dn.cjs +24 -0
  8. package/dist/future-xRdLl3vf.mjs +22 -0
  9. package/dist/{index-CUmYqKWt.mjs → index-BByhFIIq.mjs} +5981 -7180
  10. package/dist/{index-DVI4b0mv.cjs → index-BOqJ9hwi.cjs} +6004 -7189
  11. package/dist/index.cjs +19 -21
  12. package/dist/index.mjs +19 -21
  13. package/dist/lib.cjs +3 -2
  14. package/dist/lib.d.cts +18 -0
  15. package/dist/lib.d.mts +18 -0
  16. package/dist/lib.mjs +2 -1
  17. package/dist/{persistence-BRH9F6RS.cjs → persistence-C33AMdtv.cjs} +10 -6
  18. package/dist/{persistence-BGsuPqaO.mjs → persistence-CzpZpiL3.mjs} +8 -4
  19. package/dist/registerKillSessionHandler-BkzQulD9.cjs +435 -0
  20. package/dist/registerKillSessionHandler-BtSK7IOa.mjs +430 -0
  21. package/dist/runClaude-CNVufgZb.cjs +3282 -0
  22. package/dist/runClaude-C_WLfM6c.mjs +3279 -0
  23. package/dist/runCodex-8eWjTPJr.mjs +1663 -0
  24. package/dist/runCodex-Dzy8anlX.cjs +1668 -0
  25. package/dist/{runGemini-C3dDtGOV.cjs → runGemini-CgsVKP7m.cjs} +40 -1377
  26. package/dist/{runGemini-B-EK_BJQ.mjs → runGemini-nbr0mm-S.mjs} +27 -1364
  27. package/dist/types-CiliQpqS.mjs +52 -0
  28. package/dist/types-DVk3crez.cjs +54 -0
  29. package/package.json +12 -11
  30. package/scripts/devtools/README.md +9 -0
  31. package/scripts/devtools/generate-mock-credentials.ts +94 -0
  32. package/scripts/env-wrapper.cjs +11 -11
  33. package/scripts/release-smoke.mjs +62 -0
  34. package/scripts/setup-dev.cjs +4 -4
  35. package/dist/config-BQNrtwRY.cjs +0 -183
  36. package/dist/config-Dn99YH37.mjs +0 -173
  37. package/dist/runCodex-Cez8cuIh.cjs +0 -1143
  38. package/dist/runCodex-X0BfjcZH.mjs +0 -1140
@@ -1,11 +1,123 @@
1
1
  'use strict';
2
2
 
3
- var api = require('./types-BSTmyv9d.cjs');
4
- var node_crypto = require('node:crypto');
5
3
  var os = require('node:os');
6
- var node_path = require('node:path');
7
- var index = require('./index-DVI4b0mv.cjs');
4
+ var path = require('node:path');
5
+ var api = require('./api-B8v4tczT.cjs');
6
+ var index = require('./index-BOqJ9hwi.cjs');
7
+ var node_events = require('node:events');
8
+ var node_crypto = require('node:crypto');
9
+
10
+ function createSessionMetadata(opts) {
11
+ const state = {
12
+ controlledByUser: false
13
+ };
14
+ const metadata = {
15
+ path: process.cwd(),
16
+ host: os.hostname(),
17
+ version: api.packageJson.version,
18
+ os: os.platform(),
19
+ machineId: opts.machineId,
20
+ homeDir: os.homedir(),
21
+ happyHomeDir: api.configuration.happyCloudHomeDir,
22
+ happyLibDir: index.projectPath(),
23
+ happyToolsDir: path.resolve(index.projectPath(), "tools", "unpacked"),
24
+ startedFromDaemon: opts.startedBy === "daemon",
25
+ hostPid: process.pid,
26
+ startedBy: opts.startedBy || "terminal",
27
+ lifecycleState: "running",
28
+ lifecycleStateSince: Date.now(),
29
+ flavor: opts.flavor
30
+ };
31
+ return { state, metadata };
32
+ }
8
33
 
34
+ function createOfflineSessionStub(sessionTag) {
35
+ const emitter = new node_events.EventEmitter();
36
+ let metadata = null;
37
+ let agentState = null;
38
+ const stub = {
39
+ sessionId: `offline-${sessionTag}`,
40
+ sendCodexMessage: () => {
41
+ },
42
+ sendAgentMessage: () => {
43
+ },
44
+ sendClaudeSessionMessage: () => {
45
+ },
46
+ keepAlive: () => {
47
+ },
48
+ sendSessionEvent: () => {
49
+ },
50
+ sendSessionDeath: () => {
51
+ },
52
+ updateLifecycleState: () => {
53
+ },
54
+ requestControlTransfer: async () => {
55
+ },
56
+ flush: async () => {
57
+ },
58
+ close: async () => {
59
+ },
60
+ updateMetadata: (handler) => {
61
+ metadata = handler(metadata || {});
62
+ emitter.emit("metadata-updated", metadata);
63
+ },
64
+ updateAgentState: (handler) => {
65
+ agentState = handler(agentState || {});
66
+ emitter.emit("agent-state-updated", agentState);
67
+ },
68
+ getMetadataSnapshot: () => metadata,
69
+ getAgentStateSnapshot: () => agentState,
70
+ waitForMetadataUpdate: async () => metadata,
71
+ onUserMessage: () => {
72
+ },
73
+ rpcHandlerManager: {
74
+ registerHandler: () => {
75
+ }
76
+ },
77
+ on: emitter.on.bind(emitter),
78
+ once: emitter.once.bind(emitter),
79
+ off: emitter.off.bind(emitter),
80
+ emit: emitter.emit.bind(emitter)
81
+ };
82
+ return stub;
83
+ }
84
+
85
+ function setupOfflineReconnection(opts) {
86
+ const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
87
+ let session;
88
+ let reconnectionHandle = null;
89
+ if (!response) {
90
+ session = createOfflineSessionStub(sessionTag);
91
+ reconnectionHandle = api.startOfflineReconnection({
92
+ serverUrl: api.configuration.serverUrl,
93
+ onReconnected: async () => {
94
+ const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
95
+ if (!resp) throw new Error("Server unavailable");
96
+ const realSession = api$1.sessionSyncClient(resp);
97
+ onSessionSwap(realSession);
98
+ return realSession;
99
+ },
100
+ onNotify: (msg) => {
101
+ console.log(msg);
102
+ }
103
+ });
104
+ return { session, reconnectionHandle, isOffline: true };
105
+ } else {
106
+ session = api$1.sessionSyncClient(response);
107
+ return { session, reconnectionHandle: null, isOffline: false };
108
+ }
109
+ }
110
+
111
+ const INTERACTION_SUPERSEDED_ERROR = "Interaction superseded by new user message";
112
+ const INTERACTION_TIMED_OUT_ERROR = "Interaction timed out waiting for user response";
113
+ const DEFAULT_INTERACTION_TIMEOUT_MS = 2 * 60 * 1e3;
114
+ function getPendingInteractionTimeoutMs() {
115
+ const raw = Number(process.env.HAPPY_INTERACTION_TIMEOUT_MS);
116
+ if (Number.isFinite(raw) && raw > 0) {
117
+ return raw;
118
+ }
119
+ return DEFAULT_INTERACTION_TIMEOUT_MS;
120
+ }
9
121
  class BasePermissionHandler {
10
122
  pendingRequests = /* @__PURE__ */ new Map();
11
123
  session;
@@ -36,6 +148,7 @@ class BasePermissionHandler {
36
148
  return;
37
149
  }
38
150
  this.pendingRequests.delete(response.id);
151
+ this.clearPendingRequestTimeout(pending);
39
152
  const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
40
153
  pending.resolve(result);
41
154
  this.session.updateAgentState((currentState) => {
@@ -77,6 +190,62 @@ class BasePermissionHandler {
77
190
  }
78
191
  }));
79
192
  }
193
+ registerPendingRequest(toolCallId, toolName, input, logSuffix = "") {
194
+ return new Promise((resolve, reject) => {
195
+ const pending = {
196
+ resolve,
197
+ reject,
198
+ toolName,
199
+ input
200
+ };
201
+ pending.timeoutHandle = setTimeout(() => {
202
+ this.handlePendingRequestTimeout(toolCallId, pending);
203
+ }, getPendingInteractionTimeoutMs());
204
+ this.pendingRequests.set(toolCallId, pending);
205
+ this.addPendingRequestToState(toolCallId, toolName, input);
206
+ api.logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})${logSuffix}`);
207
+ });
208
+ }
209
+ hasPendingRequests() {
210
+ return this.pendingRequests.size > 0;
211
+ }
212
+ supersedePendingRequests(reason = INTERACTION_SUPERSEDED_ERROR) {
213
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
214
+ if (pendingSnapshot.length === 0) {
215
+ return 0;
216
+ }
217
+ this.pendingRequests.clear();
218
+ const completedAt = Date.now();
219
+ for (const [, pending] of pendingSnapshot) {
220
+ this.clearPendingRequestTimeout(pending);
221
+ pending.resolve({ decision: "abort" });
222
+ }
223
+ this.session.updateAgentState((currentState) => {
224
+ const requests = { ...currentState.requests || {} };
225
+ const completedRequests = { ...currentState.completedRequests || {} };
226
+ for (const [id, request] of Object.entries(requests)) {
227
+ if (request.requestKind === "selection") {
228
+ continue;
229
+ }
230
+ completedRequests[id] = {
231
+ ...request,
232
+ completedAt,
233
+ status: "denied",
234
+ reason,
235
+ decision: "abort",
236
+ requestKind: request.requestKind || "permission"
237
+ };
238
+ delete requests[id];
239
+ }
240
+ return {
241
+ ...currentState,
242
+ requests,
243
+ completedRequests
244
+ };
245
+ });
246
+ api.logger.debug(`${this.getLogPrefix()} Superseded ${pendingSnapshot.length} pending permission request(s)`);
247
+ return pendingSnapshot.length;
248
+ }
80
249
  /**
81
250
  * Reset state for new sessions.
82
251
  * This method is idempotent - safe to call multiple times.
@@ -92,6 +261,7 @@ class BasePermissionHandler {
92
261
  this.pendingRequests.clear();
93
262
  for (const [id, pending] of pendingSnapshot) {
94
263
  try {
264
+ this.clearPendingRequestTimeout(pending);
95
265
  pending.reject(new Error("Session reset"));
96
266
  } catch (err) {
97
267
  api.logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
@@ -119,6 +289,50 @@ class BasePermissionHandler {
119
289
  this.isResetting = false;
120
290
  }
121
291
  }
292
+ clearPendingRequestTimeout(pending) {
293
+ if (pending?.timeoutHandle) {
294
+ clearTimeout(pending.timeoutHandle);
295
+ pending.timeoutHandle = void 0;
296
+ }
297
+ }
298
+ handlePendingRequestTimeout(toolCallId, pending) {
299
+ const active = this.pendingRequests.get(toolCallId);
300
+ if (!active || active !== pending) {
301
+ return;
302
+ }
303
+ this.pendingRequests.delete(toolCallId);
304
+ this.clearPendingRequestTimeout(active);
305
+ active.resolve({ decision: "abort" });
306
+ this.session.updateAgentState((currentState) => {
307
+ const request = currentState.requests?.[toolCallId] || {
308
+ tool: active.toolName,
309
+ arguments: active.input,
310
+ createdAt: Date.now(),
311
+ requestKind: "permission"
312
+ };
313
+ const { [toolCallId]: _, ...remainingRequests } = currentState.requests || {};
314
+ return {
315
+ ...currentState,
316
+ requests: remainingRequests,
317
+ completedRequests: {
318
+ ...currentState.completedRequests,
319
+ [toolCallId]: {
320
+ ...request,
321
+ completedAt: Date.now(),
322
+ status: "canceled",
323
+ reason: INTERACTION_TIMED_OUT_ERROR,
324
+ decision: "abort",
325
+ requestKind: request.requestKind || "permission"
326
+ }
327
+ }
328
+ };
329
+ });
330
+ this.session.sendSessionEvent({
331
+ type: "message",
332
+ message: "Pending interaction timed out waiting for a response. Send a new message to continue."
333
+ });
334
+ api.logger.debug(`${this.getLogPrefix()} Permission request timed out for ${active.toolName} (${toolCallId})`);
335
+ }
122
336
  }
123
337
 
124
338
  class BaseReasoningProcessor {
@@ -315,93 +529,10 @@ class BaseReasoningProcessor {
315
529
  }
316
530
  }
317
531
 
318
- function createSessionMetadata(opts) {
319
- const state = {
320
- controlledByUser: false
321
- };
322
- const metadata = {
323
- path: process.cwd(),
324
- host: os.hostname(),
325
- version: api.packageJson.version,
326
- os: os.platform(),
327
- machineId: opts.machineId,
328
- homeDir: os.homedir(),
329
- happyHomeDir: api.configuration.happyCloudHomeDir,
330
- happyLibDir: index.projectPath(),
331
- happyToolsDir: node_path.resolve(index.projectPath(), "tools", "unpacked"),
332
- startedFromDaemon: opts.startedBy === "daemon",
333
- hostPid: process.pid,
334
- startedBy: opts.startedBy || "terminal",
335
- lifecycleState: "running",
336
- lifecycleStateSince: Date.now(),
337
- flavor: opts.flavor
338
- };
339
- return { state, metadata };
340
- }
341
-
342
- function createOfflineSessionStub(sessionTag) {
343
- return {
344
- sessionId: `offline-${sessionTag}`,
345
- sendCodexMessage: () => {
346
- },
347
- sendAgentMessage: () => {
348
- },
349
- sendClaudeSessionMessage: () => {
350
- },
351
- keepAlive: () => {
352
- },
353
- sendSessionEvent: () => {
354
- },
355
- sendSessionDeath: () => {
356
- },
357
- updateLifecycleState: () => {
358
- },
359
- requestControlTransfer: async () => {
360
- },
361
- flush: async () => {
362
- },
363
- close: async () => {
364
- },
365
- updateMetadata: () => {
366
- },
367
- updateAgentState: () => {
368
- },
369
- onUserMessage: () => {
370
- },
371
- rpcHandlerManager: {
372
- registerHandler: () => {
373
- }
374
- }
375
- };
376
- }
377
-
378
- function setupOfflineReconnection(opts) {
379
- const { api: api$1, sessionTag, metadata, state, response, onSessionSwap } = opts;
380
- let session;
381
- let reconnectionHandle = null;
382
- if (!response) {
383
- session = createOfflineSessionStub(sessionTag);
384
- reconnectionHandle = api.startOfflineReconnection({
385
- serverUrl: api.configuration.serverUrl,
386
- onReconnected: async () => {
387
- const resp = await api$1.getOrCreateSession({ tag: sessionTag, metadata, state });
388
- if (!resp) throw new Error("Server unavailable");
389
- const realSession = api$1.sessionSyncClient(resp);
390
- onSessionSwap(realSession);
391
- return realSession;
392
- },
393
- onNotify: (msg) => {
394
- console.log(msg);
395
- }
396
- });
397
- return { session, reconnectionHandle, isOffline: true };
398
- } else {
399
- session = api$1.sessionSyncClient(response);
400
- return { session, reconnectionHandle: null, isOffline: false };
401
- }
402
- }
403
-
404
532
  exports.BasePermissionHandler = BasePermissionHandler;
405
533
  exports.BaseReasoningProcessor = BaseReasoningProcessor;
534
+ exports.INTERACTION_SUPERSEDED_ERROR = INTERACTION_SUPERSEDED_ERROR;
535
+ exports.INTERACTION_TIMED_OUT_ERROR = INTERACTION_TIMED_OUT_ERROR;
406
536
  exports.createSessionMetadata = createSessionMetadata;
537
+ exports.getPendingInteractionTimeoutMs = getPendingInteractionTimeoutMs;
407
538
  exports.setupOfflineReconnection = setupOfflineReconnection;
@@ -1,9 +1,121 @@
1
- import { l as logger, c as configuration, p as packageJson, s as startOfflineReconnection } from './types-BXyraW9R.mjs';
2
- import { randomUUID } from 'node:crypto';
3
1
  import os from 'node:os';
4
2
  import { resolve } from 'node:path';
5
- import { p as projectPath } from './index-CUmYqKWt.mjs';
3
+ import { c as configuration, p as packageJson, s as startOfflineReconnection, l as logger } from './api-B5Ui8Fw0.mjs';
4
+ import { p as projectPath } from './index-BByhFIIq.mjs';
5
+ import { EventEmitter } from 'node:events';
6
+ import { randomUUID } from 'node:crypto';
7
+
8
+ function createSessionMetadata(opts) {
9
+ const state = {
10
+ controlledByUser: false
11
+ };
12
+ const metadata = {
13
+ path: process.cwd(),
14
+ host: os.hostname(),
15
+ version: packageJson.version,
16
+ os: os.platform(),
17
+ machineId: opts.machineId,
18
+ homeDir: os.homedir(),
19
+ happyHomeDir: configuration.happyCloudHomeDir,
20
+ happyLibDir: projectPath(),
21
+ happyToolsDir: resolve(projectPath(), "tools", "unpacked"),
22
+ startedFromDaemon: opts.startedBy === "daemon",
23
+ hostPid: process.pid,
24
+ startedBy: opts.startedBy || "terminal",
25
+ lifecycleState: "running",
26
+ lifecycleStateSince: Date.now(),
27
+ flavor: opts.flavor
28
+ };
29
+ return { state, metadata };
30
+ }
31
+
32
+ function createOfflineSessionStub(sessionTag) {
33
+ const emitter = new EventEmitter();
34
+ let metadata = null;
35
+ let agentState = null;
36
+ const stub = {
37
+ sessionId: `offline-${sessionTag}`,
38
+ sendCodexMessage: () => {
39
+ },
40
+ sendAgentMessage: () => {
41
+ },
42
+ sendClaudeSessionMessage: () => {
43
+ },
44
+ keepAlive: () => {
45
+ },
46
+ sendSessionEvent: () => {
47
+ },
48
+ sendSessionDeath: () => {
49
+ },
50
+ updateLifecycleState: () => {
51
+ },
52
+ requestControlTransfer: async () => {
53
+ },
54
+ flush: async () => {
55
+ },
56
+ close: async () => {
57
+ },
58
+ updateMetadata: (handler) => {
59
+ metadata = handler(metadata || {});
60
+ emitter.emit("metadata-updated", metadata);
61
+ },
62
+ updateAgentState: (handler) => {
63
+ agentState = handler(agentState || {});
64
+ emitter.emit("agent-state-updated", agentState);
65
+ },
66
+ getMetadataSnapshot: () => metadata,
67
+ getAgentStateSnapshot: () => agentState,
68
+ waitForMetadataUpdate: async () => metadata,
69
+ onUserMessage: () => {
70
+ },
71
+ rpcHandlerManager: {
72
+ registerHandler: () => {
73
+ }
74
+ },
75
+ on: emitter.on.bind(emitter),
76
+ once: emitter.once.bind(emitter),
77
+ off: emitter.off.bind(emitter),
78
+ emit: emitter.emit.bind(emitter)
79
+ };
80
+ return stub;
81
+ }
6
82
 
83
+ function setupOfflineReconnection(opts) {
84
+ const { api, sessionTag, metadata, state, response, onSessionSwap } = opts;
85
+ let session;
86
+ let reconnectionHandle = null;
87
+ if (!response) {
88
+ session = createOfflineSessionStub(sessionTag);
89
+ reconnectionHandle = startOfflineReconnection({
90
+ serverUrl: configuration.serverUrl,
91
+ onReconnected: async () => {
92
+ const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
93
+ if (!resp) throw new Error("Server unavailable");
94
+ const realSession = api.sessionSyncClient(resp);
95
+ onSessionSwap(realSession);
96
+ return realSession;
97
+ },
98
+ onNotify: (msg) => {
99
+ console.log(msg);
100
+ }
101
+ });
102
+ return { session, reconnectionHandle, isOffline: true };
103
+ } else {
104
+ session = api.sessionSyncClient(response);
105
+ return { session, reconnectionHandle: null, isOffline: false };
106
+ }
107
+ }
108
+
109
+ const INTERACTION_SUPERSEDED_ERROR = "Interaction superseded by new user message";
110
+ const INTERACTION_TIMED_OUT_ERROR = "Interaction timed out waiting for user response";
111
+ const DEFAULT_INTERACTION_TIMEOUT_MS = 2 * 60 * 1e3;
112
+ function getPendingInteractionTimeoutMs() {
113
+ const raw = Number(process.env.HAPPY_INTERACTION_TIMEOUT_MS);
114
+ if (Number.isFinite(raw) && raw > 0) {
115
+ return raw;
116
+ }
117
+ return DEFAULT_INTERACTION_TIMEOUT_MS;
118
+ }
7
119
  class BasePermissionHandler {
8
120
  pendingRequests = /* @__PURE__ */ new Map();
9
121
  session;
@@ -34,6 +146,7 @@ class BasePermissionHandler {
34
146
  return;
35
147
  }
36
148
  this.pendingRequests.delete(response.id);
149
+ this.clearPendingRequestTimeout(pending);
37
150
  const result = response.approved ? { decision: response.decision === "approved_for_session" ? "approved_for_session" : "approved" } : { decision: response.decision === "denied" ? "denied" : "abort" };
38
151
  pending.resolve(result);
39
152
  this.session.updateAgentState((currentState) => {
@@ -75,6 +188,62 @@ class BasePermissionHandler {
75
188
  }
76
189
  }));
77
190
  }
191
+ registerPendingRequest(toolCallId, toolName, input, logSuffix = "") {
192
+ return new Promise((resolve, reject) => {
193
+ const pending = {
194
+ resolve,
195
+ reject,
196
+ toolName,
197
+ input
198
+ };
199
+ pending.timeoutHandle = setTimeout(() => {
200
+ this.handlePendingRequestTimeout(toolCallId, pending);
201
+ }, getPendingInteractionTimeoutMs());
202
+ this.pendingRequests.set(toolCallId, pending);
203
+ this.addPendingRequestToState(toolCallId, toolName, input);
204
+ logger.debug(`${this.getLogPrefix()} Permission request sent for tool: ${toolName} (${toolCallId})${logSuffix}`);
205
+ });
206
+ }
207
+ hasPendingRequests() {
208
+ return this.pendingRequests.size > 0;
209
+ }
210
+ supersedePendingRequests(reason = INTERACTION_SUPERSEDED_ERROR) {
211
+ const pendingSnapshot = Array.from(this.pendingRequests.entries());
212
+ if (pendingSnapshot.length === 0) {
213
+ return 0;
214
+ }
215
+ this.pendingRequests.clear();
216
+ const completedAt = Date.now();
217
+ for (const [, pending] of pendingSnapshot) {
218
+ this.clearPendingRequestTimeout(pending);
219
+ pending.resolve({ decision: "abort" });
220
+ }
221
+ this.session.updateAgentState((currentState) => {
222
+ const requests = { ...currentState.requests || {} };
223
+ const completedRequests = { ...currentState.completedRequests || {} };
224
+ for (const [id, request] of Object.entries(requests)) {
225
+ if (request.requestKind === "selection") {
226
+ continue;
227
+ }
228
+ completedRequests[id] = {
229
+ ...request,
230
+ completedAt,
231
+ status: "denied",
232
+ reason,
233
+ decision: "abort",
234
+ requestKind: request.requestKind || "permission"
235
+ };
236
+ delete requests[id];
237
+ }
238
+ return {
239
+ ...currentState,
240
+ requests,
241
+ completedRequests
242
+ };
243
+ });
244
+ logger.debug(`${this.getLogPrefix()} Superseded ${pendingSnapshot.length} pending permission request(s)`);
245
+ return pendingSnapshot.length;
246
+ }
78
247
  /**
79
248
  * Reset state for new sessions.
80
249
  * This method is idempotent - safe to call multiple times.
@@ -90,6 +259,7 @@ class BasePermissionHandler {
90
259
  this.pendingRequests.clear();
91
260
  for (const [id, pending] of pendingSnapshot) {
92
261
  try {
262
+ this.clearPendingRequestTimeout(pending);
93
263
  pending.reject(new Error("Session reset"));
94
264
  } catch (err) {
95
265
  logger.debug(`${this.getLogPrefix()} Error rejecting pending request ${id}:`, err);
@@ -117,6 +287,50 @@ class BasePermissionHandler {
117
287
  this.isResetting = false;
118
288
  }
119
289
  }
290
+ clearPendingRequestTimeout(pending) {
291
+ if (pending?.timeoutHandle) {
292
+ clearTimeout(pending.timeoutHandle);
293
+ pending.timeoutHandle = void 0;
294
+ }
295
+ }
296
+ handlePendingRequestTimeout(toolCallId, pending) {
297
+ const active = this.pendingRequests.get(toolCallId);
298
+ if (!active || active !== pending) {
299
+ return;
300
+ }
301
+ this.pendingRequests.delete(toolCallId);
302
+ this.clearPendingRequestTimeout(active);
303
+ active.resolve({ decision: "abort" });
304
+ this.session.updateAgentState((currentState) => {
305
+ const request = currentState.requests?.[toolCallId] || {
306
+ tool: active.toolName,
307
+ arguments: active.input,
308
+ createdAt: Date.now(),
309
+ requestKind: "permission"
310
+ };
311
+ const { [toolCallId]: _, ...remainingRequests } = currentState.requests || {};
312
+ return {
313
+ ...currentState,
314
+ requests: remainingRequests,
315
+ completedRequests: {
316
+ ...currentState.completedRequests,
317
+ [toolCallId]: {
318
+ ...request,
319
+ completedAt: Date.now(),
320
+ status: "canceled",
321
+ reason: INTERACTION_TIMED_OUT_ERROR,
322
+ decision: "abort",
323
+ requestKind: request.requestKind || "permission"
324
+ }
325
+ }
326
+ };
327
+ });
328
+ this.session.sendSessionEvent({
329
+ type: "message",
330
+ message: "Pending interaction timed out waiting for a response. Send a new message to continue."
331
+ });
332
+ logger.debug(`${this.getLogPrefix()} Permission request timed out for ${active.toolName} (${toolCallId})`);
333
+ }
120
334
  }
121
335
 
122
336
  class BaseReasoningProcessor {
@@ -313,90 +527,4 @@ class BaseReasoningProcessor {
313
527
  }
314
528
  }
315
529
 
316
- function createSessionMetadata(opts) {
317
- const state = {
318
- controlledByUser: false
319
- };
320
- const metadata = {
321
- path: process.cwd(),
322
- host: os.hostname(),
323
- version: packageJson.version,
324
- os: os.platform(),
325
- machineId: opts.machineId,
326
- homeDir: os.homedir(),
327
- happyHomeDir: configuration.happyCloudHomeDir,
328
- happyLibDir: projectPath(),
329
- happyToolsDir: resolve(projectPath(), "tools", "unpacked"),
330
- startedFromDaemon: opts.startedBy === "daemon",
331
- hostPid: process.pid,
332
- startedBy: opts.startedBy || "terminal",
333
- lifecycleState: "running",
334
- lifecycleStateSince: Date.now(),
335
- flavor: opts.flavor
336
- };
337
- return { state, metadata };
338
- }
339
-
340
- function createOfflineSessionStub(sessionTag) {
341
- return {
342
- sessionId: `offline-${sessionTag}`,
343
- sendCodexMessage: () => {
344
- },
345
- sendAgentMessage: () => {
346
- },
347
- sendClaudeSessionMessage: () => {
348
- },
349
- keepAlive: () => {
350
- },
351
- sendSessionEvent: () => {
352
- },
353
- sendSessionDeath: () => {
354
- },
355
- updateLifecycleState: () => {
356
- },
357
- requestControlTransfer: async () => {
358
- },
359
- flush: async () => {
360
- },
361
- close: async () => {
362
- },
363
- updateMetadata: () => {
364
- },
365
- updateAgentState: () => {
366
- },
367
- onUserMessage: () => {
368
- },
369
- rpcHandlerManager: {
370
- registerHandler: () => {
371
- }
372
- }
373
- };
374
- }
375
-
376
- function setupOfflineReconnection(opts) {
377
- const { api, sessionTag, metadata, state, response, onSessionSwap } = opts;
378
- let session;
379
- let reconnectionHandle = null;
380
- if (!response) {
381
- session = createOfflineSessionStub(sessionTag);
382
- reconnectionHandle = startOfflineReconnection({
383
- serverUrl: configuration.serverUrl,
384
- onReconnected: async () => {
385
- const resp = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
386
- if (!resp) throw new Error("Server unavailable");
387
- const realSession = api.sessionSyncClient(resp);
388
- onSessionSwap(realSession);
389
- return realSession;
390
- },
391
- onNotify: (msg) => {
392
- console.log(msg);
393
- }
394
- });
395
- return { session, reconnectionHandle, isOffline: true };
396
- } else {
397
- session = api.sessionSyncClient(response);
398
- return { session, reconnectionHandle: null, isOffline: false };
399
- }
400
- }
401
-
402
- export { BasePermissionHandler as B, BaseReasoningProcessor as a, createSessionMetadata as c, setupOfflineReconnection as s };
530
+ export { BasePermissionHandler as B, INTERACTION_SUPERSEDED_ERROR as I, BaseReasoningProcessor as a, INTERACTION_TIMED_OUT_ERROR as b, createSessionMetadata as c, getPendingInteractionTimeoutMs as g, setupOfflineReconnection as s };