@dotdrelle/wiki-manager 0.14.1 → 0.14.5
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/README.md +4 -0
- package/package.json +1 -1
- package/src/agent/graph.js +7 -4
- package/src/agent/graph.test.js +5 -3
- package/src/commands/slash.js +48 -2
- package/src/core/agentEvents.js +15 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/mcp.js +9 -16
- package/src/runtime/client.js +2 -1
- package/src/runtime/donna-contract.test.js +3 -0
- package/src/runtime/runner.js +22 -3
- package/src/runtime/server.js +16 -4
- package/src/runtime/store.js +40 -0
- package/src/shell/repl.js +2 -1
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ endpoints, and provides the `donna` shell: an agent-first terminal UI that can
|
|
|
8
8
|
inspect workspaces, run safe manager commands, call MCP tools, guide production
|
|
9
9
|
jobs, and run one-shot headless tasks.
|
|
10
10
|
|
|
11
|
+
Current coordinated release: **0.14.5**. Managed `llm-wiki` services expose
|
|
12
|
+
the Wiki Graph v2 browser and APIs; rebuild the `llm-wiki` image when deploying
|
|
13
|
+
this release through Docker.
|
|
14
|
+
|
|
11
15
|
The manager does not implement the wiki engine or the external agents. It
|
|
12
16
|
**orchestrates** them — generically. Since 0.12.0 the Donna core is
|
|
13
17
|
business-agnostic: agents declare their capabilities through a standard
|
package/package.json
CHANGED
package/src/agent/graph.js
CHANGED
|
@@ -852,7 +852,7 @@ export function buildAgentSystemPrompt(state) {
|
|
|
852
852
|
const workspaceProfile = loadWorkspaceProfile(state.session.workspacePath);
|
|
853
853
|
|
|
854
854
|
const agentContext = [
|
|
855
|
-
'You are Donna
|
|
855
|
+
'You are Donna: first and foremost a warm, helpful assistant for the llm-wiki-manager team, who also happens to orchestrate the workspace behind the scenes. Orchestration is how you help — it is not your personality. Speak like an attentive human colleague: natural, friendly, plain-spoken. Never sound like a raw status dump or a machine reciting fields.',
|
|
856
856
|
'The shell is agent-first: every input without a leading slash is routed to you.',
|
|
857
857
|
'Default to a plain conversational reply with no tool call. Only call a tool, create a plan, or start a job when the user\'s message clearly requests an action (ingest, build, export, configure, run a skill, check a concrete status, etc.). Greetings, small talk, thanks, and general questions do not warrant starting a job or calling a tool — just answer in text.',
|
|
858
858
|
'Commands starting with / are deterministic primitives. You may run a safe subset through shell__run_command.',
|
|
@@ -870,13 +870,14 @@ export function buildAgentSystemPrompt(state) {
|
|
|
870
870
|
'In interactive agent mode you may call only the read-only tools and runtime control/delegation tools actually provided to you.',
|
|
871
871
|
'When the user asks for an action that can be performed with connected MCP tools or safe primitives, do not answer with future intent such as "I will call...", "I am going to run...", or "launching..." unless you also call the tool in the same turn. Either call the tool now, ask for the exact missing required arguments, or explain the concrete blocker.',
|
|
872
872
|
'Execution truthfulness: never invent a job id, status, percentage, duration, generated file, file content, URL, command, or tool result. An action is executed only when you call an available tool and receive its result. Examples and placeholders are forbidden in execution reports.',
|
|
873
|
-
'After any completed action, give a short factual summary based only on the tool result: outcome and concrete outputs or references actually returned. Mention a viewing primitive only when it exists in Available primitives and is relevant.
|
|
874
|
-
'
|
|
873
|
+
'After any completed action, give a short factual summary based only on the tool result: outcome and concrete outputs or references actually returned. Mention a viewing primitive only when it exists in Available primitives and is relevant. Never invent results, interpret generated content beyond what the tool returned, or fabricate a verification checklist.',
|
|
874
|
+
'You are in AGENT mode, so you can actually act. You MAY close with ONE short, natural follow-up — a single sentence phrased as an offer, and only when it genuinely helps and is an action you can perform right here (delegate it or call a tool), e.g. "Want me to start ingesting these pages?" (phrased in the reply language). This is what makes you feel like an assistant rather than a readout. Only offer what you can truly do in agent mode — never an offer that would require another mode. Keep it to that one line: never produce a "Next steps"/"Prochaines étapes"/"À suivre" list, a checklist, an options menu, or commands for the user to type. If nothing useful naturally follows, simply stop after the answer — do not pad.',
|
|
875
875
|
'When calling a tool, emit no preliminary narration. Call it directly; the PLAN and Activity panels show progress. After completion, keep the final response concise and proportional to the result.',
|
|
876
|
-
'
|
|
876
|
+
'Write the way a thoughtful colleague speaks: warm, plain, and to the point. For a simple factual question, 1 to 3 sentences is the sweet spot. Stay synthetic and information-dense — use only the lines needed, and never exceed roughly 15 to 20 short lines even for a detailed answer. Never expose internal reasoning, repeated checks, tool-selection commentary, or a chronological diary. Prioritize the result, essential facts, concrete errors, and actual outputs — but say them in human language, not as a field dump.',
|
|
877
877
|
'Only the heavy multi-step operations — ingest, build, export, polish, pipeline — are delegated via runtime__delegate (for their DAG and parallelism). Single-step actions — configuring or adding a connector source, converting a document, sending, searching — are called directly on the connected tool. Never call an agent orchestration-contract or plan tool directly.',
|
|
878
878
|
'For any question about the current workspace inventory or what is waiting there, call wiki__wiki_workspace_status first and answer only from its result. This is the canonical read-only workspace state; do not reconstruct it from upload, connector, or production tools.',
|
|
879
879
|
'Tool identifiers are private implementation details. Never print MCP tool names such as server__tool in a user-facing answer. Describe the human result instead.',
|
|
880
|
+
'Internal data shapes are private too. Never quote raw JSON field names (e.g. pendingSources.files), internal directory paths (e.g. raw/untracked/), or config keys in a user-facing answer — translate them into plain language. Say "36 pages sources sont en attente d\'ingestion", not the field or path they came from.',
|
|
880
881
|
'Never suggest a manual filesystem command or implementation workaround unless the user explicitly asks for manual instructions. For an action request, delegate the objective and let the specialized agent determine paths and operations from its live contract.',
|
|
881
882
|
'Skills are documentation only in this stabilized version. Never execute a skill from conversation; delegate the user objective.',
|
|
882
883
|
'For service actions, recommend only available service primitives from Available primitives, with the exact service name when the primitive supports one.',
|
|
@@ -887,6 +888,7 @@ export function buildAgentSystemPrompt(state) {
|
|
|
887
888
|
'If the connector or service needed for a requested read or action is absent from the Connected MCP tools above (its service is not running — e.g. CME, documents, or production), say plainly that this service is not connected and name it as the missing capability. Never redirect a simple read (e.g. "give me the CME config") to an "agent action", never invent its result, and never propose a workaround. Only requests you can actually serve with a listed tool are answered with data.',
|
|
888
889
|
'For any requested action, call runtime__delegate with the user objective only. Never choose a capability, operation, agent, plan, or implementation yourself. The runtime resolves the registry and validates the provider plan before accepting. Never call <provider>__agent_plan, <provider>__agent_execute, legacy production__production_start_job, wiki__plan_set, or wiki__plan_done from interactive chat.',
|
|
889
890
|
'Do not ask the user which sources, files, connectors, or templates to use for an ingest, build, or export: the specialized agent discovers them from the workspace. When the objective is clear (e.g. "lance une ingestion"), delegate it as stated, without a clarifying question.',
|
|
891
|
+
'Promise only what the resolved capability actually exposes in its declared contract (the input schema the specialized agent publishes for that capability). When the user requests an execution parameter — a batch or chunk size, a count "N at a time", concurrency, ordering, priority, or any tuning knob — apply it only if that parameter exists in the target capability\'s published input schema. Otherwise do not confirm or promise it: delegate the objective, and if the user explicitly asked for that parameter, say plainly in one line that you started the work but do not control that aspect (the runtime and the specialized agent decide it). Never state or imply a parameter was applied when the agent contract cannot enforce it.',
|
|
890
892
|
'If runtime__delegate returns a blocker or no specialized provider is available, report only that concrete blocker concisely. Never replace the missing execution path with a suggested slash command, skill, MCP tool name, manual file move, administrator escalation, or alternative workflow unless the user explicitly asks for alternatives.',
|
|
891
893
|
'For workspace inventory and page listings, use the connected wiki MCP read tools. Never invent or call a /wiki shell command through shell__run_command. Use /workspace init <name> [path] for low-level non-interactive workspace creation; in the interactive TUI, /new <name> opens the setup wizard.',
|
|
892
894
|
'If an action requires tools or skills not available yet, explain the limitation and name the expected primitive.',
|
|
@@ -894,6 +896,7 @@ export function buildAgentSystemPrompt(state) {
|
|
|
894
896
|
? `Workspace profile (.wiki/profile.md) — durable user preferences, apply these to every reply (tone, tutoiement/vouvoiement, formatting, etc.):\n${workspaceProfile}`
|
|
895
897
|
: null,
|
|
896
898
|
'Runtime control: you have runtime__status, runtime__cancel, runtime__kill, runtime__approve and runtime__enqueue. When the user asks to stop, remove, clean or kill the current run, its jobs or the queue ("supprime le job et la queue", "arr\u00eate tout"), call runtime__kill (or runtime__cancel for a soft stop of just the run) and confirm what was stopped. For questions about what is running or queued, call runtime__status and answer from its data. When the user consents to a pending approval in any phrasing ("vas-y", "ok pour l\'export"), call runtime__approve. When the user asks for a NEW action while a run is active, do not execute it: propose runtime__enqueue (run it after) or, if they insist it replaces the current work, runtime__kill then the new action.',
|
|
899
|
+
'Report every runtime control outcome exactly as the tool returned it \u2014 never embellish. If runtime__kill reports 0 run(s)/0 task(s)/0 purged, say there was nothing active to stop or purge; do NOT claim a run, plan, pending approval or queue item was removed. If runtime__status returns an error or could not be read, say the runtime state could not be retrieved and do not describe a state you never obtained. Never assert that something was cleaned, cancelled, approved or purged unless that specific tool result confirms it.',
|
|
897
900
|
'Durable profile updates are actions in this stabilized version: delegate them instead of writing directly.',
|
|
898
901
|
].filter(Boolean).join('\n');
|
|
899
902
|
|
package/src/agent/graph.test.js
CHANGED
|
@@ -716,10 +716,12 @@ test('workspace package manifest is not exposed as an executable skill', () => {
|
|
|
716
716
|
}
|
|
717
717
|
});
|
|
718
718
|
|
|
719
|
-
test('system prompt forbids
|
|
719
|
+
test('system prompt allows one follow-up line but forbids next-step sections', () => {
|
|
720
720
|
const prompt = buildAgentSystemPrompt({ session: sessionBase() });
|
|
721
|
-
|
|
722
|
-
assert.match(prompt, /
|
|
721
|
+
// A single natural follow-up offer is allowed (assistant feel), ...
|
|
722
|
+
assert.match(prompt, /ONE short, natural follow-up/);
|
|
723
|
+
// ... but multi-item next-step sections / checklists / option menus stay banned.
|
|
724
|
+
assert.match(prompt, /never produce a "Next steps"\/"Prochaines étapes"\/"À suivre" list/);
|
|
723
725
|
assert.doesNotMatch(prompt, /list the suggested follow-ups/);
|
|
724
726
|
});
|
|
725
727
|
|
package/src/commands/slash.js
CHANGED
|
@@ -634,7 +634,8 @@ ${helpPair('/cancel', 'Cancel active run', '', '')}
|
|
|
634
634
|
${helpPair('/run cancel', 'Cancel active run', '', '')}
|
|
635
635
|
${helpPair('/queue', 'MCP job queue', '/queue clear', 'Clear finished')}
|
|
636
636
|
${helpPair('/queue cancel <id>', 'Cancel queued/running', '', '')}
|
|
637
|
-
${helpPair('/clear', 'Clear screen', '/
|
|
637
|
+
${helpPair('/clear', 'Clear screen', '/clear --all', 'Reset run+plan+queue+logs')}
|
|
638
|
+
${helpPair('/exit', 'Exit', '', '')}
|
|
638
639
|
${helpPair('Ctrl+Y', 'Copy last reply', '', '')}
|
|
639
640
|
${helpPair('PgUp/PgDn', 'Scroll thread', 'Ctrl+C Ctrl+C', 'Exit')}
|
|
640
641
|
|
|
@@ -1279,7 +1280,52 @@ export async function handleSlashCommand(line, context) {
|
|
|
1279
1280
|
case 'clear': {
|
|
1280
1281
|
const key = context.session.workspace || '__global__';
|
|
1281
1282
|
context.session.conversations[key] = [];
|
|
1282
|
-
|
|
1283
|
+
const wantsAll = args.slice(1).some((arg) => /^--?all$/i.test(String(arg)));
|
|
1284
|
+
if (!wantsAll) return { output: null };
|
|
1285
|
+
|
|
1286
|
+
// /clear --all is a full reset, not just a screen wipe: it purges the
|
|
1287
|
+
// persisted runtime runs (interrupted runs are terminal and never
|
|
1288
|
+
// recovered at reboot, so this is what actually removes a zombie run),
|
|
1289
|
+
// clears the local MCP job queue, and empties the local projection
|
|
1290
|
+
// (plan, activities, logs, workflow) so the UI clears immediately
|
|
1291
|
+
// instead of waiting for the next SSE sync.
|
|
1292
|
+
const runtime = context.runtime ?? {};
|
|
1293
|
+
const workspace = context.session.workspace ?? null;
|
|
1294
|
+
const parts = [];
|
|
1295
|
+
if (runtime.url) {
|
|
1296
|
+
try {
|
|
1297
|
+
const killed = await postRuntimeKill({ url: runtime.url, workspace, runId: null, purge: true });
|
|
1298
|
+
const purged = killed.purged ?? { runs: 0, events: 0, queue: 0 };
|
|
1299
|
+
parts.push(`runtime : ${killed.runs ?? 0} run(s) interrompu(s), ${killed.tasks ?? 0} tâche(s), ${killed.queued ?? 0} requête(s)`);
|
|
1300
|
+
parts.push(`store purgé : ${purged.runs ?? 0} run(s), ${purged.events ?? 0} événement(s), ${purged.queue ?? 0} item(s) de file`);
|
|
1301
|
+
} catch (err) {
|
|
1302
|
+
parts.push(`runtime kill échoué : ${err instanceof Error ? err.message : String(err)}`);
|
|
1303
|
+
}
|
|
1304
|
+
} else {
|
|
1305
|
+
parts.push('runtime non connecté (rien à purger côté serveur)');
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const clearedQueue = clearFinishedQueueItems(context.session);
|
|
1309
|
+
context.session.agentProjection = {
|
|
1310
|
+
conversation: [],
|
|
1311
|
+
chain: [],
|
|
1312
|
+
plan: null,
|
|
1313
|
+
activities: [],
|
|
1314
|
+
logs: [],
|
|
1315
|
+
summary: null,
|
|
1316
|
+
status: 'idle',
|
|
1317
|
+
planRevision: 0,
|
|
1318
|
+
planPatches: [],
|
|
1319
|
+
};
|
|
1320
|
+
context.session.headlessPlan = null;
|
|
1321
|
+
context.session.activities = {};
|
|
1322
|
+
context.session.controlQueue = [];
|
|
1323
|
+
context.session.workflow = null;
|
|
1324
|
+
context.session.jobQueue = [];
|
|
1325
|
+
context.session.productionActivity = null;
|
|
1326
|
+
parts.push(`file locale : ${clearedQueue} item(s) terminés nettoyés`);
|
|
1327
|
+
|
|
1328
|
+
return { output: `Interface réinitialisée (--all) — ${parts.join(' · ')}.` };
|
|
1283
1329
|
}
|
|
1284
1330
|
case 'exit':
|
|
1285
1331
|
case 'quit':
|
package/src/core/agentEvents.js
CHANGED
|
@@ -105,6 +105,21 @@ export function dispatchAgentEvent(session, event) {
|
|
|
105
105
|
return normalized;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Full in-memory projection reset for a session. The runtime keeps the live
|
|
109
|
+
// projection in memory (session.agentProjection) and serves it from /state, so
|
|
110
|
+
// interrupting runs is not enough to clear the PLAN/ACTIVITY/LOGS panels — the
|
|
111
|
+
// projection has to be emptied here too. Pair with store.clearWorkspaceState so
|
|
112
|
+
// the reset also survives a reboot (otherwise hydrateSession replays it back).
|
|
113
|
+
export function resetSessionProjection(session) {
|
|
114
|
+
if (!session || typeof session !== 'object') return;
|
|
115
|
+
session.agentEvents = [];
|
|
116
|
+
session._agentProjectionState = createProjectionState();
|
|
117
|
+
session.agentProjection = publicProjection(session._agentProjectionState);
|
|
118
|
+
applyAgentProjectionToSession(session, session.agentProjection);
|
|
119
|
+
session.jobQueue = [];
|
|
120
|
+
session._onPlanUpdate?.();
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
function withSessionRunIdentity(event, session) {
|
|
109
124
|
const identity = session?._currentRunIdentity;
|
|
110
125
|
if (!identity) return event;
|
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.14.
|
|
4
|
+
const WIKI_MANAGER_VERSION = '0.14.5';
|
|
5
5
|
|
|
6
6
|
function envValue(key) {
|
|
7
7
|
const filePath = managerEnvFile();
|
|
@@ -197,21 +197,14 @@ function compactDescription(value) {
|
|
|
197
197
|
return text.length > 420 ? `${text.slice(0, 417)}...` : text;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
function clarifyToolDescription(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (serverName === 'production' && toolName === 'production_start_job') {
|
|
209
|
-
return compactDescription([
|
|
210
|
-
base,
|
|
211
|
-
'Production export means wiki deliverable/publication export only. Do not use type=export for Confluence/CME/source export; use cme__cme_export_run instead.',
|
|
212
|
-
].filter(Boolean).join(' '));
|
|
213
|
-
}
|
|
214
|
-
return base;
|
|
200
|
+
function clarifyToolDescription(_serverName, _toolName, description) {
|
|
201
|
+
// Agnostic by design: the orchestrator does NOT inject per-agent knowledge
|
|
202
|
+
// here. Tool meaning — including disambiguation like "this export publishes a
|
|
203
|
+
// wiki deliverable, not a Confluence source export" — must live in each
|
|
204
|
+
// agent's own MCP tool description, so any operator (our orchestrator or a
|
|
205
|
+
// third-party host such as Claude) gets the same self-sufficient contract.
|
|
206
|
+
// We only normalize whitespace; we never rewrite what the agent published.
|
|
207
|
+
return compactDescription(description ?? '');
|
|
215
208
|
}
|
|
216
209
|
|
|
217
210
|
async function listMcpTools(endpoint) {
|
package/src/runtime/client.js
CHANGED
|
@@ -130,8 +130,9 @@ export async function postRuntimeKill({
|
|
|
130
130
|
token = runtimeToken(),
|
|
131
131
|
workspace = null,
|
|
132
132
|
runId = null,
|
|
133
|
+
purge = false,
|
|
133
134
|
} = {}) {
|
|
134
|
-
const response = await fetch(runtimeEndpointWithParams(url, '/kill', { workspace, runId }), {
|
|
135
|
+
const response = await fetch(runtimeEndpointWithParams(url, '/kill', { workspace, runId, ...(purge ? { purge: 'true' } : {}) }), {
|
|
135
136
|
method: 'POST',
|
|
136
137
|
headers: runtimeHeaders(token),
|
|
137
138
|
});
|
|
@@ -227,6 +227,9 @@ test('CME export is dispatched only from an approved DAG task', async () => {
|
|
|
227
227
|
let executeCalls = 0;
|
|
228
228
|
const session = baseSession({
|
|
229
229
|
workspace: 'demo-workspace',
|
|
230
|
+
// Production runs wait indefinitely for a human approval. This contract
|
|
231
|
+
// test is headless, so give the scheduler an explicit bounded deadline.
|
|
232
|
+
_approvalTimeoutMs: 10,
|
|
230
233
|
mcp: {
|
|
231
234
|
cme: {
|
|
232
235
|
status: 'connected',
|
package/src/runtime/runner.js
CHANGED
|
@@ -214,7 +214,7 @@ export async function runRuntimeAgenticWorkflow(agent, session, input, {
|
|
|
214
214
|
},
|
|
215
215
|
}));
|
|
216
216
|
if (!evaluation.ok) {
|
|
217
|
-
if (
|
|
217
|
+
if (isAwaitingUserInputEvaluation(evaluation)) {
|
|
218
218
|
dispatchAgentEvent(session, createAgentEvent('assistant_message', {
|
|
219
219
|
origin: 'runtime',
|
|
220
220
|
runId,
|
|
@@ -820,7 +820,7 @@ export async function finishRuntimeRun(session, input, {
|
|
|
820
820
|
},
|
|
821
821
|
}));
|
|
822
822
|
if (!evaluation.ok) {
|
|
823
|
-
if (
|
|
823
|
+
if (isAwaitingUserInputEvaluation(evaluation)) {
|
|
824
824
|
dispatchAgentEvent(session, createAgentEvent('assistant_message', {
|
|
825
825
|
origin: 'runtime',
|
|
826
826
|
runId,
|
|
@@ -874,8 +874,10 @@ export async function evaluateRuntimeRun(session, input, {
|
|
|
874
874
|
system: [
|
|
875
875
|
'You are a strict evaluator for an agentic runtime run.',
|
|
876
876
|
'Inspect whether the original task was accomplished using the final plan and recent conversation.',
|
|
877
|
-
'Return only JSON with this exact shape: {"ok":boolean,"reason":"...","suggestedAction":string|null}.',
|
|
877
|
+
'Return only JSON with this exact shape: {"ok":boolean,"awaitingUserInput":boolean,"reason":"...","suggestedAction":string|null}.',
|
|
878
878
|
'Use ok=false only when a concrete missing action, failed requirement, or wrong result is visible.',
|
|
879
|
+
'Set awaitingUserInput=true when the run correctly stopped to obtain information or a decision only the user can provide (e.g. credentials, choosing between options, an explicit confirmation) — this is expected behaviour, NOT a failure or a missing action. In that case still set ok=false, put the pending question in reason, and do not treat the unasked configuration as a failed requirement.',
|
|
880
|
+
'Otherwise set awaitingUserInput=false.',
|
|
879
881
|
].join('\n'),
|
|
880
882
|
tools: [],
|
|
881
883
|
messages: [{ role: 'user', content: buildEvaluationPrompt(input, session, { runId }) }],
|
|
@@ -956,6 +958,7 @@ function parseJsonFenced(content, label = 'JSON response') {
|
|
|
956
958
|
function normalizeEvaluation(value) {
|
|
957
959
|
return {
|
|
958
960
|
ok: value?.ok === true,
|
|
961
|
+
awaitingUserInput: value?.awaitingUserInput === true,
|
|
959
962
|
reason: String(value?.reason ?? '').trim() || (value?.ok === true ? 'Task completed.' : 'Evaluator rejected the run.'),
|
|
960
963
|
suggestedAction: value?.suggestedAction == null ? null : String(value.suggestedAction),
|
|
961
964
|
};
|
|
@@ -969,6 +972,22 @@ function isUndefinedObjectiveEvaluation(evaluation) {
|
|
|
969
972
|
return /\b(vague|undefined|indefini|unclear|clarif|ambiguous|missing objective|no objective)\b/.test(text);
|
|
970
973
|
}
|
|
971
974
|
|
|
975
|
+
// A run that correctly hands back to the user for required input/decision is
|
|
976
|
+
// NOT a failed run \u2014 it is the expected end of an interactive turn. Treat it
|
|
977
|
+
// like the undefined-objective case: surface the pending question in chat and
|
|
978
|
+
// close the run cleanly (ok, run_done), never replan or emit run_error. The
|
|
979
|
+
// explicit evaluator field is authoritative; the keyword fallback covers models
|
|
980
|
+
// that answer in prose without emitting the flag.
|
|
981
|
+
function isAwaitingUserInputEvaluation(evaluation) {
|
|
982
|
+
if (evaluation?.awaitingUserInput === true) return true;
|
|
983
|
+
if (isUndefinedObjectiveEvaluation(evaluation)) return true;
|
|
984
|
+
const text = `${evaluation?.reason ?? ''} ${evaluation?.suggestedAction ?? ''}`
|
|
985
|
+
.toLowerCase()
|
|
986
|
+
.normalize('NFD')
|
|
987
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
988
|
+
return /\b(awaiting user|await user|user input|user decision|user confirmation|needs? (?:the )?user|requires? (?:the )?user|what to modify|which .* (?:to|should)|ask(?:ed|ing)? the user|attend .* utilisateur|demande .* utilisateur)\b/.test(text);
|
|
989
|
+
}
|
|
990
|
+
|
|
972
991
|
function clarificationMessageForEvaluation(evaluation) {
|
|
973
992
|
const reason = String(evaluation?.reason ?? '').trim();
|
|
974
993
|
return reason
|
package/src/runtime/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
2
|
import { randomUUID, timingSafeEqual } from 'node:crypto';
|
|
3
|
-
import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
3
|
+
import { createAgentEvent, dispatchAgentEvent, resetSessionProjection } from '../core/agentEvents.js';
|
|
4
4
|
import { activeCacertPath } from '../core/cacert.js';
|
|
5
5
|
import { normalizePlanPatch, rebasePlanPatch } from '../core/planPatch.js';
|
|
6
6
|
import { validateContractInDev } from '../contracts/schemas.js';
|
|
@@ -322,8 +322,9 @@ export function startRuntimeServer({
|
|
|
322
322
|
const body = await readJson(request);
|
|
323
323
|
const workspace = workspaceFromBody(body) ?? workspaceFromUrl(url);
|
|
324
324
|
const runId = url.searchParams.get('runId') ?? body.runId ?? null;
|
|
325
|
+
const purge = body.purge === true || url.searchParams.get('purge') === 'true';
|
|
325
326
|
const context = await resolveContext({ workspace });
|
|
326
|
-
const result = await killRuntimeRuns(context, { workspace, runId });
|
|
327
|
+
const result = await killRuntimeRuns(context, { workspace, runId, purge });
|
|
327
328
|
publishState(context.workspace ?? workspace ?? null, context);
|
|
328
329
|
sendJson(response, 202, result);
|
|
329
330
|
return;
|
|
@@ -409,7 +410,7 @@ export function startRuntimeServer({
|
|
|
409
410
|
return { body, workspace, context };
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
async function killRuntimeRuns(context, { workspace = null, runId = null } = {}) {
|
|
413
|
+
async function killRuntimeRuns(context, { workspace = null, runId = null, purge = false } = {}) {
|
|
413
414
|
const targetWorkspace = context?.workspace ?? workspace ?? null;
|
|
414
415
|
const targetRunId = runId ? String(runId) : null;
|
|
415
416
|
if (!targetRunId || targetRunId === context?.currentRunId) {
|
|
@@ -423,7 +424,18 @@ export function startRuntimeServer({
|
|
|
423
424
|
? store.cancelActiveTasksForInterruptedRuns({ workspace: targetWorkspace, runId: targetRunId })
|
|
424
425
|
: 0;
|
|
425
426
|
const queued = cancelQueuedControlItems(context?.session, targetWorkspace);
|
|
426
|
-
|
|
427
|
+
// Purge (/clear --all): interrupting recoverable runs does not clear a
|
|
428
|
+
// terminal 'error' run still projected in memory, nor the persisted event
|
|
429
|
+
// log. Empty both so PLAN/ACTIVITY/LOGS disappear and stay gone after a
|
|
430
|
+
// reboot. runId-scoped kills never purge (too coarse — it is workspace-wide).
|
|
431
|
+
let purged = null;
|
|
432
|
+
if (purge && !targetRunId) {
|
|
433
|
+
resetSessionProjection(context?.session);
|
|
434
|
+
purged = typeof store.clearWorkspaceState === 'function'
|
|
435
|
+
? store.clearWorkspaceState({ workspace: targetWorkspace })
|
|
436
|
+
: { runs: 0, events: 0, queue: 0 };
|
|
437
|
+
}
|
|
438
|
+
return { killed: true, workspace: targetWorkspace, runId: targetRunId, runs, tasks, queued, ...(purged !== null ? { purged } : {}) };
|
|
427
439
|
}
|
|
428
440
|
|
|
429
441
|
function startRuntimeRun(context, body, { controlItemId = null, waitForPlan = false } = {}) {
|
package/src/runtime/store.js
CHANGED
|
@@ -670,6 +670,45 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
670
670
|
return tasks.length;
|
|
671
671
|
}
|
|
672
672
|
|
|
673
|
+
// Full wipe of a workspace's persisted runtime state: runs (and, via the
|
|
674
|
+
// ON DELETE CASCADE foreign keys, their task_groups/tasks/task_dependencies/
|
|
675
|
+
// plan_revisions/approval_grants), the event log, and the queue. The
|
|
676
|
+
// non-cascading task_* side tables are cleared explicitly. This is what makes
|
|
677
|
+
// /clear --all survive a reboot: without it, hydrateSession replays the events
|
|
678
|
+
// and the PLAN/ACTIVITY/LOGS come straight back. Destructive by design — only
|
|
679
|
+
// reached on an explicit purge.
|
|
680
|
+
function clearWorkspaceState({ workspace = null } = {}) {
|
|
681
|
+
const runIds = (workspace
|
|
682
|
+
? db.prepare('SELECT id FROM runs WHERE workspace = ?').all(workspace)
|
|
683
|
+
: db.prepare('SELECT id FROM runs').all()
|
|
684
|
+
).map((row) => row.id);
|
|
685
|
+
db.exec('BEGIN');
|
|
686
|
+
try {
|
|
687
|
+
const delResults = db.prepare('DELETE FROM task_results WHERE task_id IN (SELECT id FROM tasks WHERE run_id = ?)');
|
|
688
|
+
const delAssignments = db.prepare('DELETE FROM task_assignments WHERE task_id IN (SELECT id FROM tasks WHERE run_id = ?)');
|
|
689
|
+
const delAttempts = db.prepare('DELETE FROM task_attempts WHERE run_id = ?');
|
|
690
|
+
for (const runId of runIds) {
|
|
691
|
+
delResults.run(runId);
|
|
692
|
+
delAssignments.run(runId);
|
|
693
|
+
delAttempts.run(runId);
|
|
694
|
+
}
|
|
695
|
+
const events = (workspace
|
|
696
|
+
? db.prepare('DELETE FROM events WHERE workspace = ?').run(workspace)
|
|
697
|
+
: db.prepare('DELETE FROM events').run()).changes ?? 0;
|
|
698
|
+
const queue = (workspace
|
|
699
|
+
? db.prepare('DELETE FROM queue_items WHERE workspace = ?').run(workspace)
|
|
700
|
+
: db.prepare('DELETE FROM queue_items').run()).changes ?? 0;
|
|
701
|
+
const runs = (workspace
|
|
702
|
+
? db.prepare('DELETE FROM runs WHERE workspace = ?').run(workspace)
|
|
703
|
+
: db.prepare('DELETE FROM runs').run()).changes ?? 0;
|
|
704
|
+
db.exec('COMMIT');
|
|
705
|
+
return { runs, events, queue };
|
|
706
|
+
} catch (error) {
|
|
707
|
+
db.exec('ROLLBACK');
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
673
712
|
function saveQueue(queue = [], { workspace = null } = {}) {
|
|
674
713
|
const items = Array.isArray(queue) ? queue : [];
|
|
675
714
|
const now = new Date().toISOString();
|
|
@@ -1166,6 +1205,7 @@ export function openRuntimeStore({ stateDir = defaultRuntimeStateDir(), fileName
|
|
|
1166
1205
|
listRecoverableWorkspaces,
|
|
1167
1206
|
interruptRuns,
|
|
1168
1207
|
cancelActiveTasksForInterruptedRuns,
|
|
1208
|
+
clearWorkspaceState,
|
|
1169
1209
|
saveQueue,
|
|
1170
1210
|
listQueue,
|
|
1171
1211
|
listTasks,
|
package/src/shell/repl.js
CHANGED
|
@@ -311,10 +311,11 @@ function buildDirectChatSystemPrompt(session) {
|
|
|
311
311
|
const wikirc = session.wikirc?.profile ?? 'no profile loaded';
|
|
312
312
|
const language = session.language ?? 'en-US';
|
|
313
313
|
return [
|
|
314
|
-
'You are Donna, the llm-wiki-manager chat assistant.',
|
|
314
|
+
'You are Donna, the llm-wiki-manager chat assistant: warm, plain-spoken, and helpful — like an attentive colleague, never a raw status dump.',
|
|
315
315
|
'You have a small READ-ONLY toolset — the tools provided to you for this turn, which may be none. Use them to answer questions about live state (e.g. "le CME est-il configuré", "quelles pages sont en attente"), and answer only from their results.',
|
|
316
316
|
'If no provided tool covers the request — or the request is an action or mutation (ingest, build, export, configure, send, delete…), or needs a service that is not connected — say plainly you cannot do it in chat mode and to switch to agent mode (/agent). Do not pretend to execute it and never guess.',
|
|
317
317
|
'Answer directly and concisely. Do not claim to have called tools or changed files beyond the tools actually provided.',
|
|
318
|
+
'Chat mode is READ-ONLY, so never offer to perform an action yourself here — do NOT say "want me to start the ingestion?", because you cannot. That offer belongs to agent mode. When a natural next step is an action, you may warmly hand off instead, in one short line (in the reply language): e.g. "If you want to run the ingestion, switch to agent mode with /agent." Point the way; never promise to do it.',
|
|
318
319
|
'Never add a "Next steps", "Prochaines étapes", "À suivre", options, or suggestions section unless the user explicitly asks what to do next. End after answering the question.',
|
|
319
320
|
'Never invent a tool name, command, job id, status, or result (e.g. do not fabricate names like "check_cme_configuration"). If you cannot know something with the tools you were given, say so.',
|
|
320
321
|
`Reply language: ${language}.`,
|