@dotdrelle/wiki-manager 0.15.71 → 0.15.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.15.71",
3
+ "version": "0.15.72",
4
4
  "description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -342,9 +342,20 @@ function applyEvent(state, event) {
342
342
  case 'task.created':
343
343
  appendCreatedTask(state, event.payload?.task);
344
344
  return;
345
- case 'task.assigned':
345
+ case 'task.assigned': {
346
+ // The plan step stays the only place the UIs read WHO runs a task. The
347
+ // workflow projection (workflow.js taskNode) exposes step.executor, and
348
+ // serve's run-graph inspector renders it as the "Agent" row — without
349
+ // this mutation that row stayed empty ("—") while the agent was only
350
+ // visible in the run journal, under Runtime.
351
+ const assignedStep = planTaskById(state, event.taskId ?? event.payload?.taskId ?? '');
352
+ const assignedAgent = event.payload?.assignment?.agentInstanceId ?? event.payload?.agentInstanceId ?? null;
353
+ if (assignedStep && assignedAgent && !assignedStep.executor) {
354
+ assignedStep.executor = String(assignedAgent);
355
+ }
346
356
  appendLog(state, taskLogLine(state, event, 'assigned'));
347
357
  return;
358
+ }
348
359
  case 'task.started':
349
360
  // Silent: always follows `task.assigned` (same task, milliseconds apart),
350
361
  // which already printed the "started" line.
@@ -1044,7 +1055,7 @@ function formatPlanErrors(errors) {
1044
1055
  : 'unknown';
1045
1056
  }
1046
1057
 
1047
- const LOG_TIME_PREFIX = /^\d{2}:\d{2}:\d{2}\s+/;
1058
+ const LOG_TIME_PREFIX = /^\d{2}:\d{2}:\d{2}(?:\s+|\s*·\s*)/;
1048
1059
  const LOG_REPEAT_SUFFIX = / \(×\d+\)$/;
1049
1060
 
1050
1061
  /*
@@ -750,3 +750,20 @@ test('appendLog collapses repeated dispatch plumbing but never a repeated busine
750
750
  assert.equal(failLines.length, 2);
751
751
  assert.equal(failLines.some((l) => / \(×\d+\)$/.test(l)), false);
752
752
  });
753
+
754
+ test('task.assigned records the executor on the plan step for the UIs', () => {
755
+ const session = { workspace: 'w', agentEvents: [] };
756
+ dispatchAgentEvent(session, createAgentEvent('plan_set', {
757
+ origin: 'tool',
758
+ payload: { steps: [{ id: 't1', description: 'Ingest', status: 'pending' }] },
759
+ }));
760
+ dispatchAgentEvent(session, createAgentEvent('task.assigned', {
761
+ origin: 'assignment_manager',
762
+ runId: 'r1',
763
+ taskId: 't1',
764
+ payload: { taskId: 't1', assignment: { agentInstanceId: 'production-main', capability: 'knowledge.update' } },
765
+ }));
766
+ const step = (session.agentProjection.plan ?? []).find((candidate) => candidate.id === 't1' || candidate.step === 't1');
767
+ assert.ok(step, 'plan step exists');
768
+ assert.equal(step.executor, 'production-main');
769
+ });
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.15.71",
3
- "commit": "d5f51af"
2
+ "version": "0.15.72",
3
+ "commit": "fccddba"
4
4
  }
package/src/core/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { managerEnvFile, managerMcpEndpointsFile, readEnvFile } from './env.js';
3
3
 
4
- const WIKI_MANAGER_VERSION = '0.15.71';
4
+ const WIKI_MANAGER_VERSION = '0.15.72';
5
5
 
6
6
  function envValue(key) {
7
7
  const filePath = managerEnvFile();
@@ -32,6 +32,29 @@ const ORDERED_FIELDS = [
32
32
  'error',
33
33
  ];
34
34
 
35
+ // The dispatch events whose payload is routing plumbing, not business content:
36
+ // rendered compactly (see formatRuntimeLogPayload). Business events keep the
37
+ // full field=value form.
38
+ const COMPACT_EVENTS = new Set([
39
+ 'agent_status',
40
+ 'agent_execute',
41
+ 'job.accepted',
42
+ 'task.ready',
43
+ 'task.starting',
44
+ 'task.started',
45
+ 'task.completed',
46
+ 'task.failed',
47
+ 'attempt.created',
48
+ 'lock.acquired',
49
+ 'lock.released',
50
+ 'runtime.execute',
51
+ 'runtime.accepted',
52
+ 'runtime.result_returned',
53
+ 'task.result_returned',
54
+ 'runtime.params_refused',
55
+ 'runtime.blind',
56
+ ]);
57
+
35
58
  export function normalizeRuntimeLog(input, { session = null } = {}) {
36
59
  if (!input || typeof input !== 'object' || Array.isArray(input)) {
37
60
  return { message: String(input ?? '') };
@@ -69,6 +92,28 @@ export function formatRuntimeLogPayload(payload = {}, ts = null) {
69
92
  }
70
93
  const time = timeLabel(ts);
71
94
  const event = eventLabel(payload.event);
95
+ // Dispatch plumbing is rendered as a readable sentence, not a field dump:
96
+ // `AGENT_STATUS run=… plan=… group=… attempt=… agentType=… workspace=…`
97
+ // buried the one thing the reader wants — WHO does WHAT on WHICH task, on
98
+ // WHICH job. The verbosity is kept for business payloads, where fields are
99
+ // the content.
100
+ if (COMPACT_EVENTS.has(String(payload.event ?? ''))) {
101
+ const parts = [time, event];
102
+ const who = payload.agentInstanceId ?? payload.agentId ?? payload.agentType;
103
+ if (who) parts.push(shortenUuids(String(who)));
104
+ const what = [payload.capability, payload.operation].filter(Boolean).join('/');
105
+ if (what) parts.push(what);
106
+ const task = shortTaskLabel(payload.taskId);
107
+ if (task) parts.push(task);
108
+ if (payload.jobId) parts.push(shortenUuids(String(payload.jobId)));
109
+ if (payload.status != null) parts.push(String(payload.status));
110
+ if (payload.error) parts.push(shortenUuids(String(payload.error)).slice(0, 120));
111
+ if (payload.detail != null && payload.detail !== ''
112
+ && String(payload.detail).toUpperCase() !== event) {
113
+ parts.push(shortenUuids(String(payload.detail)));
114
+ }
115
+ return parts.join(' · ');
116
+ }
72
117
  const fields = ORDERED_FIELDS
73
118
  .map((key) => (key === 'taskId'
74
119
  ? formatField(FIELD_ALIASES[key], shortTaskLabel(payload[key]))
@@ -123,7 +168,7 @@ export function shortLogId(value, { maxLength = 40 } = {}) {
123
168
  // token — so this one shape separates the two without an event-name list
124
169
  // (which is what an earlier enumeration got wrong: it only ever matched the
125
170
  // two underscore-form events and missed every dotted one).
126
- const DISPATCH_PLUMBING_LINE = /^(?:\d{1,2}:\d{2}(?::\d{2})?\s+)?[A-Z][A-Z0-9_]{2,}(?:\s|$)/;
171
+ const DISPATCH_PLUMBING_LINE = /^(?:\d{1,2}:\d{2}(?::\d{2})?\s*(?:·\s*)?)?[A-Z][A-Z0-9_]{2,}(?:\s|$)/;
127
172
 
128
173
  export function isDispatchPlumbingLine(line) {
129
174
  return DISPATCH_PLUMBING_LINE.test(String(line ?? ''));
@@ -39,22 +39,27 @@ function payload(event) {
39
39
  };
40
40
  }
41
41
 
42
- test('formatRuntimeLogPayload formats every dispatcher cycle event with required ids', () => {
42
+ test('formatRuntimeLogPayload renders dispatch plumbing as a compact sentence, business events keep their fields', () => {
43
+ // Dispatch plumbing: who · what · task · job — no run/plan/group/attempt
44
+ // soup. Business events (capability.resolving, agent.selected,
45
+ // task.assigned) keep the full field=value form: those fields ARE the
46
+ // content.
43
47
  for (const event of CYCLE_EVENTS) {
44
48
  const line = formatRuntimeLogPayload(payload(event), '2026-07-08T14:42:18.000Z');
45
- assert.match(line, /^14:42:18 [A-Z_]+ /);
46
- assert.match(line, /run=run-123/);
47
- assert.match(line, /plan=4/);
48
- assert.match(line, /group=group-build/);
49
- assert.match(line, /task=task-build/);
50
- assert.match(line, /attempt=attempt-1/);
51
- assert.match(line, /agentType=production/);
52
- assert.match(line, /agentInstance=production-main/);
53
- assert.match(line, /agent=worker-02/);
54
- assert.match(line, /job=job-789/);
55
- assert.match(line, /workspace=docs/);
56
- assert.match(line, /capability=document\.build/);
57
- assert.match(line, /operation=build/);
49
+ if (['capability.resolving', 'agent.selected', 'task.assigned'].includes(event)) {
50
+ assert.match(line, /run=run-123/);
51
+ assert.match(line, /workspace=docs/);
52
+ assert.match(line, /capability=document\.build/);
53
+ assert.match(line, /operation=build/);
54
+ continue;
55
+ }
56
+ assert.match(line, /^14:42:18 · [A-Z_]+ · /);
57
+ assert.match(line, /production-main/);
58
+ assert.match(line, /document\.build\/build/);
59
+ assert.match(line, /task-build/);
60
+ assert.match(line, /job-789/);
61
+ assert.match(line, /cycle detail/);
62
+ assert.doesNotMatch(line, /run=|plan=|group=|attempt=|agentType=|agent=|workspace=|capability=|operation=/);
58
63
  }
59
64
  });
60
65
 
@@ -111,12 +116,10 @@ test('long UUIDs collapse to a short prefix so log lines stay on one line', () =
111
116
  operation: 'build',
112
117
  }, '2026-07-08T14:42:18.000Z');
113
118
 
114
- assert.match(line, /run=7fadad27…/);
115
- // The taskId's UUID prefix and hash suffix are dropped entirely: the field
119
+ assert.match(line, /production-7fadad27…/);
120
+ // The taskId's UUID prefix and hash suffix are dropped entirely: the line
116
121
  // names the work ("taxonomy synthesis"), not an opaque id.
117
- assert.match(line, /task="taxonomy synthesis"/);
118
- assert.match(line, /attempt=attempt-7fadad27…/);
119
- assert.match(line, /agentInstance=production-7fadad27…/);
122
+ assert.match(line, /taxonomy synthesis/);
120
123
  assert.doesNotMatch(line, /7fadad27-0be6-4d08-96e5-664fe7ee841e/);
121
124
  });
122
125
 
@@ -786,14 +786,14 @@ test('runRuntimeParallelPlan dispatches orchestrated tasks without child LLM loo
786
786
  for (const label of ['READY', 'RESOLVING', 'SELECTED', 'ASSIGNED', 'CREATED', 'ACQUIRED', 'AGENT_EXECUTE', 'ACCEPTED', 'AGENT_STATUS', 'RESULT_RETURNED', 'RELEASED', 'COMPLETED']) {
787
787
  assert.match(logs, new RegExp(`\\b${label}\\b`));
788
788
  }
789
- assert.match(logs, /run=run-dispatch/);
789
+ // Compact dispatch plumbing: the routing fields ride the line as a sentence
790
+ // (who · what · task · job), no run=/plan=/attempt= soup.
791
+ assert.match(logs, /AGENT_EXECUTE · production-main · workspace\.diagnose\/doctor · a\b/);
792
+ assert.match(logs, /ACCEPTED · production-main · workspace\.diagnose\/doctor · a · job-a · queued/);
790
793
  assert.match(logs, /task=a/);
791
- assert.match(logs, /attempt=a:attempt-1/);
792
- assert.match(logs, /agentInstance=production-main/);
793
- assert.match(logs, /job=job-a/);
794
+ // Business events keep the full field=value form.
795
+ assert.match(logs, /run=run-dispatch/);
794
796
  assert.match(logs, /workspace=demo-workspace/);
795
- assert.match(logs, /capability=workspace\.diagnose/);
796
- assert.match(logs, /operation=doctor/);
797
797
  });
798
798
 
799
799
  test('runRuntimeParallelPlan fails cleanly when scheduler budget is exceeded', async () => {