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.
Files changed (34) hide show
  1. package/dist/BaseReasoningProcessor-5ACv9gKu.mjs +320 -0
  2. package/dist/BaseReasoningProcessor-COrRWyn0.cjs +323 -0
  3. package/dist/ProviderSelectionHandler-BljOLMQn.mjs +261 -0
  4. package/dist/ProviderSelectionHandler-DBGobhGZ.cjs +265 -0
  5. package/dist/{api-BjxmW-0W.mjs → api-BcWf5v4i.mjs} +174 -46
  6. package/dist/{api-DUE5TJBE.cjs → api-Ye-rPX6s.cjs} +174 -46
  7. package/dist/{command-Df7u5eAT.cjs → command-BK93nizl.cjs} +6 -6
  8. package/dist/{command-ComOeFLY.mjs → command-nOI80Mnm.mjs} +6 -6
  9. package/dist/{index-CzvgPwr1.mjs → index-DnsqY6I_.mjs} +189 -60
  10. package/dist/{index-Cuvfa15L.cjs → index-J7QKJ8lc.cjs} +194 -61
  11. package/dist/index.cjs +8 -8
  12. package/dist/index.mjs +8 -8
  13. package/dist/lib.cjs +2 -3
  14. package/dist/lib.d.cts +2 -0
  15. package/dist/lib.d.mts +2 -0
  16. package/dist/lib.mjs +2 -3
  17. package/dist/{persistence-BxP6Jw1f.mjs → persistence-BMa6cyw9.mjs} +2 -3
  18. package/dist/{persistence-D7JtnrYA.cjs → persistence-xK5CKhbn.cjs} +2 -3
  19. package/dist/registerKillSessionHandler-CH6yN0eG.cjs +1198 -0
  20. package/dist/registerKillSessionHandler-Dxwg4L4J.mjs +1179 -0
  21. package/dist/{runClaude-C1W_Nw0C.cjs → runClaude-BMHlBny_.cjs} +884 -398
  22. package/dist/{runClaude-B_fTMxc4.mjs → runClaude-cQ-UT0Ke.mjs} +882 -396
  23. package/dist/{runCodex-CUgOiIEz.mjs → runCodex-AnJUPIhX.mjs} +155 -578
  24. package/dist/{runCodex-D2VCWVEK.cjs → runCodex-roOSOWWL.cjs} +158 -583
  25. package/dist/{runGemini-2_FEtJYa.mjs → runGemini-BGo_0mzA.mjs} +142 -387
  26. package/dist/{runGemini-CiGnjflq.cjs → runGemini-Cg6Zbqlz.cjs} +145 -390
  27. package/package.json +9 -8
  28. package/scripts/e2e/fake-codex-acp-agent.mjs +139 -0
  29. package/scripts/e2e/local-server-session-roundtrip.mjs +1063 -0
  30. package/scripts/release-smoke.mjs +23 -6
  31. package/dist/names-CicSgRNg.mjs +0 -625
  32. package/dist/names-yJNZoTv_.cjs +0 -636
  33. package/dist/registerKillSessionHandler-ARQrPvnT.mjs +0 -454
  34. package/dist/registerKillSessionHandler-DCMFiXyA.cjs +0 -459
@@ -1,43 +1,44 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { l as logger, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-BjxmW-0W.mjs';
3
- import { readSettings } from './persistence-BxP6Jw1f.mjs';
4
- import { f as formatDisplayMessage, v as validateCodexAcpSpawn, e as createCodexBackend, t as truncateDisplayMessage, b as stopCaffeinate, i as initialMachineMetadata, p as publishSessionRegistration } from './index-CzvgPwr1.mjs';
5
- import { B as BasePermissionHandler, g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, d as INTERACTION_TIMED_OUT_ERROR, a as BaseReasoningProcessor, b as attachToolHappierMetaV2, r as resolveCanonicalToolNameV2, i as inferToolResultError, c as createSessionMetadata, s as setupOfflineReconnection } from './names-CicSgRNg.mjs';
6
- import { h as hashObject, a as MessageBuffer, r as registerKillSessionHandler, M as MessageQueue2 } from './registerKillSessionHandler-ARQrPvnT.mjs';
7
- import React, { useState, useRef, useEffect, useCallback } from 'react';
8
- import { useStdout, useInput, Box, Text, render } from 'ink';
9
- import 'axios';
10
- import 'chalk';
2
+ import { l as logger, b as connectionState, A as ApiClient } from './api-BcWf5v4i.mjs';
3
+ import { B as BasePermissionHandler, h as hashObject, d as MessageBuffer, C as ConversationHistory$1, w as waitForResponseCompleteWithAbort, r as registerKillSessionHandler, l as launchRuntimeHandleWithFactoryResult, i as inferToolResultError, j as forwardAgentMessageToProviderSession, f as closeProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, s as syncControlledByUserState } from './registerKillSessionHandler-Dxwg4L4J.mjs';
4
+ import { f as formatDisplayMessage, v as validateCodexAcpSpawn, d as createCodexBackend, t as truncateDisplayMessage, b as stopCaffeinate } from './index-DnsqY6I_.mjs';
5
+ import 'cross-spawn';
6
+ import '@agentclientprotocol/sdk';
7
+ import 'ps-list';
11
8
  import 'fs';
9
+ import 'path';
10
+ import 'os';
11
+ import 'child_process';
12
12
  import 'node:fs';
13
- import 'node:os';
14
13
  import 'node:path';
14
+ import 'node:os';
15
+ import './persistence-BMa6cyw9.mjs';
16
+ import 'node:fs/promises';
17
+ import 'fs/promises';
18
+ import 'crypto';
19
+ import 'node:child_process';
20
+ import 'chalk';
15
21
  import 'node:events';
22
+ import 'axios';
23
+ import 'tweetnacl';
24
+ import 'open';
25
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
26
+ import { useStdout, useInput, Box, Text, render } from 'ink';
27
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-BljOLMQn.mjs';
28
+ import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-5ACv9gKu.mjs';
16
29
  import 'socket.io-client';
17
30
  import 'zod';
18
- import 'tweetnacl';
19
- import 'child_process';
20
- import 'util';
21
- import 'fs/promises';
22
- import 'crypto';
23
- import 'path';
24
31
  import 'expo-server-sdk';
25
- import 'node:fs/promises';
26
- import 'os';
32
+ import './types-CiliQpqS.mjs';
27
33
  import 'tmp';
28
34
  import 'qrcode-terminal';
29
35
  import 'node:module';
30
- import 'open';
31
36
  import 'url';
32
- import 'node:child_process';
33
- import 'ps-list';
34
- import 'cross-spawn';
35
37
  import 'fastify';
36
38
  import 'fastify-type-provider-zod';
37
39
  import 'node:readline';
38
40
  import 'http';
39
- import '@agentclientprotocol/sdk';
40
- import './types-CiliQpqS.mjs';
41
+ import 'util';
41
42
 
42
43
  const DEFAULT_CAPABILITIES = {
43
44
  committedMessageAcks: false,
@@ -127,7 +128,7 @@ class CodexSession {
127
128
  sessionId;
128
129
  mode;
129
130
  thinking = false;
130
- keepAliveInterval;
131
+ keepAliveController;
131
132
  onModeChangeCallback;
132
133
  clientSwapCallbacks = [];
133
134
  constructor(opts) {
@@ -143,13 +144,16 @@ class CodexSession {
143
144
  this.queue = opts.messageQueue;
144
145
  this.codexArgs = opts.codexArgs;
145
146
  this.onModeChangeCallback = opts.onModeChange;
146
- this.client.keepAlive(this.thinking, this.mode);
147
- this.keepAliveInterval = setInterval(() => {
148
- this.client.keepAlive(this.thinking, this.mode);
149
- }, 2e3);
147
+ this.keepAliveController = createKeepAliveController({
148
+ initialMode: this.mode,
149
+ initialThinking: this.thinking,
150
+ send: (thinking, mode) => {
151
+ this.client.keepAlive(thinking, mode);
152
+ }
153
+ });
150
154
  }
151
155
  cleanup() {
152
- clearInterval(this.keepAliveInterval);
156
+ this.keepAliveController.dispose();
153
157
  logger.debug("[CodexSession] Cleaned up resources");
154
158
  }
155
159
  updateClient = (client) => {
@@ -172,11 +176,11 @@ class CodexSession {
172
176
  };
173
177
  onThinkingChange = (thinking) => {
174
178
  this.thinking = thinking;
175
- this.client.keepAlive(thinking, this.mode);
179
+ this.keepAliveController.setThinking(thinking);
176
180
  };
177
181
  onModeChange = async (mode) => {
178
182
  this.mode = mode;
179
- this.client.keepAlive(this.thinking, mode);
183
+ this.keepAliveController.setMode(mode);
180
184
  await this.onModeChangeCallback(mode);
181
185
  };
182
186
  onSessionFound = (sessionId) => {
@@ -329,208 +333,9 @@ class CodexPermissionHandler extends BasePermissionHandler {
329
333
  }
330
334
  }
331
335
 
332
- class CodexSelectionHandler {
333
- pendingRequests = /* @__PURE__ */ new Map();
334
- session;
336
+ class CodexSelectionHandler extends ProviderSelectionHandler {
335
337
  constructor(session) {
336
- this.session = session;
337
- this.setupRpcHandler();
338
- }
339
- updateSession(newSession) {
340
- this.session = newSession;
341
- this.setupRpcHandler();
342
- }
343
- async requestSelection(request) {
344
- return new Promise((resolve, reject) => {
345
- const pending = {
346
- resolve,
347
- reject,
348
- request
349
- };
350
- pending.timeoutHandle = setTimeout(() => {
351
- this.handleSelectionTimeout(request.id, pending);
352
- }, getPendingInteractionTimeoutMs());
353
- this.pendingRequests.set(request.id, pending);
354
- this.session.updateAgentState((currentState) => ({
355
- ...currentState,
356
- requests: {
357
- ...currentState.requests,
358
- [request.id]: {
359
- tool: "AskUserQuestion",
360
- arguments: {
361
- requestKind: "selection",
362
- questions: [
363
- {
364
- header: "Codex",
365
- question: request.message,
366
- multiSelect: false,
367
- options: request.options.map((option) => ({
368
- label: option.label,
369
- description: option.description || option.optionId,
370
- optionId: option.optionId
371
- }))
372
- }
373
- ]
374
- },
375
- createdAt: Date.now(),
376
- requestKind: "selection",
377
- options: request.options,
378
- defaultOptionId: request.defaultOptionId
379
- }
380
- }
381
- }));
382
- logger.debug(`[Codex] Selection request sent (${request.id}) with ${request.options.length} options`);
383
- });
384
- }
385
- hasPendingRequests() {
386
- return this.pendingRequests.size > 0;
387
- }
388
- supersedePendingRequests(reason = INTERACTION_SUPERSEDED_ERROR) {
389
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
390
- if (pendingSnapshot.length === 0) {
391
- return 0;
392
- }
393
- this.pendingRequests.clear();
394
- const completedAt = Date.now();
395
- for (const [, pending] of pendingSnapshot) {
396
- this.clearPendingRequestTimeout(pending);
397
- pending.reject(new Error(reason));
398
- }
399
- this.session.updateAgentState((currentState) => {
400
- const requests = { ...currentState.requests || {} };
401
- const completedRequests = { ...currentState.completedRequests || {} };
402
- for (const [id, request] of Object.entries(requests)) {
403
- if (request.requestKind !== "selection") {
404
- continue;
405
- }
406
- completedRequests[id] = {
407
- ...request,
408
- completedAt,
409
- status: "canceled",
410
- reason,
411
- requestKind: "selection"
412
- };
413
- delete requests[id];
414
- }
415
- return {
416
- ...currentState,
417
- requests,
418
- completedRequests
419
- };
420
- });
421
- logger.debug(`[Codex] Superseded ${pendingSnapshot.length} pending selection request(s)`);
422
- return pendingSnapshot.length;
423
- }
424
- reset(reason = "Session reset") {
425
- const pendingSnapshot = Array.from(this.pendingRequests.entries());
426
- this.pendingRequests.clear();
427
- for (const [, pending] of pendingSnapshot) {
428
- this.clearPendingRequestTimeout(pending);
429
- pending.reject(new Error(reason));
430
- }
431
- this.session.updateAgentState((currentState) => {
432
- const requests = { ...currentState.requests || {} };
433
- const completedRequests = { ...currentState.completedRequests || {} };
434
- for (const [id, request] of Object.entries(requests)) {
435
- if (request.requestKind !== "selection") {
436
- continue;
437
- }
438
- completedRequests[id] = {
439
- ...request,
440
- completedAt: Date.now(),
441
- status: "canceled",
442
- reason,
443
- requestKind: "selection"
444
- };
445
- delete requests[id];
446
- }
447
- return {
448
- ...currentState,
449
- requests,
450
- completedRequests
451
- };
452
- });
453
- }
454
- setupRpcHandler() {
455
- this.session.rpcHandlerManager.registerHandler("selection", async (response) => {
456
- const pending = this.pendingRequests.get(response.id);
457
- if (!pending) {
458
- logger.debug("[Codex] Selection request not found or already resolved");
459
- return;
460
- }
461
- this.pendingRequests.delete(response.id);
462
- this.clearPendingRequestTimeout(pending);
463
- pending.resolve(response);
464
- this.session.updateAgentState((currentState) => {
465
- const request = currentState.requests?.[response.id];
466
- if (!request) {
467
- return currentState;
468
- }
469
- const { [response.id]: _, ...remainingRequests } = currentState.requests || {};
470
- return {
471
- ...currentState,
472
- requests: remainingRequests,
473
- completedRequests: {
474
- ...currentState.completedRequests,
475
- [response.id]: {
476
- ...request,
477
- completedAt: Date.now(),
478
- status: "approved",
479
- requestKind: "selection",
480
- selectedOptionId: response.optionId
481
- }
482
- }
483
- };
484
- });
485
- });
486
- }
487
- clearPendingRequestTimeout(pending) {
488
- if (pending?.timeoutHandle) {
489
- clearTimeout(pending.timeoutHandle);
490
- pending.timeoutHandle = void 0;
491
- }
492
- }
493
- handleSelectionTimeout(requestId, pending) {
494
- const active = this.pendingRequests.get(requestId);
495
- if (!active || active !== pending) {
496
- return;
497
- }
498
- this.pendingRequests.delete(requestId);
499
- this.clearPendingRequestTimeout(active);
500
- active.reject(new Error(INTERACTION_TIMED_OUT_ERROR));
501
- this.session.updateAgentState((currentState) => {
502
- const request = currentState.requests?.[requestId] || {
503
- tool: "AskUserQuestion",
504
- arguments: {
505
- requestKind: "selection",
506
- questions: []
507
- },
508
- createdAt: Date.now(),
509
- requestKind: "selection",
510
- options: active.request.options,
511
- defaultOptionId: active.request.defaultOptionId
512
- };
513
- const { [requestId]: _, ...remainingRequests } = currentState.requests || {};
514
- return {
515
- ...currentState,
516
- requests: remainingRequests,
517
- completedRequests: {
518
- ...currentState.completedRequests,
519
- [requestId]: {
520
- ...request,
521
- completedAt: Date.now(),
522
- status: "canceled",
523
- reason: INTERACTION_TIMED_OUT_ERROR,
524
- requestKind: "selection"
525
- }
526
- }
527
- };
528
- });
529
- this.session.sendSessionEvent({
530
- type: "message",
531
- message: "Pending interaction timed out waiting for a response. Send a new message to continue."
532
- });
533
- logger.debug(`[Codex] Selection request timed out (${requestId})`);
338
+ super(session, "Codex");
534
339
  }
535
340
  }
536
341
 
@@ -558,101 +363,6 @@ class ReasoningProcessor extends BaseReasoningProcessor {
558
363
  }
559
364
  }
560
365
 
561
- class ConversationHistory {
562
- messages = [];
563
- maxMessages;
564
- maxCharacters;
565
- constructor(options = {}) {
566
- this.maxMessages = options.maxMessages ?? 20;
567
- this.maxCharacters = options.maxCharacters ?? 5e4;
568
- }
569
- isDuplicate(role, content) {
570
- if (this.messages.length === 0) {
571
- return false;
572
- }
573
- for (let index = this.messages.length - 1; index >= 0; index -= 1) {
574
- const message = this.messages[index];
575
- if (message.role !== role) {
576
- continue;
577
- }
578
- const normalizedIncoming = content.trim().replace(/\s+/g, " ");
579
- const normalizedExisting = message.content.replace(/\s+/g, " ");
580
- return normalizedIncoming === normalizedExisting;
581
- }
582
- return false;
583
- }
584
- addUserMessage(content) {
585
- this.addMessage("user", content);
586
- }
587
- addAssistantMessage(content) {
588
- this.addMessage("assistant", content);
589
- }
590
- hasHistory() {
591
- return this.messages.length > 0;
592
- }
593
- size() {
594
- return this.messages.length;
595
- }
596
- clear() {
597
- this.messages = [];
598
- logger.debug("[CodexConversationHistory] History cleared");
599
- }
600
- getContextForNewSession() {
601
- if (this.messages.length === 0) {
602
- return "";
603
- }
604
- const formattedMessages = this.messages.map((message) => {
605
- const role = message.role === "user" ? "User" : "Assistant";
606
- const content = message.content.length > 2e3 ? `${message.content.slice(0, 2e3)}... [truncated]` : message.content;
607
- return `${role}: ${content}`;
608
- }).join("\n\n");
609
- return [
610
- "[PREVIOUS CONVERSATION CONTEXT]",
611
- "Continue from the prior Codex session using the conversation below as context.",
612
- "",
613
- formattedMessages,
614
- "",
615
- "[END OF PREVIOUS CONTEXT]",
616
- ""
617
- ].join("\n");
618
- }
619
- getSummary() {
620
- const totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
621
- const userCount = this.messages.filter((message) => message.role === "user").length;
622
- const assistantCount = this.messages.filter((message) => message.role === "assistant").length;
623
- return `${this.messages.length} messages (${userCount} user, ${assistantCount} assistant), ${totalChars} chars`;
624
- }
625
- addMessage(role, content) {
626
- const trimmedContent = content.trim();
627
- if (!trimmedContent) {
628
- return;
629
- }
630
- if (this.isDuplicate(role, trimmedContent)) {
631
- logger.debug(`[CodexConversationHistory] Skipping duplicate ${role} message (${trimmedContent.length} chars)`);
632
- return;
633
- }
634
- this.messages.push({
635
- role,
636
- content: trimmedContent,
637
- timestamp: Date.now()
638
- });
639
- this.trimHistory();
640
- logger.debug(`[CodexConversationHistory] Added ${role} message (${trimmedContent.length} chars), total: ${this.messages.length}`);
641
- }
642
- trimHistory() {
643
- while (this.messages.length > this.maxMessages) {
644
- this.messages.shift();
645
- }
646
- let totalChars = this.messages.reduce((sum, message) => sum + message.content.length, 0);
647
- while (totalChars > this.maxCharacters && this.messages.length > 1) {
648
- const removed = this.messages.shift();
649
- if (removed) {
650
- totalChars -= removed.content.length;
651
- }
652
- }
653
- }
654
- }
655
-
656
366
  function resolveCodexAcpExecutionMode(mode) {
657
367
  const approvalPolicy = (() => {
658
368
  switch (mode.permissionMode) {
@@ -704,26 +414,6 @@ function getCodexExecutionFingerprint(mode) {
704
414
  return hashObject(resolveCodexAcpExecutionMode(mode));
705
415
  }
706
416
 
707
- function createAbortError() {
708
- const error = new Error("Operation aborted");
709
- error.name = "AbortError";
710
- return error;
711
- }
712
- async function waitForResponseCompleteWithAbort(backend, signal, timeoutMs = 12e4) {
713
- if (!backend.waitForResponseComplete) {
714
- return;
715
- }
716
- if (signal.aborted) {
717
- throw createAbortError();
718
- }
719
- await new Promise((resolve, reject) => {
720
- const onAbort = () => reject(createAbortError());
721
- signal.addEventListener("abort", onAbort, { once: true });
722
- backend.waitForResponseComplete(timeoutMs).then(resolve).catch(reject).finally(() => {
723
- signal.removeEventListener("abort", onAbort);
724
- });
725
- });
726
- }
727
417
  function normalizeCodexBackendError(error) {
728
418
  const record = typeof error === "object" && error !== null ? error : null;
729
419
  const text = formatDisplayMessage(error).trim();
@@ -780,15 +470,14 @@ function registerCodexRemoteRpcHandlers(clientSession, handlers) {
780
470
  registerKillSessionHandler(clientSession.rpcHandlerManager, handlers.handleKillSession);
781
471
  }
782
472
  async function codexRemoteLauncher(session) {
783
- const messageBuffer = new MessageBuffer();
784
473
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
474
+ const messageBuffer = new MessageBuffer({ enabled: hasTTY });
785
475
  let inkInstance = null;
786
476
  let shouldExit = false;
787
477
  let abortController = new AbortController();
788
478
  let turnInFlight = false;
789
- let backend = null;
790
- let acpSessionId = null;
791
- let wasSessionCreated = false;
479
+ let runtimeHandle = null;
480
+ let unsubscribeRuntimeMessages = null;
792
481
  let currentModeHash = null;
793
482
  let pending = null;
794
483
  let readyAlreadySent = false;
@@ -801,7 +490,7 @@ async function codexRemoteLauncher(session) {
801
490
  let currentThinkingMessageId = null;
802
491
  const permissionHandler = new CodexPermissionHandler(session.client);
803
492
  const selectionHandler = new CodexSelectionHandler(session.client);
804
- const conversationHistory = new ConversationHistory({ maxMessages: 20, maxCharacters: 5e4 });
493
+ const conversationHistory = new ConversationHistory$1({ maxMessages: 20, maxCharacters: 5e4 });
805
494
  const reasoningProcessor = new ReasoningProcessor((message) => {
806
495
  session.runtimeSession.sendCodexMessage(message);
807
496
  });
@@ -837,22 +526,22 @@ async function codexRemoteLauncher(session) {
837
526
  activeController.abort();
838
527
  reasoningProcessor.abort();
839
528
  session.onThinkingChange(false);
840
- if (backend && acpSessionId) {
841
- void backend.cancel(acpSessionId).catch((error) => {
529
+ if (runtimeHandle) {
530
+ void runtimeHandle.cancel().catch((error) => {
842
531
  logger.debug("[Codex] Error cancelling ACP session:", error);
843
532
  });
844
533
  }
845
534
  };
846
- const disposeBackend = async () => {
847
- if (!backend) {
535
+ const disposeRuntimeHandle = async () => {
536
+ if (!runtimeHandle) {
848
537
  return;
849
538
  }
850
- const currentBackend = backend;
851
- backend = null;
852
- acpSessionId = null;
853
- wasSessionCreated = false;
539
+ const activeRuntimeHandle = runtimeHandle;
540
+ runtimeHandle = null;
541
+ unsubscribeRuntimeMessages?.();
542
+ unsubscribeRuntimeMessages = null;
854
543
  try {
855
- await currentBackend.dispose();
544
+ await activeRuntimeHandle.dispose();
856
545
  } catch (error) {
857
546
  logger.debug("[Codex] Error disposing ACP backend:", error);
858
547
  }
@@ -893,8 +582,16 @@ async function codexRemoteLauncher(session) {
893
582
  accumulatedResponse = "";
894
583
  isResponseInProgress = false;
895
584
  };
896
- const setupBackendMessageHandler = (currentBackend) => {
897
- currentBackend.onMessage((msg) => {
585
+ const setupRuntimeMessageHandler = (activeRuntimeHandle) => {
586
+ const forwardAgentMessage = (agentMessage) => {
587
+ forwardAgentMessageToProviderSession(agentMessage, {
588
+ provider: "codex",
589
+ send: (body) => session.runtimeSession.sendCodexMessage(body),
590
+ toolResultType: "tool-call-result"
591
+ });
592
+ };
593
+ unsubscribeRuntimeMessages?.();
594
+ unsubscribeRuntimeMessages = activeRuntimeHandle.onMessage((msg) => {
898
595
  switch (msg.type) {
899
596
  case "model-output": {
900
597
  const text = msg.textDelta ?? msg.fullText ?? "";
@@ -945,24 +642,11 @@ async function codexRemoteLauncher(session) {
945
642
  }
946
643
  case "tool-call": {
947
644
  const toolArgs = msg.args ? truncateDisplayMessage(msg.args, 100) : "";
948
- const canonicalToolName = resolveCanonicalToolNameV2(msg.toolName);
949
645
  messageBuffer.addMessage(
950
646
  `Executing: ${msg.toolName}${toolArgs ? ` ${toolArgs}` : ""}`,
951
647
  "tool"
952
648
  );
953
- session.runtimeSession.sendCodexMessage({
954
- type: "tool-call",
955
- name: canonicalToolName,
956
- callId: msg.callId,
957
- input: attachToolHappierMetaV2(msg.args, {
958
- v: 2,
959
- protocol: "acp",
960
- provider: "codex",
961
- rawToolName: msg.toolName,
962
- canonicalToolName
963
- }),
964
- id: randomUUID()
965
- });
649
+ forwardAgentMessage(msg);
966
650
  return;
967
651
  }
968
652
  case "tool-result": {
@@ -972,95 +656,37 @@ async function codexRemoteLauncher(session) {
972
656
  `${isError ? "Error:" : "Result:"} ${resultText}`.trim(),
973
657
  "result"
974
658
  );
975
- session.runtimeSession.sendCodexMessage({
976
- type: "tool-call-result",
977
- callId: msg.callId,
978
- output: attachToolHappierMetaV2(msg.result, {
979
- v: 2,
980
- protocol: "acp",
981
- provider: "codex",
982
- rawToolName: msg.toolName,
983
- canonicalToolName: resolveCanonicalToolNameV2(msg.toolName)
984
- }),
985
- id: randomUUID(),
986
- isError
987
- });
659
+ forwardAgentMessage(msg);
988
660
  return;
989
661
  }
990
662
  case "fs-edit": {
991
663
  messageBuffer.addMessage(`File edit: ${msg.description}`, "tool");
992
- session.runtimeSession.sendCodexMessage({
993
- type: "file-edit",
994
- description: msg.description,
995
- filePath: msg.path || "unknown",
996
- diff: msg.diff,
997
- id: randomUUID()
998
- });
664
+ forwardAgentMessage(msg);
999
665
  return;
1000
666
  }
1001
667
  case "terminal-output": {
1002
668
  const terminalOutput = formatDisplayMessage(msg.data);
1003
669
  messageBuffer.addMessage(terminalOutput, "result");
1004
- session.runtimeSession.sendCodexMessage({
1005
- type: "terminal-output",
1006
- data: terminalOutput,
1007
- callId: msg.callId ?? randomUUID()
670
+ forwardAgentMessage({
671
+ ...msg,
672
+ data: terminalOutput
1008
673
  });
1009
674
  return;
1010
675
  }
1011
676
  case "permission-request": {
1012
- const payload = msg.payload || {};
1013
- session.runtimeSession.sendCodexMessage({
1014
- type: "permission-request",
1015
- permissionId: msg.id,
1016
- toolName: typeof payload.toolName === "string" ? payload.toolName : msg.reason,
1017
- description: msg.reason,
1018
- options: payload
1019
- });
677
+ forwardAgentMessage(msg);
1020
678
  return;
1021
679
  }
1022
680
  case "exec-approval-request": {
1023
- const { call_id, type, ...inputs } = msg;
1024
- messageBuffer.addMessage(`Exec approval requested: ${call_id}`, "tool");
1025
- const rawToolName = "CodexBash";
1026
- const canonicalToolName = resolveCanonicalToolNameV2(rawToolName);
1027
- session.runtimeSession.sendCodexMessage({
1028
- type: "tool-call",
1029
- name: canonicalToolName,
1030
- callId: call_id,
1031
- input: attachToolHappierMetaV2(inputs, {
1032
- v: 2,
1033
- protocol: "acp",
1034
- provider: "codex",
1035
- rawToolName,
1036
- canonicalToolName
1037
- }),
1038
- id: randomUUID()
1039
- });
681
+ messageBuffer.addMessage(`Exec approval requested: ${msg.call_id}`, "tool");
682
+ forwardAgentMessage(msg);
1040
683
  return;
1041
684
  }
1042
685
  case "patch-apply-begin": {
1043
686
  const changeCount = Object.keys(msg.changes || {}).length;
1044
687
  const filesMsg = changeCount === 1 ? "1 file" : `${changeCount} files`;
1045
688
  messageBuffer.addMessage(`Modifying ${filesMsg}...`, "tool");
1046
- const rawToolName = "CodexPatch";
1047
- const canonicalToolName = resolveCanonicalToolNameV2(rawToolName);
1048
- session.runtimeSession.sendCodexMessage({
1049
- type: "tool-call",
1050
- name: canonicalToolName,
1051
- callId: msg.call_id,
1052
- input: attachToolHappierMetaV2({
1053
- auto_approved: msg.auto_approved,
1054
- changes: msg.changes
1055
- }, {
1056
- v: 2,
1057
- protocol: "acp",
1058
- provider: "codex",
1059
- rawToolName,
1060
- canonicalToolName
1061
- }),
1062
- id: randomUUID()
1063
- });
689
+ forwardAgentMessage(msg);
1064
690
  return;
1065
691
  }
1066
692
  case "patch-apply-end": {
@@ -1069,30 +695,11 @@ async function codexRemoteLauncher(session) {
1069
695
  } else {
1070
696
  messageBuffer.addMessage(`Error: ${truncateDisplayMessage(msg.stderr || "Failed to modify files", 200)}`, "result");
1071
697
  }
1072
- session.runtimeSession.sendCodexMessage({
1073
- type: "tool-call-result",
1074
- callId: msg.call_id,
1075
- output: attachToolHappierMetaV2({
1076
- stdout: msg.stdout,
1077
- stderr: msg.stderr,
1078
- success: msg.success
1079
- }, {
1080
- v: 2,
1081
- protocol: "acp",
1082
- provider: "codex",
1083
- rawToolName: "CodexPatch",
1084
- canonicalToolName: resolveCanonicalToolNameV2("CodexPatch")
1085
- }),
1086
- id: randomUUID(),
1087
- isError: !msg.success
1088
- });
698
+ forwardAgentMessage(msg);
1089
699
  return;
1090
700
  }
1091
701
  case "token-count": {
1092
- session.runtimeSession.sendCodexMessage({
1093
- ...msg,
1094
- id: randomUUID()
1095
- });
702
+ forwardAgentMessage(msg);
1096
703
  return;
1097
704
  }
1098
705
  case "event": {
@@ -1118,7 +725,7 @@ async function codexRemoteLauncher(session) {
1118
725
  }
1119
726
  });
1120
727
  };
1121
- const createBackend = (mode) => {
728
+ const createRuntimeHandle = async (mode) => {
1122
729
  const executionMode = resolveCodexAcpExecutionMode(mode);
1123
730
  const validation = validateCodexAcpSpawn({
1124
731
  command: process.env.HAPPY_CODEX_ACP_COMMAND,
@@ -1130,20 +737,25 @@ async function codexRemoteLauncher(session) {
1130
737
  if (!validation.ok) {
1131
738
  throw new Error(validation.errorMessage);
1132
739
  }
1133
- const result = createCodexBackend({
740
+ const { session: nextRuntimeHandle } = await launchRuntimeHandleWithFactoryResult({
741
+ provider: "codex",
1134
742
  cwd: session.path,
1135
- baseArgs: session.codexArgs,
1136
- model: executionMode.model,
1137
- sandbox: executionMode.sandbox,
1138
- approvalPolicy: executionMode.approvalPolicy,
1139
- permissionHandler,
1140
- selectionHandler: {
1141
- handleSelection: (request) => selectionHandler.requestSelection(request)
1142
- }
743
+ createBackendResult: (opts) => createCodexBackend({
744
+ ...opts,
745
+ baseArgs: session.codexArgs,
746
+ model: executionMode.model,
747
+ sandbox: executionMode.sandbox,
748
+ approvalPolicy: executionMode.approvalPolicy,
749
+ permissionHandler,
750
+ selectionHandler: {
751
+ handleSelection: (request) => selectionHandler.requestSelection(request)
752
+ }
753
+ })
1143
754
  });
1144
- backend = result.backend;
1145
- setupBackendMessageHandler(result.backend);
1146
- return result.backend;
755
+ runtimeHandle = nextRuntimeHandle;
756
+ setupRuntimeMessageHandler(nextRuntimeHandle);
757
+ session.onSessionFound(nextRuntimeHandle.sessionId);
758
+ return nextRuntimeHandle;
1147
759
  };
1148
760
  const handleSwitchToLocal = async () => {
1149
761
  const message = getCodexLegacySwitchIgnoredMessage();
@@ -1162,17 +774,10 @@ async function codexRemoteLauncher(session) {
1162
774
  logger.debug("[Codex] Kill session requested - terminating process");
1163
775
  await handleAbort();
1164
776
  try {
1165
- session.runtimeSession.updateMetadata((currentMetadata) => ({
1166
- ...currentMetadata,
1167
- lifecycleState: "archived",
1168
- lifecycleStateSince: Date.now(),
1169
- archivedBy: "cli",
777
+ await closeProviderSession(session.runtimeSession, {
1170
778
  archiveReason: "User terminated"
1171
- }));
1172
- session.runtimeSession.sendSessionDeath();
1173
- await session.runtimeSession.flush();
1174
- await session.runtimeSession.close();
1175
- await disposeBackend();
779
+ });
780
+ await disposeRuntimeHandle();
1176
781
  stopCaffeinate();
1177
782
  process.exit(0);
1178
783
  } catch (error) {
@@ -1236,9 +841,9 @@ async function codexRemoteLauncher(session) {
1236
841
  if (!message) {
1237
842
  break;
1238
843
  }
1239
- if (wasSessionCreated && currentModeHash && message.hash !== currentModeHash) {
844
+ if (runtimeHandle && currentModeHash && message.hash !== currentModeHash) {
1240
845
  queueHistoryInjectionForRestart("Starting new Codex session (execution settings changed)...");
1241
- await disposeBackend();
846
+ await disposeRuntimeHandle();
1242
847
  session.clearSessionId();
1243
848
  currentModeHash = null;
1244
849
  pending = message;
@@ -1254,16 +859,10 @@ async function codexRemoteLauncher(session) {
1254
859
  try {
1255
860
  turnInFlight = true;
1256
861
  shouldCommitAccumulatedResponse = false;
1257
- const activeBackend = backend ?? createBackend(message.mode);
1258
- if (!activeBackend) {
862
+ const activeRuntimeHandle = runtimeHandle ?? await createRuntimeHandle(message.mode);
863
+ if (!activeRuntimeHandle) {
1259
864
  throw new Error("Failed to create Codex ACP backend");
1260
865
  }
1261
- if (!acpSessionId) {
1262
- const { sessionId } = await activeBackend.startSession();
1263
- acpSessionId = sessionId;
1264
- wasSessionCreated = true;
1265
- session.onSessionFound(sessionId);
1266
- }
1267
866
  session.onThinkingChange(true);
1268
867
  let promptToSend = message.message;
1269
868
  if (shouldInjectHistoryOnNextSession && conversationHistory.hasHistory()) {
@@ -1272,8 +871,8 @@ async function codexRemoteLauncher(session) {
1272
871
  logger.debug(`[Codex] Injected conversation history context (${historyContext.length} chars)`);
1273
872
  }
1274
873
  conversationHistory.addUserMessage(message.message);
1275
- await activeBackend.sendPrompt(acpSessionId, promptToSend);
1276
- await waitForResponseCompleteWithAbort(activeBackend, turnSignal);
874
+ await activeRuntimeHandle.sendPrompt(promptToSend);
875
+ await waitForResponseCompleteWithAbort(activeRuntimeHandle.backend, turnSignal);
1277
876
  reasoningProcessor.completeCurrent();
1278
877
  shouldCommitAccumulatedResponse = true;
1279
878
  shouldInjectHistoryOnNextSession = false;
@@ -1293,7 +892,7 @@ async function codexRemoteLauncher(session) {
1293
892
  if (conversationHistory.hasHistory()) {
1294
893
  shouldInjectHistoryOnNextSession = true;
1295
894
  }
1296
- await disposeBackend();
895
+ await disposeRuntimeHandle();
1297
896
  session.clearSessionId();
1298
897
  }
1299
898
  } finally {
@@ -1322,7 +921,7 @@ async function codexRemoteLauncher(session) {
1322
921
  }
1323
922
  }
1324
923
  } finally {
1325
- await disposeBackend();
924
+ await disposeRuntimeHandle();
1326
925
  permissionHandler.reset();
1327
926
  selectionHandler.reset();
1328
927
  session.removeClientSwapCallback(handleClientSwap);
@@ -1353,49 +952,24 @@ async function codexRemoteLauncher(session) {
1353
952
 
1354
953
  async function codexLoop(opts) {
1355
954
  const displayMode = opts.startingMode ?? "local";
1356
- await opts.session.onModeChange(displayMode);
1357
955
  logger.debug(`[codex-loop] Starting ACP-only Codex launcher with display mode: ${displayMode}`);
1358
- await codexRemoteLauncher(opts.session);
1359
- return 0;
1360
- }
1361
-
1362
- function supportsAgentStateUpdateEvents(sessionClient) {
1363
- return typeof sessionClient.once === "function" && typeof sessionClient.off === "function";
1364
- }
1365
- async function syncControlledByUserState(sessionClient, controlledByUser) {
1366
- if (!supportsAgentStateUpdateEvents(sessionClient)) {
1367
- sessionClient.updateAgentState((currentState) => ({
1368
- ...currentState,
1369
- controlledByUser
1370
- }));
1371
- return;
1372
- }
1373
- await new Promise((resolve) => {
1374
- let settled = false;
1375
- const handleUpdated = () => {
1376
- if (settled) {
1377
- return;
1378
- }
1379
- settled = true;
1380
- clearTimeout(timeout);
1381
- sessionClient.off("agent-state-updated", handleUpdated);
1382
- resolve();
1383
- };
1384
- const timeout = setTimeout(() => {
1385
- if (settled) {
1386
- return;
1387
- }
1388
- settled = true;
1389
- sessionClient.off("agent-state-updated", handleUpdated);
1390
- resolve();
1391
- }, 1500);
1392
- sessionClient.once("agent-state-updated", handleUpdated);
1393
- sessionClient.updateAgentState((currentState) => ({
1394
- ...currentState,
1395
- controlledByUser
1396
- }));
956
+ const launchCurrentMode = async () => {
957
+ await codexRemoteLauncher(opts.session);
958
+ return { type: "exit", value: 0 };
959
+ };
960
+ return await runModeLoop({
961
+ startingMode: displayMode,
962
+ notifyInitialMode: true,
963
+ onModeChange: async (mode) => {
964
+ await opts.session.onModeChange(mode);
965
+ },
966
+ launchers: {
967
+ local: launchCurrentMode,
968
+ remote: launchCurrentMode
969
+ }
1397
970
  });
1398
971
  }
972
+
1399
973
  function resolveInitialCodexPermissionMode(opts) {
1400
974
  if (opts.permissionMode) {
1401
975
  return opts.permissionMode;
@@ -1434,48 +1008,38 @@ async function runCodex(opts) {
1434
1008
  }
1435
1009
  const api = await ApiClient.create(opts.credentials);
1436
1010
  logger.debug(`[codex] Starting with options: startedBy=${opts.startedBy || "terminal"}, requestedStartingMode=${requestedStartingMode}, executionMode=acp-only`);
1437
- const settings = await readSettings();
1438
- const machineId = settings?.machineId;
1439
- if (!machineId) {
1440
- console.error("[START] No machine ID found in settings.");
1441
- process.exit(1);
1442
- }
1443
- await api.getOrCreateMachine({
1444
- machineId,
1445
- metadata: initialMachineMetadata
1446
- });
1447
- const { state, metadata } = createSessionMetadata({
1448
- flavor: "codex",
1449
- machineId,
1450
- startedBy: opts.startedBy
1451
- });
1452
- let response = null;
1011
+ let machineId;
1453
1012
  try {
1454
- response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
1013
+ machineId = await ensureManagedProviderMachine({
1014
+ api,
1015
+ missingMachineIdMessage: "[START] No machine ID found in settings."
1016
+ });
1455
1017
  } catch (error) {
1456
- if (!isAuthenticationRequiredError(error)) {
1457
- throw error;
1018
+ if (error instanceof MissingMachineIdError) {
1019
+ console.error(error.message);
1020
+ process.exit(1);
1458
1021
  }
1459
- logger.debug("[codex] Falling back to offline session stub after authentication failure");
1022
+ throw error;
1460
1023
  }
1461
1024
  let sessionClient;
1462
1025
  let codexSession = null;
1463
- const { session: initialSession, reconnectionHandle } = setupOfflineReconnection({
1026
+ const { metadata, session: initialSession, reconnectionHandle } = await bootstrapManagedProviderSession({
1464
1027
  api,
1465
1028
  sessionTag,
1466
- metadata,
1467
- state,
1468
- response,
1029
+ flavor: "codex",
1030
+ machineId,
1031
+ startedBy: opts.startedBy,
1032
+ authFallbackLogMessage: "[codex] Falling back to offline session stub after authentication failure",
1469
1033
  onSessionSwap: (newSession) => {
1470
1034
  sessionClient = newSession;
1471
1035
  codexSession?.updateClient(newSession);
1472
- void publishSessionRegistration(newSession.sessionId, metadata);
1036
+ void syncControlledByUserState(
1037
+ newSession,
1038
+ (codexSession?.mode ?? requestedStartingMode) === "local"
1039
+ );
1473
1040
  }
1474
1041
  });
1475
1042
  sessionClient = initialSession;
1476
- if (response) {
1477
- await publishSessionRegistration(response.id, metadata);
1478
- }
1479
1043
  const messageQueue = new MessageQueue2(getCodexExecutionFingerprint);
1480
1044
  let currentPermissionMode = initialPermissionMode;
1481
1045
  let currentModel;
@@ -1515,20 +1079,33 @@ async function runCodex(opts) {
1515
1079
  await syncControlledByUserState(sessionClient, mode === "local");
1516
1080
  }
1517
1081
  });
1082
+ let exitCode = null;
1083
+ let loopError;
1084
+ let closeError;
1518
1085
  try {
1519
- const exitCode = await codexLoop({
1086
+ exitCode = await codexLoop({
1520
1087
  session: codexSession,
1521
1088
  startingMode: requestedStartingMode
1522
1089
  });
1523
- sessionClient.sendSessionDeath();
1524
- await sessionClient.flush();
1525
- await sessionClient.close();
1526
- stopCaffeinate();
1527
- process.exit(exitCode);
1090
+ } catch (error) {
1091
+ loopError = error;
1528
1092
  } finally {
1093
+ try {
1094
+ await closeProviderSession(sessionClient);
1095
+ } catch (error) {
1096
+ closeError = error;
1097
+ }
1098
+ stopCaffeinate();
1529
1099
  reconnectionHandle?.cancel();
1530
1100
  codexSession.cleanup();
1531
1101
  }
1102
+ if (loopError !== void 0) {
1103
+ throw loopError;
1104
+ }
1105
+ if (closeError !== void 0) {
1106
+ throw closeError;
1107
+ }
1108
+ process.exit(exitCode ?? 1);
1532
1109
  }
1533
1110
 
1534
- export { resolveIncomingCodexPermissionMode, resolveInitialCodexPermissionMode, resolveQueuedCodexPermissionMode, runCodex, supportsAgentStateUpdateEvents, syncControlledByUserState };
1111
+ export { resolveIncomingCodexPermissionMode, resolveInitialCodexPermissionMode, resolveQueuedCodexPermissionMode, runCodex };