nanocodex 0.6.5 → 0.6.7

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.
Files changed (121) hide show
  1. package/README.md +311 -6
  2. package/THIRD-PARTY-LICENSES +23 -0
  3. package/browser/Agent.d.mts +5 -4
  4. package/browser/Agent.mjs +4 -0
  5. package/browser/Claude.d.mts +4 -0
  6. package/browser/Claude.mjs +11 -0
  7. package/browser/InlineAgent.mjs +19 -1
  8. package/browser/Voice.mjs +11 -4
  9. package/browser/VoiceSession.mjs +20 -6
  10. package/browser/host.d.mts +54 -1
  11. package/browser/host.mjs +65 -16
  12. package/browser/hostManagedWebSocket.mjs +11 -2
  13. package/browser/index.d.mts +3 -10
  14. package/browser/index.mjs +2 -5
  15. package/browser/socket-observations.mjs +203 -0
  16. package/cloud/Decorator.d.mts +2 -2
  17. package/cloud/Decorator.mjs +1 -0
  18. package/cloud/Dialog.d.mts +13 -0
  19. package/cloud/Dialog.mjs +52 -6
  20. package/cloud/Transport.d.mts +0 -11
  21. package/cloud/Transport.mjs +1 -386
  22. package/cloud/actions/account.d.mts +12 -0
  23. package/cloud/actions/account.mjs +11 -0
  24. package/cloud/actions/agent.mjs +3 -0
  25. package/cloud/actions/connection.d.mts +3 -1
  26. package/cloud/actions/connection.mjs +19 -4
  27. package/cloud/index.d.mts +1 -1
  28. package/cloud/index.mjs +1 -1
  29. package/cloud/internal.mjs +19 -4
  30. package/cloud/mercator.mjs +1 -1
  31. package/cloud/mock.d.mts +17 -0
  32. package/cloud/mock.mjs +392 -0
  33. package/cloud/types.d.mts +4 -1
  34. package/cloudflare/Agent.d.mts +6 -0
  35. package/cloudflare/Agent.mjs +190 -10
  36. package/cloudflare/agent-settings.d.mts +1 -1
  37. package/cloudflare/agent-settings.mjs +12 -3
  38. package/cloudflare/egress.mjs +9 -6
  39. package/cloudflare/gateway-binding-responses.mjs +3 -3
  40. package/cloudflare/gateway-responses.d.mts +1 -0
  41. package/cloudflare/gateway-responses.mjs +7 -7
  42. package/cloudflare/managed-auth.d.mts +4 -4
  43. package/cloudflare/managed-auth.mjs +20 -2
  44. package/cloudflare/managed-live.d.mts +6 -0
  45. package/cloudflare/managed-live.mjs +56 -1
  46. package/cloudflare/tracing.d.mts +16 -0
  47. package/cloudflare/tracing.mjs +47 -0
  48. package/cloudflare/workers-ai-responses.d.mts +3 -2
  49. package/cloudflare/workers-ai-responses.mjs +83 -8
  50. package/host/Agent.d.mts +6 -2
  51. package/host/Claude.d.mts +3 -0
  52. package/host/Claude.mjs +2 -0
  53. package/host/index.d.mts +6 -10
  54. package/host/index.mjs +2 -5
  55. package/index.d.mts +11 -10
  56. package/index.mjs +0 -5
  57. package/internal.mjs +33 -3
  58. package/managed/Agent.d.mts +10 -3
  59. package/managed/Agent.mjs +60 -10
  60. package/managed/ManagedError.d.mts +3 -0
  61. package/managed/Voice.mjs +3 -1
  62. package/managed/internal.mjs +2 -1
  63. package/node/Agent.d.mts +6 -2
  64. package/node/Agent.mjs +14 -1
  65. package/node/Claude.d.mts +4 -0
  66. package/node/Claude.mjs +24 -0
  67. package/node/host.mjs +18 -9
  68. package/node/index.d.mts +6 -10
  69. package/node/index.mjs +2 -5
  70. package/package.json +45 -9
  71. package/pkg-node/nanocodex.d.ts +123 -2
  72. package/pkg-node/nanocodex.js +879 -89
  73. package/pkg-node/nanocodex_bg.wasm.d.ts +49 -3
  74. package/pkg-web/.nanocodex-bindgen-stamp +4 -4
  75. package/pkg-web/nanocodex-build.json +1 -1
  76. package/pkg-web/nanocodex.d.ts +172 -5
  77. package/pkg-web/nanocodex.js +872 -88
  78. package/pkg-web/nanocodex_bg.js +872 -88
  79. package/pkg-web/nanocodex_bg.wasm +0 -0
  80. package/pkg-web/nanocodex_bg.wasm.d.ts +49 -3
  81. package/pkg-web/nanocodex_worker.js +1 -1
  82. package/runtime/claude-host.mjs +308 -0
  83. package/runtime/claude-subscription.mjs +79 -0
  84. package/runtime/claude.d.mts +112 -0
  85. package/runtime/claude.mjs +221 -0
  86. package/runtime/code-effect-identity.mjs +123 -0
  87. package/runtime/harnesses.mjs +51 -0
  88. package/runtime/mcp-payment.mjs +8 -0
  89. package/runtime/mcp-runtime.d.mts +10 -0
  90. package/runtime/mcp-runtime.mjs +242 -55
  91. package/runtime/mercator.mjs +2 -0
  92. package/runtime/quickjs-evaluator.mjs +23 -11
  93. package/runtime/response-controls.mjs +16 -10
  94. package/runtime/responses-http.mjs +4 -2
  95. package/runtime/retry-after.d.mts +10 -0
  96. package/runtime/retry-after.mjs +86 -0
  97. package/runtime/steer-receipt.mjs +8 -1
  98. package/runtime/subagent-routing.d.mts +4 -2
  99. package/runtime/subagent-routing.mjs +9 -3
  100. package/runtime/subagents.d.mts +8 -3
  101. package/services/account.d.mts +76 -0
  102. package/services/account.mjs +151 -0
  103. package/services/hosted.mjs +72 -0
  104. package/services/index.d.mts +95 -0
  105. package/services/index.mjs +192 -0
  106. package/services/scope.mjs +36 -0
  107. package/{runtime/tempo-provider.d.mts → tempo/index.d.mts} +20 -1
  108. package/{runtime/tempo-provider.mjs → tempo/index.mjs} +39 -3
  109. package/tools/browser/browserShell.mjs +18 -8
  110. package/tools/browser/python.worker.mjs +17 -1
  111. package/tools/browser/threadGit.mjs +12 -2
  112. package/tools/environment.d.mts +17 -0
  113. package/tools/environment.mjs +24 -1
  114. package/tools/index.d.mts +6 -3
  115. package/types.d.mts +107 -6
  116. package/worker/Claude.d.mts +4 -0
  117. package/worker/Claude.mjs +11 -0
  118. package/worker/ClaudeSubscription.d.mts +24 -0
  119. package/worker/ClaudeSubscription.mjs +9 -0
  120. package/worker/index.d.mts +9 -0
  121. package/worker/index.mjs +4 -0
package/README.md CHANGED
@@ -80,6 +80,15 @@ Worker, Durable Object, or application proxy that owns rotating credentials.
80
80
  Authentication modes are constructors rather than a union of mutually
81
81
  exclusive fields on `Agent.create`.
82
82
 
83
+ ### Explicit Claude runtime
84
+
85
+ `Claude.create` is an additive Messages backend with explicit host-owned auth
86
+ and a Claude-only tool array, using the existing durability store contract.
87
+ It does not silently switch managed providers, install Codex tools, or supply
88
+ a subscription sign-in screen. Managed account connection is documented in the
89
+ [managed Claude guide](../../docs/CLAUDE_MANAGED.md). See the [Claude JavaScript guide](../../docs/CLAUDE_JAVASCRIPT.md)
90
+ for durable reopen, replay, and placement boundaries.
91
+
83
92
  ### Personal memories and caller context
84
93
 
85
94
  Managed agents can keep user preferences separate from team knowledge:
@@ -252,6 +261,20 @@ normal browser Agent, an account-owned managed Agent, or a grant-scoped
252
261
  one-use ticket for that path. The browser binding never receives ChatGPT
253
262
  credentials or places its reusable grant bearer in a WebSocket URL.
254
263
 
264
+ ### Content-free Worker tracing
265
+
266
+ `nanocodex/cloudflare/tracing` wraps native `tracing.enterSpan(name, callback)`
267
+ with sanitized exception events. Use static operation names: they become
268
+ exception codes on failure. The original error is rethrown; its message and
269
+ stack are never passed to `recordException`.
270
+
271
+ `setSpanAttributes(span, attributes)` sets bulk metadata, while
272
+ `annotateActiveSpan(attributes)` annotates the current invocation or active
273
+ span without changing nesting. `recordSpanException(span, code)` handles
274
+ failures represented by results rather than thrown errors. Supply only static
275
+ codes and content-free attributes. On older workerd versions, these retain
276
+ individual attributes and `error.type` without requiring the September APIs.
277
+
255
278
  ### Durable Cloudflare Agent
256
279
 
257
280
  `nanocodex/cloudflare` is the standard Durable Object consumer. It keeps the
@@ -303,10 +326,17 @@ membership, room, quota, or application routing policy. Event frames are
303
326
  `1013`, then continues by reconnecting with that pause cursor as
304
327
  `?cursor=<decimal>`.
305
328
 
329
+ `toolMode: "code-only"` exposes only `exec` and `wait` to the model. Workspace,
330
+ application, discovery (`tools.tool_search`) and local subagent tools are callable
331
+ inside Code Mode. Discovery returns tool information as cell output; it does not
332
+ add direct schemas. Newly discovered tools are available in the next cell. The
333
+ existing `"code"` mode retains direct workspace/subagent controls, and `"direct"`
334
+ continues to expose function tools without requiring an evaluator.
335
+
306
336
  Cloudflare Agents default to direct tool mode because Workers prohibit dynamic
307
337
  `eval`/`new Function`. Caller-defined tools therefore work without a code
308
- evaluator. Select `toolMode: "code"` only when also supplying an evaluator that
309
- is explicitly compatible with the deployed Worker runtime. Runtime-owned
338
+ evaluator. Select `toolMode: "code"` or `toolMode: "code-only"` only when also
339
+ supplying an evaluator explicitly compatible with the deployed Worker runtime. Runtime-owned
310
340
  Subagents are installed by default, including on a durable root. All children are
311
341
  ephemeral: their identities, topology, conversations, results, and routing exist
312
342
  only for the lifetime of the root runtime. Root shutdown or restart discards the
@@ -588,7 +618,7 @@ shared registry and installs fresh tools for every root, spawn, and fork:
588
618
  ```rust,ignore
589
619
  let (registry, control, updates) = nanocodex_subagents::channel(max_concurrency);
590
620
  let tools = Tools::builder().without_defaults().build()?;
591
- let tools = nanocodex_tools::embedded::bind_host(tools, javascript_host);
621
+ let tools = nanocodex_oai_tools::embedded::bind_host(tools, javascript_host);
592
622
  let (agent, events) = Nanocodex::builder(openai)
593
623
  .tools_factory(move |handle| {
594
624
  nanocodex_subagents::install_tools(tools.clone(), handle, registry.clone())
@@ -655,8 +685,17 @@ API key. Pass an MPP session with a `ws(endpoint)` method; an `mppx` Tempo
655
685
  session manager has this shape. Nanocodex defaults the socket to
656
686
  `wss://openai.mpp.tempo.xyz/v1/responses` when `mpp` is present.
657
687
 
688
+ Tempo/MPP helpers live in `nanocodex/tempo`, whose optional peer dependencies
689
+ `mppx` and `viem` are needed only when you import it; the core entry points
690
+ never reference them.
691
+
692
+ ```sh
693
+ npm install nanocodex mppx viem
694
+ ```
695
+
658
696
  ```js
659
- import { Agent, createTempoProviderFromAccounts, Transport } from "nanocodex/node";
697
+ import { Agent, Transport } from "nanocodex/node";
698
+ import { createTempoProviderFromAccounts } from "nanocodex/tempo";
660
699
  import { Expiry } from "accounts";
661
700
  import { Provider } from "accounts/cli";
662
701
  import { parseUnits } from "viem";
@@ -759,6 +798,22 @@ the relay preserves MPP challenges, credentials, and receipts but never signs.
759
798
  Passing a generic `MppSession`, an OpenAI key, or ChatGPT host auth does not
760
799
  initialize Mercator. Pass `mcp: false` to opt out explicitly.
761
800
 
801
+ Trusted hosts can set a server's `privateResult` policy to intercept raw MCP
802
+ results before model output, Code Mode values, and tool-result tracing.
803
+ `beforeCall({ name, arguments }, context)` runs after payment context validation
804
+ and before remote dispatch. It may reject the call or return `{ privateContext }`. An own
805
+ `result` property instead supplies an already-safe receipt and bypasses both
806
+ remote dispatch and `transformResult`, allowing durable policies to replay a
807
+ receipt without repeating an effect.
808
+ `transformResult({ name, arguments, result, privateContext }, context)` receives
809
+ that invocation's `privateContext` and must return only safe MCP content. State stays local
810
+ to each call, including concurrent calls; it is `undefined` without `beforeCall`.
811
+ Both hooks run within the tool deadline. Failed private requests return a fixed
812
+ failure receipt without the original error or cause. Discovery failures also use
813
+ a fixed message. Callbacks and caller-owned clients remain trusted: they must
814
+ not independently log private payloads or return private state. No notification
815
+ consumer is configured by this policy.
816
+
762
817
  Remote Streamable HTTP MCP servers are configured directly on the agent. The
763
818
  JavaScript binding uses the official MCP SDK transport, keeps remote tools
764
819
  deferred, and mirrors native Nanocodex exposure: the initial Responses request
@@ -775,6 +830,9 @@ supports Tempo charge and session challenges, so paid services composed behind
775
830
  Mercator use the same signer and spending policy as the model:
776
831
 
777
832
  ```js
833
+ import { tempo } from "mppx/client";
834
+ import { createTempoProvider } from "nanocodex/tempo";
835
+
778
836
  const mcpMethod = tempo({
779
837
  account,
780
838
  channelStore,
@@ -795,6 +853,17 @@ const agent = await Agent.create({
795
853
 
796
854
  Explicit `mcp` entries are merged over the Tempo defaults, so an application
797
855
  can replace `mercator` or add other servers without rebuilding the provider.
856
+ A paid server configured directly must wrap its options with `mcpPayment()` from
857
+ `nanocodex/tempo`, which loads `mppx/mcp/client` on first connection; a plain
858
+ `payment` object is rejected with a `TypeError`:
859
+
860
+ ```js
861
+ import { mcpPayment } from "nanocodex/tempo";
862
+
863
+ const mcp = {
864
+ paid: { url: "https://paid.example/mcp", payment: mcpPayment({ methods: [mcpMethod] }) },
865
+ };
866
+ ```
798
867
 
799
868
  Each server also accepts `headers`, `fetch`, allow/deny tool lists, a timeout,
800
869
  or an already initialized MCP SDK-compatible `client`. Nanocodex closes clients
@@ -810,6 +879,80 @@ session and are invalidated when the host shuts down; a persisted `wait` never
810
879
  restarts missing work. Embedded cells retain ownership of all nested tool calls
811
880
  until they finish or are cancelled.
812
881
 
882
+ Opt into observer-only instant steering with `instantToolSteering: true` in
883
+ Node/browser or Cloudflare Agent creation. Managed creation uses
884
+ `configuration: { instant_tool_steering: true }`. The default is false. Accepted
885
+ steering yields a foreground `exec`/`wait` with its output and live cell ID,
886
+ without cancelling evaluation or nested effects. Later `wait` resumes that
887
+ same cell. Model streams are not interrupted by this option; see
888
+ [the runtime boundaries](../../docs/codex-code-mode.md#observer-only-instant-steering).
889
+
890
+ Owned `nanocodex/node` and `nanocodex/host` agents may opt into
891
+ `codeEffectJournal` alongside their durability store. This trusted host adapter
892
+ must durably acknowledge `begin(context)` before dispatch, validate the original
893
+ session/call identity plus source/tool/input fingerprint, and fence stale owners.
894
+ Journal keys must include
895
+ `[sessionId, operationId ?? "", modelCallIndex ?? 0, parentCallId, callId]`.
896
+ Owned SDK hosts synchronously observe original Rust request/idempotency identity
897
+ and model-call ordinal before dispatch; missing canonical metadata interrupts
898
+ rather than falling back to a projected turn/run ID. Provider call IDs may repeat
899
+ across model responses within one operation and across operations. Use an explicit
900
+ prompt operation `id` with a durable owned journal. Deliberately non-durable
901
+ prompts (including ephemeral child tasks) emit an explicit null Rust operation
902
+ ID; the host scopes those invocations to the unique trusted accepted input item.
903
+ That scope does not promise cold replay of a non-durable task. Absent or malformed
904
+ metadata still interrupts before effects. Generic explicit runtimes without
905
+ an identity resolver retain their existing session/parent identity semantics.
906
+ `turnId` is diagnostic metadata, not the effect replay key.
907
+ Child event forwarding may race the host invocation. The owned SDK rendezvous
908
+ with the exact accepted session/turn and fresh tool-call metadata before journal
909
+ admission (one-second deadline, at most 128 waiters, abort/release/dispose cleanup).
910
+ It never derives effect authority from a parent correlation envelope or reuses
911
+ consumed model-call metadata. Parallel nested calls share one identity promise;
912
+ missing or conflicting metadata fails closed without effect dispatch.
913
+ This covers direct application tools (`toolMode: "direct"`) as well as tools
914
+ invoked by Code Mode. Direct contexts use `parentCallId = callId` and
915
+ `source = "host-tool:" + name`; nested contexts use the exact guest source
916
+ and a stable cell/ordinal.
917
+ Adapters can additionally implement `beginCell(context)` and
918
+ `commitStore(context, writes)` together to persist Code Mode `store()` state.
919
+ Before evaluation, `beginCell` durably pins the cell's immutable starting entries;
920
+ recovery returns those same entries even when later cells changed session state.
921
+ The context has `name: "code-cell"`, `callId: parentCallId`, and `input: null`.
922
+ `commitStore` atomically merges only the cell's writes, once per original cell
923
+ identity. A completed-cell replay must not overwrite newer committed writes.
924
+ Completed failed scripts also commit their writes; interrupted/aborted cells do
925
+ not. Session stores remain isolated and entry snapshots are bounded to 8 MiB and
926
+ 32,768 nodes. Hosts must fail closed when prior effects exist but their original
927
+ starting snapshot is missing, corrupt, or cannot be proved during an upgrade.
928
+ Without these optional methods, stores remain local to a runtime instance.
929
+
930
+ Deterministic adapter conflicts and corruption should throw an error with
931
+ `code: "CODE_EFFECT_UNKNOWN"`. These errors and invalid replay receipts settle
932
+ as failed unknown outcomes, without admitting further effects. Storage/transport
933
+ exceptions remain host interruptions so genuinely transient failures can recover.
934
+
935
+ Return `execute` for a new retained intent, `replay` with its exact completed
936
+ receipt, or `unknown` for an intent without a durable outcome. `complete` must
937
+ acknowledge storage before either direct tool output or a nested guest result is
938
+ returned. Direct receipts use `value: null` and `thrown: false` (handler failures
939
+ remain failed encoded tool outputs, not guest throws). Unknown direct intents
940
+ return an explicit failed tool result with `outcome: "unknown"` without dispatch.
941
+ Unknown nested effects fail the recovered cell even if guest code catches the error; they are never silently
942
+ rerun. Reconcile external state using the original operation identity.
943
+
944
+ Receipts must contain plain JSON data and are bounded to an aggregate 8 MiB
945
+ and 32,768 entries before output/value copies. Undefined results and raw undefined
946
+ rejections have explicit markers. Identical output/structured/value payloads use
947
+ receipt-local references (or derived JSON text), so ordinary inline media does
948
+ not consume three copies. An oversized or unretainable post-effect receipt interrupts the host and
949
+ leaves its original intent unknown, rather than granting permission to redispatch. Route larger media through a bounded
950
+ artifact/reference tool instead of embedding it in the replay receipt.
951
+ The journal is opt-in for generic SDK hosts; durability alone does not make
952
+ direct or nested application effects safe to rerun after abrupt owner loss.
953
+ It does not journal arbitrary native JavaScript/evaluator effects, built-in
954
+ provider-native tools, or external operations outside the application-tool router.
955
+
813
956
  Custom evaluators receive `audio`, `notify`, `yield_control`, `setTimeout`, and
814
957
  `clearTimeout` alongside the existing globals in `CodeEvaluatorEnvironment`.
815
958
  Forward those helpers into the guest environment to preserve the model-visible
@@ -824,7 +967,7 @@ keeps deferred MCP plus Code Mode functional in Cloudflare Workers:
824
967
 
825
968
  ```js
826
969
  import asyncVariant from "@jitl/quickjs-wasmfile-release-asyncify";
827
- import { Agent, createQuickJsEvaluator, createTempoProvider, Transport } from "nanocodex/host";
970
+ import { Agent, createQuickJsEvaluator, Transport } from "nanocodex/host";
828
971
  import { newQuickJSAsyncWASMModuleFromVariant } from "quickjs-emscripten-core";
829
972
 
830
973
  const quickJs = await newQuickJSAsyncWASMModuleFromVariant(asyncVariant);
@@ -1167,7 +1310,7 @@ package manager or build step:
1167
1310
 
1168
1311
  ```html
1169
1312
  <script type="module">
1170
- import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.5/host/index.mjs";
1313
+ import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.7/host/index.mjs";
1171
1314
  const agent = await Agent.create({
1172
1315
  transport: Transport.hostManaged({
1173
1316
  websocketUrl: "/api/responses",
@@ -1347,3 +1490,165 @@ against the Connect Worker with fixture provider responses, covering reads,
1347
1490
  writes, pagination, rate limits, and revoked grants. Worker tests cover routing
1348
1491
  and service denial for all 13 API connectors. SDKs that hardcode their API host need a custom transport instead of only
1349
1492
  a base URL setting.
1493
+
1494
+
1495
+ ## Connect dialog appearance
1496
+
1497
+ Pass visual tokens when creating an embedded or popup Connect dialog:
1498
+
1499
+ ```js
1500
+ import { Client, Dialog } from "nanocodex/connect";
1501
+
1502
+ const appearance = {
1503
+ theme: "system",
1504
+ accentColor: "#635bff",
1505
+ fontFamily: '"Open Sans", system-ui, sans-serif',
1506
+ borderRadius: 12,
1507
+ };
1508
+ const client = Client.create({
1509
+ appId: "your-app-id",
1510
+ dialog: Dialog.iframe({ appearance }),
1511
+ });
1512
+ // Dialog.popup({ appearance }) accepts the same options for account authorization.
1513
+ ```
1514
+
1515
+ All fields are optional. `theme` accepts `light`, `dark`, or `system`;
1516
+ `accentColor` requires six hex digits; `fontFamily` accepts an installed or
1517
+ self-hosted font list of at most 160 characters; `borderRadius` is a number from
1518
+ 0 to 24 pixels. Arbitrary CSS, CSS functions, and unknown options are rejected.
1519
+ The SDK snapshots the configuration when the dialog is created and carries it
1520
+ in the `nanocodex_appearance` URL parameter to wallet and funding iframes or the
1521
+ account popup. The hosted dialog limits the JSON value to 1,024 characters and
1522
+ uses native defaults for malformed values. Omit `appearance` for native defaults.
1523
+
1524
+ ### Standalone account services
1525
+
1526
+ `nanocodex/services` accesses Vault and dedicated SMS numbers without an agent,
1527
+ thread, model, or WASM runtime. Keep direct account API keys on your server:
1528
+
1529
+ ```js
1530
+ import { createServicesClient } from "nanocodex/services";
1531
+
1532
+ const services = createServicesClient({ apiKey: process.env.NANOCODEX_API_KEY });
1533
+ const { vault } = await services.vault.list();
1534
+ const { status, ok } = await services.vault.request({
1535
+ vault_id: vault[0].id,
1536
+ url: "https://example.com/verify",
1537
+ method: "POST",
1538
+ body_encoding: "json",
1539
+ body: '{"code":"{{NANOCODEX_VAULT_TOTP}}"}',
1540
+ });
1541
+ ```
1542
+
1543
+ Direct calls use `https://nanocodex.gakonst.workers.dev/v1/services`. Reads require
1544
+ `data:read`, Vault requests require `tools:use`, and phone intents require
1545
+ `data:write` plus `tools:use`. Vault returns metadata and destination HTTP status
1546
+ only. TOTP seeds and generated codes stay in the broker; the saved exact HTTPS
1547
+ origin bounds code injection. There is no code-export method.
1548
+
1549
+ Browser apps use an existing Connect client and exact signed service capabilities:
1550
+
1551
+ ```js
1552
+ import { Client } from "nanocodex/connect";
1553
+ import { createServicesClient } from "nanocodex/services";
1554
+
1555
+ const connect = Client.create({ appId: "example-app" });
1556
+ const connection = await connect.connection.connect({
1557
+ capabilities: {
1558
+ services: {
1559
+ vault: { ids: ["selected-vault-id"], origins: ["https://example.com"], request: true },
1560
+ phone: { numberIds: [], read: false, provision: true, release: false },
1561
+ },
1562
+ },
1563
+ });
1564
+ const services = createServicesClient({ connect, grantId: connection.grant.id });
1565
+ ```
1566
+
1567
+ Service-only connections default to `services.use` and hosted authorization.
1568
+ They contain no `agentId`, access key, or MPP budget. Scope is bound into the
1569
+ signed approval and checked against the returned grant. Reconnect with the same
1570
+ `capabilities.services` to require that exact scope. Service requests use
1571
+ `/v1/grants/:grantId/services/...` and the Connect session. Number and Vault IDs
1572
+ are explicit; they never expand to the entire account.
1573
+
1574
+ Use `phone.available({ country: "US", area_code: "202" })`, `phone.list()`,
1575
+ `phone.get(id)`, and `phone.messages(id, { limit: 20, cursor })` for reads. SMS
1576
+ messages expire according to the service's retention policy; support for
1577
+ verification messages depends on the sender and carrier.
1578
+
1579
+ Provisioning and release create requests for human review:
1580
+
1581
+ ```js
1582
+ import { createHostedRequest, openHostedPopup } from "nanocodex/services";
1583
+
1584
+ // Persist this UUID before sending; keep it when reconciling an uncertain request.
1585
+ const operation_id = crypto.randomUUID();
1586
+ const { request } = await services.phone.provision({
1587
+ operation_id, phone_number: "+12025550101", country: "US",
1588
+ });
1589
+ const approval = createHostedRequest({
1590
+ service: "phone", operationId: request.approval_request_id ?? request.operation_id, appOrigin: location.origin,
1591
+ });
1592
+ // Open from a separate click handler after the request is ready.
1593
+ await openHostedPopup(approval);
1594
+ const result = await services.phone.requests.get(request.operation_id);
1595
+ ```
1596
+
1597
+ `phone.release(numberId, { operation_id })` also requires hosted human approval.
1598
+ Poll with the **original** caller UUID, including after a dropped intent response.
1599
+ Connect returns an additional `request.approval_request_id` for the hosted owner
1600
+ approval; use `request.approval_request_id ?? request.operation_id` for its link.
1601
+ Any deliberate identical intent replay must retain the original caller UUID.
1602
+ A newly provisioned number needs a fresh exact-ID Connect approval before inbox
1603
+ access. The SDK never automatically retries a write. `ServiceError` exposes
1604
+ `status`, `code`, and `outcomeUnknown`; an unknown outcome requires reconciliation,
1605
+ not a new operation ID. No SDK method approves purchases or releases.
1606
+
1607
+ TOTP enrollment uses a private hosted form:
1608
+
1609
+ ```js
1610
+ const enrollment = createHostedRequest({ appOrigin: location.origin });
1611
+ // Call from a user click. Inputs stay on the account origin.
1612
+ const metadata = await openHostedPopup(enrollment);
1613
+ if (metadata.service === "vault") console.log(metadata.vault_id);
1614
+ ```
1615
+
1616
+ The popup helper checks the sender window, exact hosted origin, and a unique
1617
+ state. Only fixed metadata fields reach the caller. `createHostedRequest`
1618
+ also accepts `host` for a trusted HTTPS account deployment and `state` for a
1619
+ caller-managed unique nonce. Secret entry and approvals require a top-level window; embedded frames cannot
1620
+ perform them. React applications can open the same request with
1621
+ `HostedServiceButton` from `nanocodex-react/services`.
1622
+
1623
+ To use an existing Vault item before granting access, open the account picker
1624
+ from a user click. No service client or Connect grant is needed:
1625
+
1626
+ ```js
1627
+ const selection = createHostedRequest({
1628
+ service: "vault", action: "select", appOrigin: location.origin,
1629
+ });
1630
+ const selected = await openHostedPopup(selection);
1631
+ if (selected.service === "vault" && selected.action === "select") {
1632
+ // The user sees the recipient origin and explicitly chooses this item.
1633
+ // Only its opaque vault_id, kind, and name are shared.
1634
+ const connection = await connect.connection.connect({
1635
+ capabilities: { services: { vault: {
1636
+ ids: [selected.vault_id], origins: ["https://example.com"], request: true,
1637
+ } } },
1638
+ });
1639
+ }
1640
+ ```
1641
+
1642
+ The picker does not create or expand a grant and does not enroll the item again.
1643
+ The separate Connect approval authorizes the exact selected ID and destination
1644
+ origins. Closing the popup or aborting its `signal` cancels the local wait;
1645
+ closing a phone approval window does not cancel a server-side operation. Poll
1646
+ that operation with its original caller UUID to learn its outcome.
1647
+
1648
+ Account management is available through `createServicesClient({ apiKey }).account`
1649
+ without an agent or WASM. Use `client.links({ connect: 'google', add: 'login' })`
1650
+ for ordinary Connections/Vault URLs, or
1651
+ `client.hosted({ service: 'vault', kind: 'card' })` for a private enrollment link.
1652
+ OAuth, Cloudflare, MCP, model sign-in, Vault/SSH management, and captured-card
1653
+ save/balance methods share the account REST contracts. See the
1654
+ [REST, JavaScript and Rust guide](../../docs/STANDALONE_SERVICES.md#account-management-and-browser-links).
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
4
+ Copyright (c) 2025-2026 Can Bölük
5
+ Copyright (c) 2026 Stencil Labs, Inc.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -22,10 +22,11 @@ type WorkerMcpServer = Readonly<{
22
22
  }>;
23
23
  type WorkerMcpServers = Readonly<Record<string, string | URL | WorkerMcpServer>>;
24
24
  type WorkerToolExposureOptions =
25
- | { mcp?: false | undefined; toolMode?: "code" | "direct" | undefined }
26
- | { mcp: WorkerMcpServers; toolMode?: "code" | undefined };
25
+ | { mcp?: false | undefined; toolMode?: "code" | "code-only" | "direct" | undefined }
26
+ | { mcp: WorkerMcpServers; toolMode?: "code" | "code-only" | undefined };
27
27
 
28
28
  /** Creates a Rust/WASM Agent in a package-owned browser module Worker. */
29
+ export function create(options: import('../runtime/claude.mjs').Options & { harness: 'claude' }): Promise<import('../runtime/claude.mjs').Agent>;
29
30
  export function create(options: create.ManagedOptions): Promise<AgentLifecycle>;
30
31
  export function create(options?: create.Options): Promise<create.ReturnType>;
31
32
  export declare namespace create {
@@ -33,7 +34,7 @@ export declare namespace create {
33
34
  transport: ManagedTransport;
34
35
  tools?: Tools | undefined;
35
36
  }>;
36
- type Options = Omit<AgentOptions, "beforeCompaction"> & WorkerToolExposureOptions & {
37
+ type Options = Omit<AgentOptions, "beforeCompaction" | "harness"> & WorkerToolExposureOptions & {
37
38
  /** Precompiled browser module; WebAssembly modules are structured-clone-safe. */
38
39
  module?: WebAssembly.Module | undefined;
39
40
  /** Fixed browser workspace facts, including its AGENTS.md snapshot. */
@@ -47,7 +48,7 @@ export declare namespace create {
47
48
  /** Set false to keep this browser session out of the IndexedDB durability store. */
48
49
  durability?: false | undefined;
49
50
  /** Set false to omit the default OPFS, shell, web, image, plan, and artifact tools. */
50
- harness?: false | undefined;
51
+ harness?: false | 'codex' | undefined;
51
52
  };
52
53
  type ReturnType = Agent;
53
54
  }
package/browser/Agent.mjs CHANGED
@@ -5,6 +5,10 @@ import {
5
5
 
6
6
  /** Create the browser Agent in its package-owned module Worker. */
7
7
  export function create(options = {}) {
8
+ if (options.harness === 'claude') return import('./Claude.mjs').then(({ create }) => create(options));
9
+ if (options.harness !== undefined && options.harness !== false && options.harness !== 'codex') throw new TypeError('unsupported harness family');
10
+ if (options.harnesses !== undefined) return import('./InlineAgent.mjs').then(({ create }) =>
11
+ create(options.harness === false ? { ...options, harness: 'codex' } : options));
8
12
  if (managedTransportOptions(options?.transport)) return createManagedAgent(options);
9
13
  return import("./WorkerAgent.mjs").then(({ createWorkerAgent }) =>
10
14
  createWorkerAgent(options));
@@ -0,0 +1,4 @@
1
+ import type { Agent, Options } from '../runtime/claude.mjs';
2
+ export type { Agent, Auth, Options, NativeToolResult, Result, Tool, ToolContent, ToolResult, Turn } from '../runtime/claude.mjs';
3
+ /** Creates the actual Claude Rust/WASM backend with explicit host-owned authentication and tools. */
4
+ export function create(options: Options): Promise<Agent>;
@@ -0,0 +1,11 @@
1
+ import { createClaude } from '../runtime/claude.mjs';
2
+ import { initializeBrowserEngine } from './engine.mjs';
3
+
4
+ /** Runs inline in this browser isolate; not the Codex Agent module Worker. */
5
+ export function create(options) {
6
+ return createClaude(options, async (module) => {
7
+ const wasm = await import('../pkg-web/nanocodex.js');
8
+ await initializeBrowserEngine({ module });
9
+ return wasm.Nanoclaude;
10
+ }, 'browser');
11
+ }
@@ -1,3 +1,5 @@
1
+ import { prepareHarnesses } from '../runtime/harnesses.mjs';
2
+ import { create as createClaude } from './Claude.mjs';
1
3
  import { applyBrowserPatch, Nanocodex } from "../pkg-web/nanocodex.js";
2
4
 
3
5
  import { agentActions } from "../actions/index.mjs";
@@ -33,6 +35,8 @@ import {
33
35
 
34
36
  /** Creates the Rust/WASM Agent in the current Web API host isolate. */
35
37
  export async function create(options = {}) {
38
+ if (options.harness === 'claude') return createClaude(options);
39
+ if (options.harness !== undefined && options.harness !== 'codex') throw new TypeError('unsupported harness family');
36
40
  if (managedTransportOptions(options?.transport)) return createManagedAgent(options);
37
41
  const internalRuntime = options[Symbol.for("nanocodex.browser.internalRuntime")];
38
42
  if (internalRuntime !== undefined
@@ -46,6 +50,7 @@ export async function create(options = {}) {
46
50
  thinking,
47
51
  reasoningMode,
48
52
  fastMode,
53
+ instantToolSteering,
49
54
  rawApiEvents,
50
55
  instructions,
51
56
  additionalInstructions,
@@ -63,6 +68,7 @@ export async function create(options = {}) {
63
68
  mcp,
64
69
  executionEnvironment,
65
70
  codeEvaluator,
71
+ codeEffectJournal,
66
72
  } = options;
67
73
  const toolProviders = internalRuntime?.toolProviders;
68
74
  const subagentSessions = internalRuntime?.subagentSessions;
@@ -109,9 +115,12 @@ export async function create(options = {}) {
109
115
  throw new TypeError("workspace must match filesystem.root when both are provided");
110
116
  }
111
117
  const events = createEventChannel();
118
+ // Host lifecycle observers need terminal events even without a public watcher.
119
+ events.subscribe(() => {});
112
120
  const tempoMcp = mpp?.[Symbol.for("nanocodex.tempo.mcp")];
113
121
  let hostDefinitionId;
114
122
  const host = createBrowserHost({
123
+ [Symbol.for("nanocodex.browser.internalRuntime")]: { traceTool: internalRuntime?.traceTool },
115
124
  WebSocketImpl,
116
125
  createWebSocket,
117
126
  createResponse,
@@ -128,16 +137,19 @@ export async function create(options = {}) {
128
137
  subagentSessions,
129
138
  subagentRouting: internalRuntime?.subagentRouting,
130
139
  onSocketTiming: internalRuntime?.onSocketTiming,
140
+ onSocketEvent: internalRuntime?.onSocketEvent,
131
141
  toolMode,
132
142
  mcp: mcp === false
133
143
  ? undefined
134
144
  : tempoMcp ? { ...tempoMcp, ...mcp } : mcp,
135
145
  codeEvaluator,
146
+ codeEffectJournal: internalRuntime?.codeEffectJournal ?? codeEffectJournal,
136
147
  applyPatch: applyBrowserPatch,
137
148
  websocketPreconnect,
138
149
  websocketUrl,
139
- onDispose: () => releaseDefinitionHost(hostDefinitionId),
150
+ onDispose: () => { releaseDefinitionHost(hostDefinitionId); void harnesses?.close(); },
140
151
  });
152
+ let harnesses;
141
153
  let durabilityOwner;
142
154
  let creationStarted = false;
143
155
  hostDefinitionId = registerDefinitionHost(host, cloudflareReservation);
@@ -157,6 +169,9 @@ export async function create(options = {}) {
157
169
  durabilityId,
158
170
  );
159
171
  }
172
+ harnesses = await prepareHarnesses(options.harnesses, events.emit, {
173
+ subagentSessions, subagentRouting: internalRuntime?.subagentRouting,
174
+ });
160
175
  activateHost(host);
161
176
  await host.ready();
162
177
  await initializeBrowserEngine({ module });
@@ -172,9 +187,11 @@ export async function create(options = {}) {
172
187
  websocketWarmup,
173
188
  stateless,
174
189
  subagents: subagentConfig,
190
+ claudeHarness: harnesses?.claude,
175
191
  subagentRouting: internalRuntime?.subagentRouting !== undefined,
176
192
  hostDefinitionId,
177
193
  beforeCompaction: beforeCompaction !== undefined,
194
+ promptCacheKey: internalRuntime?.promptCacheKey,
178
195
  ...config,
179
196
  durabilityHostId: durabilityOwner?.id,
180
197
  }));
@@ -246,6 +263,7 @@ export async function create(options = {}) {
246
263
  thinking,
247
264
  reasoningMode,
248
265
  fastMode,
266
+ instantToolSteering,
249
267
  rawApiEvents,
250
268
  instructions,
251
269
  additionalInstructions,
package/browser/Voice.mjs CHANGED
@@ -198,10 +198,17 @@ export function create(agent, options = {}) {
198
198
  // Media readiness updates the UI independently; start still waits for task
199
199
  // admission, and a later rejection closes media through the same error path.
200
200
  startPromise = next.start().then(publishReady).catch(async (cause) => {
201
- if (session === next) session = undefined;
202
- if (activeResources.get(agent) === resource) activeResources.delete(agent);
203
- cleanupWatcher();
204
- await next.close().catch(() => next.abort());
201
+ if (session === next) {
202
+ session = undefined;
203
+ if (activeResources.get(agent) === resource) activeResources.delete(agent);
204
+ cleanupWatcher();
205
+ }
206
+ // Keep replacement starts behind failed startup cleanup, just like stop().
207
+ // A stale attempt must never detach a replacement session's event watcher.
208
+ const closing = next.close().catch(() => next.abort());
209
+ if (generation === current) stopPromise = closing;
210
+ await closing;
211
+ if (stopPromise === closing) stopPromise = undefined;
205
212
  if (destroyed || generation !== current) return;
206
213
  if (attempt === 0 && cause?.code === "peer_connection_timeout") return start(parameters, 1);
207
214
  const error = cause instanceof Error ? cause : new Error(String(cause));