@dsh-enhanced/lark-channel 0.1.3

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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +342 -0
  3. package/bin/dsh-lark-setup.js +8 -0
  4. package/bin/dsh-supervised-growth-setup.js +8 -0
  5. package/cordis.patch.yml +21 -0
  6. package/lib/adapter.d.ts +135 -0
  7. package/lib/adapter.d.ts.map +1 -0
  8. package/lib/adapter.js +965 -0
  9. package/lib/adapter.js.map +1 -0
  10. package/lib/approval.d.ts +26 -0
  11. package/lib/approval.d.ts.map +1 -0
  12. package/lib/approval.js +104 -0
  13. package/lib/approval.js.map +1 -0
  14. package/lib/config.d.ts +21 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +56 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/group-thread.d.ts +11 -0
  19. package/lib/group-thread.d.ts.map +1 -0
  20. package/lib/group-thread.js +22 -0
  21. package/lib/group-thread.js.map +1 -0
  22. package/lib/index.d.ts +27 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +30 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/launchd.d.ts +44 -0
  27. package/lib/launchd.d.ts.map +1 -0
  28. package/lib/launchd.js +205 -0
  29. package/lib/launchd.js.map +1 -0
  30. package/lib/model-picker.d.ts +24 -0
  31. package/lib/model-picker.d.ts.map +1 -0
  32. package/lib/model-picker.js +95 -0
  33. package/lib/model-picker.js.map +1 -0
  34. package/lib/normalize.d.ts +12 -0
  35. package/lib/normalize.d.ts.map +1 -0
  36. package/lib/normalize.js +77 -0
  37. package/lib/normalize.js.map +1 -0
  38. package/lib/permission-picker.d.ts +29 -0
  39. package/lib/permission-picker.d.ts.map +1 -0
  40. package/lib/permission-picker.js +145 -0
  41. package/lib/permission-picker.js.map +1 -0
  42. package/lib/progress.d.ts +15 -0
  43. package/lib/progress.d.ts.map +1 -0
  44. package/lib/progress.js +137 -0
  45. package/lib/progress.js.map +1 -0
  46. package/lib/resident.d.ts +13 -0
  47. package/lib/resident.d.ts.map +1 -0
  48. package/lib/resident.js +41 -0
  49. package/lib/resident.js.map +1 -0
  50. package/lib/sdk.d.ts +131 -0
  51. package/lib/sdk.d.ts.map +1 -0
  52. package/lib/sdk.js +945 -0
  53. package/lib/sdk.js.map +1 -0
  54. package/lib/service.d.ts +30 -0
  55. package/lib/service.d.ts.map +1 -0
  56. package/lib/service.js +157 -0
  57. package/lib/service.js.map +1 -0
  58. package/lib/setup-materialization.d.ts +15 -0
  59. package/lib/setup-materialization.d.ts.map +1 -0
  60. package/lib/setup-materialization.js +221 -0
  61. package/lib/setup-materialization.js.map +1 -0
  62. package/lib/setup-profile.d.ts +23 -0
  63. package/lib/setup-profile.d.ts.map +1 -0
  64. package/lib/setup-profile.js +1126 -0
  65. package/lib/setup-profile.js.map +1 -0
  66. package/lib/setup.d.ts +206 -0
  67. package/lib/setup.d.ts.map +1 -0
  68. package/lib/setup.js +2143 -0
  69. package/lib/setup.js.map +1 -0
  70. package/lib/supervised-growth-profile.d.ts +74 -0
  71. package/lib/supervised-growth-profile.d.ts.map +1 -0
  72. package/lib/supervised-growth-profile.js +485 -0
  73. package/lib/supervised-growth-profile.js.map +1 -0
  74. package/lib/supervised-growth-setup.d.ts +85 -0
  75. package/lib/supervised-growth-setup.d.ts.map +1 -0
  76. package/lib/supervised-growth-setup.js +371 -0
  77. package/lib/supervised-growth-setup.js.map +1 -0
  78. package/lib/systemd.d.ts +39 -0
  79. package/lib/systemd.d.ts.map +1 -0
  80. package/lib/systemd.js +142 -0
  81. package/lib/systemd.js.map +1 -0
  82. package/lib/tool-approval.d.ts +22 -0
  83. package/lib/tool-approval.d.ts.map +1 -0
  84. package/lib/tool-approval.js +107 -0
  85. package/lib/tool-approval.js.map +1 -0
  86. package/lib/types.d.ts +158 -0
  87. package/lib/types.d.ts.map +1 -0
  88. package/lib/types.js +11 -0
  89. package/lib/types.js.map +1 -0
  90. package/lib/version.d.ts +2 -0
  91. package/lib/version.d.ts.map +1 -0
  92. package/lib/version.js +2 -0
  93. package/lib/version.js.map +1 -0
  94. package/lib/windows-task.d.ts +39 -0
  95. package/lib/windows-task.d.ts.map +1 -0
  96. package/lib/windows-task.js +117 -0
  97. package/lib/windows-task.js.map +1 -0
  98. package/lib/ws-card-callback.d.ts +7 -0
  99. package/lib/ws-card-callback.d.ts.map +1 -0
  100. package/lib/ws-card-callback.js +51 -0
  101. package/lib/ws-card-callback.js.map +1 -0
  102. package/package.json +96 -0
@@ -0,0 +1,1126 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isAbsolute, join, win32 } from 'node:path';
3
+ import { externalPrincipalId } from '@dsh-enhanced/assistant-delivery';
4
+ import { isMap, isScalar, isSeq, parseDocument, } from 'yaml';
5
+ const setupKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
6
+ const providerKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._@/-]{0,255}$/u;
7
+ const presetIdPattern = /^[a-z0-9][a-z0-9-]*$/u;
8
+ // The grant is expressed as one wildcard allow rather than an enumerated
9
+ // allowlist. Tools are registered dynamically by whichever plugins and skills a
10
+ // deployment mounts, so any name list is stale the moment a new tool appears:
11
+ // the agent then reports a refusal the owner cannot fix from the chat, and every
12
+ // addition needs a setup release. Risk is already judged by behaviour in
13
+ // `assistant-policy`'s `tools/pre-execute` reviewer, which inspects arguments and
14
+ // still routes writes, network access and dangerous commands to approval, so this
15
+ // rule decides reachability, not privilege.
16
+ //
17
+ // Delivery keeps its rule bound to the exact preset, absolute workspace and
18
+ // `external` initiator. The local Web foreground grant deliberately accepts
19
+ // every preset/workspace because both are user-selectable and can change
20
+ // independently; it still applies only to human foreground Agent operations.
21
+ const TOOL_WILDCARD = '*';
22
+ const FOREGROUND_CAPABILITY_RULE_ID = `dsh-enhanced-foreground-capability-${TOOL_WILDCARD}`;
23
+ // No capability is denied at this setup layer. Explicit Policy denies retain
24
+ // precedence over the wildcard grants, while tool execution additionally stays
25
+ // behind the session sandbox/reviewer and parameter-risk gates.
26
+ //
27
+ // Populating this list again re-denies those ids: Policy resolves `deny` ahead
28
+ // of `allow` at any specificity, so entries here override the wildcard.
29
+ const deniedExternalTools = [];
30
+ // Per-tool ids emitted by earlier releases: first the enumerated allowlist, then
31
+ // the denylist that replaced it. Removal must still recognise both. A stale
32
+ // allow rule is one `--agent-tools disable` can no longer revoke, and a stale
33
+ // deny rule keeps overriding the wildcard and blocking the tool it names.
34
+ const retiredToolRuleIds = [
35
+ 'bash', 'pwsh', 'read', 'glob', 'grep', 'skill',
36
+ 'memory_search', 'wiki_search', 'wiki_read',
37
+ 'memory_manage', 'wiki_upsert', 'wiki_lint',
38
+ 'automation_create', 'automation_manage', 'automation_run',
39
+ 'evolution_propose', 'knowledge_pin', 'knowledge_promote',
40
+ 'heartbeat_scratch_update',
41
+ ];
42
+ const managedToolRuleSuffixes = [
43
+ TOOL_WILDCARD, ...deniedExternalTools, ...retiredToolRuleIds,
44
+ ];
45
+ const managedApprovalSources = [
46
+ 'dsh-enhanced-personal-memory',
47
+ 'dsh-enhanced-personal-wiki',
48
+ 'dsh-enhanced-assistant-automations',
49
+ 'dsh-enhanced-assistant-evolution',
50
+ ];
51
+ const managedProfileRows = [
52
+ { id: 'dsh-enhanced-lark-channel', packageName: '@dsh-enhanced/lark-channel' },
53
+ { id: 'dsh-enhanced-credentials-keychain', packageName: '@dsh-enhanced/credentials-keychain' },
54
+ ];
55
+ function requireSetupKey(value, field) {
56
+ const normalized = value.trim();
57
+ if (!setupKeyPattern.test(normalized))
58
+ throw new Error(`lark-channel setup: invalid ${field}`);
59
+ return normalized;
60
+ }
61
+ function requireAccount(value) {
62
+ const account = requireSetupKey(value, 'account');
63
+ if (account.includes('-legacy-')) {
64
+ throw new Error('lark-channel setup: invalid account (reserved "-legacy-" segment)');
65
+ }
66
+ return account;
67
+ }
68
+ function requireProviderKey(value, field) {
69
+ const normalized = value.trim();
70
+ if (!providerKeyPattern.test(normalized))
71
+ throw new Error(`lark-channel setup: invalid ${field}`);
72
+ return normalized;
73
+ }
74
+ function asMap(node, label) {
75
+ if (!isMap(node))
76
+ throw new Error(`lark-channel setup: ${label} must be a YAML mapping`);
77
+ return node;
78
+ }
79
+ function asSequence(node, label) {
80
+ if (!isSeq(node))
81
+ throw new Error(`lark-channel setup: ${label} must be a YAML sequence`);
82
+ return node;
83
+ }
84
+ function findRow(rows, id) {
85
+ return rows.items.find(item => isMap(item) && item.get('id') === id);
86
+ }
87
+ function ensureRow(document, rows, id) {
88
+ const existing = findRow(rows, id);
89
+ if (existing !== undefined)
90
+ return existing;
91
+ const created = document.createNode({ id, config: {} });
92
+ if (!isMap(created))
93
+ throw new Error('lark-channel setup: failed to create profile row');
94
+ rows.add(created);
95
+ return created;
96
+ }
97
+ function assertManagedProfileRowIntegrity(rows) {
98
+ for (const expected of managedProfileRows) {
99
+ const matches = rows.items.filter(item => isMap(item) && item.get('id') === expected.id);
100
+ if (matches.length > 1) {
101
+ throw new Error(`lark-channel setup: managed profile row ${expected.id} is duplicated; refusing to change it`);
102
+ }
103
+ const row = matches[0];
104
+ if (row === undefined)
105
+ continue;
106
+ const name = row.get('name', true);
107
+ const disabled = row.get('disabled', true);
108
+ if (name !== undefined && (!isScalar(name) || name.value !== expected.packageName)) {
109
+ throw new Error(`lark-channel setup: managed profile row ${expected.id} is shadowed by an invalid package`);
110
+ }
111
+ if (disabled !== undefined && (!isScalar(disabled) || disabled.value !== false)) {
112
+ throw new Error(`lark-channel setup: managed profile row ${expected.id} is disabled or invalid`);
113
+ }
114
+ }
115
+ }
116
+ function upsertById(document, values, value) {
117
+ const node = document.createNode(value);
118
+ const id = value.id;
119
+ const index = values.items.findIndex(item => isMap(item) && item.get('id') === id);
120
+ if (index === -1)
121
+ values.add(node);
122
+ else
123
+ values.items[index] = node;
124
+ }
125
+ function isLiteralAbsolutePath(value) {
126
+ return typeof value === 'string'
127
+ && (isAbsolute(value) || win32.isAbsolute(value))
128
+ && !value.includes('*')
129
+ && !value.includes('\0');
130
+ }
131
+ function sequenceEquals(node, expected) {
132
+ if (!isSeq(node) || node.items.length !== expected.length)
133
+ return false;
134
+ return node.items.every((item, index) => isScalar(item) && item.value === expected[index]);
135
+ }
136
+ function sequenceIncludes(node, expected) {
137
+ return isSeq(node) && node.items.some(item => isScalar(item) && item.value === expected);
138
+ }
139
+ function managedReplyIdentities(rules, account) {
140
+ const identities = new Map();
141
+ for (const item of rules.items) {
142
+ if (!isMap(item))
143
+ continue;
144
+ const id = item.get('id');
145
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
146
+ if (managedId?.kind !== 'reply' || managedId.account !== account)
147
+ continue;
148
+ const identity = managedExternalRuleIdentity(item, managedId);
149
+ if (identity === undefined) {
150
+ throw new Error(`lark-channel setup: managed reply rule ${String(id)} is invalid`);
151
+ }
152
+ identities.set(`${identity.preset}\0${identity.workspace}`, identity);
153
+ }
154
+ return [...identities.values()];
155
+ }
156
+ function legacyIdentitySuffix(identity) {
157
+ const digest = createHash('sha256')
158
+ .update(identity.preset)
159
+ .update('\0')
160
+ .update(identity.workspace)
161
+ .digest('hex')
162
+ .slice(0, 16);
163
+ return `-legacy-${identity.preset}-${digest}`;
164
+ }
165
+ function configuredAgentIdentity(rows, dshHome) {
166
+ const delivery = findRow(rows, 'dsh-enhanced-assistant-delivery');
167
+ if (delivery === undefined) {
168
+ throw new Error('lark-channel setup: dsh-enhanced-assistant-delivery profile row is required');
169
+ }
170
+ const config = asMap(delivery.get('config', true), 'assistant-delivery config');
171
+ const preset = config.get('defaultAgentPreset');
172
+ if (typeof preset !== 'string' || !presetIdPattern.test(preset)) {
173
+ throw new Error('lark-channel setup: assistant-delivery defaultAgentPreset is invalid');
174
+ }
175
+ const configuredWorkspace = config.get('defaultWorkspace');
176
+ const defaultExpression = /^dshHomePath\((['"])assistant-workspace\1\)$/u;
177
+ const defaultWorkspace = !isAbsolute(dshHome) && win32.isAbsolute(dshHome)
178
+ ? win32.join(dshHome, 'assistant-workspace')
179
+ : join(dshHome, 'assistant-workspace');
180
+ const workspace = typeof configuredWorkspace === 'string' && defaultExpression.test(configuredWorkspace)
181
+ ? defaultWorkspace
182
+ : configuredWorkspace;
183
+ if (!isLiteralAbsolutePath(workspace)) {
184
+ throw new Error('lark-channel setup: assistant-delivery defaultWorkspace is invalid');
185
+ }
186
+ return { preset, workspace };
187
+ }
188
+ function upsertExternalReplyRule(document, rules, input) {
189
+ const suffix = input.legacy ? legacyIdentitySuffix(input.identity) : '';
190
+ upsertById(document, rules, {
191
+ id: `lark-owner-reply-${input.account}${suffix}`,
192
+ effect: 'allow',
193
+ subject: {
194
+ kind: 'agent',
195
+ id: input.identity.preset,
196
+ workspace: input.identity.workspace,
197
+ principal: input.principal,
198
+ },
199
+ actions: ['reply'],
200
+ resource: { kind: 'message', id: '*' },
201
+ context: { initiators: ['external'] },
202
+ });
203
+ }
204
+ function upsertExternalToolRules(document, rules, input) {
205
+ const suffix = input.legacy ? legacyIdentitySuffix(input.identity) : '';
206
+ const subject = {
207
+ kind: 'agent',
208
+ id: input.identity.preset,
209
+ workspace: input.identity.workspace,
210
+ principal: input.principal,
211
+ };
212
+ // Tool reachability alone is insufficient for plugin tools whose service
213
+ // performs a second Policy check (memory.search, wiki.read, automation
214
+ // propose, and future domains). Keep the Agent identity exact, but allow its
215
+ // full external capability plane; tools/pre-execute and every explicit deny
216
+ // still run before/inside the operation.
217
+ upsertById(document, rules, {
218
+ id: `lark-owner-capability-${TOOL_WILDCARD}-${input.account}${suffix}`,
219
+ effect: 'allow',
220
+ subject,
221
+ actions: [TOOL_WILDCARD],
222
+ resource: { kind: TOOL_WILDCARD, id: TOOL_WILDCARD },
223
+ context: { initiators: ['external'] },
224
+ });
225
+ // One reachability grant for every tool the mounted preset exposes.
226
+ upsertById(document, rules, {
227
+ id: `lark-owner-tool-${TOOL_WILDCARD}-${input.account}${suffix}`,
228
+ effect: 'allow',
229
+ subject,
230
+ actions: ['execute'],
231
+ resource: { kind: 'tool', id: TOOL_WILDCARD },
232
+ context: { initiators: ['external'] },
233
+ });
234
+ // Keep any explicitly configured denials after the broad reachability grant
235
+ // so deny precedence remains obvious in the serialized policy.
236
+ for (const tool of deniedExternalTools) {
237
+ upsertById(document, rules, {
238
+ id: `lark-owner-tool-${tool}-${input.account}${suffix}`,
239
+ effect: 'deny',
240
+ subject,
241
+ actions: ['execute'],
242
+ resource: { kind: 'tool', id: tool },
243
+ context: { initiators: ['external'] },
244
+ });
245
+ }
246
+ }
247
+ function upsertForegroundCapabilityRule(document, rules) {
248
+ upsertById(document, rules, {
249
+ id: FOREGROUND_CAPABILITY_RULE_ID,
250
+ effect: 'allow',
251
+ subject: { kind: 'agent', id: TOOL_WILDCARD, workspace: TOOL_WILDCARD },
252
+ actions: [TOOL_WILDCARD],
253
+ resource: { kind: TOOL_WILDCARD, id: TOOL_WILDCARD },
254
+ context: { initiators: ['foreground'] },
255
+ });
256
+ }
257
+ function isManagedForegroundRuleId(id) {
258
+ return id === FOREGROUND_CAPABILITY_RULE_ID
259
+ // Migrate account-scoped ids written before the foreground grant was
260
+ // separated from Lark onboarding. They all represented this same local
261
+ // capability and are safe to collapse into one idempotent rule.
262
+ || id.startsWith('lark-foreground-capability-')
263
+ || id.startsWith('lark-foreground-tool-');
264
+ }
265
+ function isReservedForegroundRuleId(id) {
266
+ return id.startsWith('dsh-enhanced-foreground-capability-')
267
+ || id.startsWith('lark-foreground-capability-')
268
+ || id.startsWith('lark-foreground-tool-');
269
+ }
270
+ function isUnrestrictedForegroundAllow(item) {
271
+ const subject = item.get('subject', true);
272
+ const resource = item.get('resource', true);
273
+ const context = item.get('context', true);
274
+ return item.get('effect') === 'allow'
275
+ && isMap(subject)
276
+ && subject.get('kind') === 'agent'
277
+ && subject.get('id') === TOOL_WILDCARD
278
+ && subject.get('workspace') === TOOL_WILDCARD
279
+ // Rebuilding a principal-restricted foreground rule without the principal
280
+ // would widen it, even when its remaining wildcard shape is unchanged.
281
+ && subject.get('principal') === undefined
282
+ && sequenceEquals(item.get('actions', true), [TOOL_WILDCARD])
283
+ && isMap(resource)
284
+ && resource.get('kind') === TOOL_WILDCARD
285
+ && resource.get('id') === TOOL_WILDCARD
286
+ && isMap(context)
287
+ && sequenceEquals(context.get('initiators', true), ['foreground']);
288
+ }
289
+ function isSafeRestrictiveForegroundRule(item) {
290
+ if (item.get('effect') === 'deny')
291
+ return true;
292
+ if (item.get('effect') !== 'allow')
293
+ return false;
294
+ const subject = item.get('subject', true);
295
+ const context = item.get('context', true);
296
+ if (!isMap(subject) || subject.get('kind') !== 'agent' || !isMap(context)
297
+ || !sequenceEquals(context.get('initiators', true), ['foreground'])) {
298
+ return false;
299
+ }
300
+ const principal = subject.get('principal');
301
+ // A literal principal narrows the local rule. A glob is still setup-wide
302
+ // authority and is not accepted as a hand-edited restriction.
303
+ return principal === undefined
304
+ || (typeof principal === 'string' && !principal.includes(TOOL_WILDCARD));
305
+ }
306
+ function assertManagedForegroundRuleIntegrity(rules) {
307
+ const seenIds = new Set();
308
+ for (const item of rules.items) {
309
+ if (!isMap(item))
310
+ continue;
311
+ const id = item.get('id');
312
+ if (typeof id !== 'string' || !isReservedForegroundRuleId(id))
313
+ continue;
314
+ if (seenIds.has(id)) {
315
+ throw new Error(`lark-channel setup: duplicate managed foreground rule ${id}; refusing to change it`);
316
+ }
317
+ seenIds.add(id);
318
+ if (isManagedForegroundRuleId(id)
319
+ && (isUnrestrictedForegroundAllow(item) || isSafeRestrictiveForegroundRule(item)))
320
+ continue;
321
+ throw new Error(`lark-channel setup: managed foreground rule ${id} is unsafe or malformed; refusing to change Agent policy`);
322
+ }
323
+ }
324
+ function hasManagedForegroundRules(rules) {
325
+ const managed = rules.items.filter(item => isMap(item)
326
+ && typeof item.get('id') === 'string'
327
+ && isManagedForegroundRuleId(item.get('id')));
328
+ return managed.length > 0 && managed.every(isUnrestrictedForegroundAllow);
329
+ }
330
+ function removeManagedForegroundRules(rules, includeDenies = false) {
331
+ for (let index = rules.items.length - 1; index >= 0; index -= 1) {
332
+ const item = rules.items[index];
333
+ if (!isMap(item))
334
+ continue;
335
+ const id = item.get('id');
336
+ if (typeof id !== 'string' || !isManagedForegroundRuleId(id))
337
+ continue;
338
+ if (includeDenies && item.get('effect') === 'deny') {
339
+ const context = item.get('context', true);
340
+ if (!isMap(context)
341
+ || !sequenceEquals(context.get('initiators', true), ['foreground'])) {
342
+ throw new Error(`lark-channel setup: managed foreground deny ${id} may protect external requests; `
343
+ + 'refusing to remove it while enabling Agent policy');
344
+ }
345
+ rules.items.splice(index, 1);
346
+ continue;
347
+ }
348
+ // Disable may safely remove either the canonical allow or a local narrowed
349
+ // allow, but preserves denies because deleting one could widen another
350
+ // rule. Explicit enable deliberately retires those denies before rebuilding
351
+ // the requested full-control grant.
352
+ if (isUnrestrictedForegroundAllow(item)
353
+ || (item.get('effect') === 'allow' && isSafeRestrictiveForegroundRule(item))) {
354
+ rules.items.splice(index, 1);
355
+ }
356
+ }
357
+ }
358
+ function parseManagedAccountTail(tail) {
359
+ const legacyIndex = tail.indexOf('-legacy-');
360
+ const account = legacyIndex === -1 ? tail : tail.slice(0, legacyIndex);
361
+ if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
362
+ return undefined;
363
+ if (legacyIndex === -1)
364
+ return { account };
365
+ const legacySuffix = tail.slice(legacyIndex);
366
+ if (legacySuffix.length <= '-legacy-'.length)
367
+ return undefined;
368
+ return { account, legacySuffix };
369
+ }
370
+ function parseManagedRuleId(id) {
371
+ const replyPrefix = 'lark-owner-reply-';
372
+ if (id.startsWith(replyPrefix)) {
373
+ const tail = parseManagedAccountTail(id.slice(replyPrefix.length));
374
+ return tail === undefined ? undefined : { kind: 'reply', ...tail };
375
+ }
376
+ const capabilityPrefix = `lark-owner-capability-${TOOL_WILDCARD}-`;
377
+ if (id.startsWith(capabilityPrefix)) {
378
+ const tail = parseManagedAccountTail(id.slice(capabilityPrefix.length));
379
+ return tail === undefined ? undefined : { kind: 'capability', ...tail };
380
+ }
381
+ for (const tool of managedToolRuleSuffixes) {
382
+ const toolPrefix = `lark-owner-tool-${tool}-`;
383
+ if (!id.startsWith(toolPrefix))
384
+ continue;
385
+ const tail = parseManagedAccountTail(id.slice(toolPrefix.length));
386
+ return tail === undefined ? undefined : { kind: 'tool', tool, ...tail };
387
+ }
388
+ return undefined;
389
+ }
390
+ function isReservedManagedExternalRuleId(id) {
391
+ return id.startsWith('lark-owner-reply-')
392
+ || id.startsWith('lark-owner-capability-')
393
+ || id.startsWith('lark-owner-tool-');
394
+ }
395
+ function managedExternalRuleIdentity(item, managedId) {
396
+ const subject = item.get('subject', true);
397
+ const resource = item.get('resource', true);
398
+ const context = item.get('context', true);
399
+ if (!isMap(subject) || !isMap(resource) || !isMap(context)
400
+ || subject.get('kind') !== 'agent'
401
+ || typeof subject.get('id') !== 'string'
402
+ || !presetIdPattern.test(subject.get('id'))
403
+ || !isLiteralAbsolutePath(subject.get('workspace'))
404
+ || !sequenceEquals(context.get('initiators', true), ['external']))
405
+ return undefined;
406
+ const identity = {
407
+ preset: subject.get('id'),
408
+ workspace: subject.get('workspace'),
409
+ };
410
+ if (managedId.legacySuffix !== undefined
411
+ && managedId.legacySuffix !== legacyIdentitySuffix(identity))
412
+ return undefined;
413
+ const principal = subject.get('principal');
414
+ if (principal !== undefined && !isCanonicalLarkPrincipalForAccount(principal, managedId.account)) {
415
+ return undefined;
416
+ }
417
+ if (managedId.kind === 'reply') {
418
+ return item.get('effect') === 'allow'
419
+ && sequenceEquals(item.get('actions', true), ['reply'])
420
+ && resource.get('kind') === 'message'
421
+ && resource.get('id') === TOOL_WILDCARD
422
+ ? identity
423
+ : undefined;
424
+ }
425
+ if (managedId.kind === 'tool') {
426
+ return (item.get('effect') === 'allow' || item.get('effect') === 'deny')
427
+ && sequenceEquals(item.get('actions', true), ['execute'])
428
+ && resource.get('kind') === 'tool'
429
+ // One retired release changed only the rule id while retaining the old
430
+ // wildcard resource. Treat that exact Agent/execute/tool shape as ours
431
+ // too, so upgrades can still revoke it.
432
+ && (resource.get('id') === managedId.tool
433
+ || (managedId.tool !== TOOL_WILDCARD && resource.get('id') === TOOL_WILDCARD))
434
+ ? identity
435
+ : undefined;
436
+ }
437
+ return (item.get('effect') === 'allow' || item.get('effect') === 'deny')
438
+ && sequenceEquals(item.get('actions', true), [TOOL_WILDCARD])
439
+ && resource.get('kind') === TOOL_WILDCARD
440
+ && resource.get('id') === TOOL_WILDCARD
441
+ ? identity
442
+ : undefined;
443
+ }
444
+ function assertManagedExternalRuleIntegrity(rules) {
445
+ const seenIds = new Set();
446
+ for (const item of rules.items) {
447
+ if (!isMap(item))
448
+ continue;
449
+ const id = item.get('id');
450
+ // These ids are setup-reserved. Exact current and explicit legacy shapes
451
+ // are accepted by the strict recognizer above; every other mutation is
452
+ // ambiguous and fails closed, regardless of which privilege plane it was
453
+ // changed to. User policy must use a non-reserved id.
454
+ if (typeof id !== 'string' || !isReservedManagedExternalRuleId(id))
455
+ continue;
456
+ if (seenIds.has(id)) {
457
+ throw new Error(`lark-channel setup: duplicate managed Agent rule ${id}; refusing to change it`);
458
+ }
459
+ seenIds.add(id);
460
+ const managedId = parseManagedRuleId(id);
461
+ if (managedId !== undefined && managedExternalRuleIdentity(item, managedId) !== undefined)
462
+ continue;
463
+ throw new Error(`lark-channel setup: reserved managed Agent rule ${id} is malformed; refusing to change Agent policy`);
464
+ }
465
+ }
466
+ function isCanonicalLarkPrincipalForAccount(value, account) {
467
+ if (typeof value !== 'string')
468
+ return false;
469
+ const parts = value.split('/');
470
+ return parts.length === 4
471
+ && parts[0] === 'lark'
472
+ && parts[1] === encodeURIComponent(account)
473
+ && isCanonicalPrincipalComponent(parts[2], setupKeyPattern)
474
+ && isCanonicalPrincipalComponent(parts[3], providerKeyPattern);
475
+ }
476
+ function isCanonicalPrincipalComponent(value, pattern) {
477
+ if (value === undefined || value === '' || value.includes(TOOL_WILDCARD))
478
+ return false;
479
+ try {
480
+ const decoded = decodeURIComponent(value);
481
+ return pattern.test(decoded) && encodeURIComponent(decoded) === value;
482
+ }
483
+ catch {
484
+ return false;
485
+ }
486
+ }
487
+ function parseManagedFixedRuleId(id) {
488
+ const fixedPrefixes = [
489
+ { prefix: 'lark-channel-credential-', kind: 'credential' },
490
+ { prefix: 'lark-owner-ingress-', kind: 'ingress' },
491
+ ];
492
+ for (const candidate of fixedPrefixes) {
493
+ if (!id.startsWith(candidate.prefix))
494
+ continue;
495
+ const account = id.slice(candidate.prefix.length);
496
+ if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
497
+ return undefined;
498
+ return { kind: candidate.kind, account };
499
+ }
500
+ for (const sourceId of managedApprovalSources) {
501
+ const prefix = `lark-owner-approval-${sourceId}-`;
502
+ if (!id.startsWith(prefix))
503
+ continue;
504
+ const account = id.slice(prefix.length);
505
+ if (!setupKeyPattern.test(account) || account.includes('-legacy-'))
506
+ return undefined;
507
+ return { kind: 'approval', account, sourceId };
508
+ }
509
+ return undefined;
510
+ }
511
+ function isReservedManagedFixedRuleId(id) {
512
+ return id.startsWith('lark-channel-credential-')
513
+ || id.startsWith('lark-owner-ingress-')
514
+ || id.startsWith('lark-owner-approval-');
515
+ }
516
+ function managedFixedRuleMatches(item, managedId) {
517
+ const subject = item.get('subject', true);
518
+ const resource = item.get('resource', true);
519
+ const context = item.get('context', true);
520
+ if (item.get('effect') !== 'allow' || !isMap(subject) || !isMap(resource) || !isMap(context)) {
521
+ return false;
522
+ }
523
+ if (managedId.kind === 'credential') {
524
+ return subject.get('kind') === 'background'
525
+ && subject.get('id') === 'dsh-enhanced-lark-channel'
526
+ && sequenceEquals(item.get('actions', true), ['credential.use'])
527
+ && resource.get('kind') === 'credential'
528
+ && resource.get('id') === `lark-app-secret-${managedId.account}`
529
+ && sequenceEquals(context.get('initiators', true), ['background']);
530
+ }
531
+ if (managedId.kind === 'ingress') {
532
+ return subject.get('kind') === 'external'
533
+ && isCanonicalLarkPrincipalForAccount(subject.get('id'), managedId.account)
534
+ && sequenceEquals(item.get('actions', true), ['approval.decide', 'ingest'])
535
+ && resource.get('kind') === 'message'
536
+ && resource.get('id') === TOOL_WILDCARD
537
+ && sequenceEquals(context.get('initiators', true), ['external']);
538
+ }
539
+ const principal = subject.get('principal');
540
+ return subject.get('kind') === 'background'
541
+ && subject.get('id') === managedId.sourceId
542
+ && isLiteralAbsolutePath(subject.get('workspace'))
543
+ // Principal-scoped approvals are current, while exact legacy approvals
544
+ // without a principal are still attributable to setup by the remaining
545
+ // source/action/resource/context tuple.
546
+ && (principal === undefined || isCanonicalLarkPrincipalForAccount(principal, managedId.account))
547
+ && sequenceEquals(item.get('actions', true), ['approval.send'])
548
+ && resource.get('kind') === 'message'
549
+ && resource.get('id') === TOOL_WILDCARD
550
+ && sequenceEquals(context.get('initiators', true), ['background']);
551
+ }
552
+ function assertManagedFixedRuleIntegrity(rules, currentAccount) {
553
+ const seenIds = new Set();
554
+ for (const item of rules.items) {
555
+ if (!isMap(item))
556
+ continue;
557
+ const id = item.get('id');
558
+ const managedId = typeof id === 'string' ? parseManagedFixedRuleId(id) : undefined;
559
+ if (typeof id !== 'string' || !isReservedManagedFixedRuleId(id))
560
+ continue;
561
+ if (seenIds.has(id)) {
562
+ throw new Error(`lark-channel setup: duplicate managed fixed rule ${id}; refusing to change it`);
563
+ }
564
+ seenIds.add(id);
565
+ if (managedId !== undefined && managedFixedRuleMatches(item, managedId))
566
+ continue;
567
+ const scope = managedId === undefined
568
+ ? 'reserved fixed'
569
+ : managedId.account === currentAccount ? 'fixed' : 'retired fixed';
570
+ throw new Error(`lark-channel setup: ${scope} rule ${id} is malformed/ambiguous; refusing to change it`);
571
+ }
572
+ }
573
+ function sweepRetiredManagedFixedRules(rules, currentAccount) {
574
+ assertManagedFixedRuleIntegrity(rules, currentAccount);
575
+ for (let index = rules.items.length - 1; index >= 0; index -= 1) {
576
+ const item = rules.items[index];
577
+ if (!isMap(item))
578
+ continue;
579
+ const id = item.get('id');
580
+ const managedId = typeof id === 'string' ? parseManagedFixedRuleId(id) : undefined;
581
+ if (managedId !== undefined
582
+ && managedId.account !== currentAccount
583
+ && managedFixedRuleMatches(item, managedId))
584
+ rules.items.splice(index, 1);
585
+ }
586
+ }
587
+ function parseManagedCredentialHandleId(id) {
588
+ const prefix = 'lark-app-secret-';
589
+ if (!id.startsWith(prefix))
590
+ return undefined;
591
+ const account = id.slice(prefix.length);
592
+ return setupKeyPattern.test(account) && !account.includes('-legacy-') ? account : undefined;
593
+ }
594
+ function managedCredentialHandleMatches(item) {
595
+ const provider = item.get('provider');
596
+ const commonShape = sequenceEquals(item.get('consumers', true), ['dsh-enhanced-lark-channel'])
597
+ && sequenceEquals(item.get('purposes', true), ['connect'])
598
+ && item.get('maxLeaseMs') === 86_400_000;
599
+ if (!commonShape)
600
+ return false;
601
+ if (provider === 'windows-dpapi')
602
+ return isLiteralAbsolutePath(item.get('path'));
603
+ return (provider === 'linux-secret-service' || provider === 'macos-keychain')
604
+ && typeof item.get('service') === 'string'
605
+ && typeof item.get('account') === 'string';
606
+ }
607
+ function sweepRetiredManagedCredentialHandles(rows, currentAccount) {
608
+ const credentialsRow = findRow(rows, 'dsh-enhanced-credentials-keychain');
609
+ if (credentialsRow === undefined)
610
+ return;
611
+ const credentials = asMap(credentialsRow.get('config', true), 'credentials-keychain config');
612
+ const handles = credentials.get('handles', true);
613
+ if (handles === undefined)
614
+ return;
615
+ const handleList = asSequence(handles, 'credentials-keychain handles');
616
+ let foundCurrent = false;
617
+ for (let index = handleList.items.length - 1; index >= 0; index -= 1) {
618
+ const item = handleList.items[index];
619
+ if (!isMap(item))
620
+ continue;
621
+ const id = item.get('id');
622
+ const managedAccount = typeof id === 'string' ? parseManagedCredentialHandleId(id) : undefined;
623
+ if (managedAccount === undefined)
624
+ continue;
625
+ if (managedAccount === currentAccount) {
626
+ if (foundCurrent) {
627
+ throw new Error(`lark-channel setup: current credential handle ${id} is duplicate; refusing to overwrite it`);
628
+ }
629
+ foundCurrent = true;
630
+ if (!managedCredentialHandleMatches(item)) {
631
+ throw new Error(`lark-channel setup: current credential handle ${id} is ambiguous; refusing to overwrite it`);
632
+ }
633
+ continue;
634
+ }
635
+ if (managedCredentialHandleMatches(item)) {
636
+ handleList.items.splice(index, 1);
637
+ continue;
638
+ }
639
+ if (sequenceIncludes(item.get('consumers', true), 'dsh-enhanced-lark-channel')) {
640
+ throw new Error(`lark-channel setup: retired credential handle ${id} is ambiguous; refusing to remove it`);
641
+ }
642
+ }
643
+ }
644
+ function hasManagedExternalWildcardAllow(rules, account, ownerPrincipal) {
645
+ if (ownerPrincipal === undefined)
646
+ return false;
647
+ const wildcardKinds = new Map();
648
+ let foundCandidate = false;
649
+ for (const item of rules.items) {
650
+ if (!isMap(item))
651
+ continue;
652
+ const id = item.get('id');
653
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
654
+ if (managedId === undefined
655
+ || managedId.account !== account
656
+ || managedId.kind === 'reply')
657
+ continue;
658
+ foundCandidate = true;
659
+ const identity = managedExternalRuleIdentity(item, managedId);
660
+ const subject = item.get('subject', true);
661
+ if (identity === undefined
662
+ || item.get('effect') !== 'allow'
663
+ || !isMap(subject)
664
+ || (subject.get('principal') !== undefined && subject.get('principal') !== ownerPrincipal)) {
665
+ return false;
666
+ }
667
+ const isWildcard = managedId.kind === 'capability'
668
+ || (managedId.kind === 'tool' && managedId.tool === TOOL_WILDCARD);
669
+ if (!isWildcard)
670
+ continue;
671
+ const identityKey = `${identity.preset}\0${identity.workspace}`;
672
+ const kinds = wildcardKinds.get(identityKey) ?? new Set();
673
+ if (kinds.has(managedId.kind))
674
+ return false;
675
+ kinds.add(managedId.kind);
676
+ wildcardKinds.set(identityKey, kinds);
677
+ }
678
+ if (!foundCandidate || wildcardKinds.size === 0)
679
+ return false;
680
+ // Earlier setup versions paired the wildcard allow with managed per-tool
681
+ // denies. Rebuilding only the allow for a new account would silently widen
682
+ // that historical policy, so preserve mode fails closed whenever any
683
+ // setup-managed rule for the source account is restrictive or malformed.
684
+ return [...wildcardKinds.values()].every(kinds => kinds.has('capability') && kinds.has('tool'));
685
+ }
686
+ function scopeManagedExternalAllowsToOwner(rules, account, ownerPrincipal) {
687
+ for (const item of rules.items) {
688
+ if (!isMap(item) || item.get('effect') !== 'allow')
689
+ continue;
690
+ const id = item.get('id');
691
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
692
+ if (managedId === undefined || managedId.account !== account)
693
+ continue;
694
+ const subject = item.get('subject', true);
695
+ // Integrity validation runs first, so a setup-reserved rule reaching this
696
+ // branch always has the strict Agent subject shape.
697
+ if (!isMap(subject)) {
698
+ throw new Error(`lark-channel setup: managed Agent rule ${id} is malformed`);
699
+ }
700
+ const rulePrincipal = subject.get('principal');
701
+ if (rulePrincipal === undefined) {
702
+ // Earlier releases emitted external allows without a principal. Narrow
703
+ // them in place before preserve can retain the rule.
704
+ subject.set('principal', ownerPrincipal);
705
+ continue;
706
+ }
707
+ if (rulePrincipal !== ownerPrincipal) {
708
+ throw new Error(`lark-channel setup: managed Agent rule ${id} targets a different owner principal; `
709
+ + 'refusing to preserve it');
710
+ }
711
+ }
712
+ }
713
+ function hasManagedExternalRuleCandidate(rules, account) {
714
+ return rules.items.some(item => {
715
+ if (!isMap(item))
716
+ return false;
717
+ const id = item.get('id');
718
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
719
+ return managedId !== undefined
720
+ && managedId.kind !== 'reply'
721
+ && managedId.account === account;
722
+ });
723
+ }
724
+ function hasManagedExternalAllowCandidate(rules, account) {
725
+ return rules.items.some(item => {
726
+ if (!isMap(item) || item.get('effect') !== 'allow')
727
+ return false;
728
+ const id = item.get('id');
729
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
730
+ return managedId !== undefined
731
+ && managedId.kind !== 'reply'
732
+ && managedId.account === account;
733
+ });
734
+ }
735
+ function removeAllManagedExternalToolRules(rules, includeDenies = false) {
736
+ for (let index = rules.items.length - 1; index >= 0; index -= 1) {
737
+ const item = rules.items[index];
738
+ if (!isMap(item))
739
+ continue;
740
+ const id = item.get('id');
741
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
742
+ if (managedId !== undefined
743
+ && managedId.kind !== 'reply'
744
+ && (includeDenies || item.get('effect') === 'allow')
745
+ && managedExternalRuleIdentity(item, managedId) !== undefined)
746
+ rules.items.splice(index, 1);
747
+ }
748
+ }
749
+ function removeManagedExternalToolRulesOutsideAccount(rules, account) {
750
+ for (let index = rules.items.length - 1; index >= 0; index -= 1) {
751
+ const item = rules.items[index];
752
+ if (!isMap(item))
753
+ continue;
754
+ const id = item.get('id');
755
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
756
+ if (managedId !== undefined
757
+ && managedId.kind !== 'reply'
758
+ && managedId.account !== account
759
+ && item.get('effect') === 'allow'
760
+ && managedExternalRuleIdentity(item, managedId) !== undefined)
761
+ rules.items.splice(index, 1);
762
+ }
763
+ }
764
+ function removeAllManagedReplyRules(rules) {
765
+ for (let index = rules.items.length - 1; index >= 0; index -= 1) {
766
+ const item = rules.items[index];
767
+ if (!isMap(item))
768
+ continue;
769
+ const id = item.get('id');
770
+ const managedId = typeof id === 'string' ? parseManagedRuleId(id) : undefined;
771
+ if (managedId?.kind === 'reply'
772
+ && managedExternalRuleIdentity(item, managedId) !== undefined)
773
+ rules.items.splice(index, 1);
774
+ }
775
+ }
776
+ function removeAllManagedExternalAgentRules(rules, includeDenies = false) {
777
+ removeAllManagedExternalToolRules(rules, includeDenies);
778
+ removeAllManagedReplyRules(rules);
779
+ }
780
+ function upsertApprovalRules(document, rules, input) {
781
+ for (const sourceId of managedApprovalSources) {
782
+ upsertById(document, rules, {
783
+ id: `lark-owner-approval-${sourceId}-${input.account}`,
784
+ effect: 'allow',
785
+ subject: {
786
+ kind: 'background',
787
+ id: sourceId,
788
+ workspace: input.workspace,
789
+ principal: input.principal,
790
+ },
791
+ actions: ['approval.send'],
792
+ resource: { kind: 'message', id: '*' },
793
+ context: { initiators: ['background'] },
794
+ });
795
+ }
796
+ }
797
+ function configuredOwnerPrincipal(rules, account) {
798
+ const rule = rules.items.find(item => isMap(item) && item.get('id') === `lark-owner-ingress-${account}`);
799
+ if (!isMap(rule) || rule.get('effect') !== 'allow')
800
+ return undefined;
801
+ const subject = rule.get('subject', true);
802
+ const resource = rule.get('resource', true);
803
+ const context = rule.get('context', true);
804
+ if (!isMap(subject) || !isMap(resource) || !isMap(context)
805
+ || subject.get('kind') !== 'external'
806
+ || !sequenceEquals(rule.get('actions', true), ['approval.decide', 'ingest'])
807
+ || resource.get('kind') !== 'message'
808
+ || resource.get('id') !== TOOL_WILDCARD
809
+ || !sequenceEquals(context.get('initiators', true), ['external']))
810
+ return undefined;
811
+ const principal = subject.get('id');
812
+ return isCanonicalLarkPrincipalForAccount(principal, account) ? principal : undefined;
813
+ }
814
+ function setDefaults(map, defaults) {
815
+ for (const [key, value] of Object.entries(defaults)) {
816
+ if (!map.has(key))
817
+ map.set(key, value);
818
+ }
819
+ }
820
+ export function refreshLarkAgentPolicyPatch(input) {
821
+ if (!isLiteralAbsolutePath(input.dshHome)) {
822
+ throw new Error('lark-channel setup: DSH home must be an absolute path');
823
+ }
824
+ if (input.agentTools !== 'disable' && input.agentTools !== 'enable') {
825
+ throw new Error('lark-channel setup: refresh agentTools mode must be enable or disable');
826
+ }
827
+ const document = parseDocument(input.profilePatch);
828
+ if (document.errors.length > 0) {
829
+ throw new Error(`lark-channel setup: profile patch is invalid YAML: ${document.errors[0]?.message}`);
830
+ }
831
+ const rows = asSequence(document.contents, 'profile patch');
832
+ assertManagedProfileRowIntegrity(rows);
833
+ const personalAssistant = findRow(rows, 'dsh-enhanced-personal-assistant');
834
+ if (personalAssistant === undefined) {
835
+ throw new Error('lark-channel setup: dsh-enhanced-personal-assistant profile override is required');
836
+ }
837
+ const personalConfig = asMap(personalAssistant.get('config', true), 'personal-assistant config');
838
+ const policy = asMap(personalConfig.get('assistantPolicy', true), 'assistantPolicy config');
839
+ const rules = asSequence(policy.get('rules', true), 'assistantPolicy rules');
840
+ // Exact-principal rules remain attributable even after their account is
841
+ // retired. Do not let explicit enable/disable report success while one of
842
+ // those executable rules survives because its context was hand-edited.
843
+ assertManagedExternalRuleIntegrity(rules);
844
+ assertManagedForegroundRuleIntegrity(rules);
845
+ // Foreground Web/direct reachability is a profile concern, not a channel
846
+ // binding concern. Apply it even when Lark is absent or deliberately skipped.
847
+ removeManagedForegroundRules(rules, input.agentTools === 'enable');
848
+ if (input.agentTools === 'enable')
849
+ upsertForegroundCapabilityRule(document, rules);
850
+ const lark = findRow(rows, 'dsh-enhanced-lark-channel');
851
+ if (lark === undefined) {
852
+ removeAllManagedExternalAgentRules(rules);
853
+ sweepRetiredManagedFixedRules(rules, undefined);
854
+ sweepRetiredManagedCredentialHandles(rows, undefined);
855
+ if (input.account !== undefined) {
856
+ throw new Error('lark-channel setup: no enabled Lark account exists to match the requested account');
857
+ }
858
+ return document.toString({ lineWidth: 0 });
859
+ }
860
+ const channel = asMap(lark.get('config', true), 'lark-channel config');
861
+ const configuredAccountValue = channel.get('account');
862
+ if (typeof configuredAccountValue !== 'string') {
863
+ throw new Error('lark-channel setup: existing Lark channel account is invalid');
864
+ }
865
+ const configuredAccount = requireAccount(configuredAccountValue);
866
+ assertManagedExternalRuleIntegrity(rules);
867
+ sweepRetiredManagedFixedRules(rules, configuredAccount);
868
+ sweepRetiredManagedCredentialHandles(rows, configuredAccount);
869
+ if (channel.get('enabled') !== true) {
870
+ // No disabled channel may leave an executable Agent rule behind, including
871
+ // reply grants and rules for retired account ids.
872
+ removeAllManagedExternalAgentRules(rules);
873
+ if (input.account !== undefined) {
874
+ throw new Error('lark-channel setup: no enabled Lark account exists to match the requested account');
875
+ }
876
+ return document.toString({ lineWidth: 0 });
877
+ }
878
+ if (input.account !== undefined && requireAccount(input.account) !== configuredAccount) {
879
+ throw new Error(`lark-channel setup: configured account is ${configuredAccount}; refusing a different account`);
880
+ }
881
+ const agent = configuredAgentIdentity(rows, input.dshHome);
882
+ const principal = configuredOwnerPrincipal(rules, configuredAccount);
883
+ if (input.agentTools === 'enable' && principal === undefined) {
884
+ throw new Error(`lark-channel setup: enabled account ${configuredAccount} has no canonical owner ingress; `
885
+ + 'refusing to enable Agent policy');
886
+ }
887
+ const legacyAgents = managedReplyIdentities(rules, configuredAccount)
888
+ .filter(identity => identity.preset !== agent.preset || identity.workspace !== agent.workspace);
889
+ removeAllManagedExternalAgentRules(rules, input.agentTools === 'enable');
890
+ if (principal !== undefined) {
891
+ upsertExternalReplyRule(document, rules, { account: configuredAccount, identity: agent, principal });
892
+ for (const legacyAgent of legacyAgents) {
893
+ upsertExternalReplyRule(document, rules, {
894
+ account: configuredAccount,
895
+ identity: legacyAgent,
896
+ principal,
897
+ legacy: true,
898
+ });
899
+ }
900
+ if (input.agentTools === 'enable') {
901
+ upsertExternalToolRules(document, rules, {
902
+ account: configuredAccount,
903
+ identity: agent,
904
+ principal,
905
+ });
906
+ for (const legacyAgent of legacyAgents) {
907
+ upsertExternalToolRules(document, rules, {
908
+ account: configuredAccount,
909
+ identity: legacyAgent,
910
+ principal,
911
+ legacy: true,
912
+ });
913
+ }
914
+ }
915
+ }
916
+ return document.toString({ lineWidth: 0 });
917
+ }
918
+ export function configureLarkProfilePatch(input) {
919
+ const appId = input.appId.trim();
920
+ if (!/^cli_[0-9a-fA-F]{16}$/u.test(appId))
921
+ throw new Error('lark-channel setup: invalid appId');
922
+ const account = requireAccount(input.account);
923
+ const tenant = requireSetupKey(input.tenant, 'tenant');
924
+ const ownerUserId = requireProviderKey(input.ownerUserId, 'ownerUserId');
925
+ const keychainService = requireProviderKey(input.keychainService, 'keychainService');
926
+ const keychainAccount = requireProviderKey(input.keychainAccount, 'keychainAccount');
927
+ const credentialProvider = input.credentialProvider ?? 'macos-keychain';
928
+ if (!['linux-secret-service', 'macos-keychain', 'windows-dpapi'].includes(credentialProvider)) {
929
+ throw new Error('lark-channel setup: invalid credentialProvider');
930
+ }
931
+ const agentToolsMode = input.agentTools ?? 'preserve';
932
+ if (!['disable', 'enable', 'preserve'].includes(agentToolsMode)) {
933
+ throw new Error('lark-channel setup: invalid agentTools mode');
934
+ }
935
+ const credentialHandle = `lark-app-secret-${account}`;
936
+ const document = parseDocument(input.profilePatch);
937
+ if (document.errors.length > 0) {
938
+ throw new Error(`lark-channel setup: profile patch is invalid YAML: ${document.errors[0]?.message}`);
939
+ }
940
+ const rows = asSequence(document.contents, 'profile patch');
941
+ assertManagedProfileRowIntegrity(rows);
942
+ const existingLarkRow = findRow(rows, 'dsh-enhanced-lark-channel');
943
+ let previousAccount;
944
+ if (existingLarkRow !== undefined) {
945
+ const existingChannel = asMap(existingLarkRow.get('config', true), 'lark-channel config');
946
+ const existingAccount = existingChannel.get('account');
947
+ if (existingAccount !== undefined) {
948
+ if (typeof existingAccount !== 'string') {
949
+ throw new Error('lark-channel setup: existing Lark channel account is invalid');
950
+ }
951
+ previousAccount = requireAccount(existingAccount);
952
+ }
953
+ }
954
+ const personalAssistant = findRow(rows, 'dsh-enhanced-personal-assistant');
955
+ if (personalAssistant === undefined) {
956
+ throw new Error('lark-channel setup: dsh-enhanced-personal-assistant profile override is required');
957
+ }
958
+ const personalConfig = asMap(personalAssistant.get('config', true), 'personal-assistant config');
959
+ const policy = asMap(personalConfig.get('assistantPolicy', true), 'assistantPolicy config');
960
+ const rules = asSequence(policy.get('rules', true), 'assistantPolicy rules');
961
+ const grantSourceAccount = previousAccount ?? account;
962
+ const principalId = externalPrincipalId({ channel: 'lark', account, tenant, user: ownerUserId });
963
+ assertManagedExternalRuleIntegrity(rules);
964
+ assertManagedForegroundRuleIntegrity(rules);
965
+ assertManagedFixedRuleIntegrity(rules, account);
966
+ const sourceOwnerPrincipal = configuredOwnerPrincipal(rules, grantSourceAccount);
967
+ const preserveOwnerPrincipal = sourceOwnerPrincipal
968
+ ?? (previousAccount === undefined ? principalId : undefined);
969
+ if (agentToolsMode === 'preserve'
970
+ && previousAccount !== undefined
971
+ && sourceOwnerPrincipal === undefined
972
+ && hasManagedExternalAllowCandidate(rules, grantSourceAccount)) {
973
+ throw new Error(`lark-channel setup: enabled account ${grantSourceAccount} has no canonical owner ingress; `
974
+ + 'refusing to preserve ambiguous Agent grants');
975
+ }
976
+ if (agentToolsMode === 'preserve' && preserveOwnerPrincipal !== undefined) {
977
+ scopeManagedExternalAllowsToOwner(rules, grantSourceAccount, preserveOwnerPrincipal);
978
+ }
979
+ const preservedExternalGrant = hasManagedExternalWildcardAllow(rules, grantSourceAccount, preserveOwnerPrincipal);
980
+ const preservedForegroundGrant = hasManagedForegroundRules(rules);
981
+ const ownerBindingChanged = (previousAccount !== undefined && previousAccount !== account)
982
+ || (sourceOwnerPrincipal !== undefined && sourceOwnerPrincipal !== principalId);
983
+ if (agentToolsMode === 'preserve'
984
+ && ownerBindingChanged
985
+ && hasManagedExternalRuleCandidate(rules, grantSourceAccount)
986
+ && !preservedExternalGrant) {
987
+ throw new Error('lark-channel setup: cannot preserve restrictive or ambiguous external Agent rules '
988
+ + 'while changing the Lark owner binding; rerun with agentTools enable or disable');
989
+ }
990
+ const agent = configuredAgentIdentity(rows, input.dshHome);
991
+ const legacyAgents = previousAccount === undefined || previousAccount === account
992
+ ? managedReplyIdentities(rules, account)
993
+ .filter(identity => identity.preset !== agent.preset || identity.workspace !== agent.workspace)
994
+ : [];
995
+ sweepRetiredManagedFixedRules(rules, account);
996
+ upsertById(document, rules, {
997
+ id: `lark-channel-credential-${account}`,
998
+ effect: 'allow',
999
+ subject: { kind: 'background', id: 'dsh-enhanced-lark-channel' },
1000
+ actions: ['credential.use'],
1001
+ resource: { kind: 'credential', id: credentialHandle },
1002
+ context: { initiators: ['background'] },
1003
+ });
1004
+ upsertApprovalRules(document, rules, { account, workspace: agent.workspace, principal: principalId });
1005
+ upsertById(document, rules, {
1006
+ id: `lark-owner-ingress-${account}`,
1007
+ effect: 'allow',
1008
+ subject: { kind: 'external', id: principalId },
1009
+ actions: ['approval.decide', 'ingest'],
1010
+ resource: { kind: 'message', id: '*' },
1011
+ context: { initiators: ['external'] },
1012
+ });
1013
+ // Rebuild setup-managed external rules from a single exact account owner.
1014
+ // This also sweeps rules left by retired accounts and migrates legacy rules
1015
+ // which predate principal-scoped Agent subjects.
1016
+ const retainCurrentExternalRules = agentToolsMode === 'preserve'
1017
+ && !ownerBindingChanged
1018
+ && !preservedExternalGrant;
1019
+ if (retainCurrentExternalRules)
1020
+ removeManagedExternalToolRulesOutsideAccount(rules, account);
1021
+ else
1022
+ removeAllManagedExternalToolRules(rules, agentToolsMode === 'enable');
1023
+ if (agentToolsMode === 'disable')
1024
+ removeManagedForegroundRules(rules);
1025
+ if (agentToolsMode === 'enable') {
1026
+ removeManagedForegroundRules(rules, true);
1027
+ upsertForegroundCapabilityRule(document, rules);
1028
+ upsertExternalToolRules(document, rules, { account, identity: agent, principal: principalId });
1029
+ for (const legacyAgent of legacyAgents) {
1030
+ upsertExternalToolRules(document, rules, {
1031
+ account,
1032
+ identity: legacyAgent,
1033
+ principal: principalId,
1034
+ legacy: true,
1035
+ });
1036
+ }
1037
+ }
1038
+ if (agentToolsMode === 'preserve') {
1039
+ if (preservedForegroundGrant) {
1040
+ removeManagedForegroundRules(rules);
1041
+ upsertForegroundCapabilityRule(document, rules);
1042
+ }
1043
+ if (preservedExternalGrant) {
1044
+ upsertExternalToolRules(document, rules, { account, identity: agent, principal: principalId });
1045
+ for (const legacyAgent of legacyAgents) {
1046
+ upsertExternalToolRules(document, rules, {
1047
+ account,
1048
+ identity: legacyAgent,
1049
+ principal: principalId,
1050
+ legacy: true,
1051
+ });
1052
+ }
1053
+ }
1054
+ }
1055
+ removeAllManagedReplyRules(rules);
1056
+ upsertExternalReplyRule(document, rules, { account, identity: agent, principal: principalId });
1057
+ for (const legacyAgent of legacyAgents) {
1058
+ upsertExternalReplyRule(document, rules, {
1059
+ account,
1060
+ identity: legacyAgent,
1061
+ principal: principalId,
1062
+ legacy: true,
1063
+ });
1064
+ }
1065
+ const credentialsRow = ensureRow(document, rows, 'dsh-enhanced-credentials-keychain');
1066
+ const credentials = asMap(credentialsRow.get('config', true), 'credentials-keychain config');
1067
+ setDefaults(credentials, {
1068
+ databasePath: join(input.dshHome, 'credentials-keychain/ledger.sqlite'),
1069
+ defaultLeaseMs: 300_000,
1070
+ maxSecretBytes: 65_536,
1071
+ providerTimeoutMs: 5_000,
1072
+ });
1073
+ let handles = credentials.get('handles', true);
1074
+ if (handles === undefined) {
1075
+ credentials.set('handles', document.createNode([]));
1076
+ handles = credentials.get('handles', true);
1077
+ }
1078
+ const handleList = asSequence(handles, 'credentials-keychain handles');
1079
+ sweepRetiredManagedCredentialHandles(rows, account);
1080
+ const commonHandle = {
1081
+ id: credentialHandle,
1082
+ consumers: ['dsh-enhanced-lark-channel'],
1083
+ purposes: ['connect'],
1084
+ maxLeaseMs: 86_400_000,
1085
+ };
1086
+ if (credentialProvider === 'windows-dpapi') {
1087
+ const path = input.credentialPath;
1088
+ if (path === undefined || path.length > 1_024 || path.includes('\0')
1089
+ || (!isAbsolute(path) && !win32.isAbsolute(path))) {
1090
+ throw new Error('lark-channel setup: invalid credentialPath');
1091
+ }
1092
+ upsertById(document, handleList, { ...commonHandle, provider: credentialProvider, path });
1093
+ }
1094
+ else {
1095
+ upsertById(document, handleList, {
1096
+ ...commonHandle,
1097
+ provider: credentialProvider,
1098
+ service: keychainService,
1099
+ account: keychainAccount,
1100
+ });
1101
+ }
1102
+ const larkRow = ensureRow(document, rows, 'dsh-enhanced-lark-channel');
1103
+ const channel = asMap(larkRow.get('config', true), 'lark-channel config');
1104
+ channel.delete('appSecretEnv');
1105
+ const channelConfig = {
1106
+ enabled: true,
1107
+ account,
1108
+ tenant,
1109
+ appId,
1110
+ credentialHandle,
1111
+ credentialPurpose: 'connect',
1112
+ credentialLeaseMs: 86_400_000,
1113
+ domain: input.domain,
1114
+ requireMentionInGroups: true,
1115
+ showProgress: true,
1116
+ statusReactions: true,
1117
+ maxTextBytes: 65_536,
1118
+ staleAfterMs: 300_000,
1119
+ handshakeTimeoutMs: 15_000,
1120
+ imageDownloadTimeoutMs: 30_000,
1121
+ };
1122
+ for (const [key, value] of Object.entries(channelConfig))
1123
+ channel.set(key, value);
1124
+ return document.toString({ lineWidth: 0 });
1125
+ }
1126
+ //# sourceMappingURL=setup-profile.js.map