

























@@ -6,6 +6,7 @@ read_when:
66 - You want to enable OpenClaw code mode for an agent run
77 - You need to explain why code mode is different from Codex Code mode
88 - You are reviewing the exec/wait contract, QuickJS-WASI sandbox, TypeScript transform, or hidden tool-catalog bridge
9+ - You are adding or reviewing an internal code-mode namespace registry integration
910---
10111112Code mode is an experimental OpenClaw agent-runtime feature. It is off by
@@ -380,6 +381,7 @@ The guest runtime exposes a small global API:
380381```typescript
381382declare const ALL_TOOLS: ToolCatalogEntry[];
382383declare const tools: ToolCatalog;
384+declare const namespaces: Record<string, unknown>;
383385384386declare function text(value: unknown): void;
385387declare function json(value: unknown): void;
@@ -433,6 +435,189 @@ const hits = await tools.web_search({ query: "OpenClaw code mode" });
433435The guest runtime must not expose host objects directly. Inputs and outputs cross
434436the bridge as JSON-compatible values with explicit size caps.
435437438+## Internal namespaces
439+440+Internal namespaces give code mode a concise domain API without adding more
441+model-visible tools. A loader-owned integration can register a namespace such
442+as `Issues`, `Fictions`, or `Calendar`; guest code then calls that namespace
443+inside the QuickJS program while OpenClaw still shows only `exec` and `wait` to
444+the model.
445+446+Namespaces are internal for now. There is no public plugin SDK namespace API:
447+external plugin namespaces need a loader-owned contract so plugin identity,
448+installed manifests, auth state, and cached catalog descriptors cannot drift
449+from the plugin tools that back the namespace. Core code mode owns only the
450+sandbox, serialization, catalog gating, and bridge dispatch.
451+452+Guest code can then use either the direct global or the `namespaces` map:
453+454+```javascript
455+const open = await Issues.list({ state: "open" });
456+const alsoOpen = await namespaces.Issues.list({ state: "open" });
457+return { count: open.length, alsoCount: alsoOpen.length };
458+```
459+460+### Registry lifecycle
461+462+The namespace registry is process-local and keyed by namespace id. A typical
463+run follows this path:
464+465+1. A trusted loader calls `registerCodeModeNamespaceForPlugin(pluginId, registration)`.
466+2. Code mode creates the hidden `ToolSearchRuntime` for the run and reads its
467+ run-scoped catalog.
468+3. `createCodeModeNamespaceRuntime(ctx, catalog)` keeps only registrations
469+ whose `requiredToolNames` are all visible and owned by the same `pluginId`.
470+4. Each visible namespace calls `createScope(ctx)` for the current run. The
471+ scope receives run context such as `agentId`, `sessionKey`, `sessionId`,
472+`runId`, config, and abort state.
473+5. Scope data is serialized into a plain descriptor and injected into QuickJS as
474+ direct globals and `namespaces.<globalName>`.
475+6. Guest calls suspend through the worker bridge, resolve the namespace path on
476+ the host, map the call to a declared plugin-owned catalog tool, and execute
477+ that tool through `ToolSearchRuntime.call`.
478+7. `wait` resumes the same namespace runtime when a code-mode run suspended on
479+ nested tool work.
480+8. Plugin rollback or uninstall calls `clearCodeModeNamespacesForPlugin(pluginId)`
481+ so stale globals do not survive a failed plugin load.
482+483+The important invariant: namespace calls are catalog tool calls. They use the
484+same policy hooks, approvals, abort handling, telemetry, transcript projection,
485+and suspend/resume behavior as `tools.call(...)`.
486+487+### Registration shape
488+489+Register namespaces from the integration that owns the backing tools. Keep the
490+scope small and only expose domain verbs that map to declared catalog tools.
491+492+```typescript
493+import {
494+createCodeModeNamespaceTool,
495+registerCodeModeNamespaceForPlugin,
496+} from "../agents/code-mode-namespaces.js";
497+498+const pluginId = "github";
499+500+registerCodeModeNamespaceForPlugin(pluginId, {
501+ id: "github-issues",
502+ globalName: "Issues",
503+ description: "GitHub issue helpers for the current repository.",
504+ requiredToolNames: ["github_list_issues", "github_update_issue"],
505+ prompt: "Use Issues.list(params) and Issues.update(number, patch).",
506+createScope: (ctx) => ({
507+ repository: ctx.config,
508+ list: createCodeModeNamespaceTool("github_list_issues", ([params]) => params ?? {}),
509+ update: createCodeModeNamespaceTool("github_update_issue", ([number, patch]) => ({
510+number,
511+patch,
512+ })),
513+ }),
514+});
515+```
516+517+`createCodeModeNamespaceTool(toolName, inputMapper)` marks a scope member as a
518+callable namespace function. The optional `inputMapper` receives the guest
519+arguments and returns the input object for the backing catalog tool. Without an
520+input mapper, the first guest argument is used, or `{}` when omitted.
521+522+Raw host functions are rejected before guest code runs:
523+524+```typescript
525+createScope: () => ({
526+// Wrong: this bypasses the catalog tool lifecycle and will be rejected.
527+list: async () => githubClient.listIssues(),
528+});
529+```
530+531+### Ownership and visibility
532+533+Namespace ownership is bound to the registration caller's `pluginId`.
534+`requiredToolNames` is both a visibility gate and an ownership check:
535+536+- every required tool must exist in the run catalog
537+- every required tool must have `sourceName === pluginId`
538+- the namespace is hidden when any required tool is absent or owned by another
539+ plugin
540+- each callable path may target only a tool named in `requiredToolNames`
541+542+This prevents another plugin from exposing a namespace by registering a
543+same-named tool. It also keeps namespaces aligned with ordinary agent policy:
544+if the run cannot see the backing tools, it cannot see the namespace.
545+546+For example, a GitHub namespace should live behind a GitHub-owned extension that
547+owns GitHub auth, REST or GraphQL clients, rate limits, write approvals, and
548+tests. Core code mode should not embed GitHub-specific APIs, token handling, or
549+provider policy.
550+551+### Scope serialization rules
552+553+`createScope(ctx)` may return a plain object containing JSON-compatible values,
554+arrays, nested objects, and `createCodeModeNamespaceTool(...)` call markers.
555+Host objects never enter QuickJS directly.
556+557+The serializer rejects:
558+559+- raw functions
560+- circular object graphs
561+- unsafe path segments: `__proto__`, `constructor`, `prototype`, empty keys, or
562+ keys containing the internal path separator
563+- `globalName` values that are not JavaScript identifiers
564+- `globalName` collisions with built-in code-mode globals such as `tools`,
565+`namespaces`, `text`, `json`, `yield_control`, or `__openclaw*`
566+567+Values that cannot be JSON-serialized are converted to JSON-safe fallback
568+values before crossing the bridge. Binary data, handles, sockets, clients, and
569+class instances should stay behind ordinary catalog tools.
570+571+### Prompts
572+573+The namespace `description` and optional `prompt` are appended to the model
574+visible `exec` schema only when the namespace is visible for that run. Use them
575+to teach the smallest useful surface:
576+577+```typescript
578+{
579+description: "Fiction production service helpers.",
580+prompt:
581+"Use Fictions.riskAudit(), Fictions.promoteIfReady(id, status), and Fictions.unpaidOver(amount).",
582+}
583+```
584+585+Keep prompts about the namespace contract, not auth setup, implementation
586+history, or unrelated plugin behavior.
587+588+### Cleanup
589+590+Namespaces are process-local registrations. Remove them when the owning plugin
591+is disabled, uninstalled, or rolled back:
592+593+```typescript
594+clearCodeModeNamespacesForPlugin(pluginId);
595+```
596+597+Use `unregisterCodeModeNamespace(namespaceId)` only when removing one known
598+namespace. Tests can call `clearCodeModeNamespacesForTest()` to avoid leaking
599+registrations across cases.
600+601+### Test checklist
602+603+Namespace changes should cover the security boundary and the guest behavior:
604+605+- namespace prompt text appears only when backing tools are visible
606+- same-named tools from another `sourceName` do not expose the namespace
607+- raw scope functions are rejected
608+- forged namespace ids and forged paths are rejected
609+- callable paths cannot target undeclared tools
610+- nested objects and shared references serialize correctly
611+- namespace calls execute through catalog tools and return JSON-safe details
612+- failures can be caught by guest code
613+- suspended namespace calls resume through `wait`
614+- plugin rollback clears the owning namespace registrations
615+616+Namespaces complement the generic `tools.search` / `tools.call` catalog. Use
617+the catalog for arbitrary enabled tools; use namespaces for plugin-owned,
618+documented domain APIs where concise code is more reliable than repeated schema
619+lookups.
620+436621## Output API
437622438623`text(value)` appends human-readable output to the `output` array.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。