@parall/openclaw-agent 1.30.0 → 1.32.0
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/dist/index.js +153 -85
- package/package.json +1 -1
- package/src/index.ts +191 -90
package/dist/index.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
// agent's credentials, installs the bundled plugin, then execs
|
|
5
5
|
// `openclaw gateway run`. The daemon supervisor spawns one of these per
|
|
6
6
|
// attached OpenClaw agent.
|
|
7
|
-
import { execFileSync, spawn } from
|
|
8
|
-
import * as fs from
|
|
9
|
-
import * as path from
|
|
7
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// 1. Read + validate ENV
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
-
function ts() {
|
|
13
|
+
function ts() {
|
|
14
|
+
return new Date().toISOString();
|
|
15
|
+
}
|
|
14
16
|
const log = {
|
|
15
17
|
info: (msg) => console.log(`${ts()} [openclaw-agent] ${msg}`),
|
|
16
18
|
warn: (msg) => console.warn(`${ts()} [openclaw-agent] ${msg}`),
|
|
@@ -24,38 +26,34 @@ function env(name) {
|
|
|
24
26
|
}
|
|
25
27
|
return v;
|
|
26
28
|
}
|
|
27
|
-
const PRLL_API_URL = env(
|
|
28
|
-
const PRLL_API_KEY = env(
|
|
29
|
-
const PRLL_ORG_ID = env(
|
|
30
|
-
const stateDir = env(
|
|
31
|
-
const PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() ||
|
|
32
|
-
const PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() ||
|
|
33
|
-
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() ||
|
|
34
|
-
const pluginArchive = process.env.PRLL_OPENCLAW_PLUGIN_ARCHIVE?.trim()
|
|
35
|
-
|| "/opt/parall-plugin/parall-plugin.tgz";
|
|
29
|
+
const PRLL_API_URL = env('PRLL_API_URL');
|
|
30
|
+
const PRLL_API_KEY = env('PRLL_API_KEY');
|
|
31
|
+
const PRLL_ORG_ID = env('PRLL_ORG_ID');
|
|
32
|
+
const stateDir = env('PRLL_STATE_DIR');
|
|
33
|
+
const PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() || '';
|
|
34
|
+
const PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() || '';
|
|
35
|
+
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() || '0';
|
|
36
|
+
const pluginArchive = process.env.PRLL_OPENCLAW_PLUGIN_ARCHIVE?.trim() || '/opt/parall-plugin/parall-plugin.tgz';
|
|
36
37
|
// ---------------------------------------------------------------------------
|
|
37
38
|
// 2. Create per-agent state directory
|
|
38
39
|
// ---------------------------------------------------------------------------
|
|
39
|
-
const openclawStateDir = path.join(stateDir,
|
|
40
|
-
const configPath = path.join(openclawStateDir,
|
|
41
|
-
fs.mkdirSync(path.join(openclawStateDir,
|
|
42
|
-
fs.mkdirSync(path.join(openclawStateDir,
|
|
40
|
+
const openclawStateDir = path.join(stateDir, '.openclaw');
|
|
41
|
+
const configPath = path.join(openclawStateDir, 'openclaw.json');
|
|
42
|
+
fs.mkdirSync(path.join(openclawStateDir, 'sessions'), { recursive: true });
|
|
43
|
+
fs.mkdirSync(path.join(openclawStateDir, 'workspace'), { recursive: true });
|
|
43
44
|
// ---------------------------------------------------------------------------
|
|
44
45
|
// 3. Install plugin from bundled archive
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
46
47
|
if (fs.existsSync(pluginArchive)) {
|
|
47
48
|
// Clean legacy extension dir before install (only when we have an archive
|
|
48
49
|
// to replace it — otherwise the existing install is the only copy).
|
|
49
|
-
const legacyExtDir = path.join(openclawStateDir,
|
|
50
|
+
const legacyExtDir = path.join(openclawStateDir, 'extensions', 'parall');
|
|
50
51
|
fs.rmSync(legacyExtDir, { recursive: true, force: true });
|
|
51
52
|
log.info(`Installing Parall plugin from ${pluginArchive}...`);
|
|
52
53
|
try {
|
|
53
|
-
execFileSync(
|
|
54
|
-
"plugins", "install", pluginArchive,
|
|
55
|
-
"--force", "--dangerously-force-unsafe-install",
|
|
56
|
-
], {
|
|
54
|
+
execFileSync('openclaw', ['plugins', 'install', pluginArchive, '--force', '--dangerously-force-unsafe-install'], {
|
|
57
55
|
env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
|
|
58
|
-
stdio:
|
|
56
|
+
stdio: 'inherit',
|
|
59
57
|
timeout: 60_000,
|
|
60
58
|
});
|
|
61
59
|
}
|
|
@@ -74,13 +72,17 @@ writeOpenclawConfig();
|
|
|
74
72
|
function writeOpenclawConfig() {
|
|
75
73
|
let cfg = {};
|
|
76
74
|
try {
|
|
77
|
-
cfg = JSON.parse(fs.readFileSync(configPath,
|
|
75
|
+
cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* fresh config */
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
gateway.mode = "local";
|
|
80
|
+
const gateway = cfg.gateway && typeof cfg.gateway === 'object' ? cfg.gateway : {};
|
|
81
|
+
gateway.mode = 'local';
|
|
82
82
|
cfg.gateway = gateway;
|
|
83
|
-
const channels =
|
|
83
|
+
const channels = cfg.channels && typeof cfg.channels === 'object'
|
|
84
|
+
? cfg.channels
|
|
85
|
+
: {};
|
|
84
86
|
const parallChannel = {
|
|
85
87
|
parall_url: PRLL_API_URL,
|
|
86
88
|
api_key: PRLL_API_KEY,
|
|
@@ -90,8 +92,10 @@ function writeOpenclawConfig() {
|
|
|
90
92
|
parallChannel.ws_url = PRLL_WS_URL;
|
|
91
93
|
channels.parall = parallChannel;
|
|
92
94
|
cfg.channels = channels;
|
|
93
|
-
const plugins =
|
|
94
|
-
const entries =
|
|
95
|
+
const plugins = cfg.plugins && typeof cfg.plugins === 'object' ? cfg.plugins : {};
|
|
96
|
+
const entries = plugins.entries && typeof plugins.entries === 'object'
|
|
97
|
+
? plugins.entries
|
|
98
|
+
: {};
|
|
95
99
|
const parallPluginConfig = {
|
|
96
100
|
parall_url: PRLL_API_URL,
|
|
97
101
|
api_key: PRLL_API_KEY,
|
|
@@ -99,7 +103,9 @@ function writeOpenclawConfig() {
|
|
|
99
103
|
};
|
|
100
104
|
if (PRLL_WS_URL)
|
|
101
105
|
parallPluginConfig.ws_url = PRLL_WS_URL;
|
|
102
|
-
const existingParall =
|
|
106
|
+
const existingParall = entries.parall && typeof entries.parall === 'object'
|
|
107
|
+
? entries.parall
|
|
108
|
+
: {};
|
|
103
109
|
entries.parall = {
|
|
104
110
|
...existingParall,
|
|
105
111
|
enabled: true,
|
|
@@ -109,11 +115,17 @@ function writeOpenclawConfig() {
|
|
|
109
115
|
plugins.entries = entries;
|
|
110
116
|
cfg.plugins = plugins;
|
|
111
117
|
// sqlite-vec vector index guard
|
|
112
|
-
const agents =
|
|
113
|
-
const defaults =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
118
|
+
const agents = cfg.agents && typeof cfg.agents === 'object' ? cfg.agents : {};
|
|
119
|
+
const defaults = agents.defaults && typeof agents.defaults === 'object'
|
|
120
|
+
? agents.defaults
|
|
121
|
+
: {};
|
|
122
|
+
const ms = defaults.memorySearch && typeof defaults.memorySearch === 'object'
|
|
123
|
+
? defaults.memorySearch
|
|
124
|
+
: {};
|
|
125
|
+
const store = ms.store && typeof ms.store === 'object' ? ms.store : {};
|
|
126
|
+
const vector = store.vector && typeof store.vector === 'object'
|
|
127
|
+
? store.vector
|
|
128
|
+
: {};
|
|
117
129
|
if (vector.enabled === undefined)
|
|
118
130
|
vector.enabled = true;
|
|
119
131
|
store.vector = vector;
|
|
@@ -122,12 +134,12 @@ function writeOpenclawConfig() {
|
|
|
122
134
|
agents.defaults = defaults;
|
|
123
135
|
cfg.agents = agents;
|
|
124
136
|
// Seed tools.alsoAllow
|
|
125
|
-
const tools =
|
|
137
|
+
const tools = cfg.tools && typeof cfg.tools === 'object' ? cfg.tools : {};
|
|
126
138
|
const alsoAllow = new Set(Array.isArray(tools.alsoAllow) ? tools.alsoAllow : []);
|
|
127
|
-
alsoAllow.add(
|
|
139
|
+
alsoAllow.add('group:plugins');
|
|
128
140
|
tools.alsoAllow = Array.from(alsoAllow);
|
|
129
141
|
cfg.tools = tools;
|
|
130
|
-
const tmp = configPath +
|
|
142
|
+
const tmp = configPath + '.tmp';
|
|
131
143
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
132
144
|
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
133
145
|
fs.renameSync(tmp, configPath);
|
|
@@ -144,7 +156,7 @@ async function preseedPlatformConfig() {
|
|
|
144
156
|
try {
|
|
145
157
|
const headers = { Authorization: `Bearer ${PRLL_API_KEY}` };
|
|
146
158
|
if (PRLL_SWIMLANE_NAME)
|
|
147
|
-
headers[
|
|
159
|
+
headers['X-Prll-Swimlane'] = PRLL_SWIMLANE_NAME;
|
|
148
160
|
const resp = await fetch(`${PRLL_API_URL}/api/v1/agents/platform-config`, {
|
|
149
161
|
headers,
|
|
150
162
|
signal: AbortSignal.timeout(15_000),
|
|
@@ -155,54 +167,106 @@ async function preseedPlatformConfig() {
|
|
|
155
167
|
const pc = (data.config ?? data);
|
|
156
168
|
let cfg = {};
|
|
157
169
|
try {
|
|
158
|
-
cfg = JSON.parse(fs.readFileSync(configPath,
|
|
170
|
+
cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
/* fresh */
|
|
159
174
|
}
|
|
160
|
-
catch { /* fresh */ }
|
|
161
175
|
// Keep in sync with deploy/openclaw-docker/entrypoint.sh and
|
|
162
176
|
// ts/openclaw-channel/src/config-manager.ts.
|
|
163
|
-
const ALLOWED_DEFAULTS = new Set([
|
|
177
|
+
const ALLOWED_DEFAULTS = new Set(['model', 'compaction', 'memorySearch']);
|
|
164
178
|
const platformDefaults = pc.agents?.defaults;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
const agents = cfg.agents && typeof cfg.agents === 'object' ? cfg.agents : {};
|
|
180
|
+
const existing = agents.defaults && typeof agents.defaults === 'object'
|
|
181
|
+
? agents.defaults
|
|
182
|
+
: {};
|
|
183
|
+
// `model` is platform-sourced (delivered only on the catalog-gated Parall
|
|
184
|
+
// route). Drop any carried-over value so a stale `parall/...` model doesn't
|
|
185
|
+
// survive a switch to runtime_auth — the server then omits agents.defaults
|
|
186
|
+
// entirely, and without this drop the runtime keeps using the Parall
|
|
187
|
+
// provider. compaction/memorySearch are operator-tunable, so preserved.
|
|
188
|
+
const hadModel = 'model' in existing;
|
|
189
|
+
delete existing.model;
|
|
190
|
+
if (platformDefaults && typeof platformDefaults === 'object') {
|
|
168
191
|
for (const [k, v] of Object.entries(platformDefaults)) {
|
|
169
192
|
if (ALLOWED_DEFAULTS.has(k))
|
|
170
193
|
existing[k] = v;
|
|
171
194
|
}
|
|
195
|
+
}
|
|
196
|
+
if (hadModel || (platformDefaults && typeof platformDefaults === 'object')) {
|
|
172
197
|
agents.defaults = existing;
|
|
173
198
|
cfg.agents = agents;
|
|
174
199
|
}
|
|
175
200
|
// Keep in sync with deploy/openclaw-docker/entrypoint.sh and
|
|
176
201
|
// ts/openclaw-channel/src/config-manager.ts.
|
|
177
|
-
const ALLOWED_MODEL_KEYS = new Set([
|
|
202
|
+
const ALLOWED_MODEL_KEYS = new Set(['id', 'name', 'contextWindow', 'maxTokens']);
|
|
178
203
|
const platformModels = pc.models?.providers;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
204
|
+
if (platformModels && typeof platformModels === 'object') {
|
|
205
|
+
const models = cfg.models && typeof cfg.models === 'object' ? cfg.models : {};
|
|
206
|
+
const providers = models.providers && typeof models.providers === 'object'
|
|
207
|
+
? models.providers
|
|
208
|
+
: {};
|
|
209
|
+
// Parall-managed providers: overlay when present. parall-anthropic is
|
|
210
|
+
// deleted when absent (rollback safety); parall is never deleted.
|
|
211
|
+
// Keep in sync with config-manager.ts.
|
|
212
|
+
for (const providerName of ['parall', 'parall-anthropic']) {
|
|
213
|
+
const platformProvider = platformModels[providerName];
|
|
214
|
+
if (!platformProvider || typeof platformProvider !== 'object') {
|
|
215
|
+
if (providerName !== 'parall')
|
|
216
|
+
delete providers[providerName];
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const existing = providers[providerName] && typeof providers[providerName] === 'object'
|
|
220
|
+
? providers[providerName]
|
|
221
|
+
: {};
|
|
222
|
+
const merged = { ...existing, ...platformProvider };
|
|
223
|
+
if (Array.isArray(merged.models)) {
|
|
224
|
+
merged.models = merged.models
|
|
225
|
+
.filter((m) => m && typeof m === 'object')
|
|
226
|
+
.map((m) => {
|
|
227
|
+
const clean = {};
|
|
228
|
+
for (const [k, v] of Object.entries(m)) {
|
|
229
|
+
if (ALLOWED_MODEL_KEYS.has(k))
|
|
230
|
+
clean[k] = v;
|
|
231
|
+
}
|
|
232
|
+
return clean;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
merged.apiKey = PRLL_API_KEY;
|
|
236
|
+
providers[providerName] = merged;
|
|
196
237
|
}
|
|
197
|
-
merged.apiKey = PRLL_API_KEY;
|
|
198
|
-
providers.parall = merged;
|
|
199
238
|
models.providers = providers;
|
|
200
239
|
cfg.models = models;
|
|
201
240
|
}
|
|
202
|
-
|
|
241
|
+
// Bidirectional model rewrite for parall-anthropic — gate on fresh platform
|
|
242
|
+
// payload for rollback safety. Keep in sync with config-manager.ts.
|
|
243
|
+
const agentsCfg = cfg.agents && typeof cfg.agents === 'object' ? cfg.agents : {};
|
|
244
|
+
const defaultsCfg = agentsCfg.defaults && typeof agentsCfg.defaults === 'object'
|
|
245
|
+
? agentsCfg.defaults
|
|
246
|
+
: {};
|
|
247
|
+
if (typeof defaultsCfg.model === 'string') {
|
|
248
|
+
const modelStr = defaultsCfg.model;
|
|
249
|
+
if (platformModels?.['parall-anthropic']) {
|
|
250
|
+
const fwd = modelStr.match(/^parall\/(anthropic\/.+)$/);
|
|
251
|
+
if (fwd) {
|
|
252
|
+
defaultsCfg.model = `parall-anthropic/${fwd[1]}`;
|
|
253
|
+
agentsCfg.defaults = defaultsCfg;
|
|
254
|
+
cfg.agents = agentsCfg;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const rev = modelStr.match(/^parall-anthropic\/(anthropic\/.+)$/);
|
|
259
|
+
if (rev) {
|
|
260
|
+
defaultsCfg.model = `parall/${rev[1]}`;
|
|
261
|
+
agentsCfg.defaults = defaultsCfg;
|
|
262
|
+
cfg.agents = agentsCfg;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const tmp = configPath + '.tmp';
|
|
203
267
|
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
204
268
|
fs.renameSync(tmp, configPath);
|
|
205
|
-
const model = cfg.agents?.defaults?.model ??
|
|
269
|
+
const model = cfg.agents?.defaults?.model ?? 'none';
|
|
206
270
|
log.info(`Platform config pre-seeded (model: ${String(model)}).`);
|
|
207
271
|
}
|
|
208
272
|
catch (err) {
|
|
@@ -213,18 +277,20 @@ async function preseedPlatformConfig() {
|
|
|
213
277
|
// 6. openclaw doctor --fix (non-fatal)
|
|
214
278
|
// ---------------------------------------------------------------------------
|
|
215
279
|
try {
|
|
216
|
-
execFileSync(
|
|
280
|
+
execFileSync('openclaw', ['doctor', '--fix'], {
|
|
217
281
|
env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
|
|
218
|
-
stdio:
|
|
282
|
+
stdio: 'inherit',
|
|
219
283
|
timeout: 30_000,
|
|
220
284
|
});
|
|
221
285
|
}
|
|
222
|
-
catch {
|
|
286
|
+
catch {
|
|
287
|
+
/* non-fatal */
|
|
288
|
+
}
|
|
223
289
|
// ---------------------------------------------------------------------------
|
|
224
290
|
// 7. Spawn openclaw gateway run with signal forwarding
|
|
225
291
|
// ---------------------------------------------------------------------------
|
|
226
|
-
log.info(
|
|
227
|
-
const workspaceDir = process.env.
|
|
292
|
+
log.info('Starting OpenClaw gateway...');
|
|
293
|
+
const workspaceDir = process.env.PRLL_WORKSPACE_DIR?.trim() || '';
|
|
228
294
|
const gatewayEnv = {
|
|
229
295
|
...process.env,
|
|
230
296
|
OPENCLAW_STATE_DIR: openclawStateDir,
|
|
@@ -235,31 +301,33 @@ if (workspaceDir) {
|
|
|
235
301
|
if (PRLL_SWIMLANE_NAME) {
|
|
236
302
|
gatewayEnv.PRLL_SWIMLANE_NAME = PRLL_SWIMLANE_NAME;
|
|
237
303
|
}
|
|
238
|
-
const gatewayArgs = [
|
|
239
|
-
if (gatewayPort !==
|
|
240
|
-
gatewayArgs.push(
|
|
304
|
+
const gatewayArgs = ['gateway', 'run'];
|
|
305
|
+
if (gatewayPort !== '0') {
|
|
306
|
+
gatewayArgs.push('--port', gatewayPort);
|
|
241
307
|
gatewayEnv.OPENCLAW_GATEWAY_PORT = gatewayPort;
|
|
242
308
|
}
|
|
243
|
-
const cwd = workspaceDir || path.join(openclawStateDir,
|
|
309
|
+
const cwd = workspaceDir || path.join(openclawStateDir, 'workspace');
|
|
244
310
|
fs.mkdirSync(cwd, { recursive: true });
|
|
245
|
-
const child = spawn(
|
|
311
|
+
const child = spawn('openclaw', gatewayArgs, {
|
|
246
312
|
env: gatewayEnv,
|
|
247
313
|
cwd,
|
|
248
|
-
stdio:
|
|
314
|
+
stdio: 'inherit',
|
|
249
315
|
detached: false,
|
|
250
316
|
});
|
|
251
317
|
function forwardSignal(sig) {
|
|
252
318
|
try {
|
|
253
319
|
child.kill(sig);
|
|
254
320
|
}
|
|
255
|
-
catch {
|
|
321
|
+
catch {
|
|
322
|
+
/* already gone */
|
|
323
|
+
}
|
|
256
324
|
}
|
|
257
|
-
process.on(
|
|
258
|
-
process.on(
|
|
259
|
-
child.on(
|
|
260
|
-
if (signal ===
|
|
325
|
+
process.on('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
326
|
+
process.on('SIGINT', () => forwardSignal('SIGINT'));
|
|
327
|
+
child.on('close', (code, signal) => {
|
|
328
|
+
if (signal === 'SIGTERM')
|
|
261
329
|
process.exit(143);
|
|
262
|
-
if (signal ===
|
|
330
|
+
if (signal === 'SIGINT')
|
|
263
331
|
process.exit(130);
|
|
264
332
|
process.exit(code ?? 1);
|
|
265
333
|
});
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
// `openclaw gateway run`. The daemon supervisor spawns one of these per
|
|
7
7
|
// attached OpenClaw agent.
|
|
8
8
|
|
|
9
|
-
import { execFileSync, spawn } from
|
|
10
|
-
import * as fs from
|
|
11
|
-
import * as path from
|
|
9
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// 1. Read + validate ENV
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
|
|
17
|
-
function ts(): string {
|
|
17
|
+
function ts(): string {
|
|
18
|
+
return new Date().toISOString();
|
|
19
|
+
}
|
|
18
20
|
const log = {
|
|
19
21
|
info: (msg: string) => console.log(`${ts()} [openclaw-agent] ${msg}`),
|
|
20
22
|
warn: (msg: string) => console.warn(`${ts()} [openclaw-agent] ${msg}`),
|
|
@@ -30,26 +32,26 @@ function env(name: string): string {
|
|
|
30
32
|
return v;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
const PRLL_API_URL = env(
|
|
34
|
-
const PRLL_API_KEY = env(
|
|
35
|
-
const PRLL_ORG_ID = env(
|
|
36
|
-
const stateDir = env(
|
|
35
|
+
const PRLL_API_URL = env('PRLL_API_URL');
|
|
36
|
+
const PRLL_API_KEY = env('PRLL_API_KEY');
|
|
37
|
+
const PRLL_ORG_ID = env('PRLL_ORG_ID');
|
|
38
|
+
const stateDir = env('PRLL_STATE_DIR');
|
|
37
39
|
|
|
38
|
-
const PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() ||
|
|
39
|
-
const PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() ||
|
|
40
|
-
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() ||
|
|
41
|
-
const pluginArchive =
|
|
42
|
-
||
|
|
40
|
+
const PRLL_WS_URL = process.env.PRLL_WS_URL?.trim() || '';
|
|
41
|
+
const PRLL_SWIMLANE_NAME = process.env.PRLL_SWIMLANE_NAME?.trim() || '';
|
|
42
|
+
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT?.trim() || '0';
|
|
43
|
+
const pluginArchive =
|
|
44
|
+
process.env.PRLL_OPENCLAW_PLUGIN_ARCHIVE?.trim() || '/opt/parall-plugin/parall-plugin.tgz';
|
|
43
45
|
|
|
44
46
|
// ---------------------------------------------------------------------------
|
|
45
47
|
// 2. Create per-agent state directory
|
|
46
48
|
// ---------------------------------------------------------------------------
|
|
47
49
|
|
|
48
|
-
const openclawStateDir = path.join(stateDir,
|
|
49
|
-
const configPath = path.join(openclawStateDir,
|
|
50
|
+
const openclawStateDir = path.join(stateDir, '.openclaw');
|
|
51
|
+
const configPath = path.join(openclawStateDir, 'openclaw.json');
|
|
50
52
|
|
|
51
|
-
fs.mkdirSync(path.join(openclawStateDir,
|
|
52
|
-
fs.mkdirSync(path.join(openclawStateDir,
|
|
53
|
+
fs.mkdirSync(path.join(openclawStateDir, 'sessions'), { recursive: true });
|
|
54
|
+
fs.mkdirSync(path.join(openclawStateDir, 'workspace'), { recursive: true });
|
|
53
55
|
|
|
54
56
|
// ---------------------------------------------------------------------------
|
|
55
57
|
// 3. Install plugin from bundled archive
|
|
@@ -58,27 +60,26 @@ fs.mkdirSync(path.join(openclawStateDir, "workspace"), { recursive: true });
|
|
|
58
60
|
if (fs.existsSync(pluginArchive)) {
|
|
59
61
|
// Clean legacy extension dir before install (only when we have an archive
|
|
60
62
|
// to replace it — otherwise the existing install is the only copy).
|
|
61
|
-
const legacyExtDir = path.join(openclawStateDir,
|
|
63
|
+
const legacyExtDir = path.join(openclawStateDir, 'extensions', 'parall');
|
|
62
64
|
fs.rmSync(legacyExtDir, { recursive: true, force: true });
|
|
63
65
|
|
|
64
66
|
log.info(`Installing Parall plugin from ${pluginArchive}...`);
|
|
65
67
|
try {
|
|
66
|
-
execFileSync(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
execFileSync(
|
|
69
|
+
'openclaw',
|
|
70
|
+
['plugins', 'install', pluginArchive, '--force', '--dangerously-force-unsafe-install'],
|
|
71
|
+
{
|
|
72
|
+
env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
|
|
73
|
+
stdio: 'inherit',
|
|
74
|
+
timeout: 60_000,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
74
77
|
} catch (err) {
|
|
75
78
|
log.error(`Failed to install Parall plugin: ${String(err)}`);
|
|
76
79
|
process.exit(1);
|
|
77
80
|
}
|
|
78
81
|
} else {
|
|
79
|
-
log.warn(
|
|
80
|
-
`Plugin archive not found at ${pluginArchive} — assuming plugin is already installed.`,
|
|
81
|
-
);
|
|
82
|
+
log.warn(`Plugin archive not found at ${pluginArchive} — assuming plugin is already installed.`);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// ---------------------------------------------------------------------------
|
|
@@ -90,14 +91,20 @@ writeOpenclawConfig();
|
|
|
90
91
|
function writeOpenclawConfig(): void {
|
|
91
92
|
let cfg: Record<string, unknown> = {};
|
|
92
93
|
try {
|
|
93
|
-
cfg = JSON.parse(fs.readFileSync(configPath,
|
|
94
|
-
} catch {
|
|
94
|
+
cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;
|
|
95
|
+
} catch {
|
|
96
|
+
/* fresh config */
|
|
97
|
+
}
|
|
95
98
|
|
|
96
|
-
const gateway =
|
|
97
|
-
|
|
99
|
+
const gateway =
|
|
100
|
+
cfg.gateway && typeof cfg.gateway === 'object' ? (cfg.gateway as Record<string, unknown>) : {};
|
|
101
|
+
gateway.mode = 'local';
|
|
98
102
|
cfg.gateway = gateway;
|
|
99
103
|
|
|
100
|
-
const channels =
|
|
104
|
+
const channels =
|
|
105
|
+
cfg.channels && typeof cfg.channels === 'object'
|
|
106
|
+
? (cfg.channels as Record<string, unknown>)
|
|
107
|
+
: {};
|
|
101
108
|
const parallChannel: Record<string, unknown> = {
|
|
102
109
|
parall_url: PRLL_API_URL,
|
|
103
110
|
api_key: PRLL_API_KEY,
|
|
@@ -107,15 +114,22 @@ function writeOpenclawConfig(): void {
|
|
|
107
114
|
channels.parall = parallChannel;
|
|
108
115
|
cfg.channels = channels;
|
|
109
116
|
|
|
110
|
-
const plugins =
|
|
111
|
-
|
|
117
|
+
const plugins =
|
|
118
|
+
cfg.plugins && typeof cfg.plugins === 'object' ? (cfg.plugins as Record<string, unknown>) : {};
|
|
119
|
+
const entries =
|
|
120
|
+
plugins.entries && typeof plugins.entries === 'object'
|
|
121
|
+
? (plugins.entries as Record<string, unknown>)
|
|
122
|
+
: {};
|
|
112
123
|
const parallPluginConfig: Record<string, unknown> = {
|
|
113
124
|
parall_url: PRLL_API_URL,
|
|
114
125
|
api_key: PRLL_API_KEY,
|
|
115
126
|
org_id: PRLL_ORG_ID,
|
|
116
127
|
};
|
|
117
128
|
if (PRLL_WS_URL) parallPluginConfig.ws_url = PRLL_WS_URL;
|
|
118
|
-
const existingParall =
|
|
129
|
+
const existingParall =
|
|
130
|
+
entries.parall && typeof entries.parall === 'object'
|
|
131
|
+
? (entries.parall as Record<string, unknown>)
|
|
132
|
+
: {};
|
|
119
133
|
entries.parall = {
|
|
120
134
|
...existingParall,
|
|
121
135
|
enabled: true,
|
|
@@ -126,11 +140,22 @@ function writeOpenclawConfig(): void {
|
|
|
126
140
|
cfg.plugins = plugins;
|
|
127
141
|
|
|
128
142
|
// sqlite-vec vector index guard
|
|
129
|
-
const agents =
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
const agents =
|
|
144
|
+
cfg.agents && typeof cfg.agents === 'object' ? (cfg.agents as Record<string, unknown>) : {};
|
|
145
|
+
const defaults =
|
|
146
|
+
agents.defaults && typeof agents.defaults === 'object'
|
|
147
|
+
? (agents.defaults as Record<string, unknown>)
|
|
148
|
+
: {};
|
|
149
|
+
const ms =
|
|
150
|
+
defaults.memorySearch && typeof defaults.memorySearch === 'object'
|
|
151
|
+
? (defaults.memorySearch as Record<string, unknown>)
|
|
152
|
+
: {};
|
|
153
|
+
const store =
|
|
154
|
+
ms.store && typeof ms.store === 'object' ? (ms.store as Record<string, unknown>) : {};
|
|
155
|
+
const vector =
|
|
156
|
+
store.vector && typeof store.vector === 'object'
|
|
157
|
+
? (store.vector as Record<string, unknown>)
|
|
158
|
+
: {};
|
|
134
159
|
if (vector.enabled === undefined) vector.enabled = true;
|
|
135
160
|
store.vector = vector;
|
|
136
161
|
ms.store = store;
|
|
@@ -139,15 +164,16 @@ function writeOpenclawConfig(): void {
|
|
|
139
164
|
cfg.agents = agents;
|
|
140
165
|
|
|
141
166
|
// Seed tools.alsoAllow
|
|
142
|
-
const tools =
|
|
167
|
+
const tools =
|
|
168
|
+
cfg.tools && typeof cfg.tools === 'object' ? (cfg.tools as Record<string, unknown>) : {};
|
|
143
169
|
const alsoAllow = new Set<string>(
|
|
144
170
|
Array.isArray(tools.alsoAllow) ? (tools.alsoAllow as string[]) : [],
|
|
145
171
|
);
|
|
146
|
-
alsoAllow.add(
|
|
172
|
+
alsoAllow.add('group:plugins');
|
|
147
173
|
tools.alsoAllow = Array.from(alsoAllow);
|
|
148
174
|
cfg.tools = tools;
|
|
149
175
|
|
|
150
|
-
const tmp = configPath +
|
|
176
|
+
const tmp = configPath + '.tmp';
|
|
151
177
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
152
178
|
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
153
179
|
fs.renameSync(tmp, configPath);
|
|
@@ -166,7 +192,7 @@ await preseedPlatformConfig();
|
|
|
166
192
|
async function preseedPlatformConfig(): Promise<void> {
|
|
167
193
|
try {
|
|
168
194
|
const headers: Record<string, string> = { Authorization: `Bearer ${PRLL_API_KEY}` };
|
|
169
|
-
if (PRLL_SWIMLANE_NAME) headers[
|
|
195
|
+
if (PRLL_SWIMLANE_NAME) headers['X-Prll-Swimlane'] = PRLL_SWIMLANE_NAME;
|
|
170
196
|
|
|
171
197
|
const resp = await fetch(`${PRLL_API_URL}/api/v1/agents/platform-config`, {
|
|
172
198
|
headers,
|
|
@@ -177,53 +203,122 @@ async function preseedPlatformConfig(): Promise<void> {
|
|
|
177
203
|
const pc = (data.config ?? data) as Record<string, unknown>;
|
|
178
204
|
|
|
179
205
|
let cfg: Record<string, unknown> = {};
|
|
180
|
-
try {
|
|
206
|
+
try {
|
|
207
|
+
cfg = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;
|
|
208
|
+
} catch {
|
|
209
|
+
/* fresh */
|
|
210
|
+
}
|
|
181
211
|
|
|
182
212
|
// Keep in sync with deploy/openclaw-docker/entrypoint.sh and
|
|
183
213
|
// ts/openclaw-channel/src/config-manager.ts.
|
|
184
|
-
const ALLOWED_DEFAULTS = new Set([
|
|
214
|
+
const ALLOWED_DEFAULTS = new Set(['model', 'compaction', 'memorySearch']);
|
|
185
215
|
const platformDefaults = (pc.agents as Record<string, unknown> | undefined)?.defaults;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
const agents =
|
|
217
|
+
cfg.agents && typeof cfg.agents === 'object' ? (cfg.agents as Record<string, unknown>) : {};
|
|
218
|
+
const existing =
|
|
219
|
+
agents.defaults && typeof agents.defaults === 'object'
|
|
220
|
+
? (agents.defaults as Record<string, unknown>)
|
|
221
|
+
: {};
|
|
222
|
+
// `model` is platform-sourced (delivered only on the catalog-gated Parall
|
|
223
|
+
// route). Drop any carried-over value so a stale `parall/...` model doesn't
|
|
224
|
+
// survive a switch to runtime_auth — the server then omits agents.defaults
|
|
225
|
+
// entirely, and without this drop the runtime keeps using the Parall
|
|
226
|
+
// provider. compaction/memorySearch are operator-tunable, so preserved.
|
|
227
|
+
const hadModel = 'model' in existing;
|
|
228
|
+
delete existing.model;
|
|
229
|
+
if (platformDefaults && typeof platformDefaults === 'object') {
|
|
189
230
|
for (const [k, v] of Object.entries(platformDefaults as Record<string, unknown>)) {
|
|
190
231
|
if (ALLOWED_DEFAULTS.has(k)) existing[k] = v;
|
|
191
232
|
}
|
|
233
|
+
}
|
|
234
|
+
if (hadModel || (platformDefaults && typeof platformDefaults === 'object')) {
|
|
192
235
|
agents.defaults = existing;
|
|
193
236
|
cfg.agents = agents;
|
|
194
237
|
}
|
|
195
238
|
|
|
196
239
|
// Keep in sync with deploy/openclaw-docker/entrypoint.sh and
|
|
197
240
|
// ts/openclaw-channel/src/config-manager.ts.
|
|
198
|
-
const ALLOWED_MODEL_KEYS = new Set([
|
|
199
|
-
const platformModels = (pc.models as Record<string, unknown> | undefined)?.providers as
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
241
|
+
const ALLOWED_MODEL_KEYS = new Set(['id', 'name', 'contextWindow', 'maxTokens']);
|
|
242
|
+
const platformModels = (pc.models as Record<string, unknown> | undefined)?.providers as
|
|
243
|
+
| Record<string, unknown>
|
|
244
|
+
| undefined;
|
|
245
|
+
if (platformModels && typeof platformModels === 'object') {
|
|
246
|
+
const models =
|
|
247
|
+
cfg.models && typeof cfg.models === 'object' ? (cfg.models as Record<string, unknown>) : {};
|
|
248
|
+
const providers =
|
|
249
|
+
models.providers && typeof models.providers === 'object'
|
|
250
|
+
? (models.providers as Record<string, unknown>)
|
|
251
|
+
: {};
|
|
252
|
+
|
|
253
|
+
// Parall-managed providers: overlay when present. parall-anthropic is
|
|
254
|
+
// deleted when absent (rollback safety); parall is never deleted.
|
|
255
|
+
// Keep in sync with config-manager.ts.
|
|
256
|
+
for (const providerName of ['parall', 'parall-anthropic']) {
|
|
257
|
+
const platformProvider = platformModels[providerName];
|
|
258
|
+
if (!platformProvider || typeof platformProvider !== 'object') {
|
|
259
|
+
if (providerName !== 'parall') delete providers[providerName];
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const existing =
|
|
263
|
+
providers[providerName] && typeof providers[providerName] === 'object'
|
|
264
|
+
? (providers[providerName] as Record<string, unknown>)
|
|
265
|
+
: {};
|
|
266
|
+
const merged = { ...existing, ...(platformProvider as Record<string, unknown>) };
|
|
267
|
+
if (Array.isArray(merged.models)) {
|
|
268
|
+
merged.models = (merged.models as Record<string, unknown>[])
|
|
269
|
+
.filter((m) => m && typeof m === 'object')
|
|
270
|
+
.map((m) => {
|
|
271
|
+
const clean: Record<string, unknown> = {};
|
|
272
|
+
for (const [k, v] of Object.entries(m)) {
|
|
273
|
+
if (ALLOWED_MODEL_KEYS.has(k)) clean[k] = v;
|
|
274
|
+
}
|
|
275
|
+
return clean;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
merged.apiKey = PRLL_API_KEY;
|
|
279
|
+
providers[providerName] = merged;
|
|
216
280
|
}
|
|
217
|
-
|
|
218
|
-
providers.parall = merged;
|
|
281
|
+
|
|
219
282
|
models.providers = providers;
|
|
220
283
|
cfg.models = models;
|
|
221
284
|
}
|
|
222
285
|
|
|
223
|
-
|
|
286
|
+
// Bidirectional model rewrite for parall-anthropic — gate on fresh platform
|
|
287
|
+
// payload for rollback safety. Keep in sync with config-manager.ts.
|
|
288
|
+
const agentsCfg =
|
|
289
|
+
cfg.agents && typeof cfg.agents === 'object' ? (cfg.agents as Record<string, unknown>) : {};
|
|
290
|
+
const defaultsCfg =
|
|
291
|
+
agentsCfg.defaults && typeof agentsCfg.defaults === 'object'
|
|
292
|
+
? (agentsCfg.defaults as Record<string, unknown>)
|
|
293
|
+
: {};
|
|
294
|
+
if (typeof defaultsCfg.model === 'string') {
|
|
295
|
+
const modelStr = defaultsCfg.model as string;
|
|
296
|
+
if (platformModels?.['parall-anthropic']) {
|
|
297
|
+
const fwd = modelStr.match(/^parall\/(anthropic\/.+)$/);
|
|
298
|
+
if (fwd) {
|
|
299
|
+
defaultsCfg.model = `parall-anthropic/${fwd[1]}`;
|
|
300
|
+
agentsCfg.defaults = defaultsCfg;
|
|
301
|
+
cfg.agents = agentsCfg;
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
const rev = modelStr.match(/^parall-anthropic\/(anthropic\/.+)$/);
|
|
305
|
+
if (rev) {
|
|
306
|
+
defaultsCfg.model = `parall/${rev[1]}`;
|
|
307
|
+
agentsCfg.defaults = defaultsCfg;
|
|
308
|
+
cfg.agents = agentsCfg;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const tmp = configPath + '.tmp';
|
|
224
314
|
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2));
|
|
225
315
|
fs.renameSync(tmp, configPath);
|
|
226
|
-
const model =
|
|
316
|
+
const model =
|
|
317
|
+
(
|
|
318
|
+
(cfg.agents as Record<string, unknown> | undefined)?.defaults as
|
|
319
|
+
| Record<string, unknown>
|
|
320
|
+
| undefined
|
|
321
|
+
)?.model ?? 'none';
|
|
227
322
|
log.info(`Platform config pre-seeded (model: ${String(model)}).`);
|
|
228
323
|
} catch (err) {
|
|
229
324
|
log.warn(`Platform config pre-seed skipped: ${String(err)}`);
|
|
@@ -235,21 +330,23 @@ async function preseedPlatformConfig(): Promise<void> {
|
|
|
235
330
|
// ---------------------------------------------------------------------------
|
|
236
331
|
|
|
237
332
|
try {
|
|
238
|
-
execFileSync(
|
|
333
|
+
execFileSync('openclaw', ['doctor', '--fix'], {
|
|
239
334
|
env: { ...process.env, OPENCLAW_STATE_DIR: openclawStateDir },
|
|
240
|
-
stdio:
|
|
335
|
+
stdio: 'inherit',
|
|
241
336
|
timeout: 30_000,
|
|
242
337
|
});
|
|
243
|
-
} catch {
|
|
338
|
+
} catch {
|
|
339
|
+
/* non-fatal */
|
|
340
|
+
}
|
|
244
341
|
|
|
245
342
|
// ---------------------------------------------------------------------------
|
|
246
343
|
// 7. Spawn openclaw gateway run with signal forwarding
|
|
247
344
|
|
|
248
345
|
// ---------------------------------------------------------------------------
|
|
249
346
|
|
|
250
|
-
log.info(
|
|
347
|
+
log.info('Starting OpenClaw gateway...');
|
|
251
348
|
|
|
252
|
-
const workspaceDir = process.env.
|
|
349
|
+
const workspaceDir = process.env.PRLL_WORKSPACE_DIR?.trim() || '';
|
|
253
350
|
|
|
254
351
|
const gatewayEnv: NodeJS.ProcessEnv = {
|
|
255
352
|
...process.env,
|
|
@@ -262,30 +359,34 @@ if (PRLL_SWIMLANE_NAME) {
|
|
|
262
359
|
gatewayEnv.PRLL_SWIMLANE_NAME = PRLL_SWIMLANE_NAME;
|
|
263
360
|
}
|
|
264
361
|
|
|
265
|
-
const gatewayArgs = [
|
|
266
|
-
if (gatewayPort !==
|
|
267
|
-
gatewayArgs.push(
|
|
362
|
+
const gatewayArgs = ['gateway', 'run'];
|
|
363
|
+
if (gatewayPort !== '0') {
|
|
364
|
+
gatewayArgs.push('--port', gatewayPort);
|
|
268
365
|
gatewayEnv.OPENCLAW_GATEWAY_PORT = gatewayPort;
|
|
269
366
|
}
|
|
270
367
|
|
|
271
|
-
const cwd = workspaceDir || path.join(openclawStateDir,
|
|
368
|
+
const cwd = workspaceDir || path.join(openclawStateDir, 'workspace');
|
|
272
369
|
fs.mkdirSync(cwd, { recursive: true });
|
|
273
370
|
|
|
274
|
-
const child = spawn(
|
|
371
|
+
const child = spawn('openclaw', gatewayArgs, {
|
|
275
372
|
env: gatewayEnv,
|
|
276
373
|
cwd,
|
|
277
|
-
stdio:
|
|
374
|
+
stdio: 'inherit',
|
|
278
375
|
detached: false,
|
|
279
376
|
});
|
|
280
377
|
|
|
281
378
|
function forwardSignal(sig: NodeJS.Signals): void {
|
|
282
|
-
try {
|
|
379
|
+
try {
|
|
380
|
+
child.kill(sig);
|
|
381
|
+
} catch {
|
|
382
|
+
/* already gone */
|
|
383
|
+
}
|
|
283
384
|
}
|
|
284
|
-
process.on(
|
|
285
|
-
process.on(
|
|
385
|
+
process.on('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
386
|
+
process.on('SIGINT', () => forwardSignal('SIGINT'));
|
|
286
387
|
|
|
287
|
-
child.on(
|
|
288
|
-
if (signal ===
|
|
289
|
-
if (signal ===
|
|
388
|
+
child.on('close', (code, signal) => {
|
|
389
|
+
if (signal === 'SIGTERM') process.exit(143);
|
|
390
|
+
if (signal === 'SIGINT') process.exit(130);
|
|
290
391
|
process.exit(code ?? 1);
|
|
291
392
|
});
|