@worca/ui 0.5.0 → 0.6.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/app/styles.css CHANGED
@@ -1504,7 +1504,8 @@ sl-details.log-history-panel::part(content) {
1504
1504
  font-size: 14px;
1505
1505
  font-weight: 600;
1506
1506
  color: var(--fg);
1507
- text-transform: capitalize;
1507
+ text-transform: uppercase;
1508
+ letter-spacing: 0.03em;
1508
1509
  }
1509
1510
 
1510
1511
  .settings-card-body {
@@ -1547,8 +1548,9 @@ sl-details.log-history-panel::part(content) {
1547
1548
 
1548
1549
  .settings-switch-row {
1549
1550
  display: flex;
1550
- align-items: center;
1551
- gap: 12px;
1551
+ flex-direction: column;
1552
+ align-items: flex-start;
1553
+ gap: 4px;
1552
1554
  padding: 6px 0;
1553
1555
  }
1554
1556
 
@@ -1576,19 +1578,10 @@ sl-details.log-history-panel::part(content) {
1576
1578
  align-items: center;
1577
1579
  gap: 4px;
1578
1580
  padding: 16px 0;
1579
- overflow-x: auto;
1581
+ flex-wrap: wrap;
1580
1582
  }
1581
1583
 
1582
1584
  .pipeline-stage-node {
1583
- display: flex;
1584
- flex-direction: column;
1585
- align-items: center;
1586
- gap: 4px;
1587
- padding: 10px 14px;
1588
- border: 1px solid var(--border-subtle);
1589
- border-radius: var(--radius);
1590
- background: var(--bg-secondary);
1591
- min-width: 140px;
1592
1585
  transition: opacity 0.2s, border-color 0.2s, background 0.2s;
1593
1586
  }
1594
1587
 
@@ -4015,7 +4008,7 @@ sl-details.learnings-panel::part(content) {
4015
4008
  .version-row { display: flex; align-items: center; padding: 4px 0; gap: 8px; }
4016
4009
  .version-row-label { font-size: 12px; color: var(--muted); white-space: nowrap; min-width: 64px; }
4017
4010
  .version-row-value { font-size: 13px; font-weight: 500; color: var(--fg); font-family: var(--sl-font-mono); white-space: nowrap; margin-left: auto; display: flex; align-items: center; gap: 6px; }
4018
- .settings-card-header > sl-badge { margin-left: auto; }
4011
+ .settings-card-header > sl-badge, .settings-card-header > sl-switch { margin-left: auto; }
4019
4012
  .version-title-exact { text-transform: none; }
4020
4013
  .version-copy-btn {
4021
4014
  background: none; border: 1px solid var(--border); border-radius: 4px;
@@ -4025,6 +4018,9 @@ sl-details.learnings-panel::part(content) {
4025
4018
  }
4026
4019
  .version-copy-btn:hover { background: var(--bg-tertiary); color: var(--fg); }
4027
4020
  .version-copy-icon { display: inline-flex; align-items: center; min-width: 12px; }
4021
+ .install-path-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 8px 12px; background: var(--bg-secondary); border-radius: var(--radius); border: 1px solid var(--border-subtle); }
4022
+ .install-path-label { font-size: 12px; color: var(--muted); white-space: nowrap; }
4023
+ .install-path-value { font-size: 12px; color: var(--fg); font-family: var(--sl-font-mono); word-break: break-all; }
4028
4024
  .version-refresh { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
4029
4025
  .version-refresh-hint { font-size: 11px; color: var(--muted); }
4030
4026
  .project-worca-version { font-size: 11px; color: var(--muted); font-family: var(--sl-font-mono); margin-top: 2px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -1,9 +1,13 @@
1
1
  // server/versions.js — version fetching + caching for worca-cc and @worca/ui
2
2
  import { execFileSync } from 'node:child_process';
3
3
  import { readFileSync } from 'node:fs';
4
- import { join } from 'node:path';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { readPreferences } from './preferences.js';
6
7
 
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const INSTALL_DIR = dirname(__dirname); // worca-ui root
10
+
7
11
  /** Cache: { data, timestamp } */
8
12
  let _cache = null;
9
13
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
@@ -66,18 +70,19 @@ export async function fetchNpmVersions(packageName) {
66
70
  }
67
71
  if (!res.ok) return nullResult;
68
72
  const data = await res.json();
69
- const latest = data['dist-tags']?.latest || null;
73
+ const distLatest = data['dist-tags']?.latest || null;
70
74
  let latestRc = data['dist-tags']?.rc || null;
71
- if (!latestRc && data.versions) {
72
- // Scan version keys for highest *-rc.* pattern
73
- let bestRc = null;
74
- let bestRcNum = -1;
75
+
76
+ // Scan all versions for the highest stable and highest RC
77
+ let bestStable = null;
78
+ let bestRc = null;
79
+ let bestRcNum = -1;
80
+ if (data.versions) {
75
81
  for (const ver of Object.keys(data.versions)) {
76
82
  const rcMatch = ver.match(/^(.+)-rc\.(\d+)$/);
77
83
  if (rcMatch) {
78
84
  const rcNum = parseInt(rcMatch[2], 10);
79
85
  const base = rcMatch[1];
80
- // Compare base+rcNum to find the highest RC
81
86
  if (
82
87
  !bestRc ||
83
88
  compareVersions(base, bestRc.base) > 0 ||
@@ -86,10 +91,21 @@ export async function fetchNpmVersions(packageName) {
86
91
  bestRc = { base, full: ver };
87
92
  bestRcNum = rcNum;
88
93
  }
94
+ } else if (!ver.includes('-')) {
95
+ // Stable version (no pre-release suffix)
96
+ if (!bestStable || compareVersions(ver, bestStable) > 0) {
97
+ bestStable = ver;
98
+ }
89
99
  }
90
100
  }
91
- if (bestRc) latestRc = bestRc.full;
92
101
  }
102
+ if (!latestRc && bestRc) latestRc = bestRc.full;
103
+
104
+ // Use dist-tags latest only if it's a stable version, otherwise fall back
105
+ // to the highest stable version found in the registry
106
+ const isRc = distLatest?.includes('-');
107
+ const latest = isRc ? bestStable || distLatest : distLatest;
108
+
93
109
  return { latest, latestRc };
94
110
  } catch {
95
111
  return nullResult;
@@ -144,13 +160,48 @@ export async function fetchPyPIVersions(packageName) {
144
160
  }
145
161
  }
146
162
 
163
+ /**
164
+ * Get git describe info for a repo: commits ahead of tag + dirty state.
165
+ * @param {string} repoPath
166
+ * @param {string} tagPrefix - e.g. 'worca-cc-v' or 'worca-ui-v'
167
+ * @returns {{ ahead: number, dirty: boolean } | null}
168
+ */
169
+ function getGitDevStatus(repoPath, tagPrefix) {
170
+ try {
171
+ const desc = execFileSync(
172
+ 'git',
173
+ ['describe', '--tags', '--long', '--match', `${tagPrefix}*`],
174
+ { cwd: repoPath, encoding: 'utf8', timeout: 3000 },
175
+ ).trim();
176
+ // Format: tagPrefix0.10.0-3-gabcdef
177
+ const m = desc.match(/-(\d+)-g[0-9a-f]+$/);
178
+ const ahead = m ? parseInt(m[1], 10) : 0;
179
+
180
+ const status = execFileSync('git', ['status', '--porcelain'], {
181
+ cwd: repoPath,
182
+ encoding: 'utf8',
183
+ timeout: 3000,
184
+ }).trim();
185
+ const dirty = status.length > 0;
186
+
187
+ return { ahead, dirty };
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
147
193
  /**
148
194
  * Read versions from local dev path.
149
195
  * @param {string} sourceRepo - path to local worca-cc repo
150
- * @returns {{ worcaCc: string|null, worcaUi: string|null }}
196
+ * @returns {{ worcaCc: string|null, worcaUi: string|null, worcaCcDev: object|null, worcaUiDev: object|null }}
151
197
  */
152
198
  export function getDevPathVersions(sourceRepo) {
153
- const result = { worcaCc: null, worcaUi: null };
199
+ const result = {
200
+ worcaCc: null,
201
+ worcaUi: null,
202
+ worcaCcDev: null,
203
+ worcaUiDev: null,
204
+ };
154
205
  if (!sourceRepo) return result;
155
206
  try {
156
207
  const pyproject = readFileSync(join(sourceRepo, 'pyproject.toml'), 'utf8');
@@ -167,6 +218,10 @@ export function getDevPathVersions(sourceRepo) {
167
218
  } catch {
168
219
  // package.json not found or unreadable
169
220
  }
221
+
222
+ result.worcaCcDev = getGitDevStatus(sourceRepo, 'worca-cc-v');
223
+ result.worcaUiDev = getGitDevStatus(sourceRepo, 'worca-ui-v');
224
+
170
225
  return result;
171
226
  }
172
227
 
@@ -246,9 +301,12 @@ export async function getVersionInfo({ prefsPath, worcaVersion, force } = {}) {
246
301
  path: sourceRepo,
247
302
  worcaCc: devVersions.worcaCc,
248
303
  worcaUi: devVersions.worcaUi,
304
+ worcaCcDev: devVersions.worcaCcDev,
305
+ worcaUiDev: devVersions.worcaUiDev,
249
306
  }
250
307
  : null,
251
308
  activeWorcaCc: devVersions?.worcaCc || installedCc,
309
+ installDir: INSTALL_DIR,
252
310
  cachedAt: new Date().toISOString(),
253
311
  };
254
312
 
@@ -35,24 +35,6 @@ import {
35
35
  import { readSettings } from './settings-reader.js';
36
36
  import { discoverRuns } from './watcher.js';
37
37
 
38
- // Legacy fallback prefixes — only used when status.json lacks a stored prompt
39
- const STAGE_PROMPT_PREFIX = {
40
- plan: 'Create a detailed implementation plan for the following work request. Write the plan to the designated plan file.\n\nWork request: ',
41
- coordinate:
42
- 'Decompose the following work request into Beads tasks with dependencies. Do NOT implement anything — only create tasks using `bd create`.\n\nWork request: ',
43
- implement:
44
- 'Implement the code changes described in the work request. Follow the plan and complete the tasks assigned to you.\n\nWork request: ',
45
- test: 'Review and test the implementation for the following work request. Run tests and report results. Do NOT modify code.\n\nWork request: ',
46
- review:
47
- 'Review the code changes for the following work request. Check for correctness, style, and adherence to the plan. Do NOT modify code.\n\nWork request: ',
48
- pr: 'Create a pull request for the following work request. Summarize the changes and ensure the commit history is clean.\n\nWork request: ',
49
- };
50
-
51
- function _buildStagePrompt(stage, rawPrompt) {
52
- const prefix = STAGE_PROMPT_PREFIX[stage];
53
- return prefix ? prefix + rawPrompt : rawPrompt;
54
- }
55
-
56
38
  /**
57
39
  * @param {{
58
40
  * watcherSets: Map<string, import('./watcher-set.js').WatcherSet>,
@@ -174,6 +156,7 @@ export function createMessageRouter({
174
156
  return;
175
157
  }
176
158
  const agentName = run.stages?.[stage]?.agent || stage;
159
+ const effectiveRunId = run.run_id || runId;
177
160
 
178
161
  const iterations = run.stages?.[stage]?.iterations || [];
179
162
  const iterationPrompts = iterations.map((iter, idx) => {
@@ -181,62 +164,116 @@ export function createMessageRouter({
181
164
  return { iteration: iter.number ?? idx, prompt };
182
165
  });
183
166
 
184
- const storedPrompt = run.stages?.[stage]?.prompt;
185
- let fallbackPrompt;
186
- let promptSource;
187
- if (storedPrompt) {
188
- fallbackPrompt = storedPrompt;
189
- promptSource = 'actual';
190
- } else {
191
- const rawPrompt =
192
- run.work_request?.description || run.work_request?.title || '';
193
- fallbackPrompt = _buildStagePrompt(stage, rawPrompt);
194
- promptSource = 'reconstructed';
195
- }
196
-
167
+ // User message (-p) — stored in status.json
168
+ const userPrompt = run.stages?.[stage]?.prompt || null;
197
169
  const hasIterationPrompts = iterationPrompts.some(
198
170
  (ip) => ip.prompt != null,
199
171
  );
200
- if (!hasIterationPrompts) {
172
+ if (!hasIterationPrompts && userPrompt) {
201
173
  for (const ip of iterationPrompts) {
202
- ip.prompt = fallbackPrompt;
174
+ ip.prompt = userPrompt;
203
175
  }
204
176
  }
205
177
 
206
- let agentInstructions = null;
207
- const candidates = [
208
- join(
209
- proj.worcaDir,
210
- 'runs',
211
- run.run_id || runId,
212
- 'agents',
213
- `${agentName}.md`,
214
- ),
215
- join(
216
- proj.worcaDir,
217
- 'results',
218
- run.run_id || runId,
219
- 'agents',
220
- `${agentName}.md`,
221
- ),
222
- ];
223
- for (const p of candidates) {
224
- if (existsSync(p)) {
225
- try {
226
- agentInstructions = readFileSync(p, 'utf8');
227
- } catch {
228
- /* ignore */
178
+ // Resolved agent prompt — the full document the agent actually received.
179
+ // Prefer per-iteration resolved files (W-037+), fall back to the
180
+ // unresolved agent template for pre-W-037 runs.
181
+ let resolvedPrompt = null;
182
+ const resolvedIterationPrompts = [];
183
+
184
+ // Collect per-iteration resolved files
185
+ // Filename format: {stage}-{agent}-iter-{N}.md (W-037+)
186
+ // Fallback: {agent}-iter-{N}.md (early W-037 runs)
187
+ for (const iter of iterations) {
188
+ const iterNum = iter.number ?? 0;
189
+ const resolvedCandidates = [
190
+ join(
191
+ proj.worcaDir,
192
+ 'runs',
193
+ effectiveRunId,
194
+ 'agents',
195
+ 'resolved',
196
+ `${stage}-${agentName}-iter-${iterNum}.md`,
197
+ ),
198
+ join(
199
+ proj.worcaDir,
200
+ 'results',
201
+ effectiveRunId,
202
+ 'agents',
203
+ 'resolved',
204
+ `${stage}-${agentName}-iter-${iterNum}.md`,
205
+ ),
206
+ // Fallback for early W-037 runs without stage prefix
207
+ join(
208
+ proj.worcaDir,
209
+ 'runs',
210
+ effectiveRunId,
211
+ 'agents',
212
+ 'resolved',
213
+ `${agentName}-iter-${iterNum}.md`,
214
+ ),
215
+ join(
216
+ proj.worcaDir,
217
+ 'results',
218
+ effectiveRunId,
219
+ 'agents',
220
+ 'resolved',
221
+ `${agentName}-iter-${iterNum}.md`,
222
+ ),
223
+ ];
224
+ let content = null;
225
+ for (const p of resolvedCandidates) {
226
+ if (existsSync(p)) {
227
+ try {
228
+ content = readFileSync(p, 'utf8');
229
+ } catch {
230
+ /* ignore */
231
+ }
232
+ break;
233
+ }
234
+ }
235
+ resolvedIterationPrompts.push({ iteration: iterNum, prompt: content });
236
+ if (!resolvedPrompt && content) resolvedPrompt = content;
237
+ }
238
+
239
+ // Fall back to unresolved agent template (pre-W-037 runs)
240
+ if (!resolvedPrompt) {
241
+ const templateCandidates = [
242
+ join(
243
+ proj.worcaDir,
244
+ 'runs',
245
+ effectiveRunId,
246
+ 'agents',
247
+ `${agentName}.md`,
248
+ ),
249
+ join(
250
+ proj.worcaDir,
251
+ 'results',
252
+ effectiveRunId,
253
+ 'agents',
254
+ `${agentName}.md`,
255
+ ),
256
+ ];
257
+ for (const p of templateCandidates) {
258
+ if (existsSync(p)) {
259
+ try {
260
+ resolvedPrompt = readFileSync(p, 'utf8');
261
+ } catch {
262
+ /* ignore */
263
+ }
264
+ break;
229
265
  }
230
- break;
231
266
  }
232
267
  }
268
+
233
269
  ws.send(
234
270
  JSON.stringify(
235
271
  makeOk(req, {
236
- agentInstructions,
237
- userPrompt: fallbackPrompt,
272
+ agentInstructions: resolvedPrompt,
273
+ userPrompt,
238
274
  iterationPrompts,
239
- promptSource,
275
+ resolvedIterationPrompts,
276
+ promptSource: userPrompt ? 'actual' : 'none',
240
277
  agent: agentName,
241
278
  }),
242
279
  ),