@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/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(private readonly config: PlashboardConfig) {}
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 args = ['--input', inputPath];
40
+ const argv = [this.config.python_bin, this.config.writer_script_path, '--input', inputPath];
79
41
  if (options.validateOnly) {
80
- args.push('--validate-only');
42
+ argv.push('--validate-only');
81
43
  } else {
82
- args.push('--output', options.outputPath);
44
+ argv.push('--output', options.outputPath);
83
45
  }
84
46
 
85
- const output = await spawnPython(
86
- this.config.python_bin,
87
- this.config.writer_script_path,
88
- args,
89
- options.displayProfile,
90
- this.config.layout_overflow_tolerance_px
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
  }
@@ -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
- this.publisher = new DashboardValidatorPublisher(config);
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
  }