@dotdrelle/wiki-manager 0.15.93 → 0.15.96
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 +39 -28
- package/docker-compose.yml +1 -1
- package/package.json +2 -2
- package/src/agent/graph.js +78 -26
- package/src/agent/graph.test.js +28 -2
- package/src/cli/wiki-manager.js +138 -18
- package/src/commands/slash.js +38 -4
- package/src/commands/slash.test.js +11 -1
- package/src/core/agentEvents.js +24 -3
- package/src/core/agentEvents.test.js +37 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/dockerCompose.test.js +14 -0
- package/src/core/googleGrants.js +0 -3
- package/src/core/json.js +9 -0
- package/src/core/mcp.js +1 -1
- package/src/core/plan.js +0 -4
- package/src/core/progressNotes.js +0 -4
- package/src/core/skillChainView.js +3 -1
- package/src/core/skillCompiler.test.js +21 -1
- package/src/core/toolLoop.js +56 -2
- package/src/core/toolLoop.test.js +35 -4
- package/src/orchestrator/agentRegistry.js +1 -3
- package/src/orchestrator/dependencyResolver.js +0 -3
- package/src/orchestrator/objectiveResolver.test.js +27 -0
- package/src/orchestrator/planValidator.js +1 -3
- package/src/orchestrator/providers/runtimeProvider.js +0 -14
- package/src/orchestrator/taskStatuses.js +8 -0
- package/src/runtime/client.js +0 -16
- package/src/runtime/controlDrain.js +6 -3
- package/src/runtime/deltaCoalescer.js +53 -0
- package/src/runtime/deltaCoalescer.test.js +56 -0
- package/src/runtime/loginPage.js +129 -0
- package/src/runtime/loginRoutes.test.js +131 -0
- package/src/runtime/loginSession.js +223 -0
- package/src/runtime/loginSession.test.js +143 -0
- package/src/runtime/qrCode.js +15 -0
- package/src/runtime/runner.js +29 -3
- package/src/runtime/runner.test.js +90 -1
- package/src/runtime/server.js +240 -1
- package/src/runtime/server.test.js +87 -0
- package/src/runtime/skillRun.js +1 -1
- package/src/runtime/skillRun.test.js +3 -0
- package/src/runtime/totp.js +87 -0
- package/src/runtime/totp.test.js +80 -0
- package/src/runtime/totpLogin.js +123 -0
- package/src/runtime/vendor/qrcode.cjs +2297 -0
- package/src/shell/repl.js +7 -3
- package/src/orchestrator/.fuse_hidden0000001c00000001 +0 -316
package/README.md
CHANGED
|
@@ -121,47 +121,60 @@ in isolated workspaces.
|
|
|
121
121
|
|
|
122
122
|
## How wikiLLM compares
|
|
123
123
|
|
|
124
|
-
Several open projects now build
|
|
124
|
+
Several open projects now build an LLM-maintained knowledge layer over your
|
|
125
|
+
sources — some as a wiki, some as a retrieval and answer platform. They target
|
|
125
126
|
**different problems** — the useful questions are *what goes in, what comes out,
|
|
126
|
-
and who operates it*. Snapshot as of 2026
|
|
127
|
+
and who operates it*. Snapshot as of **September 2026**; all of these move quickly.
|
|
127
128
|
|
|
128
129
|
✅ first-class · 🟡 partial or indirect · ❌ not a goal
|
|
129
130
|
|
|
130
|
-
Projects compared: [OpenWiki](https://github.com/langchain-ai/openwiki)
|
|
131
|
-
[DeepWiki-Open](https://github.com/asyncfuncai/deepwiki-open)
|
|
131
|
+
Projects compared: [OpenWiki](https://github.com/langchain-ai/openwiki) and
|
|
132
|
+
[DeepWiki-Open](https://github.com/asyncfuncai/deepwiki-open) (code / knowledge
|
|
133
|
+
wikis), [RAGFlow](https://github.com/infiniflow/ragflow) and
|
|
134
|
+
[Onyx](https://github.com/onyx-dot-app/onyx) (RAG platforms), and
|
|
132
135
|
[GraphRAG](https://github.com/microsoft/graphrag).
|
|
133
136
|
|
|
134
137
|
<table>
|
|
135
138
|
<thead>
|
|
136
|
-
<tr
|
|
139
|
+
<tr>
|
|
140
|
+
<th><small>Need</small></th>
|
|
141
|
+
<th><small><strong>wikiLLM</strong></small></th>
|
|
142
|
+
<th><small><strong>Code / knowledge wikis</strong><br>OpenWiki · DeepWiki-Open</small></th>
|
|
143
|
+
<th><small><strong>RAG platforms</strong><br>RAGFlow · Onyx</small></th>
|
|
144
|
+
<th><small><strong>Graph pipeline</strong><br>GraphRAG</small></th>
|
|
145
|
+
</tr>
|
|
137
146
|
</thead>
|
|
138
147
|
<tbody>
|
|
139
|
-
<tr><td><small>Input</small></td><td><small>✅ Business docs — Confluence, PDF, Office, SaaS</small></td><td><small>✅
|
|
140
|
-
<tr><td><small>Output</small></td><td><small>✅ Maintained wiki <strong>+ deliverables</strong
|
|
141
|
-
<tr><td><small>
|
|
142
|
-
<tr><td><small>
|
|
143
|
-
<tr><td><small>
|
|
144
|
-
<tr><td><small>
|
|
145
|
-
<tr><td><small>
|
|
146
|
-
<tr><td><small>
|
|
147
|
-
<tr><td><small>Connectors as services</small></td><td><small>✅ Independent MCP agents (Confluence, docs, e-mail…)</small></td><td><small>🟡 Built-in connector set</small></td><td><small>❌</small></td><td><small>❌</small></td></tr>
|
|
148
|
-
<tr><td><small>Offline / local models</small></td><td><small>✅ Per-workspace OpenAI-compatible or gateway (Ollama, vLLM, MLX…)</small></td><td><small>✅ 13+ providers</small></td><td><small>✅ Ollama</small></td><td><small>✅ Any OpenAI-compatible</small></td></tr>
|
|
149
|
-
<tr><td><small>License</small></td><td><small>❌ PolyForm <strong>Noncommercial</strong></small></td><td><small>✅ MIT</small></td><td><small>✅ MIT</small></td><td><small>✅ MIT</small></td></tr>
|
|
148
|
+
<tr><td><small>Input</small></td><td><small>✅ Business docs — Confluence, PDF, Office, SaaS</small></td><td><small>✅ Code repo; OpenWiki also personal sources (Notion, Gmail, Slack…)</small></td><td><small>✅ PDF/Office/images, Confluence, S3, 50+ connectors</small></td><td><small>🟡 Plain text</small></td></tr>
|
|
149
|
+
<tr><td><small>Output</small></td><td><small>✅ Maintained wiki <strong>+ deliverables</strong></small></td><td><small>✅ Linked Markdown wiki (+ graph / diagrams)</small></td><td><small>🟡 Grounded RAG answers, not a wiki</small></td><td><small>❌ Entity graph + summaries</small></td></tr>
|
|
150
|
+
<tr><td><small>Agents & actions</small></td><td><small>✅ Capability dispatcher, external agentic runtime, MCP tools</small></td><td><small>✅ Doc-generation agents, MCP connectors</small></td><td><small>✅ Agentic workflow, MCP actions, deep research</small></td><td><small>❌ Indexing pipeline</small></td></tr>
|
|
151
|
+
<tr><td><small>Keep current</small></td><td><small>✅ Re-ingest / scheduled</small></td><td><small>✅ <code>--update</code> / regenerate, CI</small></td><td><small>✅ Scheduled connector sync</small></td><td><small>🟡 Maintenance mode</small></td></tr>
|
|
152
|
+
<tr><td><small>Team UI</small></td><td><small>✅ Web console — wiki, graph, chat, runs</small></td><td><small>🟡 CLI + visualizer; DeepWiki-Open web app</small></td><td><small>✅ Self-hosted web app</small></td><td><small>❌ Library / CLI</small></td></tr>
|
|
153
|
+
<tr><td><small>Multi-project isolation</small></td><td><small>✅ Workspaces, own services, ports, secrets</small></td><td><small>❌ One wiki per repo / directory</small></td><td><small>🟡 Datasets / teams, not isolated services</small></td><td><small>❌ One index per corpus</small></td></tr>
|
|
154
|
+
<tr><td><small>Human approval & governance</small></td><td><small>✅ Approval-gated dispatcher, budgets, crash recovery</small></td><td><small>❌ PR review only</small></td><td><small>🟡 RBAC / SSO (Onyx enterprise)</small></td><td><small>❌</small></td></tr>
|
|
155
|
+
<tr><td><small>License</small></td><td><small>❌ PolyForm <strong>Noncommercial</strong></small></td><td><small>✅ MIT</small></td><td><small>✅ Apache-2.0 / MIT CE</small></td><td><small>✅ MIT</small></td></tr>
|
|
150
156
|
</tbody>
|
|
151
157
|
</table>
|
|
152
158
|
|
|
159
|
+
<small>Per-project details: OpenWiki emits OKF v0.2 and maintains a versioned
|
|
160
|
+
evidence graph of “grounded claims”; GraphRAG is a research project now in
|
|
161
|
+
maintenance mode; Onyx’s SSO/RBAC live in its enterprise edition.</small>
|
|
162
|
+
|
|
153
163
|
**The short version:**
|
|
154
164
|
|
|
155
|
-
- **OpenWiki** and **DeepWiki-Open**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
- **OpenWiki** and **DeepWiki-Open** build wikis from a **code repository**
|
|
166
|
+
(OpenWiki also ingests personal sources — Notion, Gmail, Slack…). Their input
|
|
167
|
+
is code or personal feeds, not a stack of business documents feeding your
|
|
168
|
+
deliverable templates.
|
|
169
|
+
- **RAGFlow** and **Onyx** are RAG platforms: they index sources and answer
|
|
170
|
+
questions from them, with agents and connectors, but the output is an answer,
|
|
171
|
+
not a wiki you browse or a document you ship.
|
|
172
|
+
- **GraphRAG** builds retrieval structure and is now in maintenance mode — a
|
|
173
|
+
complementary back end, not a wiki.
|
|
174
|
+
- **wikiLLM** is the only one whose output is *both* a browsable wiki *and*
|
|
175
|
+
regenerated business documents, and the only one with the operational layer —
|
|
176
|
+
isolated projects, bounded approvals, automatic recovery, a web console —
|
|
177
|
+
that a shared internal tool needs.
|
|
165
178
|
|
|
166
179
|
**What wikiLLM does *not* try to do (today):**
|
|
167
180
|
|
|
@@ -170,8 +183,6 @@ Projects compared: [OpenWiki](https://github.com/langchain-ai/openwiki),
|
|
|
170
183
|
- Serve a true multi-user instance with per-user identity and an attributed
|
|
171
184
|
audit trail. This is a single-user deployment baseline (see the scope note
|
|
172
185
|
above).
|
|
173
|
-
- Expose a graph-query API over the corpus the way GraphRAG does; retrieval is
|
|
174
|
-
BM25 plus a vector index feeding generation.
|
|
175
186
|
- Ship or host the multi-provider AI gateway — routing to several providers is
|
|
176
187
|
supported, the gateway itself is infrastructure you bring.
|
|
177
188
|
|
package/docker-compose.yml
CHANGED
|
@@ -130,7 +130,7 @@ services:
|
|
|
130
130
|
# error. Every compose-deployed ingest then ran without the Lot 4 barrier
|
|
131
131
|
# and left the published map stale — the very defect that work fixed.
|
|
132
132
|
# `copy` stays out on purpose: it is the legacy step, opt-in only.
|
|
133
|
-
- PRODUCTION_ALLOWED_STEPS=${PRODUCTION_ALLOWED_STEPS:-doctor,doctor_apply,ingest,ingest_plan,ingest_apply,build,export,polish,restore,pipeline}
|
|
133
|
+
- PRODUCTION_ALLOWED_STEPS=${PRODUCTION_ALLOWED_STEPS:-doctor,doctor_apply,copy,ingest,ingest_plan,ingest_apply,ingest_rebuild,build,export,polish,restore,pipeline,lint}
|
|
134
134
|
- PRODUCTION_REQUIRE_CONFIRMATION=${PRODUCTION_REQUIRE_CONFIRMATION:-false}
|
|
135
135
|
# Parallelism levers — effective concurrency ≈ recommendedConcurrency.
|
|
136
136
|
# Intermediate defaults (4/8). Low profile 2/4, high profile 8/16.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.96",
|
|
4
4
|
"description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "bun ./bin/wiki-manager.js",
|
|
22
|
-
"test": "node --test src/core/skillInvocation.test.js src/core/skillCompiler.test.js src/runtime/skillRun.test.js src/runtime/controlDrain.test.js src/runtime/controlCancellation.test.js src/cli/runtimeStartup.test.js src/cli/wiki-manager.test.js src/agent/graph.test.js src/agent/skillRecursion.test.js src/contracts/schemas.test.js src/core/activity.test.js src/core/env.test.js src/core/agentsCompose.test.js src/core/profileServiceStatus.test.js src/core/workspaceProfile.test.js src/core/buildInfo.test.js src/core/agentEvents.test.js src/core/skillChainView.test.js src/core/runtimeLog.test.js src/core/runtimeEventAdapter.test.js src/activity/activityAggregator.test.js src/graph/runGraphProjector.test.js src/core/workflow.test.js src/core/planPatch.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/toolLoop.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/otherWorkspacesRunning.test.js src/core/wikiSetup.test.js src/core/wikiWorkspace.test.js src/core/wikiWorkspaceStart.test.js src/core/wikirc.test.js src/core/workspaceInherit.test.js src/core/cacert.test.js src/core/composeOverrides.test.js src/core/setEnvValue.test.js src/core/commandFailure.test.js src/core/currentArtifact.test.js src/core/googleGrants.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/orchestrator/agentRegistry.test.js src/orchestrator/capabilityRegistry.test.js src/orchestrator/capabilityResolver.test.js src/orchestrator/planValidator.test.js src/orchestrator/planIntegrator.test.js src/orchestrator/taskStatuses.test.js src/orchestrator/scheduler.test.js src/orchestrator/attemptManager.test.js src/orchestrator/resultAggregator.test.js src/orchestrator/approvalPolicy.test.js src/orchestrator/dispatcher.test.js src/orchestrator/objectiveResolver.test.js src/orchestrator/providers/fakeRuntimeProvider.test.js src/orchestrator/providers/runtimeProviders.test.js src/orchestrator/providers/dispatcherExternalRuntime.test.js src/orchestrator/providers/deepAgentsProvider.test.js src/commands/slash.test.js src/shell/repl.test.js src/shell/setupWizardModality.test.js src/shell/setupWizardPlaceholders.test.js src/shell/setupWizardSuggestions.test.js src/shell/setupWizardDiscovery.test.js src/shell/wrapText.test.js src/runtime/lifecycle.test.js src/runtime/store.test.js src/runtime/workspaceIsolation.test.js src/runtime/controlMessages.test.js src/runtime/recoveryManager.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/delegation.test.js src/runtime/runner.test.js src/runtime/runner.e2e.test.js src/runtime/skillChain.e2e.test.js src/runtime/donna-contract.test.js src/runtime/approvals.test.js src/runtime/auth.test.js",
|
|
22
|
+
"test": "node --test src/core/skillInvocation.test.js src/core/skillCompiler.test.js src/runtime/skillRun.test.js src/runtime/controlDrain.test.js src/runtime/controlCancellation.test.js src/cli/runtimeStartup.test.js src/cli/wiki-manager.test.js src/agent/graph.test.js src/agent/skillRecursion.test.js src/contracts/schemas.test.js src/core/activity.test.js src/core/env.test.js src/core/agentsCompose.test.js src/core/profileServiceStatus.test.js src/core/workspaceProfile.test.js src/core/buildInfo.test.js src/core/agentEvents.test.js src/core/skillChainView.test.js src/core/runtimeLog.test.js src/core/runtimeEventAdapter.test.js src/activity/activityAggregator.test.js src/graph/runGraphProjector.test.js src/core/workflow.test.js src/core/planPatch.test.js src/core/agentLoop.test.js src/core/plan.test.js src/core/mcp.test.js src/core/toolLoop.test.js src/core/documentIntake.test.js src/core/dockerCompose.test.js src/core/otherWorkspacesRunning.test.js src/core/wikiSetup.test.js src/core/wikiWorkspace.test.js src/core/wikiWorkspaceStart.test.js src/core/wikirc.test.js src/core/workspaceInherit.test.js src/core/cacert.test.js src/core/composeOverrides.test.js src/core/setEnvValue.test.js src/core/commandFailure.test.js src/core/currentArtifact.test.js src/core/googleGrants.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/core/queueStore.test.js src/orchestrator/agentRegistry.test.js src/orchestrator/capabilityRegistry.test.js src/orchestrator/capabilityResolver.test.js src/orchestrator/planValidator.test.js src/orchestrator/planIntegrator.test.js src/orchestrator/taskStatuses.test.js src/orchestrator/scheduler.test.js src/orchestrator/attemptManager.test.js src/orchestrator/resultAggregator.test.js src/orchestrator/approvalPolicy.test.js src/orchestrator/dispatcher.test.js src/orchestrator/objectiveResolver.test.js src/orchestrator/providers/fakeRuntimeProvider.test.js src/orchestrator/providers/runtimeProviders.test.js src/orchestrator/providers/dispatcherExternalRuntime.test.js src/orchestrator/providers/deepAgentsProvider.test.js src/commands/slash.test.js src/shell/repl.test.js src/shell/setupWizardModality.test.js src/shell/setupWizardPlaceholders.test.js src/shell/setupWizardSuggestions.test.js src/shell/setupWizardDiscovery.test.js src/shell/wrapText.test.js src/runtime/lifecycle.test.js src/runtime/store.test.js src/runtime/workspaceIsolation.test.js src/runtime/controlMessages.test.js src/runtime/deltaCoalescer.test.js src/runtime/recoveryManager.test.js src/runtime/server.test.js src/runtime/supervisor.test.js src/runtime/delegation.test.js src/runtime/runner.test.js src/runtime/runner.e2e.test.js src/runtime/skillChain.e2e.test.js src/runtime/donna-contract.test.js src/runtime/approvals.test.js src/runtime/auth.test.js src/runtime/totp.test.js src/runtime/loginSession.test.js src/runtime/loginRoutes.test.js",
|
|
23
23
|
"check-versions": "node scripts/check-versions.js",
|
|
24
24
|
"prepack": "node scripts/check-versions.js",
|
|
25
25
|
"prepublishOnly": "node scripts/check-versions.js",
|
package/src/agent/graph.js
CHANGED
|
@@ -31,6 +31,7 @@ import { artifactFromToolCall, currentArtifactFor, currentArtifactPromptLine, re
|
|
|
31
31
|
import { capabilityRegistryForSession } from '../orchestrator/capabilityRegistry.js';
|
|
32
32
|
import { objectiveForResolution } from '../orchestrator/objectiveResolver.js';
|
|
33
33
|
import { fetchRuntimeState, postRuntimeCancel, postRuntimeControl, postRuntimeDelegate, postRuntimeKill, postRuntimeSkill } from '../runtime/client.js';
|
|
34
|
+
import { formatPublicSkillInvocation, generateSkillAcknowledgment } from '../runtime/skillRun.js';
|
|
34
35
|
|
|
35
36
|
const MAX_TOOL_ITERATIONS = 80;
|
|
36
37
|
/**
|
|
@@ -555,6 +556,27 @@ function isUnresolvedTargetFailure(rawFailure) {
|
|
|
555
556
|
return /file does not exist|does not exist|no files match/i.test(rawFailure);
|
|
556
557
|
}
|
|
557
558
|
|
|
559
|
+
// An agent that plans zero tasks is not a failure: it found nothing to act on
|
|
560
|
+
// (no pending source, no template, no deliverable, no archived source to
|
|
561
|
+
// re-file) and says so in its own synthesis sentence. Relaying that outcome is
|
|
562
|
+
// the whole point — the old generic path called it "could not be started" and
|
|
563
|
+
// sent the reader hunting a connectivity problem that did not exist.
|
|
564
|
+
export function nothingToDoForDonna(rawReason) {
|
|
565
|
+
const reason = String(rawReason ?? '')
|
|
566
|
+
.replace(/\s*Available capabilities:\s*[\s\S]*$/i, '')
|
|
567
|
+
.replace(/\b(?:provider|endpoint)=[^\s,]+/g, '')
|
|
568
|
+
.replace(/\s{2,}/g, ' ')
|
|
569
|
+
.trim()
|
|
570
|
+
.slice(0, 400);
|
|
571
|
+
return JSON.stringify({
|
|
572
|
+
delegated: false,
|
|
573
|
+
nothingToDo: true,
|
|
574
|
+
reason,
|
|
575
|
+
instruction:
|
|
576
|
+
'The agent ran its planning and found nothing to act on. Answer the user naturally in their language, stating that plainly (use the reason above), and do not call it a failure, do not suggest restarting a service, and do not retry the same action. Do not expose exception names, capability identifiers, tool names, UUIDs, or internal routing details.',
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
558
580
|
function unresolvedTargetForDonna(rawFailure) {
|
|
559
581
|
const cleaned = String(rawFailure ?? '')
|
|
560
582
|
.replace(/\b(?:provider|endpoint)=[^\s,]+/g, '')
|
|
@@ -941,14 +963,19 @@ export async function handleRuntimeControlTool(session, tool, args = {}) {
|
|
|
941
963
|
});
|
|
942
964
|
}
|
|
943
965
|
const result = await postRuntimeDelegate(objective, { url, workspace });
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
966
|
+
if (result?.runId) {
|
|
967
|
+
return JSON.stringify({
|
|
968
|
+
delegated: true,
|
|
969
|
+
runId: result.runId,
|
|
970
|
+
summary: result.delegation ?? null,
|
|
971
|
+
message: `Action started (${String(result.runId).slice(0, 8)}) after real-plan validation: ${result.delegation?.tasks ?? 0} task(s), ${result.delegation?.agent ?? 'resolved agent'}. Execution in progress.`,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const refused = String(result?.error ?? JSON.stringify(result));
|
|
975
|
+
// Same EMPTY_PLAN outcome as the in-run path above: relay it, don't call
|
|
976
|
+
// it a failure.
|
|
977
|
+
if (/^EMPTY_PLAN:\s*/i.test(refused)) return nothingToDoForDonna(refused.replace(/^EMPTY_PLAN:\s*/i, ''));
|
|
978
|
+
return `Delegation refused: ${refused}`;
|
|
952
979
|
}
|
|
953
980
|
if (tool === 'run_skill') {
|
|
954
981
|
const skillName = String(args.skillName ?? '').trim();
|
|
@@ -1351,24 +1378,6 @@ export function buildAgentSystemPrompt(state) {
|
|
|
1351
1378
|
return customPrompt ? `${customPrompt}\n\n${agentContext}` : agentContext;
|
|
1352
1379
|
}
|
|
1353
1380
|
|
|
1354
|
-
export function buildLimitedAgentResponse(state, reason = 'no workspace loaded with .wikirc.yaml') {
|
|
1355
|
-
const workspace = state.session.workspace ?? 'no workspace selected';
|
|
1356
|
-
const wikirc = state.session.wikirc?.profile ?? 'no profile loaded';
|
|
1357
|
-
return [
|
|
1358
|
-
`Donna is active. Current workspace: ${workspace}.`,
|
|
1359
|
-
`Current wikirc profile: ${wikirc}.`,
|
|
1360
|
-
'',
|
|
1361
|
-
'I am the shell agent mode: use `/agent` to route free text through this LangGraph graph, or `/chat` for direct chat.',
|
|
1362
|
-
`LLM connection: limited mode (${reason}).`,
|
|
1363
|
-
`Available primitives: ${commandList(state.session)}.`,
|
|
1364
|
-
'',
|
|
1365
|
-
'Connected MCP tools:',
|
|
1366
|
-
formatMcpToolsForAgent(state.session.mcp),
|
|
1367
|
-
'',
|
|
1368
|
-
'Limited mode: workspace, Docker Compose tools, MCP calls, /wiki fallback, skill discovery, and headless mode are wired.',
|
|
1369
|
-
'Use `/help` to see deterministic shell commands.',
|
|
1370
|
-
].join('\n');
|
|
1371
|
-
}
|
|
1372
1381
|
|
|
1373
1382
|
export function formatLlmUnavailableMessage(reason) {
|
|
1374
1383
|
const clean = String(reason ?? 'unknown reason').replace(/\s+/g, ' ').trim();
|
|
@@ -1877,6 +1886,7 @@ export function createAgentGraph(options = {}) {
|
|
|
1877
1886
|
const toolCalls = state.pendingToolCalls ?? [];
|
|
1878
1887
|
const toolResultMessages = [];
|
|
1879
1888
|
let terminalFailure = null;
|
|
1889
|
+
let skillLaunch = null;
|
|
1880
1890
|
|
|
1881
1891
|
for (const call of toolCalls) {
|
|
1882
1892
|
const resolved = resolveToolCallName(state.session.mcp, call.function.name, INTERNAL_TOOL_SERVERS);
|
|
@@ -1991,6 +2001,19 @@ export function createAgentGraph(options = {}) {
|
|
|
1991
2001
|
if (skillResult?.terminal === true) {
|
|
1992
2002
|
terminalFailure = skillResult.code ?? 'skill_failed';
|
|
1993
2003
|
ok = false;
|
|
2004
|
+
} else if (skillResult?.accepted === true) {
|
|
2005
|
+
// The skill owns execution from here. Without this the turn kept
|
|
2006
|
+
// going and the model could re-delegate or refuse the very
|
|
2007
|
+
// objective it had just launched — the "launched, then no agent
|
|
2008
|
+
// capable" contradiction. End the turn with the launch
|
|
2009
|
+
// acknowledgement instead of another tool round.
|
|
2010
|
+
skillLaunch = {
|
|
2011
|
+
publicInput: formatPublicSkillInvocation(
|
|
2012
|
+
skillResult.skill ?? args.skillName ?? '',
|
|
2013
|
+
args.arguments && typeof args.arguments === 'object' ? args.arguments : {},
|
|
2014
|
+
),
|
|
2015
|
+
objectives: Number(skillResult.objectiveCount ?? skillResult.objectives ?? 1) || 1,
|
|
2016
|
+
};
|
|
1994
2017
|
}
|
|
1995
2018
|
}
|
|
1996
2019
|
if (tool === 'delegate' && /^Runtime control error \(delegate\):/i.test(resultText)) {
|
|
@@ -2009,6 +2032,14 @@ export function createAgentGraph(options = {}) {
|
|
|
2009
2032
|
missingRequiredFields: needsInput[1].split(',').map((item) => item.trim()).filter(Boolean),
|
|
2010
2033
|
instruction: 'Ask the user for the missing required information. Do not expose internal validation details.',
|
|
2011
2034
|
});
|
|
2035
|
+
} else if (/^EMPTY_PLAN:\s*/i.test(delegationFailure)) {
|
|
2036
|
+
// Valid planning, nothing to do (no pending source/template/
|
|
2037
|
+
// deliverable). Not a failure and not retryable — Donna relays
|
|
2038
|
+
// the agent's own sentence instead of a connectivity-sounding
|
|
2039
|
+
// generic error. Checked before the target heuristic: a plan
|
|
2040
|
+
// reason is authoritative and could mention a missing file
|
|
2041
|
+
// without meaning "unresolved target".
|
|
2042
|
+
resultText = nothingToDoForDonna(delegationFailure.replace(/^EMPTY_PLAN:\s*/i, ''));
|
|
2012
2043
|
} else if (isUnresolvedTargetFailure(delegationFailure)) {
|
|
2013
2044
|
// A named target that resolves to nothing is not an unsupported
|
|
2014
2045
|
// action: Donna can look it up and retry (or ask), so the turn
|
|
@@ -2147,6 +2178,24 @@ export function createAgentGraph(options = {}) {
|
|
|
2147
2178
|
invalidResponseRetries: 0,
|
|
2148
2179
|
};
|
|
2149
2180
|
}
|
|
2181
|
+
if (skillLaunch) {
|
|
2182
|
+
// Deterministic, localized acknowledgement, generated in the session
|
|
2183
|
+
// language like the `/turn` skill path. No further model turn: the skill
|
|
2184
|
+
// is launched and owns execution, so nothing can contradict it.
|
|
2185
|
+
const response = await generateSkillAcknowledgment(state.session, skillLaunch).catch(
|
|
2186
|
+
() => `Started ${skillLaunch.publicInput} — ${skillLaunch.objectives} step(s) in progress.`,
|
|
2187
|
+
);
|
|
2188
|
+
return {
|
|
2189
|
+
messages: toolResultMessages,
|
|
2190
|
+
pendingToolCalls: null,
|
|
2191
|
+
response,
|
|
2192
|
+
forceDelegation: false,
|
|
2193
|
+
retryWithoutTool: false,
|
|
2194
|
+
terminalToolFailure: false,
|
|
2195
|
+
invalidToolCallRetries: 0,
|
|
2196
|
+
invalidResponseRetries: 0,
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2150
2199
|
return {
|
|
2151
2200
|
messages: toolResultMessages,
|
|
2152
2201
|
pendingToolCalls: null,
|
|
@@ -2159,6 +2208,9 @@ export function createAgentGraph(options = {}) {
|
|
|
2159
2208
|
}
|
|
2160
2209
|
|
|
2161
2210
|
function routeToolExecutor(state) {
|
|
2211
|
+
// A skill launch already produced its final acknowledgement: end the turn
|
|
2212
|
+
// instead of routing back for another model call that could contradict it.
|
|
2213
|
+
if (state.response != null) return END;
|
|
2162
2214
|
return 'orchestrator';
|
|
2163
2215
|
}
|
|
2164
2216
|
|
package/src/agent/graph.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import test from 'node:test';
|
|
|
3
3
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { bareToolCallJson, buildAgentSystemPrompt, connectorConfigurationTarget, createAgentGraph, invalidSuggestedSlashCommands, invalidUserFacingToolNames, isOrchestrationBypassTool, knownCapabilityIds, normalizeToolArgumentsFromSchema } from './graph.js';
|
|
6
|
+
import { bareToolCallJson, buildAgentSystemPrompt, connectorConfigurationTarget, createAgentGraph, invalidSuggestedSlashCommands, invalidUserFacingToolNames, isOrchestrationBypassTool, knownCapabilityIds, normalizeToolArgumentsFromSchema, nothingToDoForDonna } from './graph.js';
|
|
7
7
|
|
|
8
8
|
test('user-facing response guard hides MCP identifiers generically', () => {
|
|
9
9
|
const session = sessionBase();
|
|
@@ -13,6 +13,29 @@ test('user-facing response guard hides MCP identifiers generically', () => {
|
|
|
13
13
|
);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
test('an empty agent plan is relayed as "nothing to do", not as a failure', () => {
|
|
17
|
+
// The agent planned zero tasks and said why: no pending source, no template,
|
|
18
|
+
// no deliverable. That is an outcome. The old generic path called it "could
|
|
19
|
+
// not be started" and sent the reader after a connectivity problem that did
|
|
20
|
+
// not exist.
|
|
21
|
+
const payload = JSON.parse(nothingToDoForDonna(
|
|
22
|
+
'No Markdown files were found in raw/untracked; no ingest operation was planned.',
|
|
23
|
+
));
|
|
24
|
+
assert.equal(payload.delegated, false);
|
|
25
|
+
assert.equal(payload.nothingToDo, true);
|
|
26
|
+
assert.match(payload.reason, /No Markdown files were found in raw\/untracked/);
|
|
27
|
+
assert.match(payload.instruction, /do not call it a failure/i);
|
|
28
|
+
assert.doesNotMatch(payload.instruction, /EMPTY_PLAN/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('the nothing-to-do reason strips internal routing details', () => {
|
|
32
|
+
const payload = JSON.parse(nothingToDoForDonna(
|
|
33
|
+
'No templates were found. Available capabilities: document.build, knowledge.update provider=production endpoint=http://host:3202',
|
|
34
|
+
));
|
|
35
|
+
assert.doesNotMatch(payload.reason, /Available capabilities/);
|
|
36
|
+
assert.doesNotMatch(payload.reason, /provider=|endpoint=/);
|
|
37
|
+
});
|
|
38
|
+
|
|
16
39
|
test('CME setup stays direct while CME export execution stays orchestrated', () => {
|
|
17
40
|
assert.equal(isOrchestrationBypassTool('cme__cme_export_run'), true);
|
|
18
41
|
assert.equal(isOrchestrationBypassTool('cme__cme_setup'), false);
|
|
@@ -601,7 +624,10 @@ test('an explicitly selected skill runs through the intra-runtime path with name
|
|
|
601
624
|
},
|
|
602
625
|
});
|
|
603
626
|
const result = await createAgentGraph().invoke({ input: 'lance le skill deliver avec le template Quarterly report', session });
|
|
604
|
-
|
|
627
|
+
// Launching a skill ends the turn: the acknowledgement is generated once and
|
|
628
|
+
// the model is not given a second chance to re-delegate or contradict it.
|
|
629
|
+
assert.equal(result.response, 'Started /deliver deliverable="Quarterly report" — 1 step(s) in progress.');
|
|
630
|
+
assert.equal(mainCalls, 1);
|
|
605
631
|
// `skillStack` accompagne désormais la demande : le run imbriqué démarre après
|
|
606
632
|
// le nettoyage de celui-ci, et c'est le seul canal par lequel il peut savoir
|
|
607
633
|
// quelles compétences sont déjà ouvertes au-dessus de lui.
|
package/src/cli/wiki-manager.js
CHANGED
|
@@ -11,6 +11,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { readFileSync } from 'node:fs';
|
|
13
13
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
14
|
+
import { createInterface } from 'node:readline/promises';
|
|
14
15
|
import { dirname, join, resolve } from 'node:path';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
16
17
|
import { ensureManagerScaffold, loadManagerEnv } from '../core/env.js';
|
|
@@ -28,6 +29,8 @@ import { extractActivity, mergePolledActivity, parseJsonText, sessionActivities,
|
|
|
28
29
|
import { syncActivitiesToPlan, formatPlanStatus } from '../core/plan.js';
|
|
29
30
|
import { createAgentEvent, dispatchAgentEvent, reduceAgentEvents } from '../core/agentEvents.js';
|
|
30
31
|
import { runAgentTurn, runAgenticLoop } from '../core/agentLoop.js';
|
|
32
|
+
import { createDeltaCoalescer } from '../runtime/deltaCoalescer.js';
|
|
33
|
+
import { TERMINAL_STATUS_SET } from '../orchestrator/taskStatuses.js';
|
|
31
34
|
import { resolveCapabilityConcurrency } from '../orchestrator/scheduler.js';
|
|
32
35
|
import { capabilityRegistryForSession } from '../orchestrator/capabilityRegistry.js';
|
|
33
36
|
import { CapabilityUnavailableError, resolve as resolveCapability } from '../orchestrator/capabilityResolver.js';
|
|
@@ -49,6 +52,17 @@ function valueAfter(argv, flag) {
|
|
|
49
52
|
return argv[index + 1];
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
async function confirmOnTty(question, defaultValue) {
|
|
56
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return defaultValue;
|
|
57
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
58
|
+
try {
|
|
59
|
+
const answer = (await readline.question(`${question} [y/N] `)).trim().toLowerCase();
|
|
60
|
+
return answer === 'y' || answer === 'yes';
|
|
61
|
+
} finally {
|
|
62
|
+
readline.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
function errorDiagnostic(err) {
|
|
53
67
|
const parts = [];
|
|
54
68
|
let current = err;
|
|
@@ -815,7 +829,7 @@ async function runHeadless(argv, agent) {
|
|
|
815
829
|
// export finished, before the ingest had even started. The control queue is the
|
|
816
830
|
// only place where the whole chain is observable, so the wait is scoped to
|
|
817
831
|
// chainId and ends when every item of that chain is terminal.
|
|
818
|
-
const CHAIN_TERMINAL_STATUSES =
|
|
832
|
+
const CHAIN_TERMINAL_STATUSES = TERMINAL_STATUS_SET;
|
|
819
833
|
|
|
820
834
|
export async function waitForRuntimeChain(session, log, {
|
|
821
835
|
chainId,
|
|
@@ -949,6 +963,11 @@ async function runRuntime(argv, agent) {
|
|
|
949
963
|
const host = valueAfter(argv, '--host') ?? process.env.WIKI_MANAGER_RUNTIME_HOST ?? '0.0.0.0';
|
|
950
964
|
const port = Number(valueAfter(argv, '--port') ?? process.env.WIKI_MANAGER_RUNTIME_PORT ?? 7788);
|
|
951
965
|
const stateDir = valueAfter(argv, '--state-dir') ?? defaultRuntimeStateDir();
|
|
966
|
+
// The TOTP login modules resolve their state directory from the ENV, not
|
|
967
|
+
// from the CLI option: without this, `runtime --state-dir X` would store
|
|
968
|
+
// totp.json/session.json in the DEFAULT manager state dir while the rest of
|
|
969
|
+
// the runtime uses X — sessions issued on one side invisible to the other.
|
|
970
|
+
process.env.WIKI_MANAGER_STATE_DIR = stateDir;
|
|
952
971
|
const auth = resolveRuntimeAuthToken({ host, stateDir });
|
|
953
972
|
if (auth.token) process.env.WIKI_MANAGER_RUNTIME_TOKEN = auth.token;
|
|
954
973
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
@@ -1351,7 +1370,15 @@ async function runRuntime(argv, agent) {
|
|
|
1351
1370
|
);
|
|
1352
1371
|
}
|
|
1353
1372
|
if (!Array.isArray(fragment?.tasks) || fragment.tasks.length === 0) {
|
|
1354
|
-
|
|
1373
|
+
// An agent may legitimately plan nothing: no pending source to ingest, no
|
|
1374
|
+
// template, no deliverable, no archived source to re-file. That is an
|
|
1375
|
+
// OUTCOME, not a failure — the agent carries its own human-readable
|
|
1376
|
+
// sentence in summary.initialSynthesis. The stable EMPTY_PLAN sentinel
|
|
1377
|
+
// tells graph.js to relay that outcome instead of the generic "could not
|
|
1378
|
+
// be started" failure (which read as a connectivity problem).
|
|
1379
|
+
const plannedReason = String(fragment?.summary?.initialSynthesis?.[0] ?? '').replace(/\s+/g, ' ').trim();
|
|
1380
|
+
const reason = plannedReason || `${selection.capability} planned no task for ${selection.operation}.`;
|
|
1381
|
+
throw new Error(`EMPTY_PLAN: ${reason}`);
|
|
1355
1382
|
}
|
|
1356
1383
|
const validation = validateFragment(fragment, {
|
|
1357
1384
|
registry: capabilityRegistryForSession(session),
|
|
@@ -1714,7 +1741,21 @@ async function runRuntime(argv, agent) {
|
|
|
1714
1741
|
workspace: context.workspace ?? ephemeral.workspace ?? null,
|
|
1715
1742
|
}));
|
|
1716
1743
|
const messages = conversationSeed({ agentProjection: persistedProjection }, input);
|
|
1744
|
+
// Streaming fragments are coalesced before they are persisted and pushed —
|
|
1745
|
+
// one synchronous SQLite insert (plus one SSE write) per token stalled the
|
|
1746
|
+
// event loop, freezing both chats (serve and ShellUI) while a long answer
|
|
1747
|
+
// was still being produced. Flushed before any non-delta event so ordering
|
|
1748
|
+
// and the final tail are preserved.
|
|
1749
|
+
const deltaCoalescer = createDeltaCoalescer((delta) => dispatchAgentEvent(ephemeral, createAgentEvent('assistant_delta', {
|
|
1750
|
+
origin: 'runtime_turn',
|
|
1751
|
+
turnId,
|
|
1752
|
+
workspace: context.workspace ?? null,
|
|
1753
|
+
payload: { delta },
|
|
1754
|
+
})), { intervalMs: 80 });
|
|
1717
1755
|
ephemeral._onAgentEvent = (event) => {
|
|
1756
|
+
// Never let a final message (or any other event) overtake the buffered
|
|
1757
|
+
// fragments that precede it: flush them first, in order.
|
|
1758
|
+
if (event.type !== 'assistant_delta' && event.type !== 'assistant_delta_reset') deltaCoalescer.flush();
|
|
1718
1759
|
const interactiveEvent = {
|
|
1719
1760
|
...event,
|
|
1720
1761
|
origin: 'runtime_turn',
|
|
@@ -1760,28 +1801,30 @@ async function runRuntime(argv, agent) {
|
|
|
1760
1801
|
response = await runHeadlessChatTurn(ephemeral, input, {
|
|
1761
1802
|
history,
|
|
1762
1803
|
onStep: ephemeral._onStep,
|
|
1763
|
-
// Fragments de réponse publiés au fil de l'eau
|
|
1764
|
-
// agrège dans la dernière
|
|
1765
|
-
// que `assistant_message`
|
|
1766
|
-
// voient la réponse s'écrire
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
})),
|
|
1804
|
+
// Fragments de réponse publiés au fil de l'eau, coalescés (voir
|
|
1805
|
+
// deltaCoalescer ci-dessus). Le réducteur les agrège dans la dernière
|
|
1806
|
+
// entrée de conversation (`assistant_delta`), que `assistant_message`
|
|
1807
|
+
// vient ensuite figer : les deux interfaces voient la réponse s'écrire
|
|
1808
|
+
// sans qu'un insert SQLite par token ne bloque le flux.
|
|
1809
|
+
onTextDelta: (delta) => deltaCoalescer.push(delta),
|
|
1810
|
+
onTextReset: () => {
|
|
1811
|
+
deltaCoalescer.reset();
|
|
1812
|
+
dispatchAgentEvent(ephemeral, createAgentEvent('assistant_delta_reset', {
|
|
1813
|
+
origin: 'runtime_turn',
|
|
1814
|
+
turnId,
|
|
1815
|
+
workspace: context.workspace ?? null,
|
|
1816
|
+
payload: {},
|
|
1817
|
+
}));
|
|
1818
|
+
},
|
|
1779
1819
|
openWikiPages,
|
|
1780
1820
|
});
|
|
1781
1821
|
} else {
|
|
1782
1822
|
ephemeral.openWikiPages = openWikiPages;
|
|
1783
1823
|
response = await runAgentTurn(agent, ephemeral, input, { messages, signal });
|
|
1784
1824
|
}
|
|
1825
|
+
// Flush the tail before the turn is finalized, then stop the timer.
|
|
1826
|
+
deltaCoalescer.flush();
|
|
1827
|
+
deltaCoalescer.dispose();
|
|
1785
1828
|
// Persist the artifact the turn may have opened/edited (template_write,
|
|
1786
1829
|
// template_read, …) back onto the long-lived session, so the next /turn —
|
|
1787
1830
|
// chat or agent — sees it. The ephemeral session is otherwise discarded.
|
|
@@ -1898,6 +1941,65 @@ export async function runCli(argv) {
|
|
|
1898
1941
|
return;
|
|
1899
1942
|
}
|
|
1900
1943
|
|
|
1944
|
+
if (argv[0] === 'login' || argv[0] === 'logout') {
|
|
1945
|
+
const { ensureRuntime } = await import('../runtime/lifecycle.js');
|
|
1946
|
+
const { fetchLoginStatus, requestTotpSession, revokeRuntimeSession } = await import('../runtime/totpLogin.js');
|
|
1947
|
+
let runtime = null;
|
|
1948
|
+
try {
|
|
1949
|
+
runtime = await ensureRuntime();
|
|
1950
|
+
} catch (err) {
|
|
1951
|
+
runtime = { url: null, error: err instanceof Error ? err.message : String(err) };
|
|
1952
|
+
}
|
|
1953
|
+
if (!runtime.url) throw new Error(`Runtime unavailable: ${runtime.error}`);
|
|
1954
|
+
if (argv[0] === 'logout') {
|
|
1955
|
+
const { currentSessionToken } = await import('../runtime/loginSession.js');
|
|
1956
|
+
const revoked = await revokeRuntimeSession(runtime, currentSessionToken());
|
|
1957
|
+
console.log(revoked ? 'Session revoked.' : 'No active session to revoke (or the runtime is not answering).');
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
if (argv.includes('--reset')) {
|
|
1961
|
+
// Re-enrollment after a lost authenticator: wipes the secret and the
|
|
1962
|
+
// active session (local files, not an HTTP route) then runs the normal
|
|
1963
|
+
// login flow, which lands on a fresh enrollment QR code.
|
|
1964
|
+
const confirmed = argv.includes('--yes') || (await confirmOnTty(
|
|
1965
|
+
'Reset the TOTP enrollment? This revokes the active session.\nContinue?',
|
|
1966
|
+
false,
|
|
1967
|
+
));
|
|
1968
|
+
if (!confirmed) {
|
|
1969
|
+
console.log('Aborted.');
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
const { resetTotpEnrollment } = await import('../runtime/loginSession.js');
|
|
1973
|
+
resetTotpEnrollment();
|
|
1974
|
+
console.log('TOTP enrollment and the active session were reset — enrolling a new authenticator now.');
|
|
1975
|
+
}
|
|
1976
|
+
const status = await fetchLoginStatus(runtime);
|
|
1977
|
+
if (status && !status.enabled) {
|
|
1978
|
+
console.log('TOTP login is disabled (WIKI_MANAGER_TOTP=off).');
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
if (!argv.includes('--reset') && status?.sessionActive) {
|
|
1982
|
+
// A session already exists, but the *browser* only gets the shared
|
|
1983
|
+
// wiki_session cookie the moment it verifies a code on the runtime's
|
|
1984
|
+
// origin. Re-open the page when asked so a browser that has none can
|
|
1985
|
+
// seed it (and then reuse it on serve, same host) without waiting for
|
|
1986
|
+
// the session to expire first.
|
|
1987
|
+
if (!argv.includes('--no-open')) {
|
|
1988
|
+
const { loginPageUrl, openBrowser } = await import('../runtime/totpLogin.js');
|
|
1989
|
+
try {
|
|
1990
|
+
await openBrowser(loginPageUrl(runtime));
|
|
1991
|
+
} catch {
|
|
1992
|
+
console.log(`Open ${loginPageUrl(runtime)} to seed this browser's session.`);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
console.log(`Session already active until ${new Date(status.sessionExpiresAt).toLocaleString()}.`);
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
const result = await requestTotpSession(runtime, { open: !argv.includes('--no-open') });
|
|
1999
|
+
if (!result.ok) throw new Error(result.error);
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1901
2003
|
if (argv.includes('--setup-wizard')) {
|
|
1902
2004
|
if (!process.versions.bun) {
|
|
1903
2005
|
throw new Error('Setup wizard requires Bun. Run: bun ./bin/wiki-manager.js --setup-wizard');
|
|
@@ -2008,6 +2110,15 @@ export async function runCli(argv) {
|
|
|
2008
2110
|
runtime = unavailableRuntime(err);
|
|
2009
2111
|
console.error(`Runtime unavailable: ${runtime.error}`);
|
|
2010
2112
|
}
|
|
2113
|
+
if (runtime.url) {
|
|
2114
|
+
const { requestTotpSession } = await import('../runtime/totpLogin.js');
|
|
2115
|
+
const gate = await requestTotpSession(runtime);
|
|
2116
|
+
if (!gate.ok) {
|
|
2117
|
+
console.error(gate.error);
|
|
2118
|
+
process.exitCode = 1;
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2011
2122
|
preflight = withRuntimePreflight(preflight, runtime);
|
|
2012
2123
|
// render() resolves at mount; the TUI owns renderer teardown. The shared
|
|
2013
2124
|
// runtime deliberately survives shell exit because `serve` may use it.
|
|
@@ -2030,6 +2141,15 @@ export async function runCli(argv) {
|
|
|
2030
2141
|
runtime = unavailableRuntime(err);
|
|
2031
2142
|
console.error(`Runtime unavailable: ${runtime.error}`);
|
|
2032
2143
|
}
|
|
2144
|
+
if (runtime.url) {
|
|
2145
|
+
const { requestTotpSession } = await import('../runtime/totpLogin.js');
|
|
2146
|
+
const gate = await requestTotpSession(runtime);
|
|
2147
|
+
if (!gate.ok) {
|
|
2148
|
+
console.error(gate.error);
|
|
2149
|
+
process.exitCode = 1;
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2033
2153
|
}
|
|
2034
2154
|
await runShell({ agent, packageJson, runtime });
|
|
2035
2155
|
}
|