@dotdrelle/wiki-manager 0.15.28 → 0.15.32
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/.env.example +5 -2
- package/README.md +5 -4
- package/agents.docker-compose.override.example.yml +1 -1
- package/agents.docker-compose.yml +10 -0
- package/bin/wiki-manager.js +1 -1
- package/docker-compose.override.example.yml +3 -2
- package/mcp.endpoints.example.json +1 -1
- package/package.json +2 -2
- package/src/agent/graph.js +77 -13
- package/src/agent/graph.test.js +165 -8
- package/src/cli/runtimeStartup.test.js +14 -0
- package/src/cli/wiki-manager.js +118 -19
- package/src/cli/wiki-manager.test.js +192 -0
- package/src/commands/slash.js +168 -39
- package/src/commands/slash.test.js +100 -1
- package/src/core/agentsCompose.js +79 -0
- package/src/core/agentsCompose.test.js +99 -0
- package/src/core/buildInfo.json +2 -2
- package/src/core/composeOverrides.test.js +17 -3
- package/src/core/env.js +27 -10
- package/src/core/mcp.js +113 -38
- package/src/core/mcp.test.js +181 -20
- package/src/core/startupCheck.js +64 -27
- package/src/core/startupCheck.test.js +49 -1
- package/src/core/wikiSetup.js +57 -22
- package/src/core/wikiSetup.test.js +87 -0
- package/src/core/wikiWorkspace.test.js +16 -0
- package/src/orchestrator/agentRegistry.js +35 -7
- package/src/orchestrator/agentRegistry.test.js +73 -0
- package/src/orchestrator/dispatcher.js +6 -1
- package/src/orchestrator/dispatcher.test.js +24 -1
- package/src/orchestrator/objectiveResolver.js +24 -0
- package/src/orchestrator/objectiveResolver.test.js +27 -0
- package/src/runtime/auth.test.js +1 -65
- package/src/runtime/donna-contract.test.js +2 -1
- package/src/runtime/lifecycle.js +0 -33
- package/src/runtime/runner.test.js +10 -1
- package/src/runtime/supervisor.test.js +11 -1
- package/src/shell/LeftPane.tsx +49 -24
- package/src/shell/repl.js +114 -21
- package/src/shell/repl.test.js +88 -17
- package/src/shell/tui.tsx +10 -19
- package/wiki-workspace +98 -12
package/src/shell/repl.test.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
applyRuntimeStateToShellSession,
|
|
8
8
|
chatAllowedTools,
|
|
9
9
|
createSession,
|
|
10
|
+
isProductHelpQuestion,
|
|
10
11
|
runHeadlessChatTurn,
|
|
11
12
|
sanitizeOpenWikiPage,
|
|
12
13
|
sanitizeOpenWikiPages,
|
|
@@ -42,23 +43,14 @@ test('ShellUI renders the user header full-width before constraining only its bo
|
|
|
42
43
|
assert.match(source.slice(userBodyBranch), /justifyContent="flex-start"/);
|
|
43
44
|
assert.doesNotMatch(source.slice(userBodyBranch, source.indexOf(') : line.status ?', userBodyBranch)), /#12263A/);
|
|
44
45
|
assert.doesNotMatch(source, /messageHeaderSegments\(message\.role, contentColumns\)/);
|
|
45
|
-
|
|
46
|
-
assert.match(source, /messageHeaderSegments\(message\.role, columns(, canRedo)?\)/);
|
|
46
|
+
assert.match(source, /messageHeaderSegments\(message\.role, columns\)/);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
test('
|
|
49
|
+
test('ShellUI exposes copy without a reply or redo action', async () => {
|
|
50
50
|
const source = await readFile(new URL('./LeftPane.tsx', import.meta.url), 'utf8');
|
|
51
|
-
assert.match(source, /const
|
|
52
|
-
assert.
|
|
53
|
-
|
|
54
|
-
const session = await readFile(new URL('./useSession.ts', import.meta.url), 'utf8');
|
|
55
|
-
// The runtime must be truncated first: its conversation is derived from the
|
|
56
|
-
// event log and would be merged straight back in otherwise.
|
|
57
|
-
const truncateIndex = session.indexOf('postRuntimeConversationTruncate(runtimeIndex');
|
|
58
|
-
const spliceIndex = session.indexOf('full.splice(index)');
|
|
59
|
-
const submitIndex = session.indexOf('return submitInput(content)');
|
|
60
|
-
assert.ok(truncateIndex > 0 && spliceIndex > truncateIndex && submitIndex > spliceIndex);
|
|
61
|
-
assert.match(session, /const windowOffset = Math\.max\(0, full\.length - messages\(\)\.length\);/);
|
|
51
|
+
assert.match(source, /const COPY_BTN = ' \[ copy \]';/);
|
|
52
|
+
assert.doesNotMatch(source, /\[\s*(?:reply|redo)\s*\]/i);
|
|
53
|
+
assert.doesNotMatch(source, /onRedo|redoContent|redoIndex|REDO_BTN/);
|
|
62
54
|
});
|
|
63
55
|
|
|
64
56
|
test('redo translates the local thread index into a runtime conversation index', async () => {
|
|
@@ -388,6 +380,29 @@ test('applyRuntimeStateToShellSession preserves terminal diagnostics when runtim
|
|
|
388
380
|
assert.deepEqual(session.agentProjection.planPatches, [{ id: 'old-patch' }]);
|
|
389
381
|
});
|
|
390
382
|
|
|
383
|
+
test('runtime sync does not resurrect a terminal plan dismissed by a newer user turn', () => {
|
|
384
|
+
const session = createSession();
|
|
385
|
+
session._dismissedTerminalRunId = 'old-run';
|
|
386
|
+
applyRuntimeStateToShellSession(session, {
|
|
387
|
+
runId: 'old-run',
|
|
388
|
+
status: 'error',
|
|
389
|
+
conversation: [{ role: 'assistant', content: 'Old failure' }],
|
|
390
|
+
plan: [{ id: 'old-task', status: 'failed' }],
|
|
391
|
+
activities: [{ key: 'old-job', status: 'failed', terminal: true }],
|
|
392
|
+
logs: ['old error'],
|
|
393
|
+
summary: 'Old run failed',
|
|
394
|
+
workflow: { nodes: [{ id: 'task:old' }], relations: [] },
|
|
395
|
+
planPatches: [{ id: 'old-patch' }],
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
assert.equal(session.headlessPlan, null);
|
|
399
|
+
assert.deepEqual(session.activities, {});
|
|
400
|
+
assert.equal(session.workflow, null);
|
|
401
|
+
assert.deepEqual(session.agentProjection.logs, []);
|
|
402
|
+
assert.equal(session.agentProjection.summary, null);
|
|
403
|
+
assert.deepEqual(session.agentProjection.conversation, [{ role: 'assistant', content: 'Old failure' }]);
|
|
404
|
+
});
|
|
405
|
+
|
|
391
406
|
test('legacy runtime sync does not synthesize per-job or canned completion messages', () => {
|
|
392
407
|
const session = createSession();
|
|
393
408
|
applyRuntimeStateToShellSession(session, {
|
|
@@ -567,6 +582,53 @@ test('/agent <question> reports an unavailable runtime without leaving chat mode
|
|
|
567
582
|
assert.match(conversationMessages(session).at(-1).content, /runtime stopped/);
|
|
568
583
|
});
|
|
569
584
|
|
|
585
|
+
test('every slash-command result is presented by Donna when an agent is available', async () => {
|
|
586
|
+
const session = createSession();
|
|
587
|
+
let synthesisInput = '';
|
|
588
|
+
const agent = {
|
|
589
|
+
async invoke({ input, session: activeSession }) {
|
|
590
|
+
synthesisInput = input;
|
|
591
|
+
assert.equal(activeSession._responseSynthesisOnly, true);
|
|
592
|
+
return { response: 'Vous utilisez wiki-manager test.' };
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const result = await runLine('/version', {
|
|
597
|
+
agent,
|
|
598
|
+
packageJson: { version: 'test' },
|
|
599
|
+
session,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
assert.equal(result.exit, false);
|
|
603
|
+
assert.ok(synthesisInput.includes('commande shell /version'));
|
|
604
|
+
assert.deepEqual(conversationMessages(session).map((message) => message.role), ['user', 'donna']);
|
|
605
|
+
assert.equal(conversationMessages(session)[0].content, '/version');
|
|
606
|
+
assert.equal(conversationMessages(session)[1].content, 'Vous utilisez wiki-manager test.');
|
|
607
|
+
assert.equal(session._responseSynthesisOnly, undefined);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test('/status remains an immediate deterministic display without Donna', async () => {
|
|
611
|
+
const session = createSession();
|
|
612
|
+
let invoked = false;
|
|
613
|
+
const agent = {
|
|
614
|
+
async invoke() {
|
|
615
|
+
invoked = true;
|
|
616
|
+
return { response: 'must not run' };
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const result = await runLine('/status', {
|
|
621
|
+
agent,
|
|
622
|
+
packageJson: { version: 'test' },
|
|
623
|
+
session,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
assert.equal(result.exit, false);
|
|
627
|
+
assert.equal(invoked, false);
|
|
628
|
+
assert.equal(conversationMessages(session).at(-1)?.role, 'command');
|
|
629
|
+
assert.match(conversationMessages(session).at(-1)?.content ?? '', /Workspace · -/);
|
|
630
|
+
});
|
|
631
|
+
|
|
570
632
|
test('runLine does not update workspace profile before Donna handles the request', async () => {
|
|
571
633
|
const workspacePath = mkdtempSync(join(tmpdir(), 'donna-profile-shell-'));
|
|
572
634
|
mkdirSync(join(workspacePath, '.wiki'), { recursive: true });
|
|
@@ -745,9 +807,9 @@ test('chatAllowedTools exposes exactly the declared MCP tools to /chat', () => {
|
|
|
745
807
|
};
|
|
746
808
|
const names = chatAllowedTools(session).map((item) => item.function.name).sort();
|
|
747
809
|
// cme_setup: not declared. documents_status: server absent from chatAccess.
|
|
748
|
-
// cme_export_run
|
|
749
|
-
//
|
|
750
|
-
assert.deepEqual(names, ['
|
|
810
|
+
// cme_export_run is an orchestration bypass and stays hidden even when an
|
|
811
|
+
// older allow-list still names it.
|
|
812
|
+
assert.deepEqual(names, ['cme__cme_sources_list', 'cme__cme_status']);
|
|
751
813
|
});
|
|
752
814
|
|
|
753
815
|
test('chatAllowedTools offers every declared tool, reads and writes alike', () => {
|
|
@@ -940,6 +1002,15 @@ test('runHeadlessChatTurn (HTTP /chat) uses the read-tool path and returns text'
|
|
|
940
1002
|
assert.doesNotMatch(reply, /STREAM_FALLBACK/);
|
|
941
1003
|
});
|
|
942
1004
|
|
|
1005
|
+
test('product-help questions are detected without treating ordinary domain questions as product help', () => {
|
|
1006
|
+
assert.equal(isProductHelpQuestion('À quoi correspond Parallelism & throughput ?'), true);
|
|
1007
|
+
assert.equal(isProductHelpQuestion('Comment fonctionne Donna ?'), true);
|
|
1008
|
+
assert.equal(isProductHelpQuestion('Explique le panneau /status'), true);
|
|
1009
|
+
assert.equal(isProductHelpQuestion('Comment fonctionnent les approbations ?'), true);
|
|
1010
|
+
assert.equal(isProductHelpQuestion('Que dit le wiki sur la météo ?'), false);
|
|
1011
|
+
assert.equal(isProductHelpQuestion('Résume ce document'), false);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
943
1014
|
test('sanitizeOpenWikiPage accepts wiki and untracked markdown context paths', () => {
|
|
944
1015
|
assert.equal(sanitizeOpenWikiPage('wiki/concepts/foo.md'), 'wiki/concepts/foo.md');
|
|
945
1016
|
assert.equal(sanitizeOpenWikiPage(' wiki/a.md '), 'wiki/a.md');
|
package/src/shell/tui.tsx
CHANGED
|
@@ -150,30 +150,22 @@ function App(props: {
|
|
|
150
150
|
const [preflightBusy, setPreflightBusy] = createSignal(false);
|
|
151
151
|
let ctrlCTimer: ReturnType<typeof setTimeout> | null = null;
|
|
152
152
|
let exiting = false;
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// screen — 0.12.9 shipped that and killed the runtime mid-session.
|
|
153
|
+
// The runtime is shared infrastructure: `llm-wiki serve` can still be using
|
|
154
|
+
// it after this shell closes. Exiting the UI only destroys the renderer;
|
|
155
|
+
// explicit lifecycle commands own runtime shutdown.
|
|
157
156
|
const exitShell = () => {
|
|
158
157
|
if (exiting) return;
|
|
159
158
|
exiting = true;
|
|
160
159
|
setExitStatus('Fermeture enclenchée…');
|
|
161
|
-
// Register the complete shutdown before destroying the renderer: onDestroy
|
|
162
|
-
// resolves runOpenTuiShell, which must still await this cleanup task.
|
|
163
160
|
const task = Promise.resolve().then(async () => {
|
|
164
161
|
renderer.destroy();
|
|
165
|
-
console.log('[wiki-manager]
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
} catch {
|
|
173
|
-
// Best effort: never block the exit on runtime cleanup.
|
|
174
|
-
}
|
|
175
|
-
for (const message of messages) console.log(`[wiki-manager] ${message}`);
|
|
176
|
-
console.log('[wiki-manager] fermeture terminée — shell clos proprement.');
|
|
162
|
+
console.log('[wiki-manager] shell closed; shared runtime left running.');
|
|
163
|
+
// MCP clients, image-refresh probes, and reconnect timers may still own
|
|
164
|
+
// event-loop handles. They belong to this shell process, not to the
|
|
165
|
+
// detached shared runtime. Once the renderer is gone there is no useful
|
|
166
|
+
// foreground work left, so terminate explicitly instead of leaving the
|
|
167
|
+
// terminal pending indefinitely.
|
|
168
|
+
process.exit(0);
|
|
177
169
|
});
|
|
178
170
|
if (props.exitRef) props.exitRef.current = task;
|
|
179
171
|
};
|
|
@@ -398,7 +390,6 @@ function App(props: {
|
|
|
398
390
|
scrollConversation={state.scrollConversation}
|
|
399
391
|
spinnerFrame={SPINNER_FRAMES[spinnerIndex()] ?? SPINNER_FRAMES[0]}
|
|
400
392
|
onInputHeightChange={setChatInputHeight}
|
|
401
|
-
onRedo={(index, content) => { void state.redoMessage(index, content); }}
|
|
402
393
|
onCopy={(content) => showCopyHint(
|
|
403
394
|
copyToClipboard(content, renderer)
|
|
404
395
|
? 'Copied.'
|
package/wiki-workspace
CHANGED
|
@@ -17,6 +17,7 @@ WORKSPACES_DIR="${WIKI_WORKSPACES_DIR:-$DEFAULT_MANAGER_DIR/workspaces}"
|
|
|
17
17
|
MANAGER_ENV_FILE="${WIKI_MANAGER_ENV_FILE:-$DEFAULT_MANAGER_DIR/.env}"
|
|
18
18
|
MANAGER_ENDPOINTS_FILE="${WIKI_MANAGER_ENDPOINTS_FILE:-$DEFAULT_MANAGER_DIR/mcp.endpoints.json}"
|
|
19
19
|
MANAGER_STATE_DIR="$(cd "$(dirname "$MANAGER_ENV_FILE")" && pwd)"
|
|
20
|
+
MANAGER_COMPOSE_DIR="$MANAGER_STATE_DIR/.wiki/compose"
|
|
20
21
|
MANAGER_RUNTIME_DIR="$MANAGER_STATE_DIR/.wiki/runtime"
|
|
21
22
|
LEGACY_MANAGER_RUNTIME_DIR="$MANAGER_STATE_DIR/.wiki-manager"
|
|
22
23
|
if [[ ! -e "$MANAGER_RUNTIME_DIR" && -d "$LEGACY_MANAGER_RUNTIME_DIR" ]]; then
|
|
@@ -83,6 +84,7 @@ Usage:
|
|
|
83
84
|
wiki-workspace [--cacert <path>] <command> ...
|
|
84
85
|
wiki-workspace config <workspace> [path]
|
|
85
86
|
wiki-workspace up <workspace>
|
|
87
|
+
wiki-workspace refresh
|
|
86
88
|
wiki-workspace wiki <workspace> <command> [args...]
|
|
87
89
|
wiki-workspace list
|
|
88
90
|
|
|
@@ -90,6 +92,7 @@ Commands:
|
|
|
90
92
|
config <workspace> [path] Create workspace folder, auto-select ports, run wiki init
|
|
91
93
|
up <workspace> [--open] Start workspace stack: wiki + production
|
|
92
94
|
list List configured workspaces
|
|
95
|
+
refresh Stop runtime/stacks and remove their Compose images
|
|
93
96
|
|
|
94
97
|
agents up [--pull] Start all external agents (cme, documents)
|
|
95
98
|
agents down Stop all external agents
|
|
@@ -120,9 +123,9 @@ Configuration:
|
|
|
120
123
|
Runtime node: WIKI_MANAGER_NODE_BIN=/path/to/node
|
|
121
124
|
Local CA: --cacert /absolute/path/to/ca.pem (Docker must be able to read this host path)
|
|
122
125
|
|
|
123
|
-
Compose overrides (
|
|
124
|
-
docker-compose.override.yml Workspace stack
|
|
125
|
-
agents.docker-compose.override.yml Agents stack
|
|
126
|
+
Compose overrides (under .wiki/compose, created once, never regenerated):
|
|
127
|
+
.wiki/compose/docker-compose.override.yml Workspace stack
|
|
128
|
+
.wiki/compose/agents.docker-compose.override.yml Agents stack
|
|
126
129
|
Use them for proxy passthrough, extra mounts and optional agents.
|
|
127
130
|
Never edit .wiki/runtime/*.compose.yml — that is generated state, rewritten
|
|
128
131
|
on every compose command.
|
|
@@ -259,6 +262,31 @@ agents_compose() {
|
|
|
259
262
|
local compose_env_args=()
|
|
260
263
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
261
264
|
|
|
265
|
+
# Local-iteration build only: the packaged connectors service carries a
|
|
266
|
+
# `build:` block pointing at the sibling agent-connectors repo, whose OAuth
|
|
267
|
+
# credentials live in a gitignored .env.build.local. Compose resolves the
|
|
268
|
+
# value-less build args from *this* process environment, not from
|
|
269
|
+
# --env-file, so the variables must be exported here or the image ships with
|
|
270
|
+
# empty WIKILLM_GOOGLE_OAUTH_* and every Google connector stays unusable.
|
|
271
|
+
local connectors_build_env="$ROOT_DIR/../agent-external/agent-connectors/.env.build.local"
|
|
272
|
+
if [[ -f "$connectors_build_env" ]]; then
|
|
273
|
+
local build_key build_value
|
|
274
|
+
while IFS='=' read -r build_key build_value; do
|
|
275
|
+
case "$build_key" in
|
|
276
|
+
WIKILLM_GOOGLE_OAUTH_CLIENT_ID|WIKILLM_GOOGLE_OAUTH_CLIENT_SECRET) ;;
|
|
277
|
+
*) continue ;;
|
|
278
|
+
esac
|
|
279
|
+
[[ -n "${!build_key:-}" ]] || export "$build_key=$build_value"
|
|
280
|
+
done < <(grep -v '^[[:space:]]*#' "$connectors_build_env" || true)
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
require_connectors_build_credentials() {
|
|
284
|
+
local build_key
|
|
285
|
+
for build_key in WIKILLM_GOOGLE_OAUTH_CLIENT_ID WIKILLM_GOOGLE_OAUTH_CLIENT_SECRET; do
|
|
286
|
+
[[ -n "${!build_key:-}" ]] || die "cannot build connectors: $build_key is missing from $connectors_build_env"
|
|
287
|
+
done
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
_agents_dc() {
|
|
263
291
|
local cacert_args=()
|
|
264
292
|
local profile_args=()
|
|
@@ -266,10 +294,10 @@ agents_compose() {
|
|
|
266
294
|
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
267
295
|
read_lines_into_array cacert_args cacert_compose_args "$agents_compose_file" "agents.cacert.compose.yml"
|
|
268
296
|
# User-owned override: optional external agents, proxy passthrough and
|
|
269
|
-
# any local fix live in a file the user maintains
|
|
297
|
+
# any local fix live in a file the user maintains under .wiki/compose —
|
|
270
298
|
# scaffolded once by ensure_compose_override, never overwritten afterwards.
|
|
271
299
|
local override_args=()
|
|
272
|
-
local user_override="$
|
|
300
|
+
local user_override="$MANAGER_COMPOSE_DIR/agents.docker-compose.override.yml"
|
|
273
301
|
[[ -f "$user_override" ]] && override_args+=(-f "$user_override")
|
|
274
302
|
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
275
303
|
COMPOSE_PROFILES= \
|
|
@@ -287,6 +315,9 @@ agents_compose() {
|
|
|
287
315
|
[[ $# -eq 0 ]] || die "agents up accepts only --pull"
|
|
288
316
|
ensure_agent_tokens
|
|
289
317
|
ensure_endpoints_file
|
|
318
|
+
if connectors_enabled && [[ -f "$ROOT_DIR/../agent-external/agent-connectors/Dockerfile" ]]; then
|
|
319
|
+
require_connectors_build_credentials
|
|
320
|
+
fi
|
|
290
321
|
# The .env may have just been created above — recompute the env-file
|
|
291
322
|
# flag so freshly generated tokens reach the containers on first run.
|
|
292
323
|
compose_env_args=()
|
|
@@ -307,12 +338,16 @@ agents_compose() {
|
|
|
307
338
|
"${CME_MCP_PORT:-3336}" "${DOCUMENTS_MCP_PORT:-3337}"
|
|
308
339
|
connectors_enabled && printf ' connectors=:%s' "${CONNECTORS_MCP_PORT:-3338}"
|
|
309
340
|
printf '\n'
|
|
310
|
-
[[ ! -f "$
|
|
341
|
+
[[ ! -f "$MANAGER_COMPOSE_DIR/agents.docker-compose.override.yml" ]] || printf 'User override: %s\n' "$MANAGER_COMPOSE_DIR/agents.docker-compose.override.yml"
|
|
311
342
|
;;
|
|
312
343
|
down)
|
|
313
344
|
[[ $# -eq 0 ]] || die "agents down does not take arguments"
|
|
314
345
|
_agents_dc down
|
|
315
346
|
;;
|
|
347
|
+
purge)
|
|
348
|
+
[[ $# -eq 0 ]] || die "agents purge does not take arguments"
|
|
349
|
+
_agents_dc down --rmi all --remove-orphans
|
|
350
|
+
;;
|
|
316
351
|
logs)
|
|
317
352
|
local log_args=()
|
|
318
353
|
read_lines_into_array log_args logs_args "$@"
|
|
@@ -534,6 +569,13 @@ if (enabled === 'true') {
|
|
|
534
569
|
Authorization: 'Bearer ${CONNECTORS_MCP_AUTH_TOKEN}',
|
|
535
570
|
},
|
|
536
571
|
};
|
|
572
|
+
const connectorEndpoint = config.mcpServers.connectors;
|
|
573
|
+
connectorEndpoint.requireApproval = [
|
|
574
|
+
...new Set([
|
|
575
|
+
...(Array.isArray(connectorEndpoint.requireApproval) ? connectorEndpoint.requireApproval : []),
|
|
576
|
+
'connectors_gmail_modify',
|
|
577
|
+
]),
|
|
578
|
+
];
|
|
537
579
|
config.chatAccess.servers.connectors ??= {};
|
|
538
580
|
const connectorAccess = config.chatAccess.servers.connectors;
|
|
539
581
|
// One shape for every server: a single `allow` list. The legacy
|
|
@@ -545,6 +587,9 @@ if (enabled === 'true') {
|
|
|
545
587
|
...(Array.isArray(connectorAccess.allowActions) ? connectorAccess.allowActions : []),
|
|
546
588
|
'connectors_google_status',
|
|
547
589
|
'connectors_google_oauth_start',
|
|
590
|
+
'connectors_gmail_summary',
|
|
591
|
+
'connectors_gmail_search',
|
|
592
|
+
'connectors_gmail_labels',
|
|
548
593
|
]),
|
|
549
594
|
];
|
|
550
595
|
delete connectorAccess.allowActions;
|
|
@@ -676,6 +721,19 @@ runtime_command() {
|
|
|
676
721
|
;;
|
|
677
722
|
down|stop)
|
|
678
723
|
[[ $# -eq 0 ]] || die "runtime down does not take arguments"
|
|
724
|
+
token="$(ensure_manager_runtime_token)"
|
|
725
|
+
if runtime_health_check; then
|
|
726
|
+
RUNTIME_URL="$(runtime_health_url)/shutdown" RUNTIME_TOKEN="$token" "$node_bin" --input-type=module -e \
|
|
727
|
+
"const h=process.env.RUNTIME_TOKEN?{Authorization:'Bearer '+process.env.RUNTIME_TOKEN}:{}; const r=await fetch(process.env.RUNTIME_URL,{method:'POST',headers:h}); process.exit(r.ok?0:1);"
|
|
728
|
+
local wait_count=0
|
|
729
|
+
while runtime_health_check && (( wait_count < 40 )); do
|
|
730
|
+
sleep 0.1
|
|
731
|
+
(( wait_count++ )) || true
|
|
732
|
+
done
|
|
733
|
+
rm -f "$pid_file"
|
|
734
|
+
printf 'agent-runtime stopped\n'
|
|
735
|
+
return
|
|
736
|
+
fi
|
|
679
737
|
if ! runtime_pid_running; then
|
|
680
738
|
printf 'agent-runtime is not running from %s\n' "$pid_file"
|
|
681
739
|
rm -f "$pid_file"
|
|
@@ -720,6 +778,23 @@ runtime_command() {
|
|
|
720
778
|
esac
|
|
721
779
|
}
|
|
722
780
|
|
|
781
|
+
refresh_project() {
|
|
782
|
+
printf 'Stopping shared runtime...\n'
|
|
783
|
+
runtime_command down
|
|
784
|
+
|
|
785
|
+
local entry workspace
|
|
786
|
+
for entry in "$WORKSPACES_DIR"/*; do
|
|
787
|
+
[[ -f "$entry/.env" ]] || continue
|
|
788
|
+
workspace="$(basename "$entry")"
|
|
789
|
+
printf 'Removing workspace stack and images: %s\n' "$workspace"
|
|
790
|
+
compose_for_workspace "$workspace" down --rmi all --remove-orphans
|
|
791
|
+
done
|
|
792
|
+
|
|
793
|
+
printf 'Removing external-agent stack and images...\n'
|
|
794
|
+
agents_compose purge
|
|
795
|
+
printf 'Refresh complete. Persistent workspace and agent data were preserved.\n'
|
|
796
|
+
}
|
|
797
|
+
|
|
723
798
|
ensure_runtime_up() {
|
|
724
799
|
if [[ "${WIKI_MANAGER_RUNTIME_AUTOSTART:-1}" == "0" ]]; then
|
|
725
800
|
return
|
|
@@ -745,10 +820,16 @@ ensure_compose_override() {
|
|
|
745
820
|
# the generated override and lose the change on the next compose command.
|
|
746
821
|
local example_name="$1"
|
|
747
822
|
local target_name="$2"
|
|
748
|
-
local target="$
|
|
823
|
+
local target="$MANAGER_COMPOSE_DIR/$target_name"
|
|
824
|
+
local legacy="$MANAGER_STATE_DIR/$target_name"
|
|
749
825
|
[[ -f "$target" ]] && return 0
|
|
826
|
+
mkdir -p "$MANAGER_COMPOSE_DIR"
|
|
827
|
+
if [[ -f "$legacy" ]]; then
|
|
828
|
+
mv "$legacy" "$target"
|
|
829
|
+
printf 'Migrated user-owned compose override: %s -> %s\n' "$legacy" "$target" >&2
|
|
830
|
+
return 0
|
|
831
|
+
fi
|
|
750
832
|
[[ -f "$ROOT_DIR/$example_name" ]] || return 0
|
|
751
|
-
mkdir -p "$MANAGER_STATE_DIR"
|
|
752
833
|
cp "$ROOT_DIR/$example_name" "$target"
|
|
753
834
|
printf 'Created %s (user-owned compose override, never regenerated)\n' "$target" >&2
|
|
754
835
|
}
|
|
@@ -889,11 +970,11 @@ compose_for_workspace() {
|
|
|
889
970
|
WIKI_MCP_PORT="$mcp_port" \
|
|
890
971
|
PRODUCTION_MCP_PORT="$prod_port" \
|
|
891
972
|
read_lines_into_array cacert_args cacert_compose_args "$ROOT_DIR/docker-compose.yml" "cacert.compose.yml"
|
|
892
|
-
# Same contract as the agents stack: a single global user-owned override
|
|
893
|
-
#
|
|
973
|
+
# Same contract as the agents stack: a single global user-owned override in
|
|
974
|
+
# .wiki/compose, merged after the packaged file and before the generated CA
|
|
894
975
|
# override so the operator can still be overruled on CA paths only.
|
|
895
976
|
local override_args=()
|
|
896
|
-
local user_override="$
|
|
977
|
+
local user_override="$MANAGER_COMPOSE_DIR/docker-compose.override.yml"
|
|
897
978
|
[[ -f "$user_override" ]] && override_args+=(-f "$user_override")
|
|
898
979
|
|
|
899
980
|
WIKI_WORKSPACE_PATH="$ws_path" \
|
|
@@ -1149,7 +1230,7 @@ config_workspace() {
|
|
|
1149
1230
|
WIKI_MCP_PORT="$mcp_port" \
|
|
1150
1231
|
read_lines_into_array cacert_args cacert_compose_args "$ROOT_DIR/docker-compose.yml" "cacert.compose.yml"
|
|
1151
1232
|
local override_args=()
|
|
1152
|
-
local user_override="$
|
|
1233
|
+
local user_override="$MANAGER_COMPOSE_DIR/docker-compose.override.yml"
|
|
1153
1234
|
[[ -f "$user_override" ]] && override_args+=(-f "$user_override")
|
|
1154
1235
|
|
|
1155
1236
|
WIKI_WORKSPACE_PATH="$target_path" \
|
|
@@ -1197,6 +1278,11 @@ main() {
|
|
|
1197
1278
|
exit 0
|
|
1198
1279
|
fi
|
|
1199
1280
|
|
|
1281
|
+
if [[ $# -eq 1 && "$1" == "refresh" ]]; then
|
|
1282
|
+
refresh_project
|
|
1283
|
+
exit 0
|
|
1284
|
+
fi
|
|
1285
|
+
|
|
1200
1286
|
# config <workspace> [path]
|
|
1201
1287
|
if [[ $# -ge 2 && "$1" == "config" ]]; then
|
|
1202
1288
|
local cfg_workspace="$2"
|