Merge branch 'main' into main
This commit is contained in:
@@ -34,7 +34,11 @@ const SDK_DISALLOWED_TOOLS = [
|
|||||||
'ExitWorktree',
|
'ExitWorktree',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Tool allowlist for NanoClaw agent containers
|
// Tool allowlist for NanoClaw agent containers. MCP-tool entries are derived
|
||||||
|
// at the call site from the registered `mcpServers` map so that any server
|
||||||
|
// added via `add_mcp_server` (or wired in container.json directly) is
|
||||||
|
// reachable to the agent — without this, the SDK's allowedTools filter
|
||||||
|
// silently drops every MCP namespace not listed here.
|
||||||
const TOOL_ALLOWLIST = [
|
const TOOL_ALLOWLIST = [
|
||||||
'Bash',
|
'Bash',
|
||||||
'Read',
|
'Read',
|
||||||
@@ -54,9 +58,15 @@ const TOOL_ALLOWLIST = [
|
|||||||
'ToolSearch',
|
'ToolSearch',
|
||||||
'Skill',
|
'Skill',
|
||||||
'NotebookEdit',
|
'NotebookEdit',
|
||||||
'mcp__nanoclaw__*',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// MCP server names are sanitized by the SDK when forming tool prefixes:
|
||||||
|
// any character outside [A-Za-z0-9_-] becomes '_'. Mirror that here so our
|
||||||
|
// allowlist patterns match what the SDK actually exposes.
|
||||||
|
function mcpAllowPattern(serverName: string): string {
|
||||||
|
return `mcp__${serverName.replace(/[^a-zA-Z0-9_-]/g, '_')}__*`;
|
||||||
|
}
|
||||||
|
|
||||||
interface SDKUserMessage {
|
interface SDKUserMessage {
|
||||||
type: 'user';
|
type: 'user';
|
||||||
message: { role: 'user'; content: string };
|
message: { role: 'user'; content: string };
|
||||||
@@ -277,7 +287,10 @@ export class ClaudeProvider implements AgentProvider {
|
|||||||
resume: input.continuation,
|
resume: input.continuation,
|
||||||
pathToClaudeCodeExecutable: '/pnpm/claude',
|
pathToClaudeCodeExecutable: '/pnpm/claude',
|
||||||
systemPrompt: instructions ? { type: 'preset' as const, preset: 'claude_code' as const, append: instructions } : undefined,
|
systemPrompt: instructions ? { type: 'preset' as const, preset: 'claude_code' as const, append: instructions } : undefined,
|
||||||
allowedTools: TOOL_ALLOWLIST,
|
allowedTools: [
|
||||||
|
...TOOL_ALLOWLIST,
|
||||||
|
...Object.keys(this.mcpServers).map(mcpAllowPattern),
|
||||||
|
],
|
||||||
disallowedTools: SDK_DISALLOWED_TOOLS,
|
disallowedTools: SDK_DISALLOWED_TOOLS,
|
||||||
env: this.env,
|
env: this.env,
|
||||||
permissionMode: 'bypassPermissions',
|
permissionMode: 'bypassPermissions',
|
||||||
|
|||||||
27
nanoclaw.sh
27
nanoclaw.sh
@@ -200,6 +200,33 @@ if [ "$LOW_MEM" = true ] || [ "$LOW_DISK" = true ]; then
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ─── pre-flight: Google Cloud VM warning (Linux) ──────────────────────
|
||||||
|
# NanoClaw is known to not run reliably on Google Compute Engine instances.
|
||||||
|
# Warn early — before the root check or bootstrap spinner — so users can
|
||||||
|
# switch providers before sinking time into setup. Detection uses DMI
|
||||||
|
# (no network round-trip), which on GCE reports "Google" / "Google
|
||||||
|
# Compute Engine".
|
||||||
|
if [ "$(uname -s)" = "Linux" ] \
|
||||||
|
&& { grep -qi 'Google' /sys/class/dmi/id/product_name 2>/dev/null \
|
||||||
|
|| grep -qi 'Google' /sys/class/dmi/id/sys_vendor 2>/dev/null; }; then
|
||||||
|
printf ' %s\n' "$(red 'Warning: Google Cloud VM detected.')"
|
||||||
|
printf ' %s\n' "$(dim 'Google blocks sudo commands, so NanoClaw is unlikely to run successfully on this VM.')"
|
||||||
|
printf ' %s\n\n' "$(dim 'If you want to run NanoClaw successfully, switch to a different provider (Hetzner, Hostinger, exe.dev and others..).')"
|
||||||
|
read -r -p " $(bold 'Try anyway?') [y/N] " GCE_ANS </dev/tty
|
||||||
|
|
||||||
|
case "${GCE_ANS:-N}" in
|
||||||
|
[Yy]*)
|
||||||
|
ph_event setup_gce_continued
|
||||||
|
printf '\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ph_event setup_gce_aborted
|
||||||
|
printf '\n %s\n\n' "$(dim 'Aborted. Re-run on a non-GCE host to continue.')"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── pre-flight: root user warning (Linux) ────────────────────────────
|
# ─── pre-flight: root user warning (Linux) ────────────────────────────
|
||||||
if [ "$(uname -s)" = "Linux" ] && [ "$(id -u)" -eq 0 ]; then
|
if [ "$(uname -s)" = "Linux" ] && [ "$(id -u)" -eq 0 ]; then
|
||||||
printf ' %s\n' \
|
printf ' %s\n' \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nanoclaw",
|
"name": "nanoclaw",
|
||||||
"version": "2.0.28",
|
"version": "2.0.29",
|
||||||
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.33.0",
|
"packageManager": "pnpm@10.33.0",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { emit as phEmit } from './diagnostics.js';
|
|||||||
import type { StepResult, SpinnerLabels } from './runner.js';
|
import type { StepResult, SpinnerLabels } from './runner.js';
|
||||||
import { dumpTranscriptOnFailure, spawnStep, writeStepEntry } from './runner.js';
|
import { dumpTranscriptOnFailure, spawnStep, writeStepEntry } from './runner.js';
|
||||||
import * as setupLog from '../logs.js';
|
import * as setupLog from '../logs.js';
|
||||||
import { brandBody, fitToWidth } from './theme.js';
|
import { brandBody, fitToWidth, fmtDuration } from './theme.js';
|
||||||
|
|
||||||
const WINDOW_SIZE = 3;
|
const WINDOW_SIZE = 3;
|
||||||
const SPINNER_FRAMES = ['◒', '◐', '◓', '◑'];
|
const SPINNER_FRAMES = ['◒', '◐', '◓', '◑'];
|
||||||
@@ -85,9 +85,8 @@ async function runUnderWindow(
|
|||||||
const redraw = (): void => {
|
const redraw = (): void => {
|
||||||
if (stallPromptActive) return;
|
if (stallPromptActive) return;
|
||||||
out.write(`\x1b[${WINDOW_SIZE + 1}A`);
|
out.write(`\x1b[${WINDOW_SIZE + 1}A`);
|
||||||
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
||||||
const icon = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
const icon = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
||||||
const suffix = ` (${elapsed}s)`;
|
const suffix = ` (${fmtDuration(Date.now() - start)})`;
|
||||||
const header = fitToWidth(labels.running, suffix);
|
const header = fitToWidth(labels.running, suffix);
|
||||||
out.write(`\x1b[2K${k.cyan(icon)} ${header}${k.dim(suffix)}\n`);
|
out.write(`\x1b[2K${k.cyan(icon)} ${header}${k.dim(suffix)}\n`);
|
||||||
|
|
||||||
@@ -164,8 +163,7 @@ async function runUnderWindow(
|
|||||||
out.write(SHOW_CURSOR);
|
out.write(SHOW_CURSOR);
|
||||||
process.off('exit', restoreCursorOnExit);
|
process.off('exit', restoreCursorOnExit);
|
||||||
|
|
||||||
const elapsed = Math.round((Date.now() - start) / 1000);
|
const suffix = ` (${fmtDuration(Date.now() - start)})`;
|
||||||
const suffix = ` (${elapsed}s)`;
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
const isSkipped = result.terminal?.fields.STATUS === 'skipped';
|
const isSkipped = result.terminal?.fields.STATUS === 'skipped';
|
||||||
const msg = isSkipped && labels.skipped ? labels.skipped : labels.done;
|
const msg = isSkipped && labels.skipped ? labels.skipped : labels.done;
|
||||||
|
|||||||
Reference in New Issue
Block a user