@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.
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +1274 -1168
- package/lib/public/js/components/agents-tab/create-channel-modal.js +259 -59
- package/lib/public/js/components/modal-shell.js +1 -1
- package/lib/public/js/lib/channel-provider-availability.js +1 -2
- package/lib/server/agents/channels.js +1 -4
- package/lib/server/exec-defaults-config.js +163 -0
- package/lib/server/init/server-lifecycle.js +2 -0
- package/lib/server/onboarding/index.js +5 -0
- package/lib/server/routes/nodes.js +9 -23
- package/lib/server/startup.js +8 -0
- package/lib/server/watchdog-notify.js +172 -55
- package/lib/server.js +14 -1
- package/package.json +2 -2
- package/patches/openclaw+2026.4.1.patch +13 -0
- package/patches/openclaw+2026.3.28.patch +0 -13
|
@@ -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
|
-
<
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
</
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
</
|
|
402
|
-
<
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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-
|
|
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"
|
|
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
|
|