happy-imou-cloud 2.1.19 → 2.1.21

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 (28) hide show
  1. package/dist/{BaseReasoningProcessor-Dtna0CRw.mjs → BaseReasoningProcessor-DdPnQSmQ.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-CuB78Igh.cjs → BaseReasoningProcessor-DxFwO2z9.cjs} +2 -2
  3. package/dist/{ProviderSelectionHandler-SWCNmor4.cjs → ProviderSelectionHandler-B5Aq5gA_.cjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-DJSSjBOW.mjs → ProviderSelectionHandler-DQITIqZK.mjs} +2 -2
  5. package/dist/{api-ljzPqsrQ.cjs → api-8xtJZeXZ.cjs} +29 -10
  6. package/dist/{api-LMpXb-Ju.mjs → api-DB30ctmX.mjs} +29 -10
  7. package/dist/{command-C4nFEFfU.cjs → command-79M8Bz4I.cjs} +2 -2
  8. package/dist/{command-wiV8CFFG.mjs → command-C6injNJE.mjs} +2 -2
  9. package/dist/{index-SnNF4vVI.cjs → index-D4A092LJ.cjs} +78 -28
  10. package/dist/{index-CYRsGHXJ.mjs → index-krVv4CWK.mjs} +75 -25
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/lib.cjs +1 -1
  14. package/dist/lib.d.cts +3 -0
  15. package/dist/lib.d.mts +3 -0
  16. package/dist/lib.mjs +1 -1
  17. package/dist/{registerKillSessionHandler-C4IlzmJl.cjs → registerKillSessionHandler-Cl_er9rg.cjs} +2 -2
  18. package/dist/{registerKillSessionHandler-BD-ChYZJ.mjs → registerKillSessionHandler-beu2g-Qo.mjs} +2 -2
  19. package/dist/{runClaude-khz1woP5.cjs → runClaude-DmXinUiz.cjs} +4 -4
  20. package/dist/{runClaude-DrWj6fju.mjs → runClaude-nzLh-orP.mjs} +4 -4
  21. package/dist/{runCodex-CfQgQ4gD.mjs → runCodex-C-Pjpbxn.mjs} +5 -5
  22. package/dist/{runCodex-Vb_tfdTK.cjs → runCodex-CoqsQ-cb.cjs} +5 -5
  23. package/dist/{runGemini-MIoPtN_R.cjs → runGemini-BC_5rNMt.cjs} +4 -4
  24. package/dist/{runGemini-BOdeej1A.mjs → runGemini-CIVm6NWC.mjs} +4 -4
  25. package/package.json +3 -2
  26. package/scripts/build.mjs +38 -34
  27. package/scripts/release-smoke.mjs +17 -39
  28. package/scripts/tooling-utils.mjs +167 -0
@@ -1,5 +1,5 @@
1
- import { a as createSessionMetadata, p as publishSessionRegistration } from './index-CYRsGHXJ.mjs';
2
- import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-LMpXb-Ju.mjs';
1
+ import { a as createSessionMetadata, p as publishSessionRegistration } from './index-krVv4CWK.mjs';
2
+ import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-DB30ctmX.mjs';
3
3
  import { EventEmitter } from 'node:events';
4
4
  import { randomUUID } from 'node:crypto';
5
5
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-SnNF4vVI.cjs');
4
- var persistence = require('./api-ljzPqsrQ.cjs');
3
+ var index = require('./index-D4A092LJ.cjs');
4
+ var persistence = require('./api-8xtJZeXZ.cjs');
5
5
  var node_events = require('node:events');
6
6
  var node_crypto = require('node:crypto');
7
7
 
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./api-ljzPqsrQ.cjs');
4
- var registerKillSessionHandler = require('./registerKillSessionHandler-C4IlzmJl.cjs');
3
+ var persistence = require('./api-8xtJZeXZ.cjs');
4
+ var registerKillSessionHandler = require('./registerKillSessionHandler-Cl_er9rg.cjs');
5
5
 
6
6
  async function runModeLoop(opts) {
7
7
  let currentMode = opts.startingMode;
@@ -1,5 +1,5 @@
1
- import { l as logger } from './api-LMpXb-Ju.mjs';
2
- import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-BD-ChYZJ.mjs';
1
+ import { l as logger } from './api-DB30ctmX.mjs';
2
+ import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-beu2g-Qo.mjs';
3
3
 
4
4
  async function runModeLoop(opts) {
5
5
  let currentMode = opts.startingMode;
@@ -38,7 +38,7 @@ function _interopNamespaceDefault(e) {
38
38
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
39
39
 
40
40
  var name = "happy-imou-cloud";
41
- var version = "2.1.19";
41
+ var version = "2.1.21";
42
42
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
43
43
  var author = "long.zhu";
44
44
  var license = "MIT";
@@ -120,7 +120,8 @@ var scripts = {
120
120
  doctor: "node scripts/env-wrapper.cjs stable doctor",
121
121
  "// ==== Development Linking ====": "",
122
122
  "link:dev": "node scripts/link-dev.cjs",
123
- "unlink:dev": "node scripts/link-dev.cjs unlink"
123
+ "unlink:dev": "node scripts/link-dev.cjs unlink",
124
+ "test:happy-org-1.2-quality": "node ../../node_modules/vitest/vitest.mjs run --config vitest.happy-org-1.2.config.mjs"
124
125
  };
125
126
  var dependencies = {
126
127
  "@agentclientprotocol/sdk": "0.16.1",
@@ -2605,6 +2606,7 @@ class ApiSessionClient extends node_events.EventEmitter {
2605
2606
  protocolV3SocketCapabilities = null;
2606
2607
  protocolV3SessionSyncWaitTimer = null;
2607
2608
  committedSessionWriteAckMode;
2609
+ pendingBackgroundTasks = /* @__PURE__ */ new Set();
2608
2610
  pendingHappyOrgDispatchBusinessAcks = /* @__PURE__ */ new Set();
2609
2611
  recordedHappyOrgDispatchBusinessAcks = /* @__PURE__ */ new Set();
2610
2612
  pendingHappyOrgTaskTurnReports = /* @__PURE__ */ new Set();
@@ -3010,7 +3012,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3010
3012
  * @param handler - Handler function that returns the updated metadata
3011
3013
  */
3012
3014
  updateMetadata(handler) {
3013
- void this.metadataLock.inLock(async () => {
3015
+ this.trackPendingBackgroundTask(this.metadataLock.inLock(async () => {
3014
3016
  await backoff(async () => {
3015
3017
  let updated = this.mergeRuntimeMetadata(handler(this.metadata));
3016
3018
  const sessionIndex = buildSessionRuntimeIndex(updated);
@@ -3039,7 +3041,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3039
3041
  });
3040
3042
  }).catch((error) => {
3041
3043
  logger.debug("[API] Metadata update failed unexpectedly", error);
3042
- });
3044
+ }));
3043
3045
  }
3044
3046
  /**
3045
3047
  * Update session agent state
@@ -3047,7 +3049,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3047
3049
  */
3048
3050
  updateAgentState(handler) {
3049
3051
  logger.debugLargeJson("Updating agent state", this.agentState);
3050
- void this.agentStateLock.inLock(async () => {
3052
+ this.trackPendingBackgroundTask(this.agentStateLock.inLock(async () => {
3051
3053
  await backoff(async () => {
3052
3054
  let updated = handler(this.agentState || {});
3053
3055
  const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
@@ -3067,7 +3069,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3067
3069
  });
3068
3070
  }).catch((error) => {
3069
3071
  logger.debug("[API] Agent state update failed unexpectedly", error);
3070
- });
3072
+ }));
3071
3073
  }
3072
3074
  /**
3073
3075
  * Wait for socket buffer to flush
@@ -3077,6 +3079,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3077
3079
  return;
3078
3080
  }
3079
3081
  await this.waitForReliableSessionMessagesToDrain();
3082
+ await this.waitForPendingBackgroundTasksToDrain();
3080
3083
  if (!this.socket.connected) {
3081
3084
  return;
3082
3085
  }
@@ -3090,11 +3093,27 @@ class ApiSessionClient extends node_events.EventEmitter {
3090
3093
  });
3091
3094
  }
3092
3095
  async close() {
3096
+ if (this.socket.connected) {
3097
+ await this.waitForPendingBackgroundTasksToDrain();
3098
+ }
3093
3099
  logger.debug("[API] socket.close() called");
3094
3100
  this.clearReconnectAfterServerDisconnectTimer();
3095
3101
  this.invalidateProtocolV3SessionSync();
3096
3102
  this.socket.close();
3097
3103
  }
3104
+ trackPendingBackgroundTask(task) {
3105
+ let trackedTask;
3106
+ trackedTask = task.finally(() => {
3107
+ this.pendingBackgroundTasks.delete(trackedTask);
3108
+ });
3109
+ this.pendingBackgroundTasks.add(trackedTask);
3110
+ return trackedTask;
3111
+ }
3112
+ async waitForPendingBackgroundTasksToDrain() {
3113
+ while (this.pendingBackgroundTasks.size > 0) {
3114
+ await Promise.allSettled(Array.from(this.pendingBackgroundTasks));
3115
+ }
3116
+ }
3098
3117
  handleSocketUpdate(data) {
3099
3118
  if (!data.body) {
3100
3119
  logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
@@ -3176,9 +3195,9 @@ class ApiSessionClient extends node_events.EventEmitter {
3176
3195
  return;
3177
3196
  }
3178
3197
  this.pendingHappyOrgDispatchBusinessAcks.add(ackKey);
3179
- void this.submitHappyOrgDispatchBusinessAck(candidate).finally(() => {
3198
+ this.trackPendingBackgroundTask(this.submitHappyOrgDispatchBusinessAck(candidate).finally(() => {
3180
3199
  this.pendingHappyOrgDispatchBusinessAcks.delete(ackKey);
3181
- });
3200
+ }));
3182
3201
  }
3183
3202
  resolveHappyOrgDispatchBusinessAckCandidate(message) {
3184
3203
  const taskContext = message.meta?.happyOrg?.taskContext ?? this.metadata?.happyOrg?.taskContext;
@@ -3208,9 +3227,9 @@ class ApiSessionClient extends node_events.EventEmitter {
3208
3227
  return;
3209
3228
  }
3210
3229
  this.pendingHappyOrgTaskTurnReports.add(submissionKey);
3211
- void this.submitHappyOrgTaskTurnReport(message.report, submissionKey).finally(() => {
3230
+ this.trackPendingBackgroundTask(this.submitHappyOrgTaskTurnReport(message.report, submissionKey).finally(() => {
3212
3231
  this.pendingHappyOrgTaskTurnReports.delete(submissionKey);
3213
- });
3232
+ }));
3214
3233
  }
3215
3234
  async ensureHappyOrgControlCredentials() {
3216
3235
  if (this.credentials.signing) {
@@ -18,7 +18,7 @@ import { unlink, readFile, mkdir, open, stat, writeFile, rename } from 'node:fs/
18
18
  import { Expo } from 'expo-server-sdk';
19
19
 
20
20
  var name = "happy-imou-cloud";
21
- var version = "2.1.19";
21
+ var version = "2.1.21";
22
22
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
23
23
  var author = "long.zhu";
24
24
  var license = "MIT";
@@ -100,7 +100,8 @@ var scripts = {
100
100
  doctor: "node scripts/env-wrapper.cjs stable doctor",
101
101
  "// ==== Development Linking ====": "",
102
102
  "link:dev": "node scripts/link-dev.cjs",
103
- "unlink:dev": "node scripts/link-dev.cjs unlink"
103
+ "unlink:dev": "node scripts/link-dev.cjs unlink",
104
+ "test:happy-org-1.2-quality": "node ../../node_modules/vitest/vitest.mjs run --config vitest.happy-org-1.2.config.mjs"
104
105
  };
105
106
  var dependencies = {
106
107
  "@agentclientprotocol/sdk": "0.16.1",
@@ -2585,6 +2586,7 @@ class ApiSessionClient extends EventEmitter {
2585
2586
  protocolV3SocketCapabilities = null;
2586
2587
  protocolV3SessionSyncWaitTimer = null;
2587
2588
  committedSessionWriteAckMode;
2589
+ pendingBackgroundTasks = /* @__PURE__ */ new Set();
2588
2590
  pendingHappyOrgDispatchBusinessAcks = /* @__PURE__ */ new Set();
2589
2591
  recordedHappyOrgDispatchBusinessAcks = /* @__PURE__ */ new Set();
2590
2592
  pendingHappyOrgTaskTurnReports = /* @__PURE__ */ new Set();
@@ -2990,7 +2992,7 @@ class ApiSessionClient extends EventEmitter {
2990
2992
  * @param handler - Handler function that returns the updated metadata
2991
2993
  */
2992
2994
  updateMetadata(handler) {
2993
- void this.metadataLock.inLock(async () => {
2995
+ this.trackPendingBackgroundTask(this.metadataLock.inLock(async () => {
2994
2996
  await backoff(async () => {
2995
2997
  let updated = this.mergeRuntimeMetadata(handler(this.metadata));
2996
2998
  const sessionIndex = buildSessionRuntimeIndex(updated);
@@ -3019,7 +3021,7 @@ class ApiSessionClient extends EventEmitter {
3019
3021
  });
3020
3022
  }).catch((error) => {
3021
3023
  logger.debug("[API] Metadata update failed unexpectedly", error);
3022
- });
3024
+ }));
3023
3025
  }
3024
3026
  /**
3025
3027
  * Update session agent state
@@ -3027,7 +3029,7 @@ class ApiSessionClient extends EventEmitter {
3027
3029
  */
3028
3030
  updateAgentState(handler) {
3029
3031
  logger.debugLargeJson("Updating agent state", this.agentState);
3030
- void this.agentStateLock.inLock(async () => {
3032
+ this.trackPendingBackgroundTask(this.agentStateLock.inLock(async () => {
3031
3033
  await backoff(async () => {
3032
3034
  let updated = handler(this.agentState || {});
3033
3035
  const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
@@ -3047,7 +3049,7 @@ class ApiSessionClient extends EventEmitter {
3047
3049
  });
3048
3050
  }).catch((error) => {
3049
3051
  logger.debug("[API] Agent state update failed unexpectedly", error);
3050
- });
3052
+ }));
3051
3053
  }
3052
3054
  /**
3053
3055
  * Wait for socket buffer to flush
@@ -3057,6 +3059,7 @@ class ApiSessionClient extends EventEmitter {
3057
3059
  return;
3058
3060
  }
3059
3061
  await this.waitForReliableSessionMessagesToDrain();
3062
+ await this.waitForPendingBackgroundTasksToDrain();
3060
3063
  if (!this.socket.connected) {
3061
3064
  return;
3062
3065
  }
@@ -3070,11 +3073,27 @@ class ApiSessionClient extends EventEmitter {
3070
3073
  });
3071
3074
  }
3072
3075
  async close() {
3076
+ if (this.socket.connected) {
3077
+ await this.waitForPendingBackgroundTasksToDrain();
3078
+ }
3073
3079
  logger.debug("[API] socket.close() called");
3074
3080
  this.clearReconnectAfterServerDisconnectTimer();
3075
3081
  this.invalidateProtocolV3SessionSync();
3076
3082
  this.socket.close();
3077
3083
  }
3084
+ trackPendingBackgroundTask(task) {
3085
+ let trackedTask;
3086
+ trackedTask = task.finally(() => {
3087
+ this.pendingBackgroundTasks.delete(trackedTask);
3088
+ });
3089
+ this.pendingBackgroundTasks.add(trackedTask);
3090
+ return trackedTask;
3091
+ }
3092
+ async waitForPendingBackgroundTasksToDrain() {
3093
+ while (this.pendingBackgroundTasks.size > 0) {
3094
+ await Promise.allSettled(Array.from(this.pendingBackgroundTasks));
3095
+ }
3096
+ }
3078
3097
  handleSocketUpdate(data) {
3079
3098
  if (!data.body) {
3080
3099
  logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
@@ -3156,9 +3175,9 @@ class ApiSessionClient extends EventEmitter {
3156
3175
  return;
3157
3176
  }
3158
3177
  this.pendingHappyOrgDispatchBusinessAcks.add(ackKey);
3159
- void this.submitHappyOrgDispatchBusinessAck(candidate).finally(() => {
3178
+ this.trackPendingBackgroundTask(this.submitHappyOrgDispatchBusinessAck(candidate).finally(() => {
3160
3179
  this.pendingHappyOrgDispatchBusinessAcks.delete(ackKey);
3161
- });
3180
+ }));
3162
3181
  }
3163
3182
  resolveHappyOrgDispatchBusinessAckCandidate(message) {
3164
3183
  const taskContext = message.meta?.happyOrg?.taskContext ?? this.metadata?.happyOrg?.taskContext;
@@ -3188,9 +3207,9 @@ class ApiSessionClient extends EventEmitter {
3188
3207
  return;
3189
3208
  }
3190
3209
  this.pendingHappyOrgTaskTurnReports.add(submissionKey);
3191
- void this.submitHappyOrgTaskTurnReport(message.report, submissionKey).finally(() => {
3210
+ this.trackPendingBackgroundTask(this.submitHappyOrgTaskTurnReport(message.report, submissionKey).finally(() => {
3192
3211
  this.pendingHappyOrgTaskTurnReports.delete(submissionKey);
3193
- });
3212
+ }));
3194
3213
  }
3195
3214
  async ensureHappyOrgControlCredentials() {
3196
3215
  if (this.credentials.signing) {
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-SnNF4vVI.cjs');
3
+ var index = require('./index-D4A092LJ.cjs');
4
4
  require('chalk');
5
- require('./api-ljzPqsrQ.cjs');
5
+ require('./api-8xtJZeXZ.cjs');
6
6
  require('axios');
7
7
  require('fs');
8
8
  require('node:fs');
@@ -1,6 +1,6 @@
1
- import { c as createDefaultRuntimeShell } from './index-CYRsGHXJ.mjs';
1
+ import { c as createDefaultRuntimeShell } from './index-krVv4CWK.mjs';
2
2
  import 'chalk';
3
- import './api-LMpXb-Ju.mjs';
3
+ import './api-DB30ctmX.mjs';
4
4
  import 'axios';
5
5
  import 'fs';
6
6
  import 'node:fs';
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var persistence = require('./api-ljzPqsrQ.cjs');
4
+ var persistence = require('./api-8xtJZeXZ.cjs');
5
5
  var z = require('zod');
6
6
  var fs$2 = require('fs/promises');
7
7
  var os$1 = require('os');
@@ -71,7 +71,7 @@ async function openBrowser(url) {
71
71
  }
72
72
  }
73
73
 
74
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-SnNF4vVI.cjs', document.baseURI).href)));
74
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D4A092LJ.cjs', document.baseURI).href)));
75
75
  const QRCode = require$1("qrcode-terminal/vendor/QRCode");
76
76
  const QRErrorCorrectLevel = require$1("qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel");
77
77
  const pendingTempFiles = /* @__PURE__ */ new Set();
@@ -636,7 +636,7 @@ function setupCleanupHandlers() {
636
636
  });
637
637
  }
638
638
 
639
- const __dirname$2 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-SnNF4vVI.cjs', document.baseURI).href))));
639
+ const __dirname$2 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D4A092LJ.cjs', document.baseURI).href))));
640
640
  function projectPath() {
641
641
  const path = path$1.resolve(__dirname$2, "..");
642
642
  return path;
@@ -2988,6 +2988,28 @@ const initialMachineMetadata = {
2988
2988
  happyHomeDir: persistence.configuration.happyCloudHomeDir,
2989
2989
  happyLibDir: projectPath()
2990
2990
  };
2991
+ function createOfflineMachine(credentials, machineId, daemonState) {
2992
+ if (credentials.encryption.type === "dataKey") {
2993
+ return {
2994
+ id: machineId,
2995
+ encryptionKey: credentials.encryption.machineKey,
2996
+ encryptionVariant: "dataKey",
2997
+ metadata: initialMachineMetadata,
2998
+ metadataVersion: 0,
2999
+ daemonState,
3000
+ daemonStateVersion: 0
3001
+ };
3002
+ }
3003
+ return {
3004
+ id: machineId,
3005
+ encryptionKey: credentials.encryption.secret,
3006
+ encryptionVariant: "legacy",
3007
+ metadata: initialMachineMetadata,
3008
+ metadataVersion: 0,
3009
+ daemonState,
3010
+ daemonStateVersion: 0
3011
+ };
3012
+ }
2991
3013
  async function getProfileEnvironmentVariablesForAgent(profileId, agentType) {
2992
3014
  try {
2993
3015
  const settings = await persistence.readSettings();
@@ -3562,19 +3584,41 @@ ${stderrSnapshot}`);
3562
3584
  persistence.logger.debug("[DAEMON RUN] Failed to start user-scoped observer, continuing without it", error);
3563
3585
  }
3564
3586
  }
3565
- const machine = await activeApi.getOrCreateMachine({
3566
- machineId,
3567
- metadata: initialMachineMetadata,
3568
- daemonState: initialDaemonState
3569
- });
3570
- persistence.logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
3571
- const apiMachine = activeApi.machineSyncClient(machine);
3572
- apiMachine.setRPCHandlers({
3587
+ let machine = createOfflineMachine(credentials, machineId, initialDaemonState);
3588
+ let daemonHasAuthenticatedSync = false;
3589
+ try {
3590
+ machine = await activeApi.getOrCreateMachine({
3591
+ machineId,
3592
+ metadata: initialMachineMetadata,
3593
+ daemonState: initialDaemonState
3594
+ });
3595
+ daemonHasAuthenticatedSync = true;
3596
+ persistence.logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
3597
+ } catch (error) {
3598
+ if (!persistence.isAuthenticationRequiredError(error)) {
3599
+ throw error;
3600
+ }
3601
+ persistence.logger.warn(
3602
+ "[DAEMON RUN] Machine registration rejected by the server. Continuing with local-only daemon control until authentication is refreshed.",
3603
+ error instanceof Error ? error.message : String(error)
3604
+ );
3605
+ try {
3606
+ await userScopedObserver?.close();
3607
+ } catch (observerCloseError) {
3608
+ persistence.logger.debug("[DAEMON RUN] Failed to close user-scoped observer after auth fallback", observerCloseError);
3609
+ }
3610
+ userScopedObserver = null;
3611
+ }
3612
+ const apiMachine = daemonHasAuthenticatedSync ? activeApi.machineSyncClient(machine) : null;
3613
+ apiMachine?.setRPCHandlers({
3573
3614
  spawnSession,
3574
3615
  stopSession,
3575
3616
  requestShutdown: () => requestShutdown("happy-app")
3576
3617
  });
3577
3618
  const runRemoteSessionIndexRecovery = async (label) => {
3619
+ if (!daemonHasAuthenticatedSync) {
3620
+ return;
3621
+ }
3578
3622
  const recoveryResult = await recoverTrackedSessionsFromRemoteIndex({
3579
3623
  api: activeApi,
3580
3624
  machineId,
@@ -3609,7 +3653,7 @@ ${stderrSnapshot}`);
3609
3653
  scheduleRemoteSessionIndexRecovery();
3610
3654
  };
3611
3655
  userScopedObserver?.onUpdate(handleUserScopedObserverUpdate);
3612
- apiMachine.connect();
3656
+ apiMachine?.connect();
3613
3657
  void runSessionRegistryRecovery("startup").catch((error) => {
3614
3658
  persistence.logger.debug("[DAEMON RUN] Session registry startup recovery failed", error);
3615
3659
  });
@@ -3713,14 +3757,16 @@ ${stderrSnapshot}`);
3713
3757
  remoteSessionIndexRefreshTimer = null;
3714
3758
  }
3715
3759
  userScopedObserver?.offUpdate(handleUserScopedObserverUpdate);
3716
- await apiMachine.updateDaemonState((state) => ({
3717
- ...state,
3718
- status: "shutting-down",
3719
- shutdownRequestedAt: Date.now(),
3720
- shutdownSource: source
3721
- }));
3722
- await new Promise((resolve) => setTimeout(resolve, 100));
3723
- apiMachine.shutdown();
3760
+ if (apiMachine) {
3761
+ await apiMachine.updateDaemonState((state) => ({
3762
+ ...state,
3763
+ status: "shutting-down",
3764
+ shutdownRequestedAt: Date.now(),
3765
+ shutdownSource: source
3766
+ }));
3767
+ await new Promise((resolve) => setTimeout(resolve, 100));
3768
+ apiMachine.shutdown();
3769
+ }
3724
3770
  await userScopedObserver?.close();
3725
3771
  await stopControlServer();
3726
3772
  await cleanupDaemonState();
@@ -9097,7 +9143,7 @@ class AbortError extends Error {
9097
9143
  }
9098
9144
  }
9099
9145
 
9100
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-SnNF4vVI.cjs', document.baseURI).href)));
9146
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D4A092LJ.cjs', document.baseURI).href)));
9101
9147
  const __dirname$1 = path.join(__filename$1, "..");
9102
9148
  function getGlobalClaudeVersion() {
9103
9149
  try {
@@ -10415,11 +10461,15 @@ var launch = /*#__PURE__*/Object.freeze({
10415
10461
 
10416
10462
  const unifiedProviderExecutors = {
10417
10463
  claude: async (opts) => {
10418
- const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-khz1woP5.cjs'); });
10419
- await runClaude(opts.credentials, opts.claudeOptions ?? {});
10464
+ const claudeOptions = opts.claudeOptions ?? {};
10465
+ const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-DmXinUiz.cjs'); });
10466
+ await runClaude(opts.credentials, {
10467
+ ...claudeOptions,
10468
+ startingMode: claudeOptions.startingMode ?? (claudeOptions.startedBy === "daemon" ? "remote" : void 0)
10469
+ });
10420
10470
  },
10421
10471
  codex: async (opts) => {
10422
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-Vb_tfdTK.cjs'); });
10472
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-CoqsQ-cb.cjs'); });
10423
10473
  await runCodex({
10424
10474
  credentials: opts.credentials,
10425
10475
  startedBy: opts.startedBy,
@@ -10428,7 +10478,7 @@ const unifiedProviderExecutors = {
10428
10478
  });
10429
10479
  },
10430
10480
  gemini: async (opts) => {
10431
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-MIoPtN_R.cjs'); });
10481
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-BC_5rNMt.cjs'); });
10432
10482
  await runGemini({
10433
10483
  credentials: opts.credentials,
10434
10484
  startedBy: opts.startedBy
@@ -10511,7 +10561,7 @@ function shouldRunMainClaudeFlow(opts) {
10511
10561
  return;
10512
10562
  } else if (subcommand === "runtime") {
10513
10563
  if (args[1] === "providers") {
10514
- const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-C4nFEFfU.cjs'); });
10564
+ const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-79M8Bz4I.cjs'); });
10515
10565
  console.log(renderRuntimeProviders());
10516
10566
  return;
10517
10567
  }
@@ -10700,8 +10750,8 @@ function shouldRunMainClaudeFlow(opts) {
10700
10750
  const projectId = args[3];
10701
10751
  try {
10702
10752
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
10703
- const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./api-ljzPqsrQ.cjs'); }).then(function (n) { return n.persistence; });
10704
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-ljzPqsrQ.cjs'); }).then(function (n) { return n.api; });
10753
+ const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./api-8xtJZeXZ.cjs'); }).then(function (n) { return n.persistence; });
10754
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-8xtJZeXZ.cjs'); }).then(function (n) { return n.api; });
10705
10755
  let userEmail = void 0;
10706
10756
  try {
10707
10757
  const credentials = await readCredentials2();
@@ -1,5 +1,5 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
- import { l as logger, j as encodeBase64, c as configuration, k as readCredentials, m as ensureSigningCredentials, r as readSettings, u as updateSettings, n as encodeBase64Url, g as delay, o as buildClientHeaders, q as decodeBase64, w as writeCredentialsLegacy, t as writeCredentialsDataKey, v as readDaemonState, x as HAPPY_CLOUD_DAEMON_PORT, y as clearDaemonState, z as packageJson, B as acquireDaemonLock, C as writeDaemonState, A as ApiClient, D as releaseDaemonLock, E as validateProfileForAgent, F as getProfileEnvironmentVariables, G as clearCredentials, I as clearMachineId, J as HeadTailPreviewBuffer, K as getLatestDaemonLog } from './api-LMpXb-Ju.mjs';
2
+ import { l as logger, j as encodeBase64, c as configuration, k as readCredentials, m as ensureSigningCredentials, r as readSettings, u as updateSettings, n as encodeBase64Url, g as delay, o as buildClientHeaders, q as decodeBase64, w as writeCredentialsLegacy, t as writeCredentialsDataKey, v as readDaemonState, x as HAPPY_CLOUD_DAEMON_PORT, y as clearDaemonState, z as packageJson, B as acquireDaemonLock, C as writeDaemonState, A as ApiClient, i as isAuthenticationRequiredError, D as releaseDaemonLock, E as validateProfileForAgent, F as getProfileEnvironmentVariables, G as clearCredentials, I as clearMachineId, J as HeadTailPreviewBuffer, K as getLatestDaemonLog } from './api-DB30ctmX.mjs';
3
3
  import { z } from 'zod';
4
4
  import fs, { writeFile as writeFile$1, rename, unlink as unlink$1 } from 'fs/promises';
5
5
  import os$1, { homedir } from 'os';
@@ -2966,6 +2966,28 @@ const initialMachineMetadata = {
2966
2966
  happyHomeDir: configuration.happyCloudHomeDir,
2967
2967
  happyLibDir: projectPath()
2968
2968
  };
2969
+ function createOfflineMachine(credentials, machineId, daemonState) {
2970
+ if (credentials.encryption.type === "dataKey") {
2971
+ return {
2972
+ id: machineId,
2973
+ encryptionKey: credentials.encryption.machineKey,
2974
+ encryptionVariant: "dataKey",
2975
+ metadata: initialMachineMetadata,
2976
+ metadataVersion: 0,
2977
+ daemonState,
2978
+ daemonStateVersion: 0
2979
+ };
2980
+ }
2981
+ return {
2982
+ id: machineId,
2983
+ encryptionKey: credentials.encryption.secret,
2984
+ encryptionVariant: "legacy",
2985
+ metadata: initialMachineMetadata,
2986
+ metadataVersion: 0,
2987
+ daemonState,
2988
+ daemonStateVersion: 0
2989
+ };
2990
+ }
2969
2991
  async function getProfileEnvironmentVariablesForAgent(profileId, agentType) {
2970
2992
  try {
2971
2993
  const settings = await readSettings();
@@ -3540,19 +3562,41 @@ ${stderrSnapshot}`);
3540
3562
  logger.debug("[DAEMON RUN] Failed to start user-scoped observer, continuing without it", error);
3541
3563
  }
3542
3564
  }
3543
- const machine = await activeApi.getOrCreateMachine({
3544
- machineId,
3545
- metadata: initialMachineMetadata,
3546
- daemonState: initialDaemonState
3547
- });
3548
- logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
3549
- const apiMachine = activeApi.machineSyncClient(machine);
3550
- apiMachine.setRPCHandlers({
3565
+ let machine = createOfflineMachine(credentials, machineId, initialDaemonState);
3566
+ let daemonHasAuthenticatedSync = false;
3567
+ try {
3568
+ machine = await activeApi.getOrCreateMachine({
3569
+ machineId,
3570
+ metadata: initialMachineMetadata,
3571
+ daemonState: initialDaemonState
3572
+ });
3573
+ daemonHasAuthenticatedSync = true;
3574
+ logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
3575
+ } catch (error) {
3576
+ if (!isAuthenticationRequiredError(error)) {
3577
+ throw error;
3578
+ }
3579
+ logger.warn(
3580
+ "[DAEMON RUN] Machine registration rejected by the server. Continuing with local-only daemon control until authentication is refreshed.",
3581
+ error instanceof Error ? error.message : String(error)
3582
+ );
3583
+ try {
3584
+ await userScopedObserver?.close();
3585
+ } catch (observerCloseError) {
3586
+ logger.debug("[DAEMON RUN] Failed to close user-scoped observer after auth fallback", observerCloseError);
3587
+ }
3588
+ userScopedObserver = null;
3589
+ }
3590
+ const apiMachine = daemonHasAuthenticatedSync ? activeApi.machineSyncClient(machine) : null;
3591
+ apiMachine?.setRPCHandlers({
3551
3592
  spawnSession,
3552
3593
  stopSession,
3553
3594
  requestShutdown: () => requestShutdown("happy-app")
3554
3595
  });
3555
3596
  const runRemoteSessionIndexRecovery = async (label) => {
3597
+ if (!daemonHasAuthenticatedSync) {
3598
+ return;
3599
+ }
3556
3600
  const recoveryResult = await recoverTrackedSessionsFromRemoteIndex({
3557
3601
  api: activeApi,
3558
3602
  machineId,
@@ -3587,7 +3631,7 @@ ${stderrSnapshot}`);
3587
3631
  scheduleRemoteSessionIndexRecovery();
3588
3632
  };
3589
3633
  userScopedObserver?.onUpdate(handleUserScopedObserverUpdate);
3590
- apiMachine.connect();
3634
+ apiMachine?.connect();
3591
3635
  void runSessionRegistryRecovery("startup").catch((error) => {
3592
3636
  logger.debug("[DAEMON RUN] Session registry startup recovery failed", error);
3593
3637
  });
@@ -3691,14 +3735,16 @@ ${stderrSnapshot}`);
3691
3735
  remoteSessionIndexRefreshTimer = null;
3692
3736
  }
3693
3737
  userScopedObserver?.offUpdate(handleUserScopedObserverUpdate);
3694
- await apiMachine.updateDaemonState((state) => ({
3695
- ...state,
3696
- status: "shutting-down",
3697
- shutdownRequestedAt: Date.now(),
3698
- shutdownSource: source
3699
- }));
3700
- await new Promise((resolve) => setTimeout(resolve, 100));
3701
- apiMachine.shutdown();
3738
+ if (apiMachine) {
3739
+ await apiMachine.updateDaemonState((state) => ({
3740
+ ...state,
3741
+ status: "shutting-down",
3742
+ shutdownRequestedAt: Date.now(),
3743
+ shutdownSource: source
3744
+ }));
3745
+ await new Promise((resolve) => setTimeout(resolve, 100));
3746
+ apiMachine.shutdown();
3747
+ }
3702
3748
  await userScopedObserver?.close();
3703
3749
  await stopControlServer();
3704
3750
  await cleanupDaemonState();
@@ -10393,11 +10439,15 @@ var launch = /*#__PURE__*/Object.freeze({
10393
10439
 
10394
10440
  const unifiedProviderExecutors = {
10395
10441
  claude: async (opts) => {
10396
- const { runClaude } = await import('./runClaude-DrWj6fju.mjs');
10397
- await runClaude(opts.credentials, opts.claudeOptions ?? {});
10442
+ const claudeOptions = opts.claudeOptions ?? {};
10443
+ const { runClaude } = await import('./runClaude-nzLh-orP.mjs');
10444
+ await runClaude(opts.credentials, {
10445
+ ...claudeOptions,
10446
+ startingMode: claudeOptions.startingMode ?? (claudeOptions.startedBy === "daemon" ? "remote" : void 0)
10447
+ });
10398
10448
  },
10399
10449
  codex: async (opts) => {
10400
- const { runCodex } = await import('./runCodex-CfQgQ4gD.mjs');
10450
+ const { runCodex } = await import('./runCodex-C-Pjpbxn.mjs');
10401
10451
  await runCodex({
10402
10452
  credentials: opts.credentials,
10403
10453
  startedBy: opts.startedBy,
@@ -10406,7 +10456,7 @@ const unifiedProviderExecutors = {
10406
10456
  });
10407
10457
  },
10408
10458
  gemini: async (opts) => {
10409
- const { runGemini } = await import('./runGemini-BOdeej1A.mjs');
10459
+ const { runGemini } = await import('./runGemini-CIVm6NWC.mjs');
10410
10460
  await runGemini({
10411
10461
  credentials: opts.credentials,
10412
10462
  startedBy: opts.startedBy
@@ -10489,7 +10539,7 @@ function shouldRunMainClaudeFlow(opts) {
10489
10539
  return;
10490
10540
  } else if (subcommand === "runtime") {
10491
10541
  if (args[1] === "providers") {
10492
- const { renderRuntimeProviders } = await import('./command-wiV8CFFG.mjs');
10542
+ const { renderRuntimeProviders } = await import('./command-C6injNJE.mjs');
10493
10543
  console.log(renderRuntimeProviders());
10494
10544
  return;
10495
10545
  }
@@ -10678,8 +10728,8 @@ function shouldRunMainClaudeFlow(opts) {
10678
10728
  const projectId = args[3];
10679
10729
  try {
10680
10730
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
10681
- const { readCredentials: readCredentials2 } = await import('./api-LMpXb-Ju.mjs').then(function (n) { return n.L; });
10682
- const { ApiClient: ApiClient2 } = await import('./api-LMpXb-Ju.mjs').then(function (n) { return n.M; });
10731
+ const { readCredentials: readCredentials2 } = await import('./api-DB30ctmX.mjs').then(function (n) { return n.L; });
10732
+ const { ApiClient: ApiClient2 } = await import('./api-DB30ctmX.mjs').then(function (n) { return n.M; });
10683
10733
  let userEmail = void 0;
10684
10734
  try {
10685
10735
  const credentials = await readCredentials2();
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./api-ljzPqsrQ.cjs');
4
+ require('./api-8xtJZeXZ.cjs');
5
5
  require('zod');
6
- require('./index-SnNF4vVI.cjs');
6
+ require('./index-D4A092LJ.cjs');
7
7
  require('node:child_process');
8
8
  require('node:fs');
9
9
  require('cross-spawn');
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import 'chalk';
2
- import './api-LMpXb-Ju.mjs';
2
+ import './api-DB30ctmX.mjs';
3
3
  import 'zod';
4
- import './index-CYRsGHXJ.mjs';
4
+ import './index-krVv4CWK.mjs';
5
5
  import 'node:child_process';
6
6
  import 'node:fs';
7
7
  import 'cross-spawn';
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./api-ljzPqsrQ.cjs');
3
+ var persistence = require('./api-8xtJZeXZ.cjs');
4
4
  var types = require('./types-DVk3crez.cjs');
5
5
  require('axios');
6
6
  require('chalk');
package/dist/lib.d.cts CHANGED
@@ -2349,6 +2349,7 @@ declare class ApiSessionClient extends EventEmitter {
2349
2349
  private protocolV3SocketCapabilities;
2350
2350
  private protocolV3SessionSyncWaitTimer;
2351
2351
  private committedSessionWriteAckMode;
2352
+ private pendingBackgroundTasks;
2352
2353
  private pendingHappyOrgDispatchBusinessAcks;
2353
2354
  private recordedHappyOrgDispatchBusinessAcks;
2354
2355
  private pendingHappyOrgTaskTurnReports;
@@ -2416,6 +2417,8 @@ declare class ApiSessionClient extends EventEmitter {
2416
2417
  */
2417
2418
  flush(): Promise<void>;
2418
2419
  close(): Promise<void>;
2420
+ private trackPendingBackgroundTask;
2421
+ private waitForPendingBackgroundTasksToDrain;
2419
2422
  private handleSocketUpdate;
2420
2423
  private dispatchEncryptedSessionMessage;
2421
2424
  private maybeAutoAcknowledgeHappyOrgDispatch;
package/dist/lib.d.mts CHANGED
@@ -2349,6 +2349,7 @@ declare class ApiSessionClient extends EventEmitter {
2349
2349
  private protocolV3SocketCapabilities;
2350
2350
  private protocolV3SessionSyncWaitTimer;
2351
2351
  private committedSessionWriteAckMode;
2352
+ private pendingBackgroundTasks;
2352
2353
  private pendingHappyOrgDispatchBusinessAcks;
2353
2354
  private recordedHappyOrgDispatchBusinessAcks;
2354
2355
  private pendingHappyOrgTaskTurnReports;
@@ -2416,6 +2417,8 @@ declare class ApiSessionClient extends EventEmitter {
2416
2417
  */
2417
2418
  flush(): Promise<void>;
2418
2419
  close(): Promise<void>;
2420
+ private trackPendingBackgroundTask;
2421
+ private waitForPendingBackgroundTasksToDrain;
2419
2422
  private handleSocketUpdate;
2420
2423
  private dispatchEncryptedSessionMessage;
2421
2424
  private maybeAutoAcknowledgeHappyOrgDispatch;
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, c as configuration, l as logger } from './api-LMpXb-Ju.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, c as configuration, l as logger } from './api-DB30ctmX.mjs';
2
2
  export { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
3
3
  import 'axios';
4
4
  import 'chalk';
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-SnNF4vVI.cjs');
4
- var persistence = require('./api-ljzPqsrQ.cjs');
3
+ var index = require('./index-D4A092LJ.cjs');
4
+ var persistence = require('./api-8xtJZeXZ.cjs');
5
5
  var node_crypto = require('node:crypto');
6
6
  var path = require('node:path');
7
7
  var crypto = require('crypto');
@@ -1,5 +1,5 @@
1
- import { k as initialMachineMetadata, R as RuntimeShell, l as resolveCanonicalToolNameV2, f as formatDisplayMessage } from './index-CYRsGHXJ.mjs';
2
- import { r as readSettings, H as HAPPY_ORG_TURN_REPORT_TAG, d as HAPPY_ORG_SUMMARY_MAX_LENGTH, e as HAPPY_ORG_REPEAT_THRESHOLD, l as logger } from './api-LMpXb-Ju.mjs';
1
+ import { k as initialMachineMetadata, R as RuntimeShell, l as resolveCanonicalToolNameV2, f as formatDisplayMessage } from './index-krVv4CWK.mjs';
2
+ import { r as readSettings, H as HAPPY_ORG_TURN_REPORT_TAG, d as HAPPY_ORG_SUMMARY_MAX_LENGTH, e as HAPPY_ORG_REPEAT_THRESHOLD, l as logger } from './api-DB30ctmX.mjs';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { basename } from 'node:path';
5
5
  import { createHash } from 'crypto';
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var node_crypto = require('node:crypto');
4
- var persistence = require('./api-ljzPqsrQ.cjs');
4
+ var persistence = require('./api-8xtJZeXZ.cjs');
5
5
  require('cross-spawn');
6
6
  require('@agentclientprotocol/sdk');
7
- var index = require('./index-SnNF4vVI.cjs');
7
+ var index = require('./index-D4A092LJ.cjs');
8
8
  require('ps-list');
9
9
  require('fs');
10
10
  require('path');
@@ -25,9 +25,9 @@ require('tweetnacl');
25
25
  require('open');
26
26
  var React = require('react');
27
27
  var ink = require('ink');
28
- var ProviderSelectionHandler = require('./ProviderSelectionHandler-SWCNmor4.cjs');
28
+ var ProviderSelectionHandler = require('./ProviderSelectionHandler-B5Aq5gA_.cjs');
29
29
  var types = require('./types-DVk3crez.cjs');
30
- var registerKillSessionHandler = require('./registerKillSessionHandler-C4IlzmJl.cjs');
30
+ var registerKillSessionHandler = require('./registerKillSessionHandler-Cl_er9rg.cjs');
31
31
  require('socket.io-client');
32
32
  require('expo-server-sdk');
33
33
  var node_util = require('node:util');
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { l as logger, f as backoff, g as delay, h as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-LMpXb-Ju.mjs';
2
+ import { l as logger, f as backoff, g as delay, h as AsyncLock, c as configuration, s as startOfflineReconnection, b as connectionState, A as ApiClient, i as isAuthenticationRequiredError } from './api-DB30ctmX.mjs';
3
3
  import 'cross-spawn';
4
4
  import '@agentclientprotocol/sdk';
5
- import { m as getProjectPath, F as Future, n as claudeLocal, E as ExitCodeError, o as trimIdent, q as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, u as claudeCheckSession, w as projectPath, x as mapToClaudeMode, P as PushableAsyncIterable, y as query, A as AbortError, e as stopCaffeinate, p as publishSessionRegistration, z as getEnvironmentInfo, a as createSessionMetadata, B as startCaffeinate, b as closeProviderSession } from './index-CYRsGHXJ.mjs';
5
+ import { m as getProjectPath, F as Future, n as claudeLocal, E as ExitCodeError, o as trimIdent, q as createClaudeBackend, f as formatDisplayMessage, t as truncateDisplayMessage, u as claudeCheckSession, w as projectPath, x as mapToClaudeMode, P as PushableAsyncIterable, y as query, A as AbortError, e as stopCaffeinate, p as publishSessionRegistration, z as getEnvironmentInfo, a as createSessionMetadata, B as startCaffeinate, b as closeProviderSession } from './index-krVv4CWK.mjs';
6
6
  import 'ps-list';
7
7
  import 'fs';
8
8
  import 'path';
@@ -23,9 +23,9 @@ import 'tweetnacl';
23
23
  import 'open';
24
24
  import React, { useState, useRef, useEffect, useCallback } from 'react';
25
25
  import { useStdout, useInput, Box, Text, render } from 'ink';
26
- import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-DJSSjBOW.mjs';
26
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-DQITIqZK.mjs';
27
27
  import { R as RawJSONLinesSchema } from './types-CiliQpqS.mjs';
28
- import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, l as launchRuntimeHandleWithFactoryResult, k as forwardAgentMessageToProviderSession, s as syncControlledByUserState, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, h as hashObject, c as registerKillSessionHandler } from './registerKillSessionHandler-BD-ChYZJ.mjs';
28
+ import { B as BasePermissionHandler, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, l as launchRuntimeHandleWithFactoryResult, k as forwardAgentMessageToProviderSession, s as syncControlledByUserState, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, h as hashObject, c as registerKillSessionHandler } from './registerKillSessionHandler-beu2g-Qo.mjs';
29
29
  import 'socket.io-client';
30
30
  import 'expo-server-sdk';
31
31
  import { isDeepStrictEqual } from 'node:util';
@@ -1,6 +1,6 @@
1
- import { p as preserveSessionRuntimeMetadata, l as logger, b as connectionState, A as ApiClient } from './api-LMpXb-Ju.mjs';
2
- import { B as BasePermissionHandler, h as hashObject, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, c as registerKillSessionHandler, l as launchRuntimeHandleWithFactoryResult, j as inferToolResultError, k as forwardAgentMessageToProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, r as resolveHappyOrgQueuedTurn, s as syncControlledByUserState } from './registerKillSessionHandler-BD-ChYZJ.mjs';
3
- import { f as formatDisplayMessage, v as validateCodexAcpSpawn, h as createCodexBackend, t as truncateDisplayMessage, b as closeProviderSession, e as stopCaffeinate, i as readManagedSessionTag, j as resolveManagedSessionTag } from './index-CYRsGHXJ.mjs';
1
+ import { p as preserveSessionRuntimeMetadata, l as logger, b as connectionState, A as ApiClient } from './api-DB30ctmX.mjs';
2
+ import { B as BasePermissionHandler, h as hashObject, d as MessageBuffer, C as ConversationHistory$1, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, c as registerKillSessionHandler, l as launchRuntimeHandleWithFactoryResult, j as inferToolResultError, k as forwardAgentMessageToProviderSession, e as ensureManagedProviderMachine, M as MissingMachineIdError, b as MessageQueue2, r as resolveHappyOrgQueuedTurn, s as syncControlledByUserState } from './registerKillSessionHandler-beu2g-Qo.mjs';
3
+ import { f as formatDisplayMessage, v as validateCodexAcpSpawn, h as createCodexBackend, t as truncateDisplayMessage, b as closeProviderSession, e as stopCaffeinate, i as readManagedSessionTag, j as resolveManagedSessionTag } from './index-krVv4CWK.mjs';
4
4
  import 'cross-spawn';
5
5
  import '@agentclientprotocol/sdk';
6
6
  import { randomUUID } from 'node:crypto';
@@ -24,8 +24,8 @@ import 'tweetnacl';
24
24
  import 'open';
25
25
  import React, { useState, useRef, useEffect, useCallback } from 'react';
26
26
  import { useStdout, useInput, Box, Text, render } from 'ink';
27
- import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-DJSSjBOW.mjs';
28
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-Dtna0CRw.mjs';
27
+ import { c as createKeepAliveController, P as ProviderSelectionHandler, r as runModeLoop } from './ProviderSelectionHandler-DQITIqZK.mjs';
28
+ import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-DdPnQSmQ.mjs';
29
29
  import 'zod';
30
30
  import 'socket.io-client';
31
31
  import 'expo-server-sdk';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./api-ljzPqsrQ.cjs');
4
- var registerKillSessionHandler = require('./registerKillSessionHandler-C4IlzmJl.cjs');
5
- var index = require('./index-SnNF4vVI.cjs');
3
+ var persistence = require('./api-8xtJZeXZ.cjs');
4
+ var registerKillSessionHandler = require('./registerKillSessionHandler-Cl_er9rg.cjs');
5
+ var index = require('./index-D4A092LJ.cjs');
6
6
  require('cross-spawn');
7
7
  require('@agentclientprotocol/sdk');
8
8
  var node_crypto = require('node:crypto');
@@ -26,8 +26,8 @@ require('tweetnacl');
26
26
  require('open');
27
27
  var React = require('react');
28
28
  var ink = require('ink');
29
- var ProviderSelectionHandler = require('./ProviderSelectionHandler-SWCNmor4.cjs');
30
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-CuB78Igh.cjs');
29
+ var ProviderSelectionHandler = require('./ProviderSelectionHandler-B5Aq5gA_.cjs');
30
+ var BaseReasoningProcessor = require('./BaseReasoningProcessor-DxFwO2z9.cjs');
31
31
  require('zod');
32
32
  require('socket.io-client');
33
33
  require('expo-server-sdk');
@@ -3,10 +3,10 @@
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
5
  var node_crypto = require('node:crypto');
6
- var persistence = require('./api-ljzPqsrQ.cjs');
7
- var registerKillSessionHandler = require('./registerKillSessionHandler-C4IlzmJl.cjs');
8
- var index = require('./index-SnNF4vVI.cjs');
9
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-CuB78Igh.cjs');
6
+ var persistence = require('./api-8xtJZeXZ.cjs');
7
+ var registerKillSessionHandler = require('./registerKillSessionHandler-Cl_er9rg.cjs');
8
+ var index = require('./index-D4A092LJ.cjs');
9
+ var BaseReasoningProcessor = require('./BaseReasoningProcessor-DxFwO2z9.cjs');
10
10
  require('cross-spawn');
11
11
  require('@agentclientprotocol/sdk');
12
12
  require('ps-list');
@@ -1,10 +1,10 @@
1
1
  import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { l as logger, b as connectionState, A as ApiClient } from './api-LMpXb-Ju.mjs';
5
- import { B as BasePermissionHandler, C as ConversationHistory$1, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, s as syncControlledByUserState, b as MessageQueue2, h as hashObject, c as registerKillSessionHandler, d as MessageBuffer, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, l as launchRuntimeHandleWithFactoryResult, j as inferToolResultError, k as forwardAgentMessageToProviderSession } from './registerKillSessionHandler-BD-ChYZJ.mjs';
6
- import { g as getInitialGeminiModel, r as readGeminiLocalConfig, G as GEMINI_MODEL_ENV, b as closeProviderSession, s as saveGeminiModelToConfig, d as createGeminiBackend, e as stopCaffeinate } from './index-CYRsGHXJ.mjs';
7
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-Dtna0CRw.mjs';
4
+ import { l as logger, b as connectionState, A as ApiClient } from './api-DB30ctmX.mjs';
5
+ import { B as BasePermissionHandler, C as ConversationHistory$1, r as resolveHappyOrgQueuedTurn, e as ensureManagedProviderMachine, M as MissingMachineIdError, s as syncControlledByUserState, b as MessageQueue2, h as hashObject, c as registerKillSessionHandler, d as MessageBuffer, f as buildHappyOrgTurnPrompt, w as waitForResponseCompleteWithAbort, i as finalizeHappyOrgTurnWithBusinessAck, l as launchRuntimeHandleWithFactoryResult, j as inferToolResultError, k as forwardAgentMessageToProviderSession } from './registerKillSessionHandler-beu2g-Qo.mjs';
6
+ import { g as getInitialGeminiModel, r as readGeminiLocalConfig, G as GEMINI_MODEL_ENV, b as closeProviderSession, s as saveGeminiModelToConfig, d as createGeminiBackend, e as stopCaffeinate } from './index-krVv4CWK.mjs';
7
+ import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-DdPnQSmQ.mjs';
8
8
  import 'cross-spawn';
9
9
  import '@agentclientprotocol/sdk';
10
10
  import 'ps-list';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-imou-cloud",
3
- "version": "2.1.19",
3
+ "version": "2.1.21",
4
4
  "description": "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI",
5
5
  "author": "long.zhu",
6
6
  "license": "MIT",
@@ -82,7 +82,8 @@
82
82
  "doctor": "node scripts/env-wrapper.cjs stable doctor",
83
83
  "// ==== Development Linking ====": "",
84
84
  "link:dev": "node scripts/link-dev.cjs",
85
- "unlink:dev": "node scripts/link-dev.cjs unlink"
85
+ "unlink:dev": "node scripts/link-dev.cjs unlink",
86
+ "test:happy-org-1.2-quality": "node ../../node_modules/vitest/vitest.mjs run --config vitest.happy-org-1.2.config.mjs"
86
87
  },
87
88
  "dependencies": {
88
89
  "@agentclientprotocol/sdk": "0.16.1",
package/scripts/build.mjs CHANGED
@@ -1,41 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from 'node:child_process';
4
- import { existsSync, readFileSync, rmSync } from 'node:fs';
4
+ import { readFileSync, rmSync } from 'node:fs';
5
5
  import { dirname, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { ensureAcpSdkCompat } from './ensureAcpSdkCompat.mjs';
8
+ import {
9
+ ensurePackageResolvableFromPackageRoot,
10
+ resolveTool,
11
+ shouldUseShell,
12
+ } from './tooling-utils.mjs';
8
13
 
9
14
  const here = dirname(fileURLToPath(import.meta.url));
10
15
  const packageRoot = resolve(here, '..');
11
16
  const workspaceRoot = resolve(packageRoot, '..', '..');
12
17
  const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8'));
13
18
 
14
- function resolveTool(binName, packageSpecs) {
15
- const suffix = process.platform === 'win32' ? '.cmd' : '';
16
- const packageLocalBin = resolve(packageRoot, 'node_modules', '.bin', `${binName}${suffix}`);
17
- const workspaceLocalBin = resolve(workspaceRoot, 'node_modules', '.bin', `${binName}${suffix}`);
18
-
19
- if (existsSync(packageLocalBin)) {
20
- return {
21
- command: packageLocalBin,
22
- prefixArgs: [],
23
- };
24
- }
25
-
26
- if (existsSync(workspaceLocalBin)) {
27
- return {
28
- command: workspaceLocalBin,
29
- prefixArgs: [],
30
- };
31
- }
32
-
33
- return {
34
- command: 'npx',
35
- prefixArgs: ['-y', ...packageSpecs.flatMap((packageSpec) => ['-p', packageSpec]), binName],
36
- };
37
- }
38
-
39
19
  function runStep(name, command, args) {
40
20
  console.log(`\n[build] ${name}`);
41
21
  console.log(`> ${command} ${args.join(' ')}`);
@@ -43,7 +23,7 @@ function runStep(name, command, args) {
43
23
  const result = spawnSync(command, args, {
44
24
  cwd: packageRoot,
45
25
  stdio: 'inherit',
46
- shell: process.platform === 'win32',
26
+ shell: shouldUseShell(command),
47
27
  env: process.env,
48
28
  });
49
29
 
@@ -56,13 +36,37 @@ function runTool(name, tool, args) {
56
36
  runStep(name, tool.command, [...tool.prefixArgs, ...args]);
57
37
  }
58
38
 
59
- const tsc = resolveTool('tsc', [`typescript@${packageJson.devDependencies.typescript}`]);
60
- const pkgroll = resolveTool('pkgroll', [
61
- `pkgroll@${packageJson.devDependencies.pkgroll}`,
62
- `typescript@${packageJson.devDependencies.typescript}`,
63
- ]);
39
+ const tsc = resolveTool({
40
+ binName: 'tsc',
41
+ packageName: 'typescript',
42
+ packageRoot,
43
+ workspaceRoot,
44
+ packageSpecs: [`typescript@${packageJson.devDependencies.typescript}`],
45
+ entryCandidates: ['bin/tsc'],
46
+ });
47
+ const pkgroll = resolveTool({
48
+ binName: 'pkgroll',
49
+ packageName: 'pkgroll',
50
+ packageRoot,
51
+ workspaceRoot,
52
+ packageSpecs: [
53
+ `pkgroll@${packageJson.devDependencies.pkgroll}`,
54
+ `typescript@${packageJson.devDependencies.typescript}`,
55
+ ],
56
+ entryCandidates: ['dist/cli.mjs'],
57
+ });
58
+ const cleanupTypeScriptResolution = ensurePackageResolvableFromPackageRoot({
59
+ packageName: 'typescript',
60
+ version: packageJson.devDependencies.typescript,
61
+ packageRoot,
62
+ workspaceRoot,
63
+ });
64
64
 
65
65
  ensureAcpSdkCompat();
66
66
  rmSync(resolve(packageRoot, 'dist'), { recursive: true, force: true });
67
- runTool('typecheck', tsc, ['--noEmit']);
68
- runTool('bundle', pkgroll, []);
67
+ try {
68
+ runTool('typecheck', tsc, ['--noEmit']);
69
+ runTool('bundle', pkgroll, []);
70
+ } finally {
71
+ cleanupTypeScriptResolution();
72
+ }
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  import { dirname, join, resolve } from 'node:path';
8
8
  import { ensureAcpSdkCompat } from './ensureAcpSdkCompat.mjs';
9
9
  import { shouldSkipPackedInstallVerification } from './release-smoke-utils.mjs';
10
+ import { resolveTool, shouldUseShell } from './tooling-utils.mjs';
10
11
 
11
12
  const here = dirname(fileURLToPath(import.meta.url));
12
13
  const packageRoot = resolve(here, '..');
@@ -14,43 +15,6 @@ const workspaceRoot = resolve(packageRoot, '..', '..');
14
15
  const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8'));
15
16
  const scriptPath = fileURLToPath(import.meta.url);
16
17
 
17
- function resolveTool(binName, packageName) {
18
- const suffix = process.platform === 'win32' ? '.cmd' : '';
19
- const packageLocalBin = resolve(packageRoot, 'node_modules', '.bin', `${binName}${suffix}`);
20
- const workspaceLocalBin = resolve(workspaceRoot, 'node_modules', '.bin', `${binName}${suffix}`);
21
-
22
- if (existsSync(packageLocalBin)) {
23
- return {
24
- command: packageLocalBin,
25
- prefixArgs: [],
26
- };
27
- }
28
-
29
- if (existsSync(workspaceLocalBin)) {
30
- return {
31
- command: workspaceLocalBin,
32
- prefixArgs: [],
33
- };
34
- }
35
-
36
- const version =
37
- packageJson.devDependencies?.[packageName]
38
- ?? packageJson.dependencies?.[packageName];
39
-
40
- return {
41
- command: 'npx',
42
- prefixArgs: ['-y', '-p', `${packageName}@${version}`, binName],
43
- };
44
- }
45
-
46
- function shouldUseShell(command) {
47
- if (process.platform !== 'win32') {
48
- return false;
49
- }
50
-
51
- return !/\.exe$/i.test(command);
52
- }
53
-
54
18
  function runStep(name, command, args, options = {}) {
55
19
  console.log(`\n[release-smoke] ${name}`);
56
20
  console.log(`> ${command} ${args.join(' ')}`);
@@ -186,8 +150,22 @@ function isDirectExecution() {
186
150
  }
187
151
 
188
152
  function main() {
189
- const tsc = resolveTool('tsc', 'typescript');
190
- const vitest = resolveTool('vitest', 'vitest');
153
+ const tsc = resolveTool({
154
+ binName: 'tsc',
155
+ packageName: 'typescript',
156
+ packageRoot,
157
+ workspaceRoot,
158
+ packageSpecs: [`typescript@${packageJson.devDependencies.typescript}`],
159
+ entryCandidates: ['bin/tsc'],
160
+ });
161
+ const vitest = resolveTool({
162
+ binName: 'vitest',
163
+ packageName: 'vitest',
164
+ packageRoot,
165
+ workspaceRoot,
166
+ packageSpecs: [`vitest@${packageJson.devDependencies.vitest}`],
167
+ entryCandidates: ['vitest.mjs'],
168
+ });
191
169
 
192
170
  ensureAcpSdkCompat();
193
171
  runToolStep('typecheck', tsc, ['--noEmit']);
@@ -0,0 +1,167 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ function getLocalPackageDirs(packageName, packageRoot, workspaceRoot) {
6
+ return [
7
+ resolve(packageRoot, 'node_modules', packageName),
8
+ resolve(workspaceRoot, 'node_modules', packageName),
9
+ ];
10
+ }
11
+
12
+ function getLocalBinPaths(binName, packageRoot, workspaceRoot) {
13
+ const suffix = process.platform === 'win32' ? '.cmd' : '';
14
+
15
+ return [
16
+ resolve(packageRoot, 'node_modules', '.bin', `${binName}${suffix}`),
17
+ resolve(workspaceRoot, 'node_modules', '.bin', `${binName}${suffix}`),
18
+ ];
19
+ }
20
+
21
+ function resolveInstalledPackageDir(packageName, packageRoot, workspaceRoot) {
22
+ return getLocalPackageDirs(packageName, packageRoot, workspaceRoot)
23
+ .find((candidatePath) => existsSync(candidatePath))
24
+ ?? null;
25
+ }
26
+
27
+ export function shouldUseShell(command) {
28
+ if (process.platform !== 'win32') {
29
+ return false;
30
+ }
31
+
32
+ return !/\.exe$/i.test(command);
33
+ }
34
+
35
+ export function resolveTool({
36
+ binName,
37
+ packageName,
38
+ packageRoot,
39
+ workspaceRoot,
40
+ packageSpecs,
41
+ entryCandidates = [],
42
+ }) {
43
+ for (const packageDir of getLocalPackageDirs(packageName, packageRoot, workspaceRoot)) {
44
+ for (const entryCandidate of entryCandidates) {
45
+ const entryPath = resolve(packageDir, entryCandidate);
46
+
47
+ if (existsSync(entryPath)) {
48
+ return {
49
+ command: process.execPath,
50
+ prefixArgs: [entryPath],
51
+ };
52
+ }
53
+ }
54
+ }
55
+
56
+ for (const localBinPath of getLocalBinPaths(binName, packageRoot, workspaceRoot)) {
57
+ if (existsSync(localBinPath)) {
58
+ return {
59
+ command: localBinPath,
60
+ prefixArgs: [],
61
+ };
62
+ }
63
+ }
64
+
65
+ return {
66
+ command: 'npx',
67
+ prefixArgs: ['-y', ...packageSpecs.flatMap((packageSpec) => ['-p', packageSpec]), binName],
68
+ };
69
+ }
70
+
71
+ export function resolvePackageDirWithNpx({
72
+ packageName,
73
+ version,
74
+ packageRoot,
75
+ env = process.env,
76
+ spawn = spawnSync,
77
+ }) {
78
+ const cacheKey = `${packageName.replace(/[@/]/g, '_')}-${version}`;
79
+ const installRoot = resolve(packageRoot, 'node_modules', '.tool-cache', cacheKey);
80
+ const installPackagePath = resolve(installRoot, 'node_modules', packageName);
81
+
82
+ if (existsSync(installPackagePath)) {
83
+ return installPackagePath;
84
+ }
85
+
86
+ mkdirSync(installRoot, { recursive: true });
87
+ writeFileSync(
88
+ resolve(installRoot, 'package.json'),
89
+ JSON.stringify(
90
+ {
91
+ private: true,
92
+ name: `happy-cli-tool-cache-${cacheKey}`,
93
+ },
94
+ null,
95
+ 2
96
+ )
97
+ );
98
+
99
+ const commandArgs = [
100
+ 'install',
101
+ '--no-save',
102
+ '--ignore-scripts',
103
+ '--no-package-lock',
104
+ '--no-audit',
105
+ '--fund=false',
106
+ `${packageName}@${version}`,
107
+ ];
108
+ const result = spawn('npm', commandArgs, {
109
+ cwd: installRoot,
110
+ encoding: 'utf8',
111
+ shell: shouldUseShell('npm'),
112
+ env,
113
+ });
114
+
115
+ if (result.status !== 0) {
116
+ const stderr = result.stderr?.trim();
117
+ const stdout = result.stdout?.trim();
118
+ throw new Error(
119
+ stderr && stderr.length > 0
120
+ ? stderr
121
+ : stdout && stdout.length > 0
122
+ ? stdout
123
+ : `Failed to install ${packageName}@${version} for local resolution`
124
+ );
125
+ }
126
+
127
+ if (!existsSync(installPackagePath)) {
128
+ throw new Error(`Installed package path missing for ${packageName}@${version}`);
129
+ }
130
+
131
+ return installPackagePath;
132
+ }
133
+
134
+ export function ensurePackageResolvableFromPackageRoot({
135
+ packageName,
136
+ version,
137
+ packageRoot,
138
+ workspaceRoot,
139
+ env = process.env,
140
+ spawn = spawnSync,
141
+ fallbackResolver = resolvePackageDirWithNpx,
142
+ }) {
143
+ const linkPath = resolve(packageRoot, 'node_modules', packageName);
144
+
145
+ if (existsSync(linkPath)) {
146
+ return () => {};
147
+ }
148
+
149
+ const localPackageDir = resolveInstalledPackageDir(packageName, packageRoot, workspaceRoot);
150
+ const targetPath = localPackageDir ?? fallbackResolver({
151
+ packageName,
152
+ version,
153
+ packageRoot,
154
+ workspaceRoot,
155
+ env,
156
+ spawn,
157
+ });
158
+
159
+ mkdirSync(dirname(linkPath), { recursive: true });
160
+ symlinkSync(targetPath, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
161
+
162
+ return () => {
163
+ try {
164
+ rmSync(linkPath, { recursive: true, force: true });
165
+ } catch {}
166
+ };
167
+ }