@jhytabest/plashboard 0.1.11 → 1.0.1
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/README.md +57 -1
- package/openclaw.plugin.json +3 -1
- package/package.json +3 -2
- package/skills/plashboard-admin/SKILL.md +2 -0
- package/src/command-runner.ts +120 -0
- package/src/config.ts +6 -0
- package/src/fill-runner.test.ts +122 -2
- package/src/fill-runner.ts +57 -111
- package/src/plugin.test.ts +186 -0
- package/src/plugin.ts +397 -120
- package/src/publisher.ts +22 -54
- package/src/runtime.test.ts +53 -2
- package/src/runtime.ts +9 -1
- package/src/types.ts +8 -0
package/src/plugin.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { constants as fsConstants } from 'node:fs';
|
|
3
|
-
import { access, stat } from 'node:fs/promises';
|
|
2
|
+
import { access, chmod, stat } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
4
|
import type { DisplayProfile, ToolResponse } from './types.js';
|
|
5
5
|
import { resolveConfig } from './config.js';
|
|
6
6
|
import { PlashboardRuntime } from './runtime.js';
|
|
7
|
+
import {
|
|
8
|
+
createRuntimeCommandRunner,
|
|
9
|
+
runCommand,
|
|
10
|
+
type CommandRunner,
|
|
11
|
+
type RuntimeCommandWithTimeout
|
|
12
|
+
} from './command-runner.js';
|
|
7
13
|
|
|
8
14
|
type UnknownApi = {
|
|
9
15
|
registerTool?: (definition: unknown) => void;
|
|
@@ -20,24 +26,7 @@ type UnknownApi = {
|
|
|
20
26
|
writeConfigFile?: (nextConfig: unknown) => Promise<void>;
|
|
21
27
|
};
|
|
22
28
|
system?: {
|
|
23
|
-
runCommandWithTimeout?:
|
|
24
|
-
argv: string[],
|
|
25
|
-
optionsOrTimeout: number | {
|
|
26
|
-
timeoutMs: number;
|
|
27
|
-
cwd?: string;
|
|
28
|
-
input?: string;
|
|
29
|
-
env?: NodeJS.ProcessEnv;
|
|
30
|
-
windowsVerbatimArguments?: boolean;
|
|
31
|
-
noOutputTimeoutMs?: number;
|
|
32
|
-
}
|
|
33
|
-
) => Promise<{
|
|
34
|
-
stdout: string;
|
|
35
|
-
stderr: string;
|
|
36
|
-
code: number | null;
|
|
37
|
-
signal?: NodeJS.Signals | null;
|
|
38
|
-
killed?: boolean;
|
|
39
|
-
termination?: string;
|
|
40
|
-
}>;
|
|
29
|
+
runCommandWithTimeout?: RuntimeCommandWithTimeout;
|
|
41
30
|
};
|
|
42
31
|
};
|
|
43
32
|
config?: unknown;
|
|
@@ -88,8 +77,10 @@ function asErrorMessage(error: unknown): string {
|
|
|
88
77
|
|
|
89
78
|
type SetupParams = {
|
|
90
79
|
fill_provider?: 'mock' | 'command' | 'openclaw';
|
|
80
|
+
allow_command_fill?: boolean;
|
|
91
81
|
fill_command?: string;
|
|
92
82
|
openclaw_fill_agent_id?: string;
|
|
83
|
+
session_strategy?: 'persistent' | 'ephemeral';
|
|
93
84
|
auto_seed_template?: boolean;
|
|
94
85
|
data_dir?: string;
|
|
95
86
|
scheduler_tick_seconds?: number;
|
|
@@ -126,16 +117,12 @@ type DoctorParams = ExposureParams & {
|
|
|
126
117
|
repo_dir?: string;
|
|
127
118
|
};
|
|
128
119
|
|
|
129
|
-
type
|
|
130
|
-
|
|
120
|
+
type PermissionsFixParams = {
|
|
121
|
+
dashboard_output_path?: string;
|
|
131
122
|
};
|
|
132
123
|
|
|
133
|
-
type
|
|
134
|
-
|
|
135
|
-
stdout: string;
|
|
136
|
-
stderr: string;
|
|
137
|
-
code: number | null;
|
|
138
|
-
error?: string;
|
|
124
|
+
type OnboardParams = DoctorParams & QuickstartParams & {
|
|
125
|
+
force_quickstart?: boolean;
|
|
139
126
|
};
|
|
140
127
|
|
|
141
128
|
function normalizeLocalUrl(raw: string | undefined): string {
|
|
@@ -155,61 +142,99 @@ function normalizePort(raw: number | undefined, fallback: number): number {
|
|
|
155
142
|
return Math.max(1, Math.min(65535, value));
|
|
156
143
|
}
|
|
157
144
|
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
145
|
+
function parseJsonLoose(input: string): unknown | undefined {
|
|
146
|
+
const trimmed = input.trim();
|
|
147
|
+
if (!trimmed) return undefined;
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(trimmed);
|
|
150
|
+
} catch {
|
|
151
|
+
// continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const starts = ['{', '['];
|
|
155
|
+
for (const opener of starts) {
|
|
156
|
+
const start = trimmed.indexOf(opener);
|
|
157
|
+
if (start < 0) continue;
|
|
158
|
+
const closer = opener === '{' ? '}' : ']';
|
|
159
|
+
const end = trimmed.lastIndexOf(closer);
|
|
160
|
+
if (end <= start) continue;
|
|
161
|
+
const candidate = trimmed.slice(start, end + 1);
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(candidate);
|
|
164
|
+
} catch {
|
|
165
|
+
// continue
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
171
|
+
function octalMode(value: number | undefined): string | undefined {
|
|
172
|
+
if (!Number.isFinite(value)) return undefined;
|
|
173
|
+
return `0${(value! & 0o777).toString(8).padStart(3, '0')}`;
|
|
174
|
+
}
|
|
167
175
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
async function listOpenClawAgentIds(commandRunner: CommandRunner | null): Promise<{
|
|
177
|
+
ok: boolean;
|
|
178
|
+
ids: string[];
|
|
179
|
+
error?: string;
|
|
180
|
+
}> {
|
|
181
|
+
const result = await runCommand(
|
|
182
|
+
commandRunner,
|
|
183
|
+
['openclaw', 'agents', 'list', '--json'],
|
|
184
|
+
12_000,
|
|
185
|
+
'openclaw agents list'
|
|
186
|
+
);
|
|
187
|
+
if (!result.ok) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
ids: [],
|
|
191
|
+
error: result.error || result.stderr || `exit ${String(result.code)}`
|
|
172
192
|
};
|
|
193
|
+
}
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
stdout,
|
|
179
|
-
stderr,
|
|
180
|
-
code: null,
|
|
181
|
-
error: `timed out after ${Math.floor(timeoutMs / 1000)}s`
|
|
182
|
-
});
|
|
183
|
-
}, timeoutMs);
|
|
195
|
+
const parsed = parseJsonLoose(result.stdout);
|
|
196
|
+
if (!Array.isArray(parsed)) {
|
|
197
|
+
return { ok: false, ids: [], error: 'unable to parse openclaw agents list output' };
|
|
198
|
+
}
|
|
184
199
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
const ids = parsed
|
|
201
|
+
.map((entry) => asObject(entry))
|
|
202
|
+
.map((entry) => asString(entry.id))
|
|
203
|
+
.filter(Boolean);
|
|
204
|
+
return { ok: true, ids };
|
|
205
|
+
}
|
|
191
206
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
207
|
+
async function checkWriterPreflight(
|
|
208
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
209
|
+
commandRunner: CommandRunner | null
|
|
210
|
+
): Promise<{
|
|
211
|
+
ready: boolean;
|
|
212
|
+
errors: string[];
|
|
213
|
+
python_version?: string;
|
|
214
|
+
}> {
|
|
215
|
+
const errors: string[] = [];
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
try {
|
|
218
|
+
await access(resolvedConfig.writer_script_path, fsConstants.R_OK);
|
|
219
|
+
} catch {
|
|
220
|
+
errors.push(`writer script is not readable: ${resolvedConfig.writer_script_path}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const version = await runCommand(
|
|
224
|
+
commandRunner,
|
|
225
|
+
[resolvedConfig.python_bin, '--version'],
|
|
226
|
+
8_000,
|
|
227
|
+
'python runtime preflight'
|
|
228
|
+
);
|
|
229
|
+
if (!version.ok) {
|
|
230
|
+
errors.push(`python runtime check failed: ${version.error || version.stderr || `exit ${String(version.code)}`}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
ready: errors.length === 0,
|
|
235
|
+
errors,
|
|
236
|
+
python_version: version.ok ? (version.stdout || version.stderr) : undefined
|
|
237
|
+
};
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
async function buildExposureGuide(resolvedConfig: ReturnType<typeof resolveConfig>, params: ExposureParams = {}) {
|
|
@@ -232,7 +257,8 @@ async function buildExposureGuide(resolvedConfig: ReturnType<typeof resolveConfi
|
|
|
232
257
|
],
|
|
233
258
|
checks: [
|
|
234
259
|
`test -f ${dashboardPath}`,
|
|
235
|
-
`curl -I ${localUrl}
|
|
260
|
+
`curl -I ${localUrl}`,
|
|
261
|
+
`curl -I ${localUrl.replace(/\/$/, '')}/data/dashboard.json`
|
|
236
262
|
],
|
|
237
263
|
notes: [
|
|
238
264
|
'plashboard only writes dashboard JSON; your local UI/server must serve it.',
|
|
@@ -277,15 +303,21 @@ async function buildWebGuide(resolvedConfig: ReturnType<typeof resolveConfig>, p
|
|
|
277
303
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
278
304
|
}
|
|
279
305
|
|
|
280
|
-
async function runExposureCheck(
|
|
306
|
+
async function runExposureCheck(
|
|
307
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
308
|
+
commandRunner: CommandRunner | null,
|
|
309
|
+
params: ExposureParams = {}
|
|
310
|
+
) {
|
|
281
311
|
const localUrl = normalizeLocalUrl(params.local_url);
|
|
282
312
|
const httpsPort = normalizePort(asNumber(params.tailscale_https_port), 8444);
|
|
283
313
|
const dashboardPath = (params.dashboard_output_path || resolvedConfig.dashboard_output_path).trim();
|
|
314
|
+
const dataDirPath = dirname(dashboardPath);
|
|
284
315
|
const errors: string[] = [];
|
|
285
316
|
|
|
286
317
|
let dashboardExists = false;
|
|
287
318
|
let dashboardSizeBytes: number | undefined;
|
|
288
319
|
let dashboardMtimeIso: string | undefined;
|
|
320
|
+
let dataDirMode: number | undefined;
|
|
289
321
|
|
|
290
322
|
try {
|
|
291
323
|
await access(dashboardPath, fsConstants.R_OK);
|
|
@@ -296,10 +328,20 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
296
328
|
} catch {
|
|
297
329
|
errors.push(`dashboard file is not readable: ${dashboardPath}`);
|
|
298
330
|
}
|
|
331
|
+
try {
|
|
332
|
+
const dirInfo = await stat(dataDirPath);
|
|
333
|
+
dataDirMode = dirInfo.mode & 0o777;
|
|
334
|
+
} catch {
|
|
335
|
+
// ignore
|
|
336
|
+
}
|
|
299
337
|
|
|
300
338
|
let localUrlOk = false;
|
|
301
339
|
let localStatusCode: number | undefined;
|
|
302
340
|
let localError: string | undefined;
|
|
341
|
+
const localDashboardUrl = new URL('/data/dashboard.json', localUrl).toString();
|
|
342
|
+
let localDashboardOk = false;
|
|
343
|
+
let localDashboardStatusCode: number | undefined;
|
|
344
|
+
let localDashboardError: string | undefined;
|
|
303
345
|
|
|
304
346
|
try {
|
|
305
347
|
const controller = new AbortController();
|
|
@@ -319,17 +361,43 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
319
361
|
errors.push(`local dashboard URL is not reachable: ${localUrl} (${localError})`);
|
|
320
362
|
}
|
|
321
363
|
|
|
322
|
-
|
|
364
|
+
try {
|
|
365
|
+
const controller = new AbortController();
|
|
366
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
367
|
+
const response = await fetch(localDashboardUrl, {
|
|
368
|
+
method: 'GET',
|
|
369
|
+
signal: controller.signal
|
|
370
|
+
});
|
|
371
|
+
clearTimeout(timer);
|
|
372
|
+
localDashboardStatusCode = response.status;
|
|
373
|
+
localDashboardOk = response.status >= 200 && response.status < 300;
|
|
374
|
+
if (!localDashboardOk) {
|
|
375
|
+
errors.push(`dashboard JSON URL returned status ${response.status}: ${localDashboardUrl}`);
|
|
376
|
+
if (response.status === 403) {
|
|
377
|
+
errors.push(`dashboard JSON access denied; check directory permissions for ${dataDirPath}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} catch (error) {
|
|
381
|
+
localDashboardError = asErrorMessage(error);
|
|
382
|
+
errors.push(`dashboard JSON URL is not reachable: ${localDashboardUrl} (${localDashboardError})`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const tailscale = await runCommand(commandRunner, ['tailscale', 'serve', 'status'], 8000, 'tailscale serve status');
|
|
323
386
|
const tailscaleOutput = `${tailscale.stdout}\n${tailscale.stderr}`.trim();
|
|
324
387
|
let tailscalePortConfigured = false;
|
|
388
|
+
let tailscaleTargetConfigured = false;
|
|
325
389
|
|
|
326
390
|
if (!tailscale.ok) {
|
|
327
391
|
errors.push(`tailscale serve status failed: ${tailscale.error || tailscale.stderr || `exit ${tailscale.code}`}`);
|
|
328
392
|
} else {
|
|
329
393
|
tailscalePortConfigured = tailscaleOutput.includes(`:${httpsPort}`);
|
|
394
|
+
tailscaleTargetConfigured = tailscaleOutput.includes(`proxy ${localUrl.replace(/\/$/, '')}`);
|
|
330
395
|
if (!tailscalePortConfigured) {
|
|
331
396
|
errors.push(`tailscale serve has no mapping for https port ${httpsPort}`);
|
|
332
397
|
}
|
|
398
|
+
if (!tailscaleTargetConfigured) {
|
|
399
|
+
errors.push(`tailscale serve mapping does not target ${localUrl}`);
|
|
400
|
+
}
|
|
333
401
|
}
|
|
334
402
|
|
|
335
403
|
return {
|
|
@@ -344,15 +412,28 @@ async function runExposureCheck(resolvedConfig: ReturnType<typeof resolveConfig>
|
|
|
344
412
|
local_url_ok: localUrlOk,
|
|
345
413
|
local_status_code: localStatusCode,
|
|
346
414
|
local_error: localError,
|
|
415
|
+
local_dashboard_url: localDashboardUrl,
|
|
416
|
+
local_dashboard_ok: localDashboardOk,
|
|
417
|
+
local_dashboard_status_code: localDashboardStatusCode,
|
|
418
|
+
local_dashboard_error: localDashboardError,
|
|
419
|
+
data_dir_path: dataDirPath,
|
|
420
|
+
data_dir_mode: dataDirMode,
|
|
421
|
+
data_dir_mode_octal: octalMode(dataDirMode),
|
|
347
422
|
tailscale_https_port: httpsPort,
|
|
348
423
|
tailscale_status_ok: tailscale.ok,
|
|
349
424
|
tailscale_port_configured: tailscalePortConfigured,
|
|
425
|
+
tailscale_target_configured: tailscaleTargetConfigured,
|
|
350
426
|
tailscale_status_excerpt: tailscaleOutput.slice(0, 1200)
|
|
351
427
|
}
|
|
352
428
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
353
429
|
}
|
|
354
430
|
|
|
355
|
-
async function runSetup(
|
|
431
|
+
async function runSetup(
|
|
432
|
+
api: UnknownApi,
|
|
433
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
434
|
+
commandRunner: CommandRunner | null,
|
|
435
|
+
params: SetupParams = {}
|
|
436
|
+
) {
|
|
356
437
|
const loadConfig = api.runtime?.config?.loadConfig;
|
|
357
438
|
const writeConfigFile = api.runtime?.config?.writeConfigFile;
|
|
358
439
|
|
|
@@ -421,12 +502,23 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
421
502
|
? currentPluginConfig.auto_seed_template
|
|
422
503
|
: resolvedConfig.auto_seed_template
|
|
423
504
|
);
|
|
505
|
+
const selectedAllowCommandFill = (
|
|
506
|
+
typeof params.allow_command_fill === 'boolean'
|
|
507
|
+
? params.allow_command_fill
|
|
508
|
+
: typeof currentPluginConfig.allow_command_fill === 'boolean'
|
|
509
|
+
? Boolean(currentPluginConfig.allow_command_fill)
|
|
510
|
+
: resolvedConfig.allow_command_fill
|
|
511
|
+
);
|
|
424
512
|
const selectedAgentId = (
|
|
425
513
|
params.openclaw_fill_agent_id
|
|
426
514
|
|| asString(currentPluginConfig.openclaw_fill_agent_id)
|
|
427
515
|
|| asString(resolvedConfig.openclaw_fill_agent_id)
|
|
428
516
|
|| 'main'
|
|
429
517
|
).trim();
|
|
518
|
+
const rawSessionStrategy = asString(params.session_strategy)
|
|
519
|
+
|| asString(currentPluginConfig.session_strategy)
|
|
520
|
+
|| asString(resolvedConfig.session_strategy);
|
|
521
|
+
const selectedSessionStrategy = rawSessionStrategy === 'ephemeral' ? 'ephemeral' : 'persistent';
|
|
430
522
|
|
|
431
523
|
if (selectedProvider === 'command' && !selectedCommand) {
|
|
432
524
|
return {
|
|
@@ -434,12 +526,50 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
434
526
|
errors: ['fill_provider=command requires fill_command']
|
|
435
527
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
436
528
|
}
|
|
529
|
+
if (selectedProvider === 'command' && !selectedAllowCommandFill) {
|
|
530
|
+
return {
|
|
531
|
+
ok: false,
|
|
532
|
+
errors: ['fill_provider=command requires allow_command_fill=true']
|
|
533
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
534
|
+
}
|
|
535
|
+
if (selectedProvider === 'command' && !commandRunner) {
|
|
536
|
+
return {
|
|
537
|
+
ok: false,
|
|
538
|
+
errors: ['fill_provider=command requires runtime command runner support in this OpenClaw build']
|
|
539
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
540
|
+
}
|
|
437
541
|
if (selectedProvider === 'openclaw' && !selectedAgentId) {
|
|
438
542
|
return {
|
|
439
543
|
ok: false,
|
|
440
544
|
errors: ['fill_provider=openclaw requires openclaw_fill_agent_id']
|
|
441
545
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
442
546
|
}
|
|
547
|
+
if (selectedProvider === 'openclaw') {
|
|
548
|
+
const agents = await listOpenClawAgentIds(commandRunner);
|
|
549
|
+
if (!agents.ok) {
|
|
550
|
+
return {
|
|
551
|
+
ok: false,
|
|
552
|
+
errors: [`unable to validate openclaw_fill_agent_id: ${agents.error || 'unknown error'}`]
|
|
553
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
554
|
+
}
|
|
555
|
+
if (!agents.ids.includes(selectedAgentId)) {
|
|
556
|
+
return {
|
|
557
|
+
ok: false,
|
|
558
|
+
errors: [
|
|
559
|
+
`openclaw_fill_agent_id not found: ${selectedAgentId}`,
|
|
560
|
+
`available agent ids: ${agents.ids.join(', ') || '(none)'}`
|
|
561
|
+
]
|
|
562
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const preflight = await checkWriterPreflight(resolvedConfig, commandRunner);
|
|
567
|
+
if (!preflight.ready) {
|
|
568
|
+
return {
|
|
569
|
+
ok: false,
|
|
570
|
+
errors: preflight.errors
|
|
571
|
+
} satisfies ToolResponse<Record<string, unknown>>;
|
|
572
|
+
}
|
|
443
573
|
|
|
444
574
|
const nextPluginConfig: Record<string, unknown> = {
|
|
445
575
|
...currentPluginConfig,
|
|
@@ -461,6 +591,8 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
461
591
|
)
|
|
462
592
|
),
|
|
463
593
|
fill_provider: selectedProvider,
|
|
594
|
+
allow_command_fill: selectedAllowCommandFill,
|
|
595
|
+
session_strategy: selectedSessionStrategy,
|
|
464
596
|
auto_seed_template: selectedAutoSeed,
|
|
465
597
|
display_profile: displayProfile
|
|
466
598
|
};
|
|
@@ -501,13 +633,16 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
501
633
|
restart_required: true,
|
|
502
634
|
plugin_id: 'plashboard',
|
|
503
635
|
fill_provider: selectedProvider,
|
|
636
|
+
allow_command_fill: selectedAllowCommandFill,
|
|
504
637
|
fill_command: selectedProvider === 'command' ? selectedCommand : undefined,
|
|
505
638
|
openclaw_fill_agent_id: selectedProvider === 'openclaw' ? selectedAgentId : undefined,
|
|
639
|
+
session_strategy: selectedSessionStrategy,
|
|
506
640
|
auto_seed_template: selectedAutoSeed,
|
|
507
641
|
data_dir: nextPluginConfig.data_dir,
|
|
508
642
|
scheduler_tick_seconds: nextPluginConfig.scheduler_tick_seconds,
|
|
509
643
|
session_timeout_seconds: nextPluginConfig.session_timeout_seconds,
|
|
510
644
|
display_profile: displayProfile,
|
|
645
|
+
python_version: preflight.python_version,
|
|
511
646
|
next_steps: [
|
|
512
647
|
'restart OpenClaw gateway',
|
|
513
648
|
'run /plashboard init'
|
|
@@ -516,13 +651,72 @@ async function runSetup(api: UnknownApi, resolvedConfig: ReturnType<typeof resol
|
|
|
516
651
|
} satisfies ToolResponse<Record<string, unknown>>;
|
|
517
652
|
}
|
|
518
653
|
|
|
654
|
+
async function runPermissionsFix(
|
|
655
|
+
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
656
|
+
params: PermissionsFixParams = {}
|
|
657
|
+
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
658
|
+
const dashboardPath = (params.dashboard_output_path || resolvedConfig.dashboard_output_path).trim();
|
|
659
|
+
const dataDirPath = dirname(dashboardPath);
|
|
660
|
+
const errors: string[] = [];
|
|
661
|
+
|
|
662
|
+
let beforeDataDirMode: number | undefined;
|
|
663
|
+
let beforeDashboardMode: number | undefined;
|
|
664
|
+
let afterDataDirMode: number | undefined;
|
|
665
|
+
let afterDashboardMode: number | undefined;
|
|
666
|
+
|
|
667
|
+
try {
|
|
668
|
+
beforeDataDirMode = (await stat(dataDirPath)).mode & 0o777;
|
|
669
|
+
} catch (error) {
|
|
670
|
+
return {
|
|
671
|
+
ok: false,
|
|
672
|
+
errors: [`data directory is missing or unreadable: ${dataDirPath} (${asErrorMessage(error)})`]
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
beforeDashboardMode = (await stat(dashboardPath)).mode & 0o777;
|
|
678
|
+
} catch {
|
|
679
|
+
// dashboard file may not exist yet
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
await chmod(dataDirPath, 0o755);
|
|
684
|
+
afterDataDirMode = (await stat(dataDirPath)).mode & 0o777;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
errors.push(`failed to set directory mode for ${dataDirPath}: ${asErrorMessage(error)}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
await access(dashboardPath, fsConstants.F_OK);
|
|
691
|
+
await chmod(dashboardPath, 0o644);
|
|
692
|
+
afterDashboardMode = (await stat(dashboardPath)).mode & 0o777;
|
|
693
|
+
} catch {
|
|
694
|
+
// dashboard file may not exist yet
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
ok: errors.length === 0,
|
|
699
|
+
errors,
|
|
700
|
+
data: {
|
|
701
|
+
dashboard_output_path: dashboardPath,
|
|
702
|
+
data_dir_path: dataDirPath,
|
|
703
|
+
before_data_dir_mode_octal: octalMode(beforeDataDirMode),
|
|
704
|
+
after_data_dir_mode_octal: octalMode(afterDataDirMode),
|
|
705
|
+
before_dashboard_mode_octal: octalMode(beforeDashboardMode),
|
|
706
|
+
after_dashboard_mode_octal: octalMode(afterDashboardMode),
|
|
707
|
+
note: 'This is an explicit compatibility fix for dashboard web servers that read through bind mounts.'
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
519
712
|
async function runQuickstart(
|
|
520
713
|
runtime: PlashboardRuntime,
|
|
521
714
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
715
|
+
commandRunner: CommandRunner | null,
|
|
522
716
|
params: QuickstartParams = {}
|
|
523
717
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
524
718
|
const quickstart = await runtime.quickstart(params);
|
|
525
|
-
const exposure = await runExposureCheck(resolvedConfig, {});
|
|
719
|
+
const exposure = await runExposureCheck(resolvedConfig, commandRunner, {});
|
|
526
720
|
const guide = await buildExposureGuide(resolvedConfig, {});
|
|
527
721
|
const webGuide = await buildWebGuide(resolvedConfig, {});
|
|
528
722
|
|
|
@@ -562,19 +756,62 @@ async function runQuickstart(
|
|
|
562
756
|
async function runDoctor(
|
|
563
757
|
runtime: PlashboardRuntime,
|
|
564
758
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
759
|
+
commandRunner: CommandRunner | null,
|
|
565
760
|
params: DoctorParams = {}
|
|
566
761
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
567
762
|
const status = await runtime.status();
|
|
568
|
-
const
|
|
763
|
+
const templateList = await runtime.templateList();
|
|
764
|
+
const exposure = await runExposureCheck(resolvedConfig, commandRunner, params);
|
|
569
765
|
const exposureGuide = await buildExposureGuide(resolvedConfig, params);
|
|
570
766
|
const webGuide = await buildWebGuide(resolvedConfig, params);
|
|
767
|
+
const writerPreflight = await checkWriterPreflight(resolvedConfig, commandRunner);
|
|
571
768
|
|
|
572
769
|
const issues: string[] = [];
|
|
770
|
+
const warnings: string[] = [];
|
|
573
771
|
const statusData = status.data;
|
|
574
772
|
const templateCount = Number(statusData?.template_count ?? 0);
|
|
575
773
|
const activeTemplateId = statusData?.active_template_id || null;
|
|
774
|
+
const runtimeCommandRunnerAvailable = Boolean(statusData?.capabilities?.runtime_command_runner_available);
|
|
775
|
+
const commandFillAllowed = Boolean(statusData?.capabilities?.command_fill_allowed);
|
|
776
|
+
|
|
777
|
+
let fillProviderReady = resolvedConfig.fill_provider === 'mock'
|
|
778
|
+
? true
|
|
779
|
+
: resolvedConfig.fill_provider === 'openclaw'
|
|
780
|
+
? runtimeCommandRunnerAvailable && Boolean((resolvedConfig.openclaw_fill_agent_id || '').trim())
|
|
781
|
+
: runtimeCommandRunnerAvailable && commandFillAllowed && Boolean((resolvedConfig.fill_command || '').trim());
|
|
782
|
+
|
|
783
|
+
let fillAgentIds: string[] = [];
|
|
784
|
+
if (resolvedConfig.fill_provider === 'openclaw') {
|
|
785
|
+
const agents = await listOpenClawAgentIds(commandRunner);
|
|
786
|
+
if (!agents.ok) {
|
|
787
|
+
fillProviderReady = false;
|
|
788
|
+
issues.push(`unable to validate openclaw_fill_agent_id: ${agents.error || 'unknown error'}`);
|
|
789
|
+
} else {
|
|
790
|
+
fillAgentIds = agents.ids;
|
|
791
|
+
if (!agents.ids.includes(resolvedConfig.openclaw_fill_agent_id || 'main')) {
|
|
792
|
+
fillProviderReady = false;
|
|
793
|
+
issues.push(`openclaw_fill_agent_id not found: ${resolvedConfig.openclaw_fill_agent_id || 'main'}`);
|
|
794
|
+
}
|
|
795
|
+
if ((resolvedConfig.openclaw_fill_agent_id || 'main').trim() === 'main') {
|
|
796
|
+
warnings.push('openclaw_fill_agent_id=main can cause session lock contention; prefer a dedicated fill agent.');
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const writerRunnerReady = writerPreflight.ready;
|
|
576
802
|
|
|
577
803
|
if (!status.ok) issues.push(...status.errors);
|
|
804
|
+
if (!templateList.ok) issues.push(...templateList.errors);
|
|
805
|
+
if (!fillProviderReady) {
|
|
806
|
+
if (resolvedConfig.fill_provider === 'command' && !commandFillAllowed) {
|
|
807
|
+
issues.push('fill_provider=command is disabled; set allow_command_fill=true');
|
|
808
|
+
} else {
|
|
809
|
+
issues.push(`fill provider "${resolvedConfig.fill_provider}" is not ready`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (!writerRunnerReady) {
|
|
813
|
+
issues.push(...writerPreflight.errors);
|
|
814
|
+
}
|
|
578
815
|
if (templateCount === 0) issues.push('no templates exist; run /plashboard quickstart "<description>"');
|
|
579
816
|
if (!activeTemplateId) issues.push('no active template; activate one with /plashboard activate <template-id>');
|
|
580
817
|
if (exposure.data?.dashboard_exists !== true) {
|
|
@@ -583,10 +820,38 @@ async function runDoctor(
|
|
|
583
820
|
if (exposure.data?.local_url_ok !== true) {
|
|
584
821
|
issues.push(`local dashboard server is not reachable at ${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}`);
|
|
585
822
|
}
|
|
823
|
+
if (exposure.data?.local_dashboard_ok !== true) {
|
|
824
|
+
issues.push(`dashboard JSON endpoint is not reachable at ${String(exposure.data?.local_dashboard_url || `${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}/data/dashboard.json`)}`);
|
|
825
|
+
}
|
|
586
826
|
if (exposure.data?.tailscale_status_ok !== true) {
|
|
587
827
|
issues.push('tailscale serve status failed');
|
|
588
828
|
} else if (exposure.data?.tailscale_port_configured !== true) {
|
|
589
829
|
issues.push(`tailscale serve mapping missing for port ${String(exposure.data?.tailscale_https_port || 8444)}`);
|
|
830
|
+
} else if (exposure.data?.tailscale_target_configured !== true) {
|
|
831
|
+
issues.push(`tailscale serve mapping does not target ${String(exposure.data?.local_url || 'http://127.0.0.1:18888')}`);
|
|
832
|
+
}
|
|
833
|
+
if (Number(exposure.data?.local_dashboard_status_code) === 403) {
|
|
834
|
+
warnings.push(`dashboard JSON returned 403; run /plashboard fix-permissions to apply compatible read modes.`);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const templates = Array.isArray(templateList.data?.templates) ? templateList.data?.templates : [];
|
|
838
|
+
const activeTemplate = templates?.find((entry) => asString(entry.id) === activeTemplateId);
|
|
839
|
+
const everyMinutes = asNumber(asObject(activeTemplate?.schedule).every_minutes);
|
|
840
|
+
const mtimeIso = asString(exposure.data?.dashboard_mtime_utc);
|
|
841
|
+
if (everyMinutes && mtimeIso) {
|
|
842
|
+
const ageMs = Date.now() - Date.parse(mtimeIso);
|
|
843
|
+
const maxAgeMs = Math.max(everyMinutes * 2 * 60_000, 10 * 60_000);
|
|
844
|
+
if (Number.isFinite(ageMs) && ageMs > maxAgeMs) {
|
|
845
|
+
issues.push(`dashboard appears stale: last update ${mtimeIso} (age ${Math.floor(ageMs / 60_000)}m)`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (!runtimeCommandRunnerAvailable) {
|
|
850
|
+
warnings.push('runtime command runner unavailable; fill/publish checks may fail in this OpenClaw build.');
|
|
851
|
+
}
|
|
852
|
+
const dataDirMode = asNumber(exposure.data?.data_dir_mode);
|
|
853
|
+
if (typeof dataDirMode === 'number' && (dataDirMode & 0o005) === 0) {
|
|
854
|
+
warnings.push(`data directory mode ${String(exposure.data?.data_dir_mode_octal || '')} may block containerized web readers.`);
|
|
590
855
|
}
|
|
591
856
|
|
|
592
857
|
const ready = issues.length === 0;
|
|
@@ -595,6 +860,14 @@ async function runDoctor(
|
|
|
595
860
|
errors: issues,
|
|
596
861
|
data: {
|
|
597
862
|
ready,
|
|
863
|
+
fill_provider_ready: fillProviderReady,
|
|
864
|
+
writer_runner_ready: writerRunnerReady,
|
|
865
|
+
warnings,
|
|
866
|
+
writer_preflight: {
|
|
867
|
+
ready: writerPreflight.ready,
|
|
868
|
+
python_version: writerPreflight.python_version
|
|
869
|
+
},
|
|
870
|
+
fill_agent_ids: fillAgentIds,
|
|
598
871
|
status: statusData,
|
|
599
872
|
exposure: exposure.data,
|
|
600
873
|
exposure_guide: exposureGuide.data,
|
|
@@ -605,6 +878,7 @@ async function runDoctor(
|
|
|
605
878
|
'run /plashboard quickstart "<description>" if no templates exist',
|
|
606
879
|
'run /plashboard web-guide and start local UI server',
|
|
607
880
|
'run /plashboard expose-guide and apply tailscale mapping',
|
|
881
|
+
'run /plashboard fix-permissions if dashboard JSON returns 403',
|
|
608
882
|
're-run /plashboard doctor'
|
|
609
883
|
]
|
|
610
884
|
}
|
|
@@ -614,6 +888,7 @@ async function runDoctor(
|
|
|
614
888
|
async function runOnboard(
|
|
615
889
|
runtime: PlashboardRuntime,
|
|
616
890
|
resolvedConfig: ReturnType<typeof resolveConfig>,
|
|
891
|
+
commandRunner: CommandRunner | null,
|
|
617
892
|
params: OnboardParams = {}
|
|
618
893
|
): Promise<ToolResponse<Record<string, unknown>>> {
|
|
619
894
|
const initResult = await runtime.init();
|
|
@@ -625,7 +900,7 @@ async function runOnboard(
|
|
|
625
900
|
|
|
626
901
|
let quickstartResult: ToolResponse<Record<string, unknown>> | null = null;
|
|
627
902
|
if (shouldQuickstart) {
|
|
628
|
-
quickstartResult = await runQuickstart(runtime, resolvedConfig, {
|
|
903
|
+
quickstartResult = await runQuickstart(runtime, resolvedConfig, commandRunner, {
|
|
629
904
|
description: params.description,
|
|
630
905
|
template_id: params.template_id,
|
|
631
906
|
template_name: params.template_name,
|
|
@@ -635,7 +910,7 @@ async function runOnboard(
|
|
|
635
910
|
});
|
|
636
911
|
}
|
|
637
912
|
|
|
638
|
-
const doctorResult = await runDoctor(runtime, resolvedConfig, {
|
|
913
|
+
const doctorResult = await runDoctor(runtime, resolvedConfig, commandRunner, {
|
|
639
914
|
local_url: params.local_url,
|
|
640
915
|
tailscale_https_port: params.tailscale_https_port,
|
|
641
916
|
dashboard_output_path: params.dashboard_output_path,
|
|
@@ -658,36 +933,13 @@ async function runOnboard(
|
|
|
658
933
|
|
|
659
934
|
export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
660
935
|
const config = resolveConfig(api);
|
|
661
|
-
const
|
|
662
|
-
const fillCommandRunner = runtimeCommand
|
|
663
|
-
? async (
|
|
664
|
-
argv: string[],
|
|
665
|
-
optionsOrTimeout: number | {
|
|
666
|
-
timeoutMs: number;
|
|
667
|
-
cwd?: string;
|
|
668
|
-
input?: string;
|
|
669
|
-
env?: NodeJS.ProcessEnv;
|
|
670
|
-
windowsVerbatimArguments?: boolean;
|
|
671
|
-
noOutputTimeoutMs?: number;
|
|
672
|
-
}
|
|
673
|
-
) => {
|
|
674
|
-
const result = await runtimeCommand(argv, optionsOrTimeout);
|
|
675
|
-
return {
|
|
676
|
-
stdout: result.stdout,
|
|
677
|
-
stderr: result.stderr,
|
|
678
|
-
code: result.code,
|
|
679
|
-
signal: result.signal,
|
|
680
|
-
killed: result.killed,
|
|
681
|
-
termination: result.termination
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
: undefined;
|
|
936
|
+
const commandRunner = createRuntimeCommandRunner(api.runtime?.system?.runCommandWithTimeout);
|
|
685
937
|
const runtime = new PlashboardRuntime(config, {
|
|
686
938
|
info: (...args) => api.logger?.info?.(...args),
|
|
687
939
|
warn: (...args) => api.logger?.warn?.(...args),
|
|
688
940
|
error: (...args) => api.logger?.error?.(...args)
|
|
689
941
|
}, {
|
|
690
|
-
commandRunner
|
|
942
|
+
commandRunner
|
|
691
943
|
});
|
|
692
944
|
|
|
693
945
|
api.registerService?.({
|
|
@@ -722,7 +974,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
722
974
|
additionalProperties: false
|
|
723
975
|
},
|
|
724
976
|
execute: async (_toolCallId: unknown, params: OnboardParams = {}) =>
|
|
725
|
-
toToolResult(await runOnboard(runtime, config, params))
|
|
977
|
+
toToolResult(await runOnboard(runtime, config, commandRunner, params))
|
|
726
978
|
});
|
|
727
979
|
|
|
728
980
|
api.registerTool?.({
|
|
@@ -756,7 +1008,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
756
1008
|
additionalProperties: false
|
|
757
1009
|
},
|
|
758
1010
|
execute: async (_toolCallId: unknown, params: ExposureParams = {}) =>
|
|
759
|
-
toToolResult(await runExposureCheck(config, params))
|
|
1011
|
+
toToolResult(await runExposureCheck(config, commandRunner, params))
|
|
760
1012
|
});
|
|
761
1013
|
|
|
762
1014
|
api.registerTool?.({
|
|
@@ -790,7 +1042,22 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
790
1042
|
additionalProperties: false
|
|
791
1043
|
},
|
|
792
1044
|
execute: async (_toolCallId: unknown, params: DoctorParams = {}) =>
|
|
793
|
-
toToolResult(await runDoctor(runtime, config, params))
|
|
1045
|
+
toToolResult(await runDoctor(runtime, config, commandRunner, params))
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
api.registerTool?.({
|
|
1049
|
+
name: 'plashboard_permissions_fix',
|
|
1050
|
+
description: 'Apply compatibility file modes for dashboard web readers (explicit action).',
|
|
1051
|
+
optional: true,
|
|
1052
|
+
parameters: {
|
|
1053
|
+
type: 'object',
|
|
1054
|
+
properties: {
|
|
1055
|
+
dashboard_output_path: { type: 'string' }
|
|
1056
|
+
},
|
|
1057
|
+
additionalProperties: false
|
|
1058
|
+
},
|
|
1059
|
+
execute: async (_toolCallId: unknown, params: PermissionsFixParams = {}) =>
|
|
1060
|
+
toToolResult(await runPermissionsFix(config, params))
|
|
794
1061
|
});
|
|
795
1062
|
|
|
796
1063
|
api.registerTool?.({
|
|
@@ -801,8 +1068,10 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
801
1068
|
type: 'object',
|
|
802
1069
|
properties: {
|
|
803
1070
|
fill_provider: { type: 'string', enum: ['mock', 'command', 'openclaw'] },
|
|
1071
|
+
allow_command_fill: { type: 'boolean' },
|
|
804
1072
|
fill_command: { type: 'string' },
|
|
805
1073
|
openclaw_fill_agent_id: { type: 'string' },
|
|
1074
|
+
session_strategy: { type: 'string', enum: ['persistent', 'ephemeral'] },
|
|
806
1075
|
auto_seed_template: { type: 'boolean' },
|
|
807
1076
|
data_dir: { type: 'string' },
|
|
808
1077
|
scheduler_tick_seconds: { type: 'number' },
|
|
@@ -817,7 +1086,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
817
1086
|
additionalProperties: false
|
|
818
1087
|
},
|
|
819
1088
|
execute: async (_toolCallId: unknown, params: SetupParams = {}) =>
|
|
820
|
-
toToolResult(await runSetup(api, config, params))
|
|
1089
|
+
toToolResult(await runSetup(api, config, commandRunner, params))
|
|
821
1090
|
});
|
|
822
1091
|
|
|
823
1092
|
api.registerTool?.({
|
|
@@ -849,7 +1118,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
849
1118
|
additionalProperties: false
|
|
850
1119
|
},
|
|
851
1120
|
execute: async (_toolCallId: unknown, params: QuickstartParams = {}) =>
|
|
852
|
-
toToolResult(await runQuickstart(runtime, config, params))
|
|
1121
|
+
toToolResult(await runQuickstart(runtime, config, commandRunner, params))
|
|
853
1122
|
});
|
|
854
1123
|
|
|
855
1124
|
api.registerTool?.({
|
|
@@ -1049,7 +1318,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1049
1318
|
const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
|
|
1050
1319
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
1051
1320
|
return toCommandResult(
|
|
1052
|
-
await runExposureCheck(config, {
|
|
1321
|
+
await runExposureCheck(config, commandRunner, {
|
|
1053
1322
|
local_url: localUrl,
|
|
1054
1323
|
tailscale_https_port: portToken ? Number(portToken) : undefined
|
|
1055
1324
|
})
|
|
@@ -1070,13 +1339,20 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1070
1339
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
1071
1340
|
const repoDir = rest.find((token) => token.startsWith('/'));
|
|
1072
1341
|
return toCommandResult(
|
|
1073
|
-
await runDoctor(runtime, config, {
|
|
1342
|
+
await runDoctor(runtime, config, commandRunner, {
|
|
1074
1343
|
local_url: localUrl,
|
|
1075
1344
|
tailscale_https_port: portToken ? Number(portToken) : undefined,
|
|
1076
1345
|
repo_dir: repoDir
|
|
1077
1346
|
})
|
|
1078
1347
|
);
|
|
1079
1348
|
}
|
|
1349
|
+
if (cmd === 'fix-permissions') {
|
|
1350
|
+
return toCommandResult(
|
|
1351
|
+
await runPermissionsFix(config, {
|
|
1352
|
+
dashboard_output_path: rest[0]
|
|
1353
|
+
})
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1080
1356
|
if (cmd === 'onboard') {
|
|
1081
1357
|
const localUrl = rest.find((token) => token.startsWith('http://') || token.startsWith('https://'));
|
|
1082
1358
|
const portToken = rest.find((token) => /^[0-9]+$/.test(token));
|
|
@@ -1084,7 +1360,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1084
1360
|
const descriptionTokens = rest.filter((token) => token !== localUrl && token !== portToken && token !== repoDir);
|
|
1085
1361
|
const description = descriptionTokens.join(' ').trim() || undefined;
|
|
1086
1362
|
return toCommandResult(
|
|
1087
|
-
await runOnboard(runtime, config, {
|
|
1363
|
+
await runOnboard(runtime, config, commandRunner, {
|
|
1088
1364
|
description,
|
|
1089
1365
|
local_url: localUrl,
|
|
1090
1366
|
tailscale_https_port: portToken ? Number(portToken) : undefined,
|
|
@@ -1098,8 +1374,9 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1098
1374
|
const fillCommand = fillProvider === 'command' ? rest.slice(1).join(' ').trim() || undefined : undefined;
|
|
1099
1375
|
const fillAgentId = fillProvider === 'openclaw' ? (rest[1] || '').trim() || undefined : undefined;
|
|
1100
1376
|
return toCommandResult(
|
|
1101
|
-
await runSetup(api, config, {
|
|
1377
|
+
await runSetup(api, config, commandRunner, {
|
|
1102
1378
|
fill_provider: fillProvider,
|
|
1379
|
+
allow_command_fill: fillProvider === 'command' ? true : undefined,
|
|
1103
1380
|
fill_command: fillCommand,
|
|
1104
1381
|
openclaw_fill_agent_id: fillAgentId
|
|
1105
1382
|
})
|
|
@@ -1107,7 +1384,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1107
1384
|
}
|
|
1108
1385
|
if (cmd === 'quickstart') {
|
|
1109
1386
|
const description = rest.join(' ').trim() || undefined;
|
|
1110
|
-
return toCommandResult(await runQuickstart(runtime, config, { description }));
|
|
1387
|
+
return toCommandResult(await runQuickstart(runtime, config, commandRunner, { description }));
|
|
1111
1388
|
}
|
|
1112
1389
|
if (cmd === 'init') return toCommandResult(await runtime.init());
|
|
1113
1390
|
if (cmd === 'status') return toCommandResult(await runtime.status());
|
|
@@ -1133,7 +1410,7 @@ export function registerPlashboardPlugin(api: UnknownApi): void {
|
|
|
1133
1410
|
return toCommandResult({
|
|
1134
1411
|
ok: false,
|
|
1135
1412
|
errors: [
|
|
1136
|
-
'unknown command. supported: onboard <description> [local_url] [https_port] [repo_dir], setup [openclaw [agent_id]|mock|command <fill_command>], quickstart <description>, doctor [local_url] [https_port] [repo_dir], web-guide [local_url] [repo_dir], expose-guide [local_url] [https_port], expose-check [local_url] [https_port], init, status, list, activate <id>, delete <id>, copy <src> <new-id> [new-name] [activate], run <id>, set-display <width> <height> <top> <bottom>'
|
|
1413
|
+
'unknown command. supported: onboard <description> [local_url] [https_port] [repo_dir], setup [openclaw [agent_id]|mock|command <fill_command>], quickstart <description>, doctor [local_url] [https_port] [repo_dir], fix-permissions [dashboard_output_path], web-guide [local_url] [repo_dir], expose-guide [local_url] [https_port], expose-check [local_url] [https_port], init, status, list, activate <id>, delete <id>, copy <src> <new-id> [new-name] [activate], run <id>, set-display <width> <height> <top> <bottom>'
|
|
1137
1414
|
]
|
|
1138
1415
|
});
|
|
1139
1416
|
}
|