@dotdrelle/wiki-manager 0.15.76 → 0.15.79
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/buildInfo.json +2 -2
- package/src/core/mcp.js +9 -2
- package/src/core/runtimeLog.js +14 -0
- package/src/core/runtimeLog.test.js +43 -1
- package/src/core/wikiSetup.js +6 -11
- package/src/core/wikirc.test.js +6 -6
- package/src/core/workspaceInherit.js +0 -28
- package/src/core/workspaceInherit.test.js +10 -38
- package/src/orchestrator/agentRegistry.js +13 -2
- package/src/orchestrator/providers/runtimeProviders.js +23 -12
- package/src/shell/RightPane.tsx +9 -8
- package/src/shell/repl.test.js +7 -0
- package/wiki-workspace +7 -5
package/package.json
CHANGED
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.79';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
|
@@ -272,7 +272,14 @@ function clarifyToolDescription(_serverName, _toolName, description) {
|
|
|
272
272
|
|
|
273
273
|
async function listMcpTools(endpoint) {
|
|
274
274
|
if (!endpoint.url) throw new Error('missing endpoint URL');
|
|
275
|
-
|
|
275
|
+
// Opt-in only, unlike callMcpTool: this probe runs inside discoverMcpTools's
|
|
276
|
+
// Promise.all on every re-scan/delegation, so an endpoint with no explicit
|
|
277
|
+
// `retry` in mcp.endpoints.json must keep costing exactly one 8s timeout
|
|
278
|
+
// when unreachable, not silently inherit the global 2-attempt default and
|
|
279
|
+
// double that cost for every unconfigured server. Only an endpoint that
|
|
280
|
+
// explicitly declares `retry` gets more than one attempt here.
|
|
281
|
+
const retry = endpoint.retry ? resolveRetryPolicy(endpoint) : { maxAttempts: 1, backoffMs: 0 };
|
|
282
|
+
const payload = await withRetry(() => mcpRequest(endpoint, 'tools/list', {}), retry);
|
|
276
283
|
return payload?.result?.tools ?? [];
|
|
277
284
|
}
|
|
278
285
|
|
package/src/core/runtimeLog.js
CHANGED
|
@@ -177,6 +177,20 @@ export function isDispatchPlumbingLine(line) {
|
|
|
177
177
|
return DISPATCH_PLUMBING_LINE.test(String(line ?? '').replace(/^runtime\s+/, ''));
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// The agent's own reasoning traces — "Agent: planning next action…",
|
|
181
|
+
// "Agent: classified input as …", "Agent: streaming final answer…" — describe
|
|
182
|
+
// how the agent works, not what the business run does. They belong with the
|
|
183
|
+
// dispatch plumbing in the Agent status tab; the Runtime tab keeps the
|
|
184
|
+
// business flow (plan, task transitions, failures). "Agents: …" (plural — the
|
|
185
|
+
// slash-command lifecycle lines from `agents up`/`agents down`, e.g. "Agents:
|
|
186
|
+
// reloading manager environment…") is the same category and must match too:
|
|
187
|
+
// the singular-only pattern let those lines fall through to the Runtime tab.
|
|
188
|
+
const AGENT_TRACE_LINE = /^(?:runtime\s+)?(?:\d{1,2}:\d{2}(?::\d{2})?\s*(?:·\s*)?)?Agents?:\s/;
|
|
189
|
+
|
|
190
|
+
export function isAgentTraceLine(line) {
|
|
191
|
+
return AGENT_TRACE_LINE.test(String(line ?? ''));
|
|
192
|
+
}
|
|
193
|
+
|
|
180
194
|
export function runtimeLogMatchesFilter(line, filter = '') {
|
|
181
195
|
const query = String(filter ?? '').trim();
|
|
182
196
|
if (!query) return true;
|
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
|
|
4
4
|
import { createAgentEvent, dispatchAgentEvent } from './agentEvents.js';
|
|
5
|
-
import { compactRuntimeLogForDisplay, formatRuntimeLogPayload, isDispatchPlumbingLine, shortLogId } from './runtimeLog.js';
|
|
5
|
+
import { compactRuntimeLogForDisplay, formatRuntimeLogPayload, isAgentTraceLine, isDispatchPlumbingLine, shortLogId } from './runtimeLog.js';
|
|
6
6
|
import { emitRuntimeLog } from '../runtime/supervisor.js';
|
|
7
7
|
|
|
8
8
|
const CYCLE_EVENTS = [
|
|
@@ -156,6 +156,48 @@ test('isDispatchPlumbingLine recognises the shell-tagged "runtime " lines for th
|
|
|
156
156
|
assert.equal(isDispatchPlumbingLine('runtime 14:42:22 Plan validated for run r1'), false);
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
test('isAgentTraceLine recognises the "Agent:" traces for the Agent status tab', () => {
|
|
160
|
+
// The agent's own reasoning traces were rendered in the Runtime tab,
|
|
161
|
+
// indistinguishable from the business flow. They describe the agent's
|
|
162
|
+
// working, so the Agent status tab shows them with the dispatch plumbing.
|
|
163
|
+
for (const line of [
|
|
164
|
+
'14:42:18 Agent: planning next action…',
|
|
165
|
+
'14:42:19 Agent: classified input as enqueue',
|
|
166
|
+
'14:42:20 Agent: streaming final answer…',
|
|
167
|
+
'runtime 14:42:18 Agent: planning next action…',
|
|
168
|
+
]) {
|
|
169
|
+
assert.equal(isAgentTraceLine(line), true, `expected an agent trace: ${line}`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('isAgentTraceLine also recognises the plural "Agents:" lifecycle lines', () => {
|
|
174
|
+
// The `agents up`/`agents down` slash-command lines are written in the
|
|
175
|
+
// plural ("Agents: reloading manager environment…") and were falling
|
|
176
|
+
// through the singular-only pattern into the Runtime tab.
|
|
177
|
+
for (const line of [
|
|
178
|
+
'13:55:25 Agents: reloading manager environment and MCP endpoints…',
|
|
179
|
+
'13:55:26 Agents: stack started degraded — one optional agent failed; the base agents are up.',
|
|
180
|
+
'13:55:27 Agents: docker output — exit code 1',
|
|
181
|
+
'runtime 13:55:25 Agents: starting external agents…',
|
|
182
|
+
]) {
|
|
183
|
+
assert.equal(isAgentTraceLine(line), true, `expected an agent trace: ${line}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('isAgentTraceLine leaves every other line for the Runtime tab', () => {
|
|
188
|
+
for (const line of [
|
|
189
|
+
'14:42:18 ▸ Polish proposition — started (knowledge.polish → agent-production)',
|
|
190
|
+
'14:42:19 ✓ Polish proposition — done (1 output)',
|
|
191
|
+
'14:42:21 Run failed: No agent provides capability workspace.restore.',
|
|
192
|
+
'14:42:22 Plan validated for run r1',
|
|
193
|
+
'14:42:23 Agentic runtime: no capabilities available',
|
|
194
|
+
'14:42:24 Plan: 3 task(s) declared from production fragment',
|
|
195
|
+
'runtime 14:42:21 Run failed: Agent was not ready',
|
|
196
|
+
]) {
|
|
197
|
+
assert.equal(isAgentTraceLine(line), false, `expected business flow: ${line}`);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
159
201
|
test('shortLogId caps an over-long task slug while shortening embedded UUIDs', () => {
|
|
160
202
|
const long = `${'x'.repeat(48)}-deadbeef`;
|
|
161
203
|
assert.match(shortLogId(long), /…$/);
|
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 { loadWikircProfile, patchWikircProfile } from './wikirc.js';
|
|
9
|
-
import { buildInheritedWikircPatch
|
|
9
|
+
import { buildInheritedWikircPatch } from './workspaceInherit.js';
|
|
10
10
|
import { resolveAgentsComposeContext } from './agentsCompose.js';
|
|
11
11
|
import { managerEnvFile, managerMcpEndpointsFile, resolveAgentsDataDir } from './env.js';
|
|
12
12
|
import { createWorkspace, findWorkspace, isValidWorkspaceName, listWorkspaces, managerRoot, workspacesDir } from './workspaces.js';
|
|
@@ -245,9 +245,11 @@ export async function createNewWorkspace(name, targetPath, options = {}) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
/**
|
|
248
|
-
* @param options.inheritFrom name of the workspace to copy LLM config
|
|
249
|
-
*
|
|
250
|
-
*
|
|
248
|
+
* @param options.inheritFrom name of the workspace to copy LLM config from —
|
|
249
|
+
* normally the session's current workspace. Omitted, or unknown, means
|
|
250
|
+
* "scaffold defaults only", the previous behaviour. Confluence credentials
|
|
251
|
+
* are NOT inherited: agent-cme stores them agent-wide (shared across all
|
|
252
|
+
* workspaces), so a new workspace already sees them.
|
|
251
253
|
*/
|
|
252
254
|
export async function finalizeCreatedWorkspace(name, options = {}) {
|
|
253
255
|
const workspace = findWorkspace(name);
|
|
@@ -282,13 +284,6 @@ export async function inheritWorkspaceSetup(workspace, sourceName) {
|
|
|
282
284
|
// Unreadable or absent profile on either side — nothing to inherit.
|
|
283
285
|
}
|
|
284
286
|
|
|
285
|
-
try {
|
|
286
|
-
const copied = await copyCmeCredentials(resolveAgentsDataDir(), source.name, workspace.name);
|
|
287
|
-
if (copied) inherited.push('cme.app_data.json');
|
|
288
|
-
} catch {
|
|
289
|
-
// Credentials are a convenience, not a prerequisite.
|
|
290
|
-
}
|
|
291
|
-
|
|
292
287
|
return inherited;
|
|
293
288
|
}
|
|
294
289
|
|
package/src/core/wikirc.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import test from 'node:test';
|
|
@@ -300,6 +300,9 @@ test('finalizeCreatedWorkspace seeds a new workspace from the one in use', async
|
|
|
300
300
|
'',
|
|
301
301
|
].join('\n'));
|
|
302
302
|
|
|
303
|
+
// Confluence credentials are agent-wide now — a stale per-workspace
|
|
304
|
+
// app_data.json from a legacy layout must NOT be copied into the new
|
|
305
|
+
// workspace, and must not be reported as inherited.
|
|
303
306
|
mkdirSync(join(agentsData, 'cme', 'acme', 'cme'), { recursive: true });
|
|
304
307
|
writeFileSync(join(agentsData, 'cme', 'acme', 'cme', 'app_data.json'), '{"pat":"x"}', 'utf8');
|
|
305
308
|
|
|
@@ -318,11 +321,8 @@ test('finalizeCreatedWorkspace seeds a new workspace from the one in use', async
|
|
|
318
321
|
// The workspace keeps ITS own MCP credential, written just before.
|
|
319
322
|
assert.equal(parsed.mcp.accessKey, token);
|
|
320
323
|
assert.ok(inherited.includes('llm.baseUrl'));
|
|
321
|
-
assert.ok(inherited.includes('cme.app_data.json'));
|
|
322
|
-
assert.equal(
|
|
323
|
-
readFileSync(join(agentsData, 'cme', 'fresh', 'cme', 'app_data.json'), 'utf8'),
|
|
324
|
-
'{"pat":"x"}',
|
|
325
|
-
);
|
|
324
|
+
assert.ok(!inherited.includes('cme.app_data.json'));
|
|
325
|
+
assert.equal(existsSync(join(agentsData, 'cme', 'fresh', 'cme', 'app_data.json')), false);
|
|
326
326
|
} finally {
|
|
327
327
|
if (previousDir === undefined) delete process.env.WIKI_WORKSPACES_DIR;
|
|
328
328
|
else process.env.WIKI_WORKSPACES_DIR = previousDir;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { copyFile, mkdir } from 'node:fs/promises';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
1
|
import { join } from 'node:path';
|
|
4
2
|
|
|
5
3
|
/**
|
|
@@ -121,29 +119,3 @@ export function buildInheritedWikircPatch(sourceConfig, targetConfig) {
|
|
|
121
119
|
if (Object.keys(vector).length > 0) patch.retrieval = { vector };
|
|
122
120
|
return { patch, inherited };
|
|
123
121
|
}
|
|
124
|
-
|
|
125
|
-
export function cmeCredentialsPath(agentsDataDir, workspaceName) {
|
|
126
|
-
return join(agentsDataDir, 'cme', workspaceName, 'cme', 'app_data.json');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Carry the Confluence credentials of an existing workspace over to a new one.
|
|
131
|
-
*
|
|
132
|
-
* `app_data.json` only — NOT `sources-manifest.yaml`. The credentials are a
|
|
133
|
-
* property of the operator's Confluence account and are identical everywhere;
|
|
134
|
-
* which spaces and pages a workspace exports is precisely what makes it a
|
|
135
|
-
* different workspace, and copying that would silently re-export someone
|
|
136
|
-
* else's scope on the first run.
|
|
137
|
-
*/
|
|
138
|
-
export async function copyCmeCredentials(agentsDataDir, sourceWorkspace, targetWorkspace) {
|
|
139
|
-
if (!agentsDataDir || !sourceWorkspace || !targetWorkspace) return null;
|
|
140
|
-
if (sourceWorkspace === targetWorkspace) return null;
|
|
141
|
-
const from = cmeCredentialsPath(agentsDataDir, sourceWorkspace);
|
|
142
|
-
const to = cmeCredentialsPath(agentsDataDir, targetWorkspace);
|
|
143
|
-
if (!existsSync(from)) return null;
|
|
144
|
-
// An existing target file is a real configuration; never clobber it.
|
|
145
|
-
if (existsSync(to)) return null;
|
|
146
|
-
await mkdir(join(agentsDataDir, 'cme', targetWorkspace, 'cme'), { recursive: true });
|
|
147
|
-
await copyFile(from, to);
|
|
148
|
-
return to;
|
|
149
|
-
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
6
4
|
import { fileURLToPath } from 'node:url';
|
|
7
5
|
import {
|
|
8
6
|
PLACEHOLDER_VALUE_RE,
|
|
9
7
|
buildInheritedWikircPatch,
|
|
10
|
-
cmeCredentialsPath,
|
|
11
|
-
copyCmeCredentials,
|
|
12
8
|
isRealValue,
|
|
13
9
|
} from './workspaceInherit.js';
|
|
14
10
|
|
|
@@ -144,38 +140,14 @@ test('nothing to inherit yields an empty patch', () => {
|
|
|
144
140
|
assert.deepEqual(inherited, []);
|
|
145
141
|
});
|
|
146
142
|
|
|
147
|
-
test('
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const copied = await copyCmeCredentials(root, 'acme', 'fresh');
|
|
156
|
-
|
|
157
|
-
assert.equal(copied, cmeCredentialsPath(root, 'fresh'));
|
|
158
|
-
assert.equal(readFileSync(copied, 'utf8'), '{"auth":{"pat":"secret"}}');
|
|
159
|
-
assert.equal(existsSync(join(root, 'cme', 'fresh', 'sources-manifest.yaml')), false);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('existing CME credentials on the target are never clobbered', async () => {
|
|
163
|
-
const root = mkdtempSync(join(tmpdir(), 'cme-inherit-keep-'));
|
|
164
|
-
mkdirSync(join(root, 'cme', 'acme', 'cme'), { recursive: true });
|
|
165
|
-
mkdirSync(join(root, 'cme', 'fresh', 'cme'), { recursive: true });
|
|
166
|
-
writeFileSync(join(root, 'cme', 'acme', 'cme', 'app_data.json'), '{"from":"source"}', 'utf8');
|
|
167
|
-
writeFileSync(join(root, 'cme', 'fresh', 'cme', 'app_data.json'), '{"from":"target"}', 'utf8');
|
|
168
|
-
|
|
169
|
-
assert.equal(await copyCmeCredentials(root, 'acme', 'fresh'), null);
|
|
170
|
-
assert.equal(
|
|
171
|
-
readFileSync(cmeCredentialsPath(root, 'fresh'), 'utf8'),
|
|
172
|
-
'{"from":"target"}',
|
|
143
|
+
test('Confluence credentials are not part of workspace inheritance', () => {
|
|
144
|
+
// agent-cme stores credentials agent-wide since the shared-config change:
|
|
145
|
+
// a new workspace sees them without any copy. Inheritance must not smuggle
|
|
146
|
+
// an app_data.json (or a sources manifest) into per-workspace state.
|
|
147
|
+
const { patch, inherited } = buildInheritedWikircPatch(
|
|
148
|
+
{ llm: WORKING_LLM },
|
|
149
|
+
{ llm: SCAFFOLD_LLM },
|
|
173
150
|
);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
test('copying is a no-op without a source, a target, or a source file', async () => {
|
|
177
|
-
const root = mkdtempSync(join(tmpdir(), 'cme-inherit-noop-'));
|
|
178
|
-
assert.equal(await copyCmeCredentials(root, 'absent', 'fresh'), null);
|
|
179
|
-
assert.equal(await copyCmeCredentials(root, null, 'fresh'), null);
|
|
180
|
-
assert.equal(await copyCmeCredentials(root, 'acme', 'acme'), null);
|
|
151
|
+
assert.equal(patch['cme.app_data.json'], undefined);
|
|
152
|
+
assert.ok(!inherited.some((key) => key.toLowerCase().includes('cme')));
|
|
181
153
|
});
|
|
@@ -72,8 +72,19 @@ export function createAgentRegistry({
|
|
|
72
72
|
const discovered = [];
|
|
73
73
|
const endpoints = Object.entries(session?.mcp ?? {});
|
|
74
74
|
const activeServers = new Set(endpoints.map(([serverName]) => serverName));
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Each probe is an independent network call (agent_describe against one
|
|
76
|
+
// server); none reads another server's result. Probing them concurrently
|
|
77
|
+
// turns the wall-clock cost from the SUM of every server's probe latency
|
|
78
|
+
// into the latency of the SLOWEST one — the sequential version made every
|
|
79
|
+
// delegation get linearly slower as more MCP servers were connected.
|
|
80
|
+
// registerAgent still runs afterward in the original endpoint order, one
|
|
81
|
+
// at a time, so event ordering and the shared-map mutations it performs
|
|
82
|
+
// are unchanged.
|
|
83
|
+
const probed = await Promise.all(
|
|
84
|
+
endpoints.map(([serverName, endpoint]) =>
|
|
85
|
+
discoverServerAgent(session, serverName, endpoint, { callTool, signal, now })),
|
|
86
|
+
);
|
|
87
|
+
for (const agent of probed) {
|
|
77
88
|
discovered.push(registerAgent(session, agent, { agentsByInstance, instanceByServer, lastProbeFailed }));
|
|
78
89
|
}
|
|
79
90
|
for (const [serverName, instanceId] of instanceByServer) {
|
|
@@ -22,35 +22,37 @@ export async function discoverRuntimeProviderAgents(runtimeProviders) {
|
|
|
22
22
|
const providers = Array.isArray(runtimeProviders)
|
|
23
23
|
? runtimeProviders
|
|
24
24
|
: (runtimeProviders?.list?.() ?? []);
|
|
25
|
-
const agents = [];
|
|
26
|
-
const unavailable = [];
|
|
27
|
-
// Configured-but-not-served capabilities, per runtime (deepagents providers
|
|
28
|
-
// fill `lastDiscovery`). A drift is not an outage: the runtime stays
|
|
29
|
-
// available with the capabilities it really serves, and the difference is
|
|
30
|
-
// reported so an operator can see WHY agent.research is not routable.
|
|
31
|
-
const drift = [];
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
// One provider's describe()/discoverCapabilities() never reads another
|
|
27
|
+
// provider's result, so probe them concurrently (same reasoning as
|
|
28
|
+
// agentRegistry.discover()) instead of paying the sum of every runtime's
|
|
29
|
+
// latency in sequence. Each entry still produces its own ordered
|
|
30
|
+
// {agents, unavailable, drift} slice, flattened below in the original
|
|
31
|
+
// `providers` order so output ordering is unchanged.
|
|
32
|
+
const results = await Promise.all(providers.map(async (entry) => {
|
|
34
33
|
const provider = entry?.provider ?? entry;
|
|
35
34
|
const runtimeId = String(entry?.id ?? provider?.runtime ?? 'external-runtime');
|
|
35
|
+
const agents = [];
|
|
36
|
+
const unavailable = [];
|
|
37
|
+
const drift = [];
|
|
36
38
|
let description;
|
|
37
39
|
try {
|
|
38
40
|
assertRuntimeProvider(provider);
|
|
39
41
|
description = await provider.describe();
|
|
40
42
|
} catch (error) {
|
|
41
43
|
unavailable.push({ runtimeId, error: error instanceof Error ? error.message : String(error) });
|
|
42
|
-
|
|
44
|
+
return { agents, unavailable, drift };
|
|
43
45
|
}
|
|
44
46
|
if (description?.health === 'unavailable') {
|
|
45
47
|
unavailable.push({ runtimeId, error: description?.error ?? 'runtime reports unavailable' });
|
|
46
|
-
|
|
48
|
+
return { agents, unavailable, drift };
|
|
47
49
|
}
|
|
48
50
|
let capabilities;
|
|
49
51
|
try {
|
|
50
52
|
capabilities = await provider.discoverCapabilities();
|
|
51
53
|
} catch (error) {
|
|
52
54
|
unavailable.push({ runtimeId, error: error instanceof Error ? error.message : String(error) });
|
|
53
|
-
|
|
55
|
+
return { agents, unavailable, drift };
|
|
54
56
|
}
|
|
55
57
|
const health = ['available', 'degraded'].includes(description?.health)
|
|
56
58
|
? description.health
|
|
@@ -62,7 +64,16 @@ export async function discoverRuntimeProviderAgents(runtimeProviders) {
|
|
|
62
64
|
if (discovery && Array.isArray(discovery.missing) && discovery.missing.length > 0) {
|
|
63
65
|
drift.push({ runtimeId, missing: [...discovery.missing], served: [...(discovery.served ?? [])] });
|
|
64
66
|
}
|
|
65
|
-
|
|
67
|
+
return { agents, unavailable, drift };
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
// Configured-but-not-served capabilities, per runtime (deepagents providers
|
|
71
|
+
// fill `lastDiscovery`). A drift is not an outage: the runtime stays
|
|
72
|
+
// available with the capabilities it really serves, and the difference is
|
|
73
|
+
// reported so an operator can see WHY agent.research is not routable.
|
|
74
|
+
const agents = results.flatMap((r) => r.agents);
|
|
75
|
+
const unavailable = results.flatMap((r) => r.unavailable);
|
|
76
|
+
const drift = results.flatMap((r) => r.drift);
|
|
66
77
|
|
|
67
78
|
return { agents, unavailable, drift };
|
|
68
79
|
}
|
package/src/shell/RightPane.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
import { createMemo, createSignal, Index, Show } from 'solid-js';
|
|
3
|
-
import { compactRuntimeLogForDisplay, filterRuntimeLogs, isDispatchPlumbingLine } from '../core/runtimeLog.js';
|
|
3
|
+
import { compactRuntimeLogForDisplay, filterRuntimeLogs, isAgentTraceLine, isDispatchPlumbingLine } from '../core/runtimeLog.js';
|
|
4
4
|
import { fit } from './textFit';
|
|
5
5
|
|
|
6
6
|
type PlanStep = { step: number; description: string; status: string };
|
|
@@ -409,13 +409,14 @@ export function LogPanel(props: { logs: string[]; width: number; filter?: string
|
|
|
409
409
|
const [activeLogTab, setActiveLogTab] = createSignal<'flow' | 'agent-status'>('flow');
|
|
410
410
|
const lineWidth = () => Math.max(8, props.width - 2);
|
|
411
411
|
// "Agent status" collects the dispatch plumbing — capability resolution,
|
|
412
|
-
// agent selection, agent_execute/agent_status polling, job acceptance —
|
|
413
|
-
// the
|
|
414
|
-
// ▸/✓/✗/↻ task lines).
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
412
|
+
// agent selection, agent_execute/agent_status polling, job acceptance — and
|
|
413
|
+
// the agent's own reasoning traces ("Agent: …"), so the "Runtime" tab is
|
|
414
|
+
// left with the readable business flow (plan + the ▸/✓/✗/↻ task lines).
|
|
415
|
+
// isDispatchPlumbingLine (shared with agentEvents' dedup) recognises the
|
|
416
|
+
// formatRuntimeLogPayload shape structurally; the old token enumeration
|
|
417
|
+
// only ever matched agent_status/agent_execute and missed every dotted
|
|
418
|
+
// event ('job.accepted' → 'ACCEPTED', …).
|
|
419
|
+
const isAgentStatus = (line: string) => isDispatchPlumbingLine(line) || isAgentTraceLine(line);
|
|
419
420
|
// Filtering preserves the runtime history order. logRenderLines performs
|
|
420
421
|
// the single block-level reversal shared by both tabs.
|
|
421
422
|
const filteredLogs = () => filterRuntimeLogs(props.logs, props.filter ?? '')
|
package/src/shell/repl.test.js
CHANGED
|
@@ -101,6 +101,13 @@ test('ShellUI renders newest-first order in both Runtime and Agent status', asyn
|
|
|
101
101
|
assert.match(filteredLogs, /activeLogTab\(\) === 'agent-status'/);
|
|
102
102
|
assert.match(filteredLogs, /isAgentStatus\(line\)/);
|
|
103
103
|
assert.doesNotMatch(filteredLogs, /\.reverse\(\)/);
|
|
104
|
+
const logPanelBody = source.slice(
|
|
105
|
+
source.indexOf('export function LogPanel'),
|
|
106
|
+
source.indexOf('const filteredLogs ='),
|
|
107
|
+
);
|
|
108
|
+
// Agent reasoning traces route to the Agent status tab with the dispatch
|
|
109
|
+
// plumbing — the Runtime tab keeps the business flow.
|
|
110
|
+
assert.match(logPanelBody, /isDispatchPlumbingLine\(line\) \|\| isAgentTraceLine\(line\)/);
|
|
104
111
|
const renderedLogs = source.slice(
|
|
105
112
|
source.indexOf('function logRenderLines'),
|
|
106
113
|
source.indexOf('function logEntryLines'),
|
package/wiki-workspace
CHANGED
|
@@ -140,7 +140,6 @@ wiki CLI commands reachable through `wiki <workspace> run`:
|
|
|
140
140
|
lint Static checks: dead links, orphans, stale deliverables
|
|
141
141
|
add-skill <source> Install a workspace method (directory, .zip, HTTPS .zip)
|
|
142
142
|
config Show the effective .wikirc.yaml
|
|
143
|
-
group-concepts Regroup wiki concepts
|
|
144
143
|
doctor, ingest, build, export Also available directly: wiki <workspace> <command>
|
|
145
144
|
|
|
146
145
|
History is versioned for wiki/, templates/, build-context/, deliverables/,
|
|
@@ -1352,10 +1351,13 @@ reset_workspace() {
|
|
|
1352
1351
|
# `.wikirc*`, templates/ and build-context/ come through untouched.
|
|
1353
1352
|
run_wiki "$workspace" init
|
|
1354
1353
|
|
|
1355
|
-
# The scaffold ships a demo
|
|
1356
|
-
#
|
|
1357
|
-
#
|
|
1358
|
-
|
|
1354
|
+
# The scaffold ships a handful of demo-*.md sources in raw/untracked/ (a
|
|
1355
|
+
# project brief plus a few notes chosen to classify into different concepts
|
|
1356
|
+
# with overlapping subjects/tags, so a first ingest already produces a
|
|
1357
|
+
# non-trivial graph). Reappearing here they would not read as samples but as
|
|
1358
|
+
# pending sources, and the next ingest would file them into the wiki as if
|
|
1359
|
+
# the user had provided them.
|
|
1360
|
+
rm -f -- "$ws_path"/raw/untracked/demo-*.md
|
|
1359
1361
|
|
|
1360
1362
|
printf 'Reset done: %s\n' "$workspace"
|
|
1361
1363
|
printf 'Kept: %s\n' "${kept[*]:-(nothing)}"
|