@chrysb/alphaclaw 0.8.3 → 0.8.4

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.
@@ -2,12 +2,15 @@ import { h } from "preact";
2
2
  import { useEffect, useMemo, useState } from "preact/hooks";
3
3
  import htm from "htm";
4
4
  import { ActionButton } from "../action-button.js";
5
- import { CloseIcon } from "../icons.js";
5
+ import { CloseIcon, FileCopyLineIcon } from "../icons.js";
6
6
  import { ModalShell } from "../modal-shell.js";
7
7
  import { PageHeader } from "../page-header.js";
8
8
  import { SecretInput } from "../secret-input.js";
9
9
  import { fetchChannelAccountToken } from "../../lib/api.js";
10
+ import { copyTextToClipboard } from "../../lib/clipboard.js";
11
+ import { isSingleAccountChannelProvider } from "../../lib/channel-provider-availability.js";
10
12
  import { ALL_CHANNELS, getChannelMeta } from "../channels.js";
13
+ import { showToast } from "../toast.js";
11
14
 
12
15
  const html = htm.bind(h);
13
16
 
@@ -25,15 +28,33 @@ const kSlackBotScopes = [
25
28
  "channels:history",
26
29
  "channels:read",
27
30
  "chat:write",
31
+ "commands",
32
+ "emoji:read",
33
+ "files:read",
34
+ "files:write",
35
+ "groups:read",
28
36
  "groups:history",
29
37
  "im:history",
30
38
  "im:read",
31
39
  "im:write",
32
40
  "mpim:history",
41
+ "mpim:read",
42
+ "mpim:write",
43
+ "pins:read",
44
+ "pins:write",
33
45
  "reactions:read",
34
46
  "reactions:write",
35
47
  "users:read",
36
48
  ];
49
+ const kSlackBotEvents = [
50
+ "app_mention",
51
+ "message.channels",
52
+ "message.groups",
53
+ "message.im",
54
+ "message.mpim",
55
+ "reaction_added",
56
+ "reaction_removed",
57
+ ];
37
58
  const kSlackInstructionsLink = "https://docs.openclaw.ai/channels/slack";
38
59
 
39
60
  const slugifyChannelAccountId = (value) =>
@@ -50,7 +71,63 @@ const deriveChannelEnvKey = ({ provider, accountId }) => {
50
71
  if (!normalizedAccountId || normalizedAccountId === "default") return baseKey;
51
72
  return `${baseKey}_${normalizedAccountId.replace(/-/g, "_").toUpperCase()}`;
52
73
  };
74
+ const deriveChannelExtraEnvKey = ({ provider, accountId, index = 0 }) => {
75
+ const baseKeys = [kChannelExtraEnvKeys[String(provider || "").trim()]].filter(
76
+ Boolean,
77
+ );
78
+ const baseKey = String(baseKeys[index] || "").trim();
79
+ const normalizedAccountId = String(accountId || "").trim();
80
+ if (!baseKey) return "";
81
+ if (!normalizedAccountId || normalizedAccountId === "default") return baseKey;
82
+ return `${baseKey}_${normalizedAccountId.replace(/-/g, "_").toUpperCase()}`;
83
+ };
53
84
  const isMaskedTokenValue = (value) => /^\*+$/.test(String(value || "").trim());
85
+ const buildSlackManifest = (appName = "AlphaClaw") =>
86
+ JSON.stringify(
87
+ {
88
+ _metadata: {
89
+ major_version: 1,
90
+ },
91
+ display_information: {
92
+ name: String(appName || "").trim() || "AlphaClaw",
93
+ description: "Slack connector for AlphaClaw",
94
+ },
95
+ features: {
96
+ bot_user: {
97
+ display_name: String(appName || "").trim() || "AlphaClaw",
98
+ always_online: false,
99
+ },
100
+ app_home: {
101
+ messages_tab_enabled: true,
102
+ messages_tab_read_only_enabled: false,
103
+ },
104
+ },
105
+ oauth_config: {
106
+ scopes: {
107
+ bot: kSlackBotScopes,
108
+ },
109
+ },
110
+ settings: {
111
+ event_subscriptions: {
112
+ bot_events: kSlackBotEvents,
113
+ },
114
+ org_deploy_enabled: false,
115
+ socket_mode_enabled: true,
116
+ is_hosted: false,
117
+ token_rotation_enabled: false,
118
+ },
119
+ },
120
+ null,
121
+ 2,
122
+ );
123
+ const copyAndToast = async (value, label = "text") => {
124
+ const copied = await copyTextToClipboard(value);
125
+ if (copied) {
126
+ showToast("Copied to clipboard", "success");
127
+ return;
128
+ }
129
+ showToast(`Could not copy ${label}`, "error");
130
+ };
54
131
 
55
132
  export const CreateChannelModal = ({
56
133
  visible = false,
@@ -152,9 +229,7 @@ export const CreateChannelModal = ({
152
229
  }
153
230
  setName(providerLabel);
154
231
  }, [provider, providerHasAccounts, nameEditedManually, isEditMode]);
155
- const isSingleAccountProvider =
156
- String(provider || "").trim() === "discord" ||
157
- String(provider || "").trim() === "slack";
232
+ const isSingleAccountProvider = isSingleAccountChannelProvider(provider);
158
233
  const needsAppToken = String(provider || "").trim() === "slack";
159
234
 
160
235
  const accountId = useMemo(() => {
@@ -170,6 +245,24 @@ export const CreateChannelModal = ({
170
245
  () => deriveChannelEnvKey({ provider, accountId }),
171
246
  [provider, accountId],
172
247
  );
248
+ const extraEnvKey = useMemo(
249
+ () =>
250
+ deriveChannelExtraEnvKey({
251
+ provider,
252
+ accountId,
253
+ }),
254
+ [provider, accountId],
255
+ );
256
+ const slackManifestName = useMemo(() => {
257
+ const normalizedName = String(name || "").trim();
258
+ if (!normalizedName) return "AlphaClaw";
259
+ if (normalizedName.toLowerCase() === "slack") return "AlphaClaw";
260
+ return normalizedName;
261
+ }, [name]);
262
+ const slackManifest = useMemo(
263
+ () => buildSlackManifest(slackManifestName),
264
+ [slackManifestName],
265
+ );
173
266
 
174
267
  const accountExists = useMemo(
175
268
  () =>
@@ -269,7 +362,7 @@ export const CreateChannelModal = ({
269
362
  <${ModalShell}
270
363
  visible=${visible}
271
364
  onClose=${onClose}
272
- panelClassName="bg-modal border border-border rounded-xl p-6 max-w-lg w-full space-y-4"
365
+ panelClassName="bg-modal border border-border rounded-xl p-6 max-w-lg w-full max-h-[calc(100vh-2rem)] overflow-y-auto space-y-4"
273
366
  >
274
367
  <${PageHeader}
275
368
  title=${
@@ -360,7 +453,7 @@ export const CreateChannelModal = ({
360
453
  <p class="text-xs text-fg-muted">
361
454
  Saved behind the scenes as
362
455
  <code class="font-mono text-fg-muted ml-1">
363
- ${kChannelExtraEnvKeys.slack}
456
+ ${extraEnvKey || kChannelExtraEnvKeys.slack}
364
457
  </code>
365
458
  .
366
459
  </p>
@@ -371,60 +464,167 @@ export const CreateChannelModal = ({
371
464
  ${
372
465
  needsAppToken
373
466
  ? html`
374
- <details class="rounded-lg border border-border bg-field px-3 py-2.5">
375
- <summary class="cursor-pointer text-xs text-body hover:text-body">
376
- Slack-specific instructions (step-by-step)
377
- </summary>
378
- <div class="mt-2 space-y-2 text-xs text-fg-muted">
379
- <ol class="list-decimal list-inside space-y-1.5">
380
- <li>
381
- In Slack app settings, turn on
382
- ${" "}
383
- <span class="text-body">Socket Mode</span>.
384
- </li>
385
- <li>
386
- In
387
- ${" "}
388
- <span class="text-body">App Home</span>, enable
389
- <code class="font-mono text-fg-muted ml-1">
390
- Allow users to send Slash commands and messages from the messages tab
391
- </code>.
392
- </li>
393
- <li>
394
- In
395
- ${" "}
396
- <span class="text-body">Event Subscriptions</span>, toggle on
397
- <code class="font-mono text-fg-muted ml-1">Subscribe to bot events</code>
398
- ${" "}
399
- and add
400
- <code class="font-mono text-fg-muted ml-1">message.im</code>.
401
- </li>
402
- <li>
403
- Create a Bot Token (<code class="font-mono text-fg-muted">xoxb-...</code>)
404
- with scopes:
405
- <code class="font-mono text-fg-muted ml-1">
406
- ${kSlackBotScopes.join(", ")}
407
- </code>
408
- </li>
409
- <li>
410
- Create an App Token (<code class="font-mono text-fg-muted">xapp-...</code>)
411
- with
412
- <code class="font-mono text-fg-muted ml-1">connections:write</code>.
413
- </li>
414
- <li>
415
- Reinstall the app after changing scopes.
416
- </li>
417
- </ol>
418
- <a
419
- href=${kSlackInstructionsLink}
420
- target="_blank"
421
- class="hover:underline"
422
- style="color: var(--accent-link)"
467
+ <div class="space-y-2">
468
+ <details
469
+ class="rounded-lg border border-border bg-field px-3 py-2.5"
470
+ >
471
+ <summary
472
+ class="cursor-pointer text-xs text-body hover:text-body"
473
+ >
474
+ <span class="inline-block ml-1">
475
+ Create app from manifest (recommended)
476
+ </span>
477
+ </summary>
478
+ <div class="mt-2 space-y-2 text-xs text-fg-muted">
479
+ <div class="flex items-center justify-between gap-3 pt-1">
480
+ <div class="space-y-0.5">
481
+ <p class="text-[12px] text-fg-muted">
482
+ ${slackManifestName} App Manifest
483
+ </p>
484
+ </div>
485
+ <button
486
+ type="button"
487
+ onclick=${() =>
488
+ copyAndToast(slackManifest, "Slack manifest")}
489
+ class="text-xs px-2 py-1 rounded-lg ac-btn-cyan inline-flex items-center gap-1.5 shrink-0"
490
+ >
491
+ <${FileCopyLineIcon} className="w-3.5 h-3.5" />
492
+ Copy
493
+ </button>
494
+ </div>
495
+ <pre
496
+ class="max-h-72 overflow-auto rounded-lg border border-border bg-field p-3 text-[11px] leading-5 whitespace-pre-wrap break-all font-mono text-body"
497
+ >
498
+ ${slackManifest}</pre
499
+ >
500
+ <ol
501
+ class="list-decimal list-inside space-y-1.5 text-[11px] text-fg-muted"
502
+ >
503
+ <li>
504
+ In Slack, click ${" "}
505
+ <span class="text-body"
506
+ >Create app from manifest</span
507
+ >
508
+ ${" "} and paste this manifest.
509
+ </li>
510
+ <li>
511
+ Open ${" "}
512
+ <span class="text-body">Basic Information</span>
513
+ ${" "} and create an ${" "}
514
+ <span class="text-body">App-Level Token</span>
515
+ ${" "} with
516
+ <code class="font-mono text-fg-muted ml-1"
517
+ >connections:write</code
518
+ >.
519
+ </li>
520
+ <li>
521
+ Open ${" "}
522
+ <span class="text-body">OAuth & Permissions</span>
523
+ ${" "} and use ${" "}
524
+ <span class="text-body">Install to Workspace</span>
525
+ ${" "} or ${" "}
526
+ <span class="text-body">Reinstall to Workspace</span>
527
+ ${" "} so Slack issues a bot token.
528
+ </li>
529
+ <li>
530
+ In ${" "}
531
+ <span class="text-body">OAuth & Permissions</span>
532
+ ${" "} copy the ${" "}
533
+ <span class="text-body">Bot User OAuth Token</span>
534
+ ${" "} (
535
+ <code class="font-mono text-fg-muted">xoxb-...</code>
536
+ ).
537
+ </li>
538
+ <li>
539
+ Paste the generated ${" "}
540
+ <code class="font-mono text-fg-muted">xoxb-...</code>
541
+ ${" "} and ${" "}
542
+ <code class="font-mono text-fg-muted">xapp-...</code>
543
+ ${" "} tokens here.
544
+ </li>
545
+ </ol>
546
+ </div>
547
+ </details>
548
+ <details
549
+ class="rounded-lg border border-border bg-field px-3 py-2.5"
550
+ >
551
+ <summary
552
+ class="cursor-pointer text-xs text-body hover:text-body"
423
553
  >
424
- Open full Slack setup guide
425
- </a>
426
- </div>
427
- </details>
554
+ <span class="inline-block ml-1">
555
+ Manual setup instructions
556
+ </span>
557
+ </summary>
558
+ <div class="mt-2 space-y-2 text-xs text-fg-muted">
559
+ <p>
560
+ Use this if you want to configure the Slack app by hand
561
+ instead of importing a manifest.
562
+ </p>
563
+ <ol class="list-decimal list-inside space-y-1.5">
564
+ <li>
565
+ In Slack app settings, turn on ${" "}
566
+ <span class="text-body">Socket Mode</span>.
567
+ </li>
568
+ <li>
569
+ In ${" "}
570
+ <span class="text-body">App Home</span>, enable
571
+ <code class="font-mono text-fg-muted ml-1">
572
+ Allow users to send Slash commands and messages from
573
+ the messages tab </code
574
+ >.
575
+ </li>
576
+ <li>
577
+ In ${" "}
578
+ <span class="text-body">Event Subscriptions</span>,
579
+ toggle on
580
+ <code class="font-mono text-fg-muted ml-1"
581
+ >Subscribe to bot events</code
582
+ >
583
+ ${" "} and add
584
+ <code class="font-mono text-fg-muted ml-1"
585
+ >message.im</code
586
+ >.
587
+ </li>
588
+ <li>
589
+ In ${" "}
590
+ <span class="text-body">OAuth & Permissions</span>,
591
+ add the bot scopes:
592
+ <code class="font-mono text-fg-muted ml-1">
593
+ ${kSlackBotScopes.join(", ")}
594
+ </code>
595
+ </li>
596
+ <li>
597
+ In ${" "}
598
+ <span class="text-body">Basic Information</span>,
599
+ create an App Token (<code
600
+ class="font-mono text-fg-muted"
601
+ >xapp-...</code
602
+ >) with
603
+ <code class="font-mono text-fg-muted ml-1"
604
+ >connections:write</code
605
+ >.
606
+ </li>
607
+ <li>
608
+ Back in ${" "}
609
+ <span class="text-body">OAuth & Permissions</span>,
610
+ install or reinstall the app, then copy the ${" "}
611
+ <span class="text-body">Bot User OAuth Token</span>
612
+ ${" "} (
613
+ <code class="font-mono text-fg-muted">xoxb-...</code>
614
+ ).
615
+ </li>
616
+ </ol>
617
+ <a
618
+ href=${kSlackInstructionsLink}
619
+ target="_blank"
620
+ class="hover:underline"
621
+ style="color: var(--accent-link)"
622
+ >
623
+ Open full Slack setup guide
624
+ </a>
625
+ </div>
626
+ </details>
627
+ </div>
428
628
  `
429
629
  : null
430
630
  }
@@ -29,7 +29,7 @@ export const ModalShell = ({
29
29
  return createPortal(
30
30
  html`
31
31
  <div
32
- class="fixed inset-0 bg-overlay flex items-center justify-center p-4 z-50"
32
+ class="fixed inset-0 bg-overlay flex items-start justify-center overflow-y-auto p-4 sm:items-center z-50"
33
33
  onclick=${(event) => {
34
34
  if (closeOnOverlayClick && event.target === event.currentTarget) onClose?.();
35
35
  }}
@@ -1,4 +1,4 @@
1
- const kSingleAccountChannelProviders = new Set(["discord", "slack"]);
1
+ const kSingleAccountChannelProviders = new Set(["discord"]);
2
2
 
3
3
  const hasConfiguredAccounts = ({ configuredChannelMap, provider }) => {
4
4
  const channelEntry = configuredChannelMap instanceof Map
@@ -20,4 +20,3 @@ export const isChannelProviderDisabledForAdd = ({
20
20
  if (!isSingleAccountChannelProvider(provider)) return false;
21
21
  return hasConfiguredAccounts({ configuredChannelMap, provider });
22
22
  };
23
-
@@ -143,10 +143,7 @@ const createChannelsDomain = ({
143
143
  `Channel account "${provider}/${accountId}" already exists`,
144
144
  );
145
145
  }
146
- if (
147
- (provider === "discord" || provider === "slack") &&
148
- Object.keys(existingAccounts).length > 0
149
- ) {
146
+ if (provider === "discord" && Object.keys(existingAccounts).length > 0) {
150
147
  throw new Error(
151
148
  `${kChannelLabels[provider] || "This provider"} supports a single channel account`,
152
149
  );
@@ -0,0 +1,163 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const {
4
+ readOpenclawConfig,
5
+ resolveOpenclawConfigPath,
6
+ writeOpenclawConfig,
7
+ } = require("./openclaw-config");
8
+
9
+ const kManagedExecApprovalsDefaults = Object.freeze({
10
+ security: "full",
11
+ ask: "off",
12
+ askFallback: "full",
13
+ });
14
+
15
+ const kManagedOpenclawExecDefaults = Object.freeze({
16
+ security: "full",
17
+ strictInlineEval: false,
18
+ });
19
+
20
+ const resolveExecApprovalsConfigPath = ({ openclawDir }) =>
21
+ path.join(openclawDir, "exec-approvals.json");
22
+
23
+ const readExecApprovalsConfig = ({
24
+ fsModule = fs,
25
+ openclawDir,
26
+ fallback = { version: 1 },
27
+ } = {}) => {
28
+ const filePath = resolveExecApprovalsConfigPath({ openclawDir });
29
+ try {
30
+ const parsed = JSON.parse(fsModule.readFileSync(filePath, "utf8"));
31
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
32
+ ? parsed
33
+ : fallback;
34
+ } catch {
35
+ return fallback;
36
+ }
37
+ };
38
+
39
+ const writeExecApprovalsConfig = ({
40
+ fsModule = fs,
41
+ openclawDir,
42
+ file = {},
43
+ spacing = 2,
44
+ } = {}) => {
45
+ const filePath = resolveExecApprovalsConfigPath({ openclawDir });
46
+ fsModule.mkdirSync(path.dirname(filePath), { recursive: true });
47
+ fsModule.writeFileSync(filePath, JSON.stringify(file, null, spacing) + "\n", "utf8");
48
+ return filePath;
49
+ };
50
+
51
+ const hasOwn = (obj, key) =>
52
+ !!obj && typeof obj === "object" && Object.prototype.hasOwnProperty.call(obj, key);
53
+
54
+ const ensureManagedExecApprovalsDefaults = (rawFile = {}) => {
55
+ const file =
56
+ rawFile && typeof rawFile === "object" && !Array.isArray(rawFile) ? rawFile : {};
57
+ const before = JSON.stringify(file);
58
+ const defaults =
59
+ file.defaults && typeof file.defaults === "object" && !Array.isArray(file.defaults)
60
+ ? file.defaults
61
+ : null;
62
+ const hasNonEmptyDefaults = !!defaults && Object.keys(defaults).length > 0;
63
+ if (!hasNonEmptyDefaults) {
64
+ if (!Number.isInteger(file.version)) file.version = 1;
65
+ file.defaults = {
66
+ security: kManagedExecApprovalsDefaults.security,
67
+ ask: kManagedExecApprovalsDefaults.ask,
68
+ askFallback: kManagedExecApprovalsDefaults.askFallback,
69
+ };
70
+ if (!file.agents || typeof file.agents !== "object" || Array.isArray(file.agents)) {
71
+ file.agents = {};
72
+ }
73
+ }
74
+ return {
75
+ file,
76
+ changed: JSON.stringify(file) !== before,
77
+ };
78
+ };
79
+
80
+ const ensureManagedOpenclawExecDefaults = (rawConfig = {}) => {
81
+ const config =
82
+ rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig) ? rawConfig : {};
83
+ const before = JSON.stringify(config);
84
+ if (!config.tools || typeof config.tools !== "object" || Array.isArray(config.tools)) {
85
+ config.tools = {};
86
+ }
87
+ if (!hasOwn(config.tools, "exec")) {
88
+ config.tools.exec = {
89
+ security: kManagedOpenclawExecDefaults.security,
90
+ strictInlineEval: kManagedOpenclawExecDefaults.strictInlineEval,
91
+ };
92
+ }
93
+ return {
94
+ config,
95
+ changed: JSON.stringify(config) !== before,
96
+ };
97
+ };
98
+
99
+ const ensureManagedExecDefaults = ({ fsModule = fs, openclawDir } = {}) => {
100
+ let openclawChanged = false;
101
+ let approvalsChanged = false;
102
+
103
+ const openclawConfigPath = resolveOpenclawConfigPath({ openclawDir });
104
+ const openclawExists =
105
+ typeof fsModule.existsSync === "function" ? fsModule.existsSync(openclawConfigPath) : null;
106
+ if (openclawExists !== false) {
107
+ const cfg = readOpenclawConfig({
108
+ fsModule,
109
+ openclawDir,
110
+ fallback: openclawExists === true ? null : {},
111
+ });
112
+ if (cfg && typeof cfg === "object" && !Array.isArray(cfg)) {
113
+ const ensuredConfig = ensureManagedOpenclawExecDefaults(cfg);
114
+ if (ensuredConfig.changed) {
115
+ writeOpenclawConfig({
116
+ fsModule,
117
+ openclawDir,
118
+ config: ensuredConfig.config,
119
+ spacing: 2,
120
+ });
121
+ openclawChanged = true;
122
+ }
123
+ }
124
+ }
125
+
126
+ const approvalsPath = resolveExecApprovalsConfigPath({ openclawDir });
127
+ const approvalsExists =
128
+ typeof fsModule.existsSync === "function" ? fsModule.existsSync(approvalsPath) : null;
129
+ const approvals = readExecApprovalsConfig({
130
+ fsModule,
131
+ openclawDir,
132
+ fallback: approvalsExists === true ? null : { version: 1 },
133
+ });
134
+ if (approvals && typeof approvals === "object" && !Array.isArray(approvals)) {
135
+ const ensuredApprovals = ensureManagedExecApprovalsDefaults(approvals);
136
+ if (ensuredApprovals.changed || approvalsExists === false) {
137
+ writeExecApprovalsConfig({
138
+ fsModule,
139
+ openclawDir,
140
+ file: ensuredApprovals.file,
141
+ spacing: 2,
142
+ });
143
+ approvalsChanged = true;
144
+ }
145
+ }
146
+
147
+ return {
148
+ changed: openclawChanged || approvalsChanged,
149
+ openclawChanged,
150
+ approvalsChanged,
151
+ };
152
+ };
153
+
154
+ module.exports = {
155
+ kManagedExecApprovalsDefaults,
156
+ kManagedOpenclawExecDefaults,
157
+ resolveExecApprovalsConfigPath,
158
+ readExecApprovalsConfig,
159
+ writeExecApprovalsConfig,
160
+ ensureManagedExecApprovalsDefaults,
161
+ ensureManagedOpenclawExecDefaults,
162
+ ensureManagedExecDefaults,
163
+ };
@@ -3,6 +3,7 @@ const startServerLifecycle = ({
3
3
  PORT,
4
4
  isOnboarded,
5
5
  runOnboardedBootSequence,
6
+ ensureManagedExecDefaults,
6
7
  ensureUsageTrackerPluginConfig,
7
8
  doSyncPromptFiles,
8
9
  reloadEnv,
@@ -18,6 +19,7 @@ const startServerLifecycle = ({
18
19
  console.log(`[alphaclaw] Express listening on :${PORT}`);
19
20
  if (isOnboarded()) {
20
21
  runOnboardedBootSequence({
22
+ ensureManagedExecDefaults,
21
23
  ensureUsageTrackerPluginConfig,
22
24
  doSyncPromptFiles,
23
25
  reloadEnv,
@@ -25,6 +25,7 @@ const {
25
25
  } = require("./cron");
26
26
  const { migrateManagedInternalFiles } = require("../internal-files-migration");
27
27
  const { installGogCliSkill } = require("../gog-skill");
28
+ const { ensureManagedExecDefaults } = require("../exec-defaults-config");
28
29
 
29
30
  const kPlaceholderEnvValue = "placeholder";
30
31
  const kEnvRefPattern = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
@@ -529,6 +530,10 @@ const createOnboardingService = ({
529
530
  });
530
531
  }
531
532
  authProfiles?.syncConfigAuthReferencesForAgent?.();
533
+ ensureManagedExecDefaults({
534
+ fsModule: fs,
535
+ openclawDir: OPENCLAW_DIR,
536
+ });
532
537
 
533
538
  installGogCliSkill({ fs, openclawDir: OPENCLAW_DIR });
534
539