@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/publisher.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { runAndReadStdout, type CommandRunner } from './command-runner.js';
|
|
4
4
|
import type { DisplayProfile, PlashboardConfig } from './types.js';
|
|
5
5
|
|
|
6
6
|
interface PublishOptions {
|
|
@@ -9,49 +9,11 @@ interface PublishOptions {
|
|
|
9
9
|
displayProfile: DisplayProfile;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function spawnPython(
|
|
13
|
-
pythonBin: string,
|
|
14
|
-
scriptPath: string,
|
|
15
|
-
args: string[],
|
|
16
|
-
profile: DisplayProfile,
|
|
17
|
-
tolerance: number
|
|
18
|
-
): Promise<string> {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const child = spawn(pythonBin, [scriptPath, ...args], {
|
|
21
|
-
env: {
|
|
22
|
-
...process.env,
|
|
23
|
-
PLASH_TARGET_VIEWPORT_HEIGHT: String(profile.height_px),
|
|
24
|
-
PLASH_LAYOUT_SAFETY_MARGIN: String(profile.layout_safety_margin_px),
|
|
25
|
-
PLASH_LAYOUT_OVERFLOW_TOLERANCE: String(tolerance),
|
|
26
|
-
PLASH_FRAME_GUTTER_TOP: String(profile.safe_top_px),
|
|
27
|
-
PLASH_FRAME_GUTTER_BOTTOM: String(profile.safe_bottom_px)
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
let stdout = '';
|
|
32
|
-
let stderr = '';
|
|
33
|
-
|
|
34
|
-
child.stdout.on('data', (chunk) => {
|
|
35
|
-
stdout += String(chunk);
|
|
36
|
-
});
|
|
37
|
-
child.stderr.on('data', (chunk) => {
|
|
38
|
-
stderr += String(chunk);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
child.on('error', (error) => reject(error));
|
|
42
|
-
|
|
43
|
-
child.on('close', (code) => {
|
|
44
|
-
if (code !== 0) {
|
|
45
|
-
reject(new Error(stderr.trim() || stdout.trim() || `writer script failed with code ${code}`));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
resolve(stdout.trim());
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
12
|
export class DashboardValidatorPublisher {
|
|
54
|
-
constructor(
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly config: PlashboardConfig,
|
|
15
|
+
private readonly commandRunner: CommandRunner | null
|
|
16
|
+
) {}
|
|
55
17
|
|
|
56
18
|
async validateOnly(payload: Record<string, unknown>, displayProfile: DisplayProfile): Promise<void> {
|
|
57
19
|
await this.run(payload, {
|
|
@@ -75,22 +37,28 @@ export class DashboardValidatorPublisher {
|
|
|
75
37
|
try {
|
|
76
38
|
await writeFile(inputPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
77
39
|
|
|
78
|
-
const
|
|
40
|
+
const argv = [this.config.python_bin, this.config.writer_script_path, '--input', inputPath];
|
|
79
41
|
if (options.validateOnly) {
|
|
80
|
-
|
|
42
|
+
argv.push('--validate-only');
|
|
81
43
|
} else {
|
|
82
|
-
|
|
44
|
+
argv.push('--output', options.outputPath);
|
|
83
45
|
}
|
|
84
46
|
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
47
|
+
return await runAndReadStdout(
|
|
48
|
+
this.commandRunner,
|
|
49
|
+
argv,
|
|
50
|
+
{
|
|
51
|
+
timeoutMs: Math.max(15, this.config.session_timeout_seconds) * 1000,
|
|
52
|
+
env: {
|
|
53
|
+
PLASH_TARGET_VIEWPORT_HEIGHT: String(options.displayProfile.height_px),
|
|
54
|
+
PLASH_LAYOUT_SAFETY_MARGIN: String(options.displayProfile.layout_safety_margin_px),
|
|
55
|
+
PLASH_LAYOUT_OVERFLOW_TOLERANCE: String(this.config.layout_overflow_tolerance_px),
|
|
56
|
+
PLASH_FRAME_GUTTER_TOP: String(options.displayProfile.safe_top_px),
|
|
57
|
+
PLASH_FRAME_GUTTER_BOTTOM: String(options.displayProfile.safe_bottom_px)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
'dashboard writer'
|
|
91
61
|
);
|
|
92
|
-
|
|
93
|
-
return output;
|
|
94
62
|
} finally {
|
|
95
63
|
await rm(tempDir, { recursive: true, force: true });
|
|
96
64
|
}
|
package/src/runtime.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { describe, expect, it } from 'vitest';
|
|
5
5
|
import { PlashboardRuntime } from './runtime.js';
|
|
6
6
|
import type { DashboardTemplate, PlashboardConfig } from './types.js';
|
|
7
|
+
import type { CommandRunner } from './command-runner.js';
|
|
7
8
|
|
|
8
9
|
function baseDashboard() {
|
|
9
10
|
return {
|
|
@@ -63,7 +64,9 @@ async function setupRuntime(overrides: Partial<PlashboardConfig> = {}) {
|
|
|
63
64
|
session_timeout_seconds: 30,
|
|
64
65
|
auto_seed_template: false,
|
|
65
66
|
fill_provider: 'mock',
|
|
67
|
+
allow_command_fill: false,
|
|
66
68
|
fill_command: undefined,
|
|
69
|
+
session_strategy: 'persistent',
|
|
67
70
|
python_bin: 'python3',
|
|
68
71
|
writer_script_path: join(process.cwd(), 'scripts', 'dashboard_write.py'),
|
|
69
72
|
dashboard_output_path: join(root, 'dashboard.json'),
|
|
@@ -80,11 +83,57 @@ async function setupRuntime(overrides: Partial<PlashboardConfig> = {}) {
|
|
|
80
83
|
...overrides
|
|
81
84
|
};
|
|
82
85
|
|
|
83
|
-
const runtime = new PlashboardRuntime(config
|
|
86
|
+
const runtime = new PlashboardRuntime(config, undefined, {
|
|
87
|
+
commandRunner: createTestCommandRunner()
|
|
88
|
+
});
|
|
84
89
|
await runtime.init();
|
|
85
90
|
return { runtime, root, config };
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
function createTestCommandRunner(): CommandRunner {
|
|
94
|
+
return async (argv: string[]) => {
|
|
95
|
+
if (argv[0] === 'openclaw' && argv[1] === 'agent') {
|
|
96
|
+
return {
|
|
97
|
+
stdout: '{"values":{"summary":"updated summary from openclaw"}}',
|
|
98
|
+
stderr: '',
|
|
99
|
+
code: 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const inputIndex = argv.indexOf('--input');
|
|
104
|
+
if (argv[0] === 'python3' && inputIndex >= 0) {
|
|
105
|
+
const inputPath = argv[inputIndex + 1];
|
|
106
|
+
const outputIndex = argv.indexOf('--output');
|
|
107
|
+
const validateOnly = argv.includes('--validate-only');
|
|
108
|
+
const inputText = await readFile(inputPath, 'utf8');
|
|
109
|
+
const payload = JSON.parse(inputText) as Record<string, unknown>;
|
|
110
|
+
|
|
111
|
+
const normalized: Record<string, unknown> = {
|
|
112
|
+
...payload,
|
|
113
|
+
version: typeof payload.version === 'string' ? payload.version : '3.0',
|
|
114
|
+
generated_at: new Date().toISOString()
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (!validateOnly && outputIndex >= 0) {
|
|
118
|
+
const outputPath = argv[outputIndex + 1];
|
|
119
|
+
await writeFile(outputPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
stdout: 'ok',
|
|
124
|
+
stderr: '',
|
|
125
|
+
code: 0
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
stdout: '',
|
|
131
|
+
stderr: `unsupported test command: ${argv.join(' ')}`,
|
|
132
|
+
code: 1
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
88
137
|
describe('PlashboardRuntime', () => {
|
|
89
138
|
it('creates template and runs pipeline with publish', async () => {
|
|
90
139
|
const { runtime, root, config } = await setupRuntime();
|
|
@@ -175,6 +224,8 @@ describe('PlashboardRuntime', () => {
|
|
|
175
224
|
const status = await runtime.status();
|
|
176
225
|
expect(status.ok).toBe(true);
|
|
177
226
|
expect(status.data?.active_template_id).toBe('starter');
|
|
227
|
+
expect(status.data?.capabilities.runtime_command_runner_available).toBe(true);
|
|
228
|
+
expect(status.data?.capabilities.command_fill_allowed).toBe(false);
|
|
178
229
|
} finally {
|
|
179
230
|
await rm(root, { recursive: true, force: true });
|
|
180
231
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { collectCurrentValues, mergeTemplateValues, validateFieldPointers } from
|
|
|
18
18
|
import { validateFillShape, validateTemplateShape } from './schema-validation.js';
|
|
19
19
|
import { DashboardValidatorPublisher } from './publisher.js';
|
|
20
20
|
import { createFillRunner, type FillRunnerDeps } from './fill-runner.js';
|
|
21
|
+
import type { CommandRunner } from './command-runner.js';
|
|
21
22
|
|
|
22
23
|
interface Logger {
|
|
23
24
|
info(message: string, ...args: unknown[]): void;
|
|
@@ -76,6 +77,7 @@ export class PlashboardRuntime {
|
|
|
76
77
|
private readonly runStore: RunStore;
|
|
77
78
|
private readonly publisher: DashboardValidatorPublisher;
|
|
78
79
|
private readonly fillRunner: FillRunner;
|
|
80
|
+
private readonly runtimeCommandRunnerAvailable: boolean;
|
|
79
81
|
|
|
80
82
|
private schedulerTimer: NodeJS.Timeout | null = null;
|
|
81
83
|
private tickInProgress = false;
|
|
@@ -91,7 +93,9 @@ export class PlashboardRuntime {
|
|
|
91
93
|
this.stateStore = new StateStore(this.paths);
|
|
92
94
|
this.templateStore = new TemplateStore(this.paths);
|
|
93
95
|
this.runStore = new RunStore(this.paths);
|
|
94
|
-
|
|
96
|
+
const commandRunner: CommandRunner | null = fillRunnerDeps.commandRunner ?? null;
|
|
97
|
+
this.runtimeCommandRunnerAvailable = Boolean(commandRunner);
|
|
98
|
+
this.publisher = new DashboardValidatorPublisher(config, commandRunner);
|
|
95
99
|
this.fillRunner = createFillRunner(config, fillRunnerDeps);
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -462,6 +466,10 @@ export class PlashboardRuntime {
|
|
|
462
466
|
template_count: templates.length,
|
|
463
467
|
enabled_template_count: templates.filter((entry) => entry.enabled).length,
|
|
464
468
|
running_template_ids: [...this.runningTemplates],
|
|
469
|
+
capabilities: {
|
|
470
|
+
runtime_command_runner_available: this.runtimeCommandRunnerAvailable,
|
|
471
|
+
command_fill_allowed: this.config.allow_command_fill
|
|
472
|
+
},
|
|
465
473
|
state
|
|
466
474
|
}
|
|
467
475
|
};
|
package/src/types.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface ModelDefaults {
|
|
|
15
15
|
max_tokens?: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export type SessionStrategy = 'persistent' | 'ephemeral';
|
|
19
|
+
|
|
18
20
|
export interface PlashboardConfig {
|
|
19
21
|
data_dir: string;
|
|
20
22
|
timezone: string;
|
|
@@ -25,8 +27,10 @@ export interface PlashboardConfig {
|
|
|
25
27
|
session_timeout_seconds: number;
|
|
26
28
|
auto_seed_template: boolean;
|
|
27
29
|
fill_provider: 'command' | 'mock' | 'openclaw';
|
|
30
|
+
allow_command_fill: boolean;
|
|
28
31
|
fill_command?: string;
|
|
29
32
|
openclaw_fill_agent_id?: string;
|
|
33
|
+
session_strategy: SessionStrategy;
|
|
30
34
|
python_bin: string;
|
|
31
35
|
writer_script_path: string;
|
|
32
36
|
dashboard_output_path: string;
|
|
@@ -137,5 +141,9 @@ export interface RuntimeStatus {
|
|
|
137
141
|
template_count: number;
|
|
138
142
|
enabled_template_count: number;
|
|
139
143
|
running_template_ids: string[];
|
|
144
|
+
capabilities: {
|
|
145
|
+
runtime_command_runner_available: boolean;
|
|
146
|
+
command_fill_allowed: boolean;
|
|
147
|
+
};
|
|
140
148
|
state: PlashboardState;
|
|
141
149
|
}
|