@dsh-enhanced/lark-channel 0.1.48 → 0.1.49

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 (173) hide show
  1. package/README.md +19 -8
  2. package/docs/rsi-setup.md +123 -11
  3. package/docs/setup.md +27 -14
  4. package/docs/supervised-growth.md +2 -0
  5. package/lib/adapter.d.ts +6 -0
  6. package/lib/adapter.d.ts.map +1 -1
  7. package/lib/adapter.js +79 -8
  8. package/lib/adapter.js.map +1 -1
  9. package/lib/business-cli-install.d.ts +9 -0
  10. package/lib/business-cli-install.d.ts.map +1 -0
  11. package/lib/business-cli-install.js +367 -0
  12. package/lib/business-cli-install.js.map +1 -0
  13. package/lib/business-environment.d.ts +2 -0
  14. package/lib/business-environment.d.ts.map +1 -0
  15. package/lib/business-environment.js +16 -0
  16. package/lib/business-environment.js.map +1 -0
  17. package/lib/business-skill.d.ts +32 -0
  18. package/lib/business-skill.d.ts.map +1 -0
  19. package/lib/business-skill.js +202 -0
  20. package/lib/business-skill.js.map +1 -0
  21. package/lib/rsi-authority-config.d.ts +29 -0
  22. package/lib/rsi-authority-config.d.ts.map +1 -0
  23. package/lib/rsi-authority-config.js +233 -0
  24. package/lib/rsi-authority-config.js.map +1 -0
  25. package/lib/rsi-authority-resources.d.ts +36 -0
  26. package/lib/rsi-authority-resources.d.ts.map +1 -0
  27. package/lib/rsi-authority-resources.js +139 -0
  28. package/lib/rsi-authority-resources.js.map +1 -0
  29. package/lib/rsi-authority-runtime.d.ts +50 -0
  30. package/lib/rsi-authority-runtime.d.ts.map +1 -0
  31. package/lib/rsi-authority-runtime.js +393 -0
  32. package/lib/rsi-authority-runtime.js.map +1 -0
  33. package/lib/rsi-bootstrap-manifest.d.ts +39 -0
  34. package/lib/rsi-bootstrap-manifest.d.ts.map +1 -0
  35. package/lib/rsi-bootstrap-manifest.js +366 -0
  36. package/lib/rsi-bootstrap-manifest.js.map +1 -0
  37. package/lib/rsi-bootstrap.d.ts +30 -0
  38. package/lib/rsi-bootstrap.d.ts.map +1 -0
  39. package/lib/rsi-bootstrap.js +363 -0
  40. package/lib/rsi-bootstrap.js.map +1 -0
  41. package/lib/rsi-build.d.ts +45 -0
  42. package/lib/rsi-build.d.ts.map +1 -0
  43. package/lib/rsi-build.js +404 -0
  44. package/lib/rsi-build.js.map +1 -0
  45. package/lib/rsi-creation-build.d.ts +47 -0
  46. package/lib/rsi-creation-build.d.ts.map +1 -0
  47. package/lib/rsi-creation-build.js +333 -0
  48. package/lib/rsi-creation-build.js.map +1 -0
  49. package/lib/rsi-host-authorities.d.ts +18 -0
  50. package/lib/rsi-host-authorities.d.ts.map +1 -0
  51. package/lib/rsi-host-authorities.js +129 -0
  52. package/lib/rsi-host-authorities.js.map +1 -0
  53. package/lib/rsi-host-update.d.ts +113 -0
  54. package/lib/rsi-host-update.d.ts.map +1 -0
  55. package/lib/rsi-host-update.js +316 -0
  56. package/lib/rsi-host-update.js.map +1 -0
  57. package/lib/rsi-install-inputs.d.ts +43 -0
  58. package/lib/rsi-install-inputs.d.ts.map +1 -0
  59. package/lib/rsi-install-inputs.js +342 -0
  60. package/lib/rsi-install-inputs.js.map +1 -0
  61. package/lib/rsi-install.d.ts +54 -0
  62. package/lib/rsi-install.d.ts.map +1 -0
  63. package/lib/rsi-install.js +404 -0
  64. package/lib/rsi-install.js.map +1 -0
  65. package/lib/rsi-local-activation.d.ts +78 -0
  66. package/lib/rsi-local-activation.d.ts.map +1 -0
  67. package/lib/rsi-local-activation.js +485 -0
  68. package/lib/rsi-local-activation.js.map +1 -0
  69. package/lib/rsi-local-cohort.d.ts +70 -0
  70. package/lib/rsi-local-cohort.d.ts.map +1 -0
  71. package/lib/rsi-local-cohort.js +781 -0
  72. package/lib/rsi-local-cohort.js.map +1 -0
  73. package/lib/rsi-local-install.d.ts +40 -0
  74. package/lib/rsi-local-install.d.ts.map +1 -0
  75. package/lib/rsi-local-install.js +221 -0
  76. package/lib/rsi-local-install.js.map +1 -0
  77. package/lib/rsi-local-pair-install.d.ts +38 -0
  78. package/lib/rsi-local-pair-install.d.ts.map +1 -0
  79. package/lib/rsi-local-pair-install.js +200 -0
  80. package/lib/rsi-local-pair-install.js.map +1 -0
  81. package/lib/rsi-local-profile-update.d.ts +25 -0
  82. package/lib/rsi-local-profile-update.d.ts.map +1 -0
  83. package/lib/rsi-local-profile-update.js +226 -0
  84. package/lib/rsi-local-profile-update.js.map +1 -0
  85. package/lib/rsi-local-resources.d.ts +37 -0
  86. package/lib/rsi-local-resources.d.ts.map +1 -0
  87. package/lib/rsi-local-resources.js +184 -0
  88. package/lib/rsi-local-resources.js.map +1 -0
  89. package/lib/rsi-local-roots-extension.d.ts +11 -0
  90. package/lib/rsi-local-roots-extension.d.ts.map +1 -0
  91. package/lib/rsi-local-roots-extension.js +31 -0
  92. package/lib/rsi-local-roots-extension.js.map +1 -0
  93. package/lib/rsi-local-update-build.d.ts +18 -0
  94. package/lib/rsi-local-update-build.d.ts.map +1 -0
  95. package/lib/rsi-local-update-build.js +37 -0
  96. package/lib/rsi-local-update-build.js.map +1 -0
  97. package/lib/rsi-local-update.d.ts +39 -0
  98. package/lib/rsi-local-update.d.ts.map +1 -0
  99. package/lib/rsi-local-update.js +205 -0
  100. package/lib/rsi-local-update.js.map +1 -0
  101. package/lib/rsi-memory-learning.d.ts +22 -0
  102. package/lib/rsi-memory-learning.d.ts.map +1 -0
  103. package/lib/rsi-memory-learning.js +76 -0
  104. package/lib/rsi-memory-learning.js.map +1 -0
  105. package/lib/rsi-owner-profile.d.ts +37 -0
  106. package/lib/rsi-owner-profile.d.ts.map +1 -0
  107. package/lib/rsi-owner-profile.js +178 -0
  108. package/lib/rsi-owner-profile.js.map +1 -0
  109. package/lib/rsi-plugin-creation.d.ts +31 -0
  110. package/lib/rsi-plugin-creation.d.ts.map +1 -0
  111. package/lib/rsi-plugin-creation.js +179 -0
  112. package/lib/rsi-plugin-creation.js.map +1 -0
  113. package/lib/rsi-profile.d.ts +23 -0
  114. package/lib/rsi-profile.d.ts.map +1 -1
  115. package/lib/rsi-profile.js +154 -43
  116. package/lib/rsi-profile.js.map +1 -1
  117. package/lib/rsi-release-build.d.ts +41 -0
  118. package/lib/rsi-release-build.d.ts.map +1 -0
  119. package/lib/rsi-release-build.js +223 -0
  120. package/lib/rsi-release-build.js.map +1 -0
  121. package/lib/rsi-service-environment.d.ts +6 -0
  122. package/lib/rsi-service-environment.d.ts.map +1 -0
  123. package/lib/rsi-service-environment.js +113 -0
  124. package/lib/rsi-service-environment.js.map +1 -0
  125. package/lib/rsi-service-setup.d.ts +10 -0
  126. package/lib/rsi-service-setup.d.ts.map +1 -0
  127. package/lib/rsi-service-setup.js +34 -0
  128. package/lib/rsi-service-setup.js.map +1 -0
  129. package/lib/rsi-setup.d.ts +35 -2
  130. package/lib/rsi-setup.d.ts.map +1 -1
  131. package/lib/rsi-setup.js +459 -32
  132. package/lib/rsi-setup.js.map +1 -1
  133. package/lib/rsi-source-maintenance.d.ts +25 -0
  134. package/lib/rsi-source-maintenance.d.ts.map +1 -0
  135. package/lib/rsi-source-maintenance.js +348 -0
  136. package/lib/rsi-source-maintenance.js.map +1 -0
  137. package/lib/rsi-source-update.d.ts +26 -0
  138. package/lib/rsi-source-update.d.ts.map +1 -0
  139. package/lib/rsi-source-update.js +276 -0
  140. package/lib/rsi-source-update.js.map +1 -0
  141. package/lib/rsi-source.d.ts +34 -0
  142. package/lib/rsi-source.d.ts.map +1 -0
  143. package/lib/rsi-source.js +375 -0
  144. package/lib/rsi-source.js.map +1 -0
  145. package/lib/rsi-systemd-bootstrap.d.ts +46 -0
  146. package/lib/rsi-systemd-bootstrap.d.ts.map +1 -0
  147. package/lib/rsi-systemd-bootstrap.js +271 -0
  148. package/lib/rsi-systemd-bootstrap.js.map +1 -0
  149. package/lib/sdk.d.ts.map +1 -1
  150. package/lib/sdk.js +26 -1
  151. package/lib/sdk.js.map +1 -1
  152. package/lib/setup-business.d.ts +49 -0
  153. package/lib/setup-business.d.ts.map +1 -0
  154. package/lib/setup-business.js +335 -0
  155. package/lib/setup-business.js.map +1 -0
  156. package/lib/setup-profile.d.ts +1 -1
  157. package/lib/setup-profile.d.ts.map +1 -1
  158. package/lib/setup-profile.js +25 -2
  159. package/lib/setup-profile.js.map +1 -1
  160. package/lib/setup.d.ts +12 -1
  161. package/lib/setup.d.ts.map +1 -1
  162. package/lib/setup.js +138 -34
  163. package/lib/setup.js.map +1 -1
  164. package/lib/systemd.d.ts +10 -0
  165. package/lib/systemd.d.ts.map +1 -1
  166. package/lib/systemd.js +23 -6
  167. package/lib/systemd.js.map +1 -1
  168. package/lib/types.d.ts +7 -0
  169. package/lib/types.d.ts.map +1 -1
  170. package/lib/types.js.map +1 -1
  171. package/lib/version.d.ts +1 -1
  172. package/lib/version.js +1 -1
  173. package/package.json +32 -20
package/lib/setup.js CHANGED
@@ -15,6 +15,9 @@ import { createOfficialLarkTransport } from './sdk.js';
15
15
  import { installDshResidentService } from './resident.js';
16
16
  import { prepareDshSystemdUserService } from './systemd.js';
17
17
  import { configureLarkProfilePatch, refreshLarkAgentPolicyPatch } from './setup-profile.js';
18
+ import { ensureLarkBusinessCli } from './business-cli-install.js';
19
+ import { setupLarkBusinessTools } from './setup-business.js';
20
+ import { reconcileLarkBusinessSkill } from './business-skill.js';
18
21
  import { assertRawManagedProfileIntegrity, materializeManagedProfileOverride, } from './setup-materialization.js';
19
22
  const setupKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
20
23
  const keychainLocatorPattern = /^[A-Za-z0-9][A-Za-z0-9/._-]{0,255}$/u;
@@ -33,13 +36,14 @@ export function parseLarkSetupArgs(argv) {
33
36
  tenant: 'personal',
34
37
  createApp: false,
35
38
  calendarReadonly: false,
39
+ businessTools: true,
36
40
  appName: 'DSH Personal Assistant',
37
41
  timeoutMs: 300_000,
38
42
  installServiceOnly: false,
39
43
  refreshAgentPolicy: false,
40
44
  manageService: true,
41
45
  linuxCredentialProvider: 'auto',
42
- agentTools: 'preserve',
46
+ agentTools: 'auto',
43
47
  help: false,
44
48
  };
45
49
  for (let index = 0; index < argv.length; index += 1) {
@@ -66,6 +70,8 @@ export function parseLarkSetupArgs(argv) {
66
70
  result.createApp = true;
67
71
  else if (option === '--calendar-readonly')
68
72
  result.calendarReadonly = true;
73
+ else if (option === '--no-business-tools')
74
+ result.businessTools = false;
69
75
  else if (option === '--install-service')
70
76
  result.installServiceOnly = true;
71
77
  else if (option === '--refresh-agent-policy')
@@ -75,11 +81,11 @@ export function parseLarkSetupArgs(argv) {
75
81
  else if (option === '--linux-credential-provider') {
76
82
  result.linuxCredentialProvider = argumentValue(argv, index++, option);
77
83
  }
78
- else if (option === '--allow-agent-tools' || option === '--disable-agent-tools') {
79
- if (result.agentTools !== 'preserve') {
80
- throw new Error('lark-channel setup: --allow-agent-tools and --disable-agent-tools are mutually exclusive');
84
+ else if (option === '--allow-agent-tools' || option === '--disable-agent-tools' || option === '--preserve-agent-tools') {
85
+ if (result.agentTools !== 'auto') {
86
+ throw new Error('lark-channel setup: --allow-agent-tools, --disable-agent-tools and --preserve-agent-tools are mutually exclusive');
81
87
  }
82
- result.agentTools = option === '--allow-agent-tools' ? 'enable' : 'disable';
88
+ result.agentTools = option === '--allow-agent-tools' ? 'enable' : option === '--disable-agent-tools' ? 'disable' : 'preserve';
83
89
  }
84
90
  else if (option === '--app-name')
85
91
  result.appName = argumentValue(argv, index++, option);
@@ -100,12 +106,12 @@ export function parseLarkSetupArgs(argv) {
100
106
  if (!result.help && result.refreshAgentPolicy) {
101
107
  const incompatible = [
102
108
  '--domain', '--tenant', '--app-id', '--create-app', '--install-service',
103
- '--no-service', '--linux-credential-provider', '--app-name', '--timeout-ms',
109
+ '--no-service', '--linux-credential-provider', '--app-name', '--timeout-ms', '--no-business-tools',
104
110
  ].find(option => argv.includes(option));
105
111
  if (incompatible !== undefined) {
106
112
  throw new Error(`lark-channel setup: --refresh-agent-policy cannot be combined with ${incompatible}`);
107
113
  }
108
- if (result.agentTools === 'preserve') {
114
+ if (result.agentTools !== 'enable' && result.agentTools !== 'disable') {
109
115
  throw new Error('lark-channel setup: --refresh-agent-policy requires --allow-agent-tools or --disable-agent-tools');
110
116
  }
111
117
  }
@@ -118,10 +124,13 @@ export function parseLarkSetupArgs(argv) {
118
124
  if (result.installServiceOnly && (result.createApp || result.appId !== undefined)) {
119
125
  throw new Error('lark-channel setup: --install-service cannot be combined with application setup options');
120
126
  }
127
+ if (result.installServiceOnly && !result.businessTools) {
128
+ throw new Error('lark-channel setup: --install-service cannot be combined with --no-business-tools');
129
+ }
121
130
  if (result.installServiceOnly && argv.includes('--linux-credential-provider')) {
122
131
  throw new Error('lark-channel setup: --install-service cannot be combined with --linux-credential-provider');
123
132
  }
124
- if (result.installServiceOnly && result.agentTools !== 'preserve') {
133
+ if (result.installServiceOnly && result.agentTools !== 'auto') {
125
134
  throw new Error('lark-channel setup: --install-service cannot be combined with agent-tools options');
126
135
  }
127
136
  const appNameHasControlCharacter = [...result.appName]
@@ -151,7 +160,7 @@ export function createLarkRegistrationOptions(input) {
151
160
  desc: '由 DeepSeek Harness 驱动的个人助理',
152
161
  },
153
162
  addons: {
154
- preset: false,
163
+ preset: input.businessTools !== false,
155
164
  scopes: { tenant: [
156
165
  'application:bot.basic_info:read',
157
166
  'im:message.group_at_msg:readonly',
@@ -289,10 +298,13 @@ Options:
289
298
  --install-service Only install/restart the profile's resident service
290
299
  --refresh-agent-policy Only refresh policy from the existing channel binding
291
300
  --no-service Configure Lark without installing a resident service
301
+ --no-business-tools Only configure the message channel; skip business CLI and user authorization
292
302
  --linux-credential-provider <mode>
293
303
  auto, protected-file, or secret-service (default: auto)
294
304
  --allow-agent-tools Allow mounted foreground/external Agent capabilities
295
305
  --disable-agent-tools Remove setup-managed Agent capability rules
306
+ --preserve-agent-tools Preserve existing rules, including on first setup
307
+ Default: allow for a new owner, preserve on reconfiguration
296
308
  --timeout-ms <ms> Owner DM wait, 30000..900000 (default: 300000)
297
309
  -h, --help Show this help
298
310
 
@@ -1146,6 +1158,7 @@ async function registerLarkApplication(args) {
1146
1158
  appName: args.appName,
1147
1159
  ...(args.appId === undefined ? {} : { appId: args.appId }),
1148
1160
  ...(args.calendarReadonly ? { calendarReadonly: true } : {}),
1161
+ businessTools: args.businessTools,
1149
1162
  signal: abortController.signal,
1150
1163
  onQRCodeReady(info) {
1151
1164
  process.stdout.write(`\n请在飞书中打开以下链接,选择已有应用或创建新应用(${info.expireIn} 秒内有效):\n${info.url}\n\n`);
@@ -1881,6 +1894,40 @@ function effectiveProfileHasEnabledLarkChannel(effectiveProfile, profile) {
1881
1894
  throw new Error(`lark-channel setup: profile ${profile} has an invalid Lark config`);
1882
1895
  return config.get('enabled') !== false;
1883
1896
  }
1897
+ function effectiveDeliveryConsumerDatabasePaths(effectiveProfile, dshHome, profile) {
1898
+ const document = parseDocument(effectiveProfile, { uniqueKeys: true });
1899
+ if (document.errors.length > 0 || !isSeq(document.contents)) {
1900
+ throw new Error(`lark-channel setup: cannot verify effective Delivery consumers in profile ${profile}`);
1901
+ }
1902
+ const paths = [];
1903
+ for (const row of document.contents.items) {
1904
+ if (!isMap(row))
1905
+ continue;
1906
+ const id = row.get('id');
1907
+ const name = row.get('name');
1908
+ if (id !== 'dsh-enhanced-assistant-delivery' && name !== '@dsh-enhanced/assistant-delivery')
1909
+ continue;
1910
+ if (!setupRowIsEnabled(row))
1911
+ continue;
1912
+ if (name !== '@dsh-enhanced/assistant-delivery') {
1913
+ throw new Error(`lark-channel setup: profile ${profile} has an invalid effective Delivery package row`);
1914
+ }
1915
+ const config = row.get('config', true);
1916
+ if (!isMap(config))
1917
+ throw new Error(`lark-channel setup: profile ${profile} has an invalid Delivery config`);
1918
+ const schedulerEnabled = config.get('schedulerEnabled');
1919
+ if (schedulerEnabled !== undefined && typeof schedulerEnabled !== 'boolean') {
1920
+ throw new Error(`lark-channel setup: profile ${profile} has a non-boolean Delivery schedulerEnabled value`);
1921
+ }
1922
+ // Delivery defaults to starting its scheduler independently of the Lark
1923
+ // adapter. A stopped service may restart later; configuration alone only
1924
+ // proves that it cannot consume when the scheduler is explicitly disabled.
1925
+ if (schedulerEnabled === false)
1926
+ continue;
1927
+ paths.push(dshEffectiveDataPath(config.get('databasePath'), dshHome, 'assistant-delivery databasePath'));
1928
+ }
1929
+ return paths;
1930
+ }
1884
1931
  function externalPrincipalKeyId(principal) {
1885
1932
  return [principal.channel, principal.account, principal.tenant, principal.user]
1886
1933
  .map(component => encodeURIComponent(component))
@@ -2181,7 +2228,7 @@ function assertEffectiveLarkOwnerBinding(input) {
2181
2228
  throw new Error(`lark-channel setup: effective Lark binding no longer represents the ${suffix}`);
2182
2229
  }
2183
2230
  }
2184
- async function assertNoOtherLarkProfileSharesDelivery(input) {
2231
+ async function assertNoOtherProfileSharesDelivery(input) {
2185
2232
  let entries;
2186
2233
  try {
2187
2234
  entries = await readdir(platformJoin(input.dshHome, 'profiles'), { withFileTypes: true });
@@ -2198,7 +2245,7 @@ async function assertNoOtherLarkProfileSharesDelivery(input) {
2198
2245
  || !setupKeyPattern.test(entry.name))
2199
2246
  continue;
2200
2247
  const patchPath = platformJoin(input.dshHome, 'profiles', entry.name, 'cordis.patch.yml');
2201
- // A profile can inherit Lark entirely from a lower bundle while its raw
2248
+ // A profile can inherit Delivery or Lark from a lower bundle while its raw
2202
2249
  // override contains no channel row. Activation and database identity must
2203
2250
  // therefore come from effective config whenever that profile composes.
2204
2251
  try {
@@ -2214,21 +2261,24 @@ async function assertNoOtherLarkProfileSharesDelivery(input) {
2214
2261
  effective = await input.readEffectiveProfile(entry.name);
2215
2262
  }
2216
2263
  catch (error) {
2217
- // Raw absence cannot prove absence: Lark may be inherited from a lower
2264
+ // Raw absence cannot prove absence: consumers may be inherited from a lower
2218
2265
  // bundle and a resident using the last valid composition may still run.
2219
2266
  // Without a durable ownership registry, an unverifiable real profile is
2220
2267
  // therefore a fail-closed handoff boundary.
2221
- throw new Error(`lark-channel setup: cannot verify effective Lark ownership in profile ${entry.name}`, {
2268
+ throw new Error(`lark-channel setup: cannot verify effective Lark ownership in profile ${entry.name} or its Delivery consumers`, {
2222
2269
  cause: error,
2223
2270
  });
2224
2271
  }
2225
- if (!effectiveProfileHasEnabledLarkChannel(effective, entry.name))
2226
- continue;
2227
- const otherDatabasePath = deliveryDatabasePathFromEffectiveProfile(effective, input.dshHome);
2228
- if (normalizeSetupResourcePath(otherDatabasePath) === databasePath) {
2272
+ if (effectiveProfileHasEnabledLarkChannel(effective, entry.name)
2273
+ && normalizeSetupResourcePath(deliveryDatabasePathFromEffectiveProfile(effective, input.dshHome)) === databasePath) {
2229
2274
  throw new Error(`lark-channel setup: profile ${entry.name} already owns Lark for the same assistant-delivery database; `
2230
2275
  + 'refusing to revoke its owner from another profile');
2231
2276
  }
2277
+ if (effectiveDeliveryConsumerDatabasePaths(effective, input.dshHome, entry.name)
2278
+ .some(path => normalizeSetupResourcePath(path) === databasePath)) {
2279
+ throw new Error(`lark-channel setup: profile ${entry.name} has an enabled assistant-delivery scheduler for the same database; `
2280
+ + 'disable its scheduler or configure a separate database before continuing');
2281
+ }
2232
2282
  }
2233
2283
  }
2234
2284
  async function withDeliveryOwnerSetupLock(databasePath, operation, options = {}) {
@@ -2406,7 +2456,7 @@ async function recoverLarkSetupJournalUnlocked(input) {
2406
2456
  stagedCredential: journal.stagedCredential,
2407
2457
  context: 'journal',
2408
2458
  });
2409
- await assertNoOtherLarkProfileSharesDelivery({
2459
+ await assertNoOtherProfileSharesDelivery({
2410
2460
  dshHome: input.dshHome,
2411
2461
  profile: input.profile,
2412
2462
  databasePath,
@@ -2588,7 +2638,7 @@ export async function executeLarkSetupProfileTransaction(input) {
2588
2638
  effectiveProfile,
2589
2639
  });
2590
2640
  await withDeliveryOwnerSetupLock(databasePath, async () => {
2591
- await assertNoOtherLarkProfileSharesDelivery({
2641
+ await assertNoOtherProfileSharesDelivery({
2592
2642
  dshHome: input.dshHome,
2593
2643
  profile: input.args.profile,
2594
2644
  databasePath,
@@ -2824,7 +2874,7 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
2824
2874
  const effectiveProfile = await readEffectiveProfile(args.profile);
2825
2875
  const databasePath = deliveryDatabasePathFromEffectiveProfile(effectiveProfile, dshHome);
2826
2876
  await withDeliveryOwnerSetupLock(databasePath, async () => {
2827
- await assertNoOtherLarkProfileSharesDelivery({
2877
+ await assertNoOtherProfileSharesDelivery({
2828
2878
  dshHome,
2829
2879
  profile: args.profile,
2830
2880
  databasePath,
@@ -2911,7 +2961,7 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
2911
2961
  });
2912
2962
  const databasePath = deliveryDatabasePathFromEffectiveProfile(await readEffectiveProfile(args.profile), dshHome);
2913
2963
  return withDeliveryOwnerSetupLock(databasePath, async () => {
2914
- await assertNoOtherLarkProfileSharesDelivery({
2964
+ await assertNoOtherProfileSharesDelivery({
2915
2965
  dshHome,
2916
2966
  profile: args.profile,
2917
2967
  databasePath,
@@ -2971,7 +3021,7 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
2971
3021
  });
2972
3022
  const databasePath = deliveryDatabasePathFromEffectiveProfile(await readEffectiveProfile(args.profile), dshHome);
2973
3023
  await withDeliveryOwnerSetupLock(databasePath, async () => {
2974
- await assertNoOtherLarkProfileSharesDelivery({
3024
+ await assertNoOtherProfileSharesDelivery({
2975
3025
  dshHome,
2976
3026
  profile: args.profile,
2977
3027
  databasePath,
@@ -2979,6 +3029,9 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
2979
3029
  });
2980
3030
  }, runtime.profileLockOptions);
2981
3031
  }, runtime.profileLockOptions);
3032
+ const businessCli = args.businessTools
3033
+ ? await (runtime.ensureBusinessCli ?? ensureLarkBusinessCli)({ dshHome })
3034
+ : undefined;
2982
3035
  let appId = args.appId;
2983
3036
  let createApp = args.createApp;
2984
3037
  if (appId === undefined && !createApp) {
@@ -2995,9 +3048,9 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
2995
3048
  let generatedSecret;
2996
3049
  if (createApp) {
2997
3050
  process.stdout.write(args.appId === undefined
2998
- ? '\n将通过飞书官方设备授权选择已有应用或创建新应用,并增量申请最小权限。App Secret 不会显示或写入 profile。\n'
2999
- : `\n将通过飞书官方设备授权更新已有应用 ${args.appId},并增量申请最小权限。App Secret 不会显示或写入 profile。\n`);
3000
- const registration = await registerLarkApplication(args);
3051
+ ? '\n将通过飞书官方设备授权选择已有应用或创建新应用,并增量申请应用权限。App Secret 不会显示或写入 profile。\n'
3052
+ : `\n将通过飞书官方设备授权更新已有应用 ${args.appId},并增量申请应用权限。App Secret 不会显示或写入 profile。\n`);
3053
+ const registration = await (runtime.registerApplication ?? registerLarkApplication)(args);
3001
3054
  appId = registration.client_id;
3002
3055
  resolvedDomain = registration.user_info?.tenant_brand ?? resolvedDomain;
3003
3056
  generatedSecret = registration.client_secret;
@@ -3014,6 +3067,7 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
3014
3067
  throw new Error('lark-channel setup: Feishu did not return an App ID');
3015
3068
  const configuredAppId = appId;
3016
3069
  let installedService;
3070
+ let businessBinding;
3017
3071
  await executeLarkSetupProfileTransaction({
3018
3072
  args,
3019
3073
  dshHome,
@@ -3049,17 +3103,38 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
3049
3103
  : undefined);
3050
3104
  },
3051
3105
  readEffectiveProfile,
3106
+ validateProfile: validate,
3107
+ ...runtime.ownerSetupOperations,
3052
3108
  async discoverOwner(ownerInput) {
3053
- const phrase = `DSH-CONNECT-${randomBytes(4).toString('hex').toUpperCase()}`;
3054
- const transport = createOfficialLarkTransport({
3055
- appId: ownerInput.appId,
3056
- appSecret: ownerInput.appSecret,
3057
- domain: ownerInput.domain,
3058
- handshakeTimeoutMs: 15_000,
3059
- requestTimeoutMs: 30_000,
3060
- imageDownloadTimeoutMs: 30_000,
3109
+ let owner;
3110
+ if (runtime.discoverOwner !== undefined)
3111
+ owner = await runtime.discoverOwner(ownerInput);
3112
+ else {
3113
+ const phrase = `DSH-CONNECT-${randomBytes(4).toString('hex').toUpperCase()}`;
3114
+ const transport = createOfficialLarkTransport({
3115
+ appId: ownerInput.appId,
3116
+ appSecret: ownerInput.appSecret,
3117
+ domain: ownerInput.domain,
3118
+ handshakeTimeoutMs: 15_000,
3119
+ requestTimeoutMs: 30_000,
3120
+ imageDownloadTimeoutMs: 30_000,
3121
+ });
3122
+ owner = await discoverOwner(transport, phrase, ownerInput.account, ownerInput.tenant, ownerInput.timeoutMs);
3123
+ }
3124
+ if (args.businessTools)
3125
+ businessBinding = { appSecret: ownerInput.appSecret, owner };
3126
+ const previousDocument = parseDocument(await readEffectiveProfile(args.profile), { uniqueKeys: true });
3127
+ const previousRow = isSeq(previousDocument.contents)
3128
+ ? previousDocument.contents.items.find(item => isMap(item) && item.get('id') === 'dsh-enhanced-lark-channel')
3129
+ : undefined;
3130
+ const previousConfig = isMap(previousRow) ? previousRow.get('config', true) : undefined;
3131
+ const previousAccount = isMap(previousConfig) ? previousConfig.get('account') : undefined;
3132
+ await reconcileLarkBusinessSkill({
3133
+ dshHome, profile: args.profile, account: args.account, appId: ownerInput.appId,
3134
+ ownerUserId: owner.user, enabled: args.businessTools,
3135
+ ...(typeof previousAccount === 'string' ? { previousAccount } : {}),
3061
3136
  });
3062
- return discoverOwner(transport, phrase, ownerInput.account, ownerInput.tenant, ownerInput.timeoutMs);
3137
+ return owner;
3063
3138
  },
3064
3139
  ...(args.manageService ? {
3065
3140
  async afterCommit() {
@@ -3068,6 +3143,35 @@ export async function runLarkSetup(argv = process.argv.slice(2), runtime = {}) {
3068
3143
  } : {}),
3069
3144
  },
3070
3145
  });
3146
+ if (businessCli !== undefined) {
3147
+ const binding = businessBinding;
3148
+ if (binding === undefined)
3149
+ throw new Error('lark-channel setup: business tools require the completed owner binding');
3150
+ process.stdout.write('\n消息通道已配置;正在接入飞书业务工具并检查 owner 用户授权。\n');
3151
+ const business = await (runtime.setupBusinessTools ?? setupLarkBusinessTools)({
3152
+ dshHome, profile: args.profile, account: args.account, appId: configuredAppId,
3153
+ appSecret: binding.appSecret, domain: resolvedDomain, ownerUserId: binding.owner.user,
3154
+ cli: businessCli, timeoutMs: args.timeoutMs,
3155
+ async onAuthorization(url) {
3156
+ process.stdout.write(`\n请用刚才绑定的飞书账号完成业务权限授权:\n${url}\n\n`);
3157
+ try {
3158
+ process.stdout.write(`${await QRCode.toString(url, { type: 'terminal', small: true })}\n`);
3159
+ }
3160
+ catch {
3161
+ process.stdout.write('终端二维码生成失败,请打开上面的链接。\n');
3162
+ }
3163
+ },
3164
+ });
3165
+ businessBinding = undefined;
3166
+ process.stdout.write(`飞书业务工具已接入${business.reusedAuthorization ? ',复用已有 owner 授权' : ''}。技能:${business.skillPath}\n`);
3167
+ if (business.permissionCompletion === 'partial') {
3168
+ process.stdout.write(`用户登录已完成;本次申请仍有 ${business.missingScopes.length} 项权限未获批,业务工具仅使用实际已授予的权限。\n`
3169
+ + `未获批权限:${business.missingScopes.join(', ')}\n`);
3170
+ }
3171
+ else if (business.permissionCompletion === 'unknown') {
3172
+ process.stdout.write('已验证 owner 登录;当前授权证据不足以确认完整权限清单,具体操作仍以平台当前授权为准。\n');
3173
+ }
3174
+ }
3071
3175
  if (args.manageService) {
3072
3176
  const service = installedService;
3073
3177
  if (service === undefined)