happy-imou-cloud 2.1.20 → 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 (26) hide show
  1. package/dist/{BaseReasoningProcessor-CQ59YfWV.mjs → BaseReasoningProcessor-DdPnQSmQ.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-BrPfiZf4.cjs → BaseReasoningProcessor-DxFwO2z9.cjs} +2 -2
  3. package/dist/{ProviderSelectionHandler-B8EGoc4L.cjs → ProviderSelectionHandler-B5Aq5gA_.cjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-D9WDj0pS.mjs → ProviderSelectionHandler-DQITIqZK.mjs} +2 -2
  5. package/dist/{api-BtjmMYKf.cjs → api-8xtJZeXZ.cjs} +3 -2
  6. package/dist/{api-CtjWkGWV.mjs → api-DB30ctmX.mjs} +3 -2
  7. package/dist/{command-D9Xa72wn.cjs → command-79M8Bz4I.cjs} +2 -2
  8. package/dist/{command-BV2wo6tq.mjs → command-C6injNJE.mjs} +2 -2
  9. package/dist/{index-WIr2Wt9U.cjs → index-D4A092LJ.cjs} +73 -27
  10. package/dist/{index-hL-ID2fR.mjs → index-krVv4CWK.mjs} +70 -24
  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.mjs +1 -1
  15. package/dist/{registerKillSessionHandler-D0EYq7uF.cjs → registerKillSessionHandler-Cl_er9rg.cjs} +2 -2
  16. package/dist/{registerKillSessionHandler-D9JiuWdb.mjs → registerKillSessionHandler-beu2g-Qo.mjs} +2 -2
  17. package/dist/{runClaude-CAelse1X.cjs → runClaude-DmXinUiz.cjs} +4 -4
  18. package/dist/{runClaude-BQ3EPwaa.mjs → runClaude-nzLh-orP.mjs} +4 -4
  19. package/dist/{runCodex-4njIyif-.mjs → runCodex-C-Pjpbxn.mjs} +5 -5
  20. package/dist/{runCodex-ofeVc4Nl.cjs → runCodex-CoqsQ-cb.cjs} +5 -5
  21. package/dist/{runGemini-ChlLWNak.cjs → runGemini-BC_5rNMt.cjs} +4 -4
  22. package/dist/{runGemini-ClDpc5hl.mjs → runGemini-CIVm6NWC.mjs} +4 -4
  23. package/package.json +3 -2
  24. package/scripts/build.mjs +38 -34
  25. package/scripts/release-smoke.mjs +17 -39
  26. package/scripts/tooling-utils.mjs +167 -0
@@ -1,5 +1,5 @@
1
- import { a as createSessionMetadata, p as publishSessionRegistration } from './index-hL-ID2fR.mjs';
2
- import { s as startOfflineReconnection, c as configuration, i as isAuthenticationRequiredError, l as logger } from './api-CtjWkGWV.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-WIr2Wt9U.cjs');
4
- var persistence = require('./api-BtjmMYKf.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-BtjmMYKf.cjs');
4
- var registerKillSessionHandler = require('./registerKillSessionHandler-D0EYq7uF.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-CtjWkGWV.mjs';
2
- import { g as getPendingInteractionTimeoutMs, I as INTERACTION_SUPERSEDED_ERROR, a as INTERACTION_TIMED_OUT_ERROR } from './registerKillSessionHandler-D9JiuWdb.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.20";
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",
@@ -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.20";
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",
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-WIr2Wt9U.cjs');
3
+ var index = require('./index-D4A092LJ.cjs');
4
4
  require('chalk');
5
- require('./api-BtjmMYKf.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-hL-ID2fR.mjs';
1
+ import { c as createDefaultRuntimeShell } from './index-krVv4CWK.mjs';
2
2
  import 'chalk';
3
- import './api-CtjWkGWV.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-BtjmMYKf.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-WIr2Wt9U.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-WIr2Wt9U.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-WIr2Wt9U.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 {
@@ -10416,14 +10462,14 @@ var launch = /*#__PURE__*/Object.freeze({
10416
10462
  const unifiedProviderExecutors = {
10417
10463
  claude: async (opts) => {
10418
10464
  const claudeOptions = opts.claudeOptions ?? {};
10419
- const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-CAelse1X.cjs'); });
10465
+ const { runClaude } = await Promise.resolve().then(function () { return require('./runClaude-DmXinUiz.cjs'); });
10420
10466
  await runClaude(opts.credentials, {
10421
10467
  ...claudeOptions,
10422
10468
  startingMode: claudeOptions.startingMode ?? (claudeOptions.startedBy === "daemon" ? "remote" : void 0)
10423
10469
  });
10424
10470
  },
10425
10471
  codex: async (opts) => {
10426
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-ofeVc4Nl.cjs'); });
10472
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-CoqsQ-cb.cjs'); });
10427
10473
  await runCodex({
10428
10474
  credentials: opts.credentials,
10429
10475
  startedBy: opts.startedBy,
@@ -10432,7 +10478,7 @@ const unifiedProviderExecutors = {
10432
10478
  });
10433
10479
  },
10434
10480
  gemini: async (opts) => {
10435
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-ChlLWNak.cjs'); });
10481
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-BC_5rNMt.cjs'); });
10436
10482
  await runGemini({
10437
10483
  credentials: opts.credentials,
10438
10484
  startedBy: opts.startedBy
@@ -10515,7 +10561,7 @@ function shouldRunMainClaudeFlow(opts) {
10515
10561
  return;
10516
10562
  } else if (subcommand === "runtime") {
10517
10563
  if (args[1] === "providers") {
10518
- const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-D9Xa72wn.cjs'); });
10564
+ const { renderRuntimeProviders } = await Promise.resolve().then(function () { return require('./command-79M8Bz4I.cjs'); });
10519
10565
  console.log(renderRuntimeProviders());
10520
10566
  return;
10521
10567
  }
@@ -10704,8 +10750,8 @@ function shouldRunMainClaudeFlow(opts) {
10704
10750
  const projectId = args[3];
10705
10751
  try {
10706
10752
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
10707
- const { readCredentials: readCredentials2 } = await Promise.resolve().then(function () { return require('./api-BtjmMYKf.cjs'); }).then(function (n) { return n.persistence; });
10708
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./api-BtjmMYKf.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; });
10709
10755
  let userEmail = void 0;
10710
10756
  try {
10711
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-CtjWkGWV.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();
@@ -10394,14 +10440,14 @@ var launch = /*#__PURE__*/Object.freeze({
10394
10440
  const unifiedProviderExecutors = {
10395
10441
  claude: async (opts) => {
10396
10442
  const claudeOptions = opts.claudeOptions ?? {};
10397
- const { runClaude } = await import('./runClaude-BQ3EPwaa.mjs');
10443
+ const { runClaude } = await import('./runClaude-nzLh-orP.mjs');
10398
10444
  await runClaude(opts.credentials, {
10399
10445
  ...claudeOptions,
10400
10446
  startingMode: claudeOptions.startingMode ?? (claudeOptions.startedBy === "daemon" ? "remote" : void 0)
10401
10447
  });
10402
10448
  },
10403
10449
  codex: async (opts) => {
10404
- const { runCodex } = await import('./runCodex-4njIyif-.mjs');
10450
+ const { runCodex } = await import('./runCodex-C-Pjpbxn.mjs');
10405
10451
  await runCodex({
10406
10452
  credentials: opts.credentials,
10407
10453
  startedBy: opts.startedBy,
@@ -10410,7 +10456,7 @@ const unifiedProviderExecutors = {
10410
10456
  });
10411
10457
  },
10412
10458
  gemini: async (opts) => {
10413
- const { runGemini } = await import('./runGemini-ClDpc5hl.mjs');
10459
+ const { runGemini } = await import('./runGemini-CIVm6NWC.mjs');
10414
10460
  await runGemini({
10415
10461
  credentials: opts.credentials,
10416
10462
  startedBy: opts.startedBy
@@ -10493,7 +10539,7 @@ function shouldRunMainClaudeFlow(opts) {
10493
10539
  return;
10494
10540
  } else if (subcommand === "runtime") {
10495
10541
  if (args[1] === "providers") {
10496
- const { renderRuntimeProviders } = await import('./command-BV2wo6tq.mjs');
10542
+ const { renderRuntimeProviders } = await import('./command-C6injNJE.mjs');
10497
10543
  console.log(renderRuntimeProviders());
10498
10544
  return;
10499
10545
  }
@@ -10682,8 +10728,8 @@ function shouldRunMainClaudeFlow(opts) {
10682
10728
  const projectId = args[3];
10683
10729
  try {
10684
10730
  const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return config; });
10685
- const { readCredentials: readCredentials2 } = await import('./api-CtjWkGWV.mjs').then(function (n) { return n.L; });
10686
- const { ApiClient: ApiClient2 } = await import('./api-CtjWkGWV.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; });
10687
10733
  let userEmail = void 0;
10688
10734
  try {
10689
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-BtjmMYKf.cjs');
4
+ require('./api-8xtJZeXZ.cjs');
5
5
  require('zod');
6
- require('./index-WIr2Wt9U.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-CtjWkGWV.mjs';
2
+ import './api-DB30ctmX.mjs';
3
3
  import 'zod';
4
- import './index-hL-ID2fR.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-BtjmMYKf.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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, c as configuration, l as logger } from './api-CtjWkGWV.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-WIr2Wt9U.cjs');
4
- var persistence = require('./api-BtjmMYKf.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-hL-ID2fR.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-CtjWkGWV.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-BtjmMYKf.cjs');
4
+ var persistence = require('./api-8xtJZeXZ.cjs');
5
5
  require('cross-spawn');
6
6
  require('@agentclientprotocol/sdk');
7
- var index = require('./index-WIr2Wt9U.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-B8EGoc4L.cjs');
28
+ var ProviderSelectionHandler = require('./ProviderSelectionHandler-B5Aq5gA_.cjs');
29
29
  var types = require('./types-DVk3crez.cjs');
30
- var registerKillSessionHandler = require('./registerKillSessionHandler-D0EYq7uF.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-CtjWkGWV.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-hL-ID2fR.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-D9WDj0pS.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-D9JiuWdb.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-CtjWkGWV.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-D9JiuWdb.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-hL-ID2fR.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-D9WDj0pS.mjs';
28
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-CQ59YfWV.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-BtjmMYKf.cjs');
4
- var registerKillSessionHandler = require('./registerKillSessionHandler-D0EYq7uF.cjs');
5
- var index = require('./index-WIr2Wt9U.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-B8EGoc4L.cjs');
30
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-BrPfiZf4.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-BtjmMYKf.cjs');
7
- var registerKillSessionHandler = require('./registerKillSessionHandler-D0EYq7uF.cjs');
8
- var index = require('./index-WIr2Wt9U.cjs');
9
- var BaseReasoningProcessor = require('./BaseReasoningProcessor-BrPfiZf4.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-CtjWkGWV.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-D9JiuWdb.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-hL-ID2fR.mjs';
7
- import { B as BaseReasoningProcessor, b as bootstrapManagedProviderSession } from './BaseReasoningProcessor-CQ59YfWV.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.20",
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
+ }