@dotdrelle/wiki-manager 0.14.12 → 0.14.13
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/activity.js +4 -0
- package/src/core/activity.test.js +31 -0
- package/src/core/agentEvents.js +11 -2
- package/src/core/buildInfo.json +2 -2
- package/src/core/env.js +16 -4
- package/src/core/mcp.js +1 -1
- package/src/core/wikiSetup.js +8 -1
- package/src/runtime/runner.js +19 -2
- package/src/runtime/runner.test.js +32 -1
- package/wiki-workspace +8 -3
package/package.json
CHANGED
package/src/core/activity.js
CHANGED
|
@@ -31,6 +31,9 @@ function normalizePlanSteps(steps) {
|
|
|
31
31
|
executor: s?.executor ?? null,
|
|
32
32
|
executorQuery: s?.executorQuery ?? null,
|
|
33
33
|
outputRefs: Array.isArray(s?.outputRefs) ? s.outputRefs.map(String) : [],
|
|
34
|
+
...(s?.status != null ? { status: String(s.status) } : {}),
|
|
35
|
+
...(s?.startedAt != null ? { startedAt: s.startedAt } : {}),
|
|
36
|
+
...(s?.finishedAt != null ? { finishedAt: s.finishedAt } : {}),
|
|
34
37
|
}));
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -149,6 +152,7 @@ function productionActivityFromPayload(payload, context = {}) {
|
|
|
149
152
|
step,
|
|
150
153
|
...(payload?.taskId ? { stepId: String(payload.taskId) } : {}),
|
|
151
154
|
},
|
|
155
|
+
plan: Array.isArray(progress?.steps) ? { steps: progress.steps } : null,
|
|
152
156
|
poll: jobId ? {
|
|
153
157
|
server: 'production',
|
|
154
158
|
tool: context.tool === 'agent_status' ? 'agent_status' : 'production_job_status',
|
|
@@ -109,6 +109,37 @@ test('extractActivity: documents conversion activity carries plan and percent',
|
|
|
109
109
|
assert.deepEqual(activity.plan.steps.map((step) => step.id), ['resolve', 'convert', 'write']);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
test('extractActivity: agent_status carries structured processing progress', () => {
|
|
113
|
+
const activity = extractActivity({
|
|
114
|
+
jobId: 'job-build',
|
|
115
|
+
taskId: 'build-report',
|
|
116
|
+
operation: 'build',
|
|
117
|
+
status: 'running',
|
|
118
|
+
progress: {
|
|
119
|
+
percent: 47,
|
|
120
|
+
phase: 'build',
|
|
121
|
+
detail: 'Batch 2/4 · LLM throttled',
|
|
122
|
+
stepIndex: 1,
|
|
123
|
+
stepTotal: 2,
|
|
124
|
+
steps: [
|
|
125
|
+
{ id: 'build', name: 'build', status: 'running' },
|
|
126
|
+
{ id: 'export', name: 'export', status: 'pending' },
|
|
127
|
+
],
|
|
128
|
+
batch: { index: 2, total: 4, status: 'running' },
|
|
129
|
+
throttling: { active: true, waitMs: 1200, retryAt: '2026-07-21T10:00:01Z' },
|
|
130
|
+
processing: { instructionCount: 8 },
|
|
131
|
+
},
|
|
132
|
+
}, { server: 'production', tool: 'agent_status' });
|
|
133
|
+
|
|
134
|
+
assert.deepEqual(activity.plan.steps.map((step) => [step.id, step.status]), [
|
|
135
|
+
['build', 'running'],
|
|
136
|
+
['export', 'pending'],
|
|
137
|
+
]);
|
|
138
|
+
assert.equal(activity.progress.batch.index, 2);
|
|
139
|
+
assert.equal(activity.progress.throttling.active, true);
|
|
140
|
+
assert.equal(activity.progress.processing.instructionCount, 8);
|
|
141
|
+
});
|
|
142
|
+
|
|
112
143
|
test('rememberActivity: returns normalized activity on success', () => {
|
|
113
144
|
const session = {};
|
|
114
145
|
const result = rememberActivity(session, { id: '1', status: 'running', source: 'x', kind: 'job' });
|
package/src/core/agentEvents.js
CHANGED
|
@@ -122,13 +122,22 @@ export function resetSessionProjection(session) {
|
|
|
122
122
|
|
|
123
123
|
function withSessionRunIdentity(event, session) {
|
|
124
124
|
const identity = session?._currentRunIdentity;
|
|
125
|
-
|
|
125
|
+
// The workspace must be attached even OUTSIDE a run identity: the SSE
|
|
126
|
+
// publisher drops events whose workspace does not match the (always
|
|
127
|
+
// workspace-scoped) serve UI client. Events dispatched between chained
|
|
128
|
+
// executions — e.g. the plan_set of the next action, run evaluation,
|
|
129
|
+
// assistant messages — used to leave with workspace=null and never reached
|
|
130
|
+
// the UI, which then displayed a stale plan from the previous action.
|
|
131
|
+
const workspace = event.workspace ?? identity?.workspace ?? session?.workspace ?? null;
|
|
132
|
+
if (!identity) {
|
|
133
|
+
return workspace === (event.workspace ?? null) ? event : { ...event, workspace };
|
|
134
|
+
}
|
|
126
135
|
return {
|
|
127
136
|
...event,
|
|
128
137
|
runId: event.runId ?? identity.runId ?? null,
|
|
129
138
|
turnId: event.turnId ?? identity.turnId ?? null,
|
|
130
139
|
taskId: event.taskId ?? identity.taskId ?? null,
|
|
131
|
-
workspace
|
|
140
|
+
workspace,
|
|
132
141
|
};
|
|
133
142
|
}
|
|
134
143
|
|
package/src/core/buildInfo.json
CHANGED
package/src/core/env.js
CHANGED
|
@@ -98,14 +98,26 @@ export function ensureManagerScaffold({ log = () => {} } = {}) {
|
|
|
98
98
|
return created;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Mirrors workspacesDir() in core/workspaces.js (which imports this module,
|
|
102
|
+
// so it cannot be imported here without a cycle).
|
|
103
|
+
function workspacesRootDir() {
|
|
104
|
+
return process.env.WIKI_WORKSPACES_DIR
|
|
105
|
+
? resolve(process.env.WIKI_WORKSPACES_DIR)
|
|
106
|
+
: join(userManagerDir(), 'workspaces');
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
// Single source of truth for where `.agents-data` lives, shared by the
|
|
102
|
-
// manager's own document intake
|
|
103
|
-
//
|
|
110
|
+
// manager's own document intake, the per-workspace serve stack, and the
|
|
111
|
+
// GLOBAL agents stack (documents/cme containers). All three must resolve the
|
|
112
|
+
// same host directory: serve writes an upload into its mount and the
|
|
113
|
+
// documents container must find it under the identical path — a divergence
|
|
114
|
+
// here is exactly the clean-install "Input file does not exist" bug.
|
|
115
|
+
// Canonical location: `<workspaces root>/.agents-data`, session or not.
|
|
104
116
|
export function resolveAgentsDataDir(session = null) {
|
|
105
117
|
const configured = process.env.AGENTS_DATA_DIR;
|
|
106
|
-
if (configured) return isAbsolute(configured) ? configured : resolve(
|
|
118
|
+
if (configured) return isAbsolute(configured) ? configured : resolve(managerStateDir(), configured);
|
|
107
119
|
if (session?.workspacePath) return resolve(dirname(session.workspacePath), '.agents-data');
|
|
108
|
-
return resolve(
|
|
120
|
+
return resolve(workspacesRootDir(), '.agents-data');
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
function parseEnvValue(value) {
|
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.14.
|
|
4
|
+
const WIKI_MANAGER_VERSION = '0.14.13';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
package/src/core/wikiSetup.js
CHANGED
|
@@ -6,7 +6,7 @@ import { promisify } from 'node:util';
|
|
|
6
6
|
import YAML from 'yaml';
|
|
7
7
|
import { checkMissingDockerImages } from './dockerImages.js';
|
|
8
8
|
import { patchWikircProfile } from './wikirc.js';
|
|
9
|
-
import { managerEnvFile, managerMcpEndpointsFile } from './env.js';
|
|
9
|
+
import { managerEnvFile, managerMcpEndpointsFile, resolveAgentsDataDir } from './env.js';
|
|
10
10
|
import { createWorkspace, findWorkspace, isValidWorkspaceName, listWorkspaces, managerRoot, workspacesDir } from './workspaces.js';
|
|
11
11
|
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
@@ -68,6 +68,12 @@ export async function startAgents(options = {}) {
|
|
|
68
68
|
// user's .env stayed empty on a fresh install.
|
|
69
69
|
WIKI_MANAGER_ENV_FILE: managerEnvFile(),
|
|
70
70
|
WIKI_MANAGER_ENDPOINTS_FILE: managerMcpEndpointsFile(),
|
|
71
|
+
// Same pinning for .agents-data: without it the script defaults to
|
|
72
|
+
// $PWD/.agents-data (the PACKAGE directory), while the serve stack
|
|
73
|
+
// mounts the workspace-root .agents-data — the documents container
|
|
74
|
+
// then reads an empty input dir and every conversion fails with
|
|
75
|
+
// "Input file does not exist" on a clean install.
|
|
76
|
+
AGENTS_DATA_DIR: resolveAgentsDataDir(),
|
|
71
77
|
},
|
|
72
78
|
timeout: options.timeout ?? 180_000,
|
|
73
79
|
maxBuffer: options.maxBuffer ?? 1024 * 1024 * 8,
|
|
@@ -88,6 +94,7 @@ export async function stopAgents(options = {}) {
|
|
|
88
94
|
env: {
|
|
89
95
|
...process.env,
|
|
90
96
|
WIKI_WORKSPACES_DIR: workspacesDir(),
|
|
97
|
+
AGENTS_DATA_DIR: resolveAgentsDataDir(),
|
|
91
98
|
},
|
|
92
99
|
timeout: options.timeout ?? 120_000,
|
|
93
100
|
maxBuffer: options.maxBuffer ?? 1024 * 1024 * 8,
|
package/src/runtime/runner.js
CHANGED
|
@@ -607,8 +607,25 @@ function failedTaskActivities(session) {
|
|
|
607
607
|
return failedKeys.size > 0 ? all.filter((activity) => failedKeys.has(activity.key)) : all;
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
|
|
611
|
-
|
|
610
|
+
// Identity of a plan as displayed: the ordered list of task ids. Two plans
|
|
611
|
+
// with the same tasks in the same order are the same plan for projection
|
|
612
|
+
// purposes (statuses update through plan_step_updated, not a re-projection).
|
|
613
|
+
function planIdentitySignature(plan) {
|
|
614
|
+
return Array.isArray(plan) ? plan.map(planTaskId).join('|') : '';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export function ensurePlanProjection(session, runId) {
|
|
618
|
+
if (!session.headlessPlan) return;
|
|
619
|
+
// Re-emit plan_set not only when the projection has no plan yet, but also
|
|
620
|
+
// when the session plan has CHANGED shape since the last projection. A
|
|
621
|
+
// chained run (e.g. /skill pipeline) sets a fresh headlessPlan for step 2
|
|
622
|
+
// while the projection still holds step 1's plan; the old guard
|
|
623
|
+
// (`agentProjection?.plan` truthy → skip) then left the UI stuck on the
|
|
624
|
+
// step 1 plan. Compare task identities and re-project when they differ.
|
|
625
|
+
const projected = session.agentProjection?.plan ?? null;
|
|
626
|
+
if (projected && planIdentitySignature(projected) === planIdentitySignature(session.headlessPlan)) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
612
629
|
dispatchAgentEvent(session, createAgentEvent('plan_set', {
|
|
613
630
|
origin: 'runtime',
|
|
614
631
|
runId,
|
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
4
|
-
import { evaluateRuntimeRun, finishRuntimeRun, materializeTaskInputs, replanRuntimeRun, runRuntimeAgenticWorkflow, runRuntimeParallelPlan, shouldUseParallelScheduler } from './runner.js';
|
|
4
|
+
import { ensurePlanProjection, evaluateRuntimeRun, finishRuntimeRun, materializeTaskInputs, replanRuntimeRun, runRuntimeAgenticWorkflow, runRuntimeParallelPlan, shouldUseParallelScheduler } from './runner.js';
|
|
5
|
+
|
|
6
|
+
test('ensurePlanProjection re-projects when the chained plan changes shape (step 2)', () => {
|
|
7
|
+
const session = { agentEvents: [], agentProjection: null };
|
|
8
|
+
// Step 1: a fresh plan is projected.
|
|
9
|
+
session.headlessPlan = [{ id: 'a', description: 'ingest', status: 'pending' }];
|
|
10
|
+
ensurePlanProjection(session, 'run-1');
|
|
11
|
+
assert.deepEqual(
|
|
12
|
+
(session.agentProjection?.plan ?? []).map((step) => step.id),
|
|
13
|
+
['a'],
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Step 1 completes in place — same plan shape, no re-projection needed.
|
|
17
|
+
session.headlessPlan = [{ id: 'a', description: 'ingest', status: 'done' }];
|
|
18
|
+
const eventsAfterStep1 = session.agentEvents.length;
|
|
19
|
+
ensurePlanProjection(session, 'run-1');
|
|
20
|
+
assert.equal(session.agentEvents.length, eventsAfterStep1, 'unchanged shape must not re-emit plan_set');
|
|
21
|
+
|
|
22
|
+
// Step 2 adds a task — the projection must now reflect the new plan.
|
|
23
|
+
session.headlessPlan = [
|
|
24
|
+
{ id: 'a', description: 'ingest', status: 'done' },
|
|
25
|
+
{ id: 'b', description: 'build', status: 'pending' },
|
|
26
|
+
];
|
|
27
|
+
ensurePlanProjection(session, 'run-1');
|
|
28
|
+
assert.deepEqual(
|
|
29
|
+
(session.agentProjection?.plan ?? []).map((step) => step.id),
|
|
30
|
+
['a', 'b'],
|
|
31
|
+
'step 2 plan must be projected instead of staying on step 1',
|
|
32
|
+
);
|
|
33
|
+
// Completed step 1 status is preserved through the re-projection.
|
|
34
|
+
assert.equal(session.agentProjection.plan.find((step) => step.id === 'a').status, 'done');
|
|
35
|
+
});
|
|
5
36
|
|
|
6
37
|
test('validated tasks waiting for approval stay in the scheduler instead of falling back to Donna', () => {
|
|
7
38
|
assert.equal(shouldUseParallelScheduler([
|
package/wiki-workspace
CHANGED
|
@@ -210,8 +210,11 @@ agents_compose() {
|
|
|
210
210
|
[[ -f "$agents_compose_file" ]] || die "agents compose file not found: $agents_compose_file"
|
|
211
211
|
|
|
212
212
|
local agents_data_dir workspaces_root
|
|
213
|
-
agents_data_dir="$(absolute_path "${AGENTS_DATA_DIR:-.agents-data}" "$DEFAULT_MANAGER_DIR")"
|
|
214
213
|
workspaces_root="$(absolute_path "$WORKSPACES_DIR" "$DEFAULT_MANAGER_DIR")"
|
|
214
|
+
# Canonical .agents-data lives next to the workspaces (same rule as the
|
|
215
|
+
# manager's resolveAgentsDataDir): the serve stack and the agents stack MUST
|
|
216
|
+
# mount the same host directory or document conversions break.
|
|
217
|
+
agents_data_dir="$(absolute_path "${AGENTS_DATA_DIR:-$workspaces_root/.agents-data}" "$DEFAULT_MANAGER_DIR")"
|
|
215
218
|
|
|
216
219
|
local compose_env_args=()
|
|
217
220
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
@@ -709,7 +712,8 @@ compose_for_workspace() {
|
|
|
709
712
|
mcp_port="$(workspace_value "$workspace" WIKI_MCP_PORT 3101)"
|
|
710
713
|
prod_port="$(workspace_value "$workspace" PRODUCTION_MCP_PORT 3102)"
|
|
711
714
|
project="$(compose_project "$workspace")"
|
|
712
|
-
|
|
715
|
+
# Same canonical rule as agents_compose: .agents-data next to the workspaces.
|
|
716
|
+
agents_data_dir="$(absolute_path "${AGENTS_DATA_DIR:-$(dirname "$ws_path")/.agents-data}" "$DEFAULT_MANAGER_DIR")"
|
|
713
717
|
|
|
714
718
|
local compose_env_args=()
|
|
715
719
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
@@ -920,7 +924,8 @@ config_workspace() {
|
|
|
920
924
|
[[ -f "$env_file" ]] && die "workspace already configured: $env_file"
|
|
921
925
|
|
|
922
926
|
local port_base serve_port mcp_port prod_port wiki_token prod_token agents_data_dir
|
|
923
|
-
|
|
927
|
+
# Same canonical rule as agents_compose: .agents-data next to the workspaces.
|
|
928
|
+
agents_data_dir="$(absolute_path "${AGENTS_DATA_DIR:-$(dirname "$target_path")/.agents-data}" "$DEFAULT_MANAGER_DIR")"
|
|
924
929
|
port_base="$(find_free_workspace_port_base 3100)"
|
|
925
930
|
serve_port="$port_base"
|
|
926
931
|
mcp_port=$((port_base + 1))
|