@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 +1 -1
- package/src/core/agentEvents.js +13 -2
- package/src/core/agentEvents.test.js +17 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/mcp.js +1 -1
- package/src/core/runtimeLog.js +46 -1
- package/src/core/runtimeLog.test.js +22 -19
- package/src/runtime/runner.test.js +6 -6
package/package.json
CHANGED
package/src/core/agentEvents.js
CHANGED
|
@@ -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}
|
|
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
|
+
});
|
package/src/core/buildInfo.json
CHANGED
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.
|
|
4
|
+
const WIKI_MANAGER_VERSION = '0.15.72';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
package/src/core/runtimeLog.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
assert.match(line,
|
|
53
|
-
assert.match(line, /
|
|
54
|
-
assert.match(line, /
|
|
55
|
-
assert.match(line, /
|
|
56
|
-
assert.match(line, /
|
|
57
|
-
assert.match(line, /
|
|
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, /
|
|
115
|
-
// The taskId's UUID prefix and hash suffix are dropped entirely: the
|
|
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, /
|
|
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
|
-
|
|
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
|
-
|
|
792
|
-
assert.match(logs, /
|
|
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 () => {
|