@worca/ui 0.1.0-rc.1 → 0.1.0-rc.3

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.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Protocol definitions for worca-ui WebSocket communication.
3
+ */
4
+
5
+ /** @typedef {'subscribe-run'|'unsubscribe-run'|'subscribe-log'|'unsubscribe-log'|'list-runs'|'get-agent-prompt'|'get-preferences'|'set-preferences'|'stop-run'|'resume-run'|'list-beads-issues'|'start-beads-issue'|'list-beads-counts'|'list-beads-refs'|'list-beads-unlinked'|'run-snapshot'|'run-update'|'runs-list'|'log-line'|'log-bulk'|'preferences'|'run-started'|'run-stopped'|'stage-restarted'|'beads-update'} MessageType */
6
+
7
+ /** @type {MessageType[]} */
8
+ export const MESSAGE_TYPES = [
9
+ 'subscribe-run',
10
+ 'unsubscribe-run',
11
+ 'subscribe-log',
12
+ 'unsubscribe-log',
13
+ 'list-runs',
14
+ 'get-agent-prompt',
15
+ 'get-preferences',
16
+ 'set-preferences',
17
+ 'stop-run',
18
+ 'resume-run',
19
+ 'list-beads-issues',
20
+ 'start-beads-issue',
21
+ 'list-beads-by-run',
22
+ 'list-beads-counts',
23
+ 'list-beads-refs',
24
+ 'list-beads-unlinked',
25
+ // Webhook inbox
26
+ 'get-webhook-inbox',
27
+ 'set-webhook-control',
28
+ 'clear-webhook-inbox',
29
+ // Protocol handshake
30
+ 'hello',
31
+ 'hello-ack',
32
+ // Parallel pipelines
33
+ 'list-pipelines',
34
+ 'subscribe-pipeline',
35
+ 'unsubscribe-pipeline',
36
+ 'pipeline-status-changed',
37
+ 'pipelines-list',
38
+ // Server → Client events
39
+ 'run-snapshot',
40
+ 'run-update',
41
+ 'runs-list',
42
+ 'log-line',
43
+ 'log-bulk',
44
+ 'preferences',
45
+ 'run-started',
46
+ 'run-stopped',
47
+ 'stage-restarted',
48
+ 'beads-update',
49
+ 'webhook-inbox-event',
50
+ 'webhook-control-changed',
51
+ 'webhook-inbox-cleared',
52
+ ];
53
+
54
+ export function nextId() {
55
+ const now = Date.now().toString(36);
56
+ const rand = Math.random().toString(36).slice(2, 8);
57
+ return `${now}-${rand}`;
58
+ }
59
+
60
+ export function makeRequest(type, payload, id = nextId()) {
61
+ return { id, type, payload };
62
+ }
63
+
64
+ export function makeOk(req, payload) {
65
+ return { id: req.id, ok: true, type: req.type, payload };
66
+ }
67
+
68
+ export function makeError(req, code, message, details) {
69
+ return {
70
+ id: req.id,
71
+ ok: false,
72
+ type: req.type,
73
+ error: { code, message, details },
74
+ };
75
+ }
76
+
77
+ export function isMessageType(value) {
78
+ return typeof value === 'string' && MESSAGE_TYPES.includes(value);
79
+ }
80
+
81
+ function isRecord(value) {
82
+ return !!value && typeof value === 'object' && !Array.isArray(value);
83
+ }
84
+
85
+ export function isRequest(value) {
86
+ if (!isRecord(value)) return false;
87
+ return typeof value.id === 'string' && typeof value.type === 'string';
88
+ }
89
+
90
+ export function decodeRequest(json) {
91
+ if (!isRequest(json)) throw new Error('Invalid request envelope');
92
+ return json;
93
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Canonical pipeline stage order — single source of truth for the UI.
3
+ * Mirrors stages.py STAGE_ORDER on the Python side.
4
+ */
5
+ export const STAGE_ORDER = [
6
+ 'preflight',
7
+ 'plan',
8
+ 'plan_review',
9
+ 'coordinate',
10
+ 'implement',
11
+ 'test',
12
+ 'review',
13
+ 'pr',
14
+ 'learn',
15
+ ];
16
+
17
+ /** Stage order with orchestrator prepended (for log display). */
18
+ export const STAGE_ORDER_WITH_ORCHESTRATOR = ['orchestrator', ...STAGE_ORDER];
19
+
20
+ /**
21
+ * Sort stage entries by canonical order. Unknown stages sort to the end.
22
+ */
23
+ export function sortByStageOrder(entries) {
24
+ return [...entries].sort(([a], [b]) => {
25
+ const ai = STAGE_ORDER.indexOf(a);
26
+ const bi = STAGE_ORDER.indexOf(b);
27
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Sort stage name strings by canonical order. Unknown stages sort to the end.
33
+ */
34
+ export function sortStageNames(names) {
35
+ return [...names].sort((a, b) => {
36
+ const ai = STAGE_ORDER.indexOf(a);
37
+ const bi = STAGE_ORDER.indexOf(b);
38
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
39
+ });
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.1.0-rc.1",
3
+ "version": "0.1.0-rc.3",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -31,6 +31,8 @@
31
31
  "app/main.bundle.js.map",
32
32
  "app/styles.css",
33
33
  "app/vendor/",
34
+ "app/protocol.js",
35
+ "app/utils/stage-order.js",
34
36
  "scripts/build-frontend.js"
35
37
  ],
36
38
  "engines": {
package/server/index.js CHANGED
@@ -31,6 +31,7 @@ function findProjectRoot(startDir) {
31
31
  return startDir; // fallback
32
32
  }
33
33
 
34
+ import { checkWorcaVersion } from './version-check.js';
34
35
  import { createInbox } from './webhook-inbox.js';
35
36
 
36
37
  let projectRoot, worcaDir, settingsPath;
@@ -102,6 +103,18 @@ app.locals.broadcast = broadcast;
102
103
  app.locals.scheduleRefresh = scheduleRefresh;
103
104
  app.locals.resolveRunProject = resolveRunProject;
104
105
 
106
+ // ─── worca-cc version check (non-blocking) ─────────────────────────────
107
+ checkWorcaVersion().then((result) => {
108
+ app.locals.worcaVersion = result;
109
+ if (result.ok) {
110
+ console.log(`[version] ${result.message}`);
111
+ } else if (result.installed) {
112
+ console.warn(`[version] ${result.message}`);
113
+ } else {
114
+ console.log(`[version] ${result.message}`);
115
+ }
116
+ });
117
+
105
118
  // ─── inotify budget check (Linux only) ─────────────────────────────────
106
119
  if (platform() === 'linux') {
107
120
  try {
@@ -0,0 +1,82 @@
1
+ // server/version-check.js — worca-cc version compatibility check
2
+ import { execFile } from 'node:child_process';
3
+
4
+ /** Minimum worca-cc version required by this @worca/ui release. */
5
+ export const MIN_WORCA_CC = '0.6.0';
6
+
7
+ /**
8
+ * Parse the version string from `worca --version` output.
9
+ * Expected format: "worca-cc X.Y.Z" or "worca-cc X.Y.Zrc3"
10
+ * @param {string} output - stdout from `worca --version`
11
+ * @returns {string|null} version string or null if unparseable
12
+ */
13
+ export function parseWorcaVersion(output) {
14
+ const match = output.trim().match(/^worca-cc\s+(\S+)/);
15
+ return match ? match[1] : null;
16
+ }
17
+
18
+ /**
19
+ * Compare two semver-ish versions, ignoring pre-release suffixes.
20
+ * "0.6.0rc3" satisfies ">= 0.6.0".
21
+ * @param {string} installed - installed version (e.g. "0.6.0rc3")
22
+ * @param {string} minimum - minimum required version (e.g. "0.6.0")
23
+ * @returns {boolean} true if installed >= minimum
24
+ */
25
+ export function meetsMinimum(installed, minimum) {
26
+ const parse = (v) =>
27
+ v.split('.').map((s) => {
28
+ const n = parseInt(s, 10);
29
+ return Number.isNaN(n) ? 0 : n;
30
+ });
31
+ const inst = parse(installed);
32
+ const min = parse(minimum);
33
+ const len = Math.max(inst.length, min.length);
34
+ for (let i = 0; i < len; i++) {
35
+ const a = inst[i] || 0;
36
+ const b = min[i] || 0;
37
+ if (a > b) return true;
38
+ if (a < b) return false;
39
+ }
40
+ return true; // equal
41
+ }
42
+
43
+ /**
44
+ * Run `worca --version` and check compatibility.
45
+ * @returns {Promise<{ok: boolean, installed: string|null, minimum: string, message: string}>}
46
+ */
47
+ export async function checkWorcaVersion() {
48
+ const minimum = MIN_WORCA_CC;
49
+ try {
50
+ const output = await new Promise((resolve, reject) => {
51
+ execFile('worca', ['--version'], { timeout: 5000 }, (err, stdout) => {
52
+ if (err) return reject(err);
53
+ resolve(stdout);
54
+ });
55
+ });
56
+ const installed = parseWorcaVersion(output);
57
+ if (!installed) {
58
+ return {
59
+ ok: false,
60
+ installed: null,
61
+ minimum,
62
+ message: `worca CLI not found — install with 'pip install worca-cc'`,
63
+ };
64
+ }
65
+ const ok = meetsMinimum(installed, minimum);
66
+ return {
67
+ ok,
68
+ installed,
69
+ minimum,
70
+ message: ok
71
+ ? `worca-cc ${installed} — compatible`
72
+ : `WARNING: worca-cc ${installed} found, minimum ${minimum} required — run 'pip install --upgrade worca-cc'`,
73
+ };
74
+ } catch {
75
+ return {
76
+ ok: false,
77
+ installed: null,
78
+ minimum,
79
+ message: `worca CLI not found — install with 'pip install worca-cc'`,
80
+ };
81
+ }
82
+ }