nanocodex 0.6.6 → 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 (114) hide show
  1. package/README.md +297 -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/host.d.mts +54 -1
  9. package/browser/host.mjs +65 -16
  10. package/browser/hostManagedWebSocket.mjs +11 -2
  11. package/browser/index.d.mts +3 -10
  12. package/browser/index.mjs +2 -5
  13. package/browser/socket-observations.mjs +203 -0
  14. package/cloud/Decorator.d.mts +2 -2
  15. package/cloud/Decorator.mjs +1 -0
  16. package/cloud/Dialog.d.mts +13 -0
  17. package/cloud/Dialog.mjs +52 -6
  18. package/cloud/Transport.d.mts +0 -11
  19. package/cloud/Transport.mjs +1 -386
  20. package/cloud/actions/account.d.mts +12 -0
  21. package/cloud/actions/account.mjs +11 -0
  22. package/cloud/actions/agent.mjs +3 -0
  23. package/cloud/actions/connection.d.mts +3 -1
  24. package/cloud/actions/connection.mjs +19 -4
  25. package/cloud/index.d.mts +1 -1
  26. package/cloud/index.mjs +1 -1
  27. package/cloud/internal.mjs +19 -4
  28. package/cloud/mercator.mjs +1 -1
  29. package/cloud/mock.d.mts +17 -0
  30. package/cloud/mock.mjs +392 -0
  31. package/cloud/types.d.mts +3 -1
  32. package/cloudflare/Agent.d.mts +2 -0
  33. package/cloudflare/Agent.mjs +88 -2
  34. package/cloudflare/agent-settings.d.mts +1 -1
  35. package/cloudflare/agent-settings.mjs +9 -0
  36. package/cloudflare/egress.mjs +9 -6
  37. package/cloudflare/gateway-responses.d.mts +1 -0
  38. package/cloudflare/gateway-responses.mjs +2 -2
  39. package/cloudflare/managed-auth.d.mts +4 -4
  40. package/cloudflare/managed-auth.mjs +14 -2
  41. package/cloudflare/managed-live.d.mts +6 -0
  42. package/cloudflare/managed-live.mjs +56 -1
  43. package/cloudflare/workers-ai-responses.d.mts +2 -1
  44. package/cloudflare/workers-ai-responses.mjs +80 -6
  45. package/host/Agent.d.mts +6 -2
  46. package/host/Claude.d.mts +3 -0
  47. package/host/Claude.mjs +2 -0
  48. package/host/index.d.mts +6 -10
  49. package/host/index.mjs +2 -5
  50. package/index.d.mts +11 -10
  51. package/index.mjs +0 -5
  52. package/internal.mjs +28 -3
  53. package/managed/Agent.d.mts +6 -2
  54. package/managed/Agent.mjs +39 -5
  55. package/managed/ManagedError.d.mts +3 -0
  56. package/node/Agent.d.mts +6 -2
  57. package/node/Agent.mjs +14 -1
  58. package/node/Claude.d.mts +4 -0
  59. package/node/Claude.mjs +24 -0
  60. package/node/host.mjs +18 -9
  61. package/node/index.d.mts +6 -10
  62. package/node/index.mjs +2 -5
  63. package/package.json +41 -9
  64. package/pkg-node/nanocodex.d.ts +109 -0
  65. package/pkg-node/nanocodex.js +818 -51
  66. package/pkg-node/nanocodex_bg.wasm.d.ts +47 -3
  67. package/pkg-web/.nanocodex-bindgen-stamp +3 -3
  68. package/pkg-web/nanocodex-build.json +1 -1
  69. package/pkg-web/nanocodex.d.ts +156 -3
  70. package/pkg-web/nanocodex.js +813 -51
  71. package/pkg-web/nanocodex_bg.js +813 -51
  72. package/pkg-web/nanocodex_bg.wasm +0 -0
  73. package/pkg-web/nanocodex_bg.wasm.d.ts +47 -3
  74. package/pkg-web/nanocodex_worker.js +1 -1
  75. package/runtime/claude-host.mjs +308 -0
  76. package/runtime/claude-subscription.mjs +79 -0
  77. package/runtime/claude.d.mts +112 -0
  78. package/runtime/claude.mjs +221 -0
  79. package/runtime/code-effect-identity.mjs +123 -0
  80. package/runtime/harnesses.mjs +51 -0
  81. package/runtime/mcp-payment.mjs +8 -0
  82. package/runtime/mcp-runtime.d.mts +10 -0
  83. package/runtime/mcp-runtime.mjs +242 -57
  84. package/runtime/mercator.mjs +2 -0
  85. package/runtime/quickjs-evaluator.mjs +23 -11
  86. package/runtime/response-controls.mjs +1 -6
  87. package/runtime/responses-http.mjs +4 -2
  88. package/runtime/retry-after.d.mts +10 -0
  89. package/runtime/retry-after.mjs +86 -0
  90. package/runtime/steer-receipt.mjs +8 -1
  91. package/runtime/subagent-routing.d.mts +4 -2
  92. package/runtime/subagent-routing.mjs +7 -2
  93. package/runtime/subagents.d.mts +8 -3
  94. package/services/account.d.mts +76 -0
  95. package/services/account.mjs +151 -0
  96. package/services/hosted.mjs +72 -0
  97. package/services/index.d.mts +95 -0
  98. package/services/index.mjs +192 -0
  99. package/services/scope.mjs +36 -0
  100. package/{runtime/tempo-provider.d.mts → tempo/index.d.mts} +20 -1
  101. package/{runtime/tempo-provider.mjs → tempo/index.mjs} +39 -3
  102. package/tools/browser/browserShell.mjs +18 -8
  103. package/tools/browser/python.worker.mjs +17 -1
  104. package/tools/browser/threadGit.mjs +12 -2
  105. package/tools/environment.d.mts +8 -0
  106. package/tools/environment.mjs +10 -0
  107. package/tools/index.d.mts +6 -3
  108. package/types.d.mts +104 -4
  109. package/worker/Claude.d.mts +4 -0
  110. package/worker/Claude.mjs +11 -0
  111. package/worker/ClaudeSubscription.d.mts +24 -0
  112. package/worker/ClaudeSubscription.mjs +9 -0
  113. package/worker/index.d.mts +9 -0
  114. 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:
@@ -317,10 +326,17 @@ membership, room, quota, or application routing policy. Event frames are
317
326
  `1013`, then continues by reconnecting with that pause cursor as
318
327
  `?cursor=<decimal>`.
319
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
+
320
336
  Cloudflare Agents default to direct tool mode because Workers prohibit dynamic
321
337
  `eval`/`new Function`. Caller-defined tools therefore work without a code
322
- evaluator. Select `toolMode: "code"` only when also supplying an evaluator that
323
- 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
324
340
  Subagents are installed by default, including on a durable root. All children are
325
341
  ephemeral: their identities, topology, conversations, results, and routing exist
326
342
  only for the lifetime of the root runtime. Root shutdown or restart discards the
@@ -602,7 +618,7 @@ shared registry and installs fresh tools for every root, spawn, and fork:
602
618
  ```rust,ignore
603
619
  let (registry, control, updates) = nanocodex_subagents::channel(max_concurrency);
604
620
  let tools = Tools::builder().without_defaults().build()?;
605
- let tools = nanocodex_tools::embedded::bind_host(tools, javascript_host);
621
+ let tools = nanocodex_oai_tools::embedded::bind_host(tools, javascript_host);
606
622
  let (agent, events) = Nanocodex::builder(openai)
607
623
  .tools_factory(move |handle| {
608
624
  nanocodex_subagents::install_tools(tools.clone(), handle, registry.clone())
@@ -669,8 +685,17 @@ API key. Pass an MPP session with a `ws(endpoint)` method; an `mppx` Tempo
669
685
  session manager has this shape. Nanocodex defaults the socket to
670
686
  `wss://openai.mpp.tempo.xyz/v1/responses` when `mpp` is present.
671
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
+
672
696
  ```js
673
- import { Agent, createTempoProviderFromAccounts, Transport } from "nanocodex/node";
697
+ import { Agent, Transport } from "nanocodex/node";
698
+ import { createTempoProviderFromAccounts } from "nanocodex/tempo";
674
699
  import { Expiry } from "accounts";
675
700
  import { Provider } from "accounts/cli";
676
701
  import { parseUnits } from "viem";
@@ -773,6 +798,22 @@ the relay preserves MPP challenges, credentials, and receipts but never signs.
773
798
  Passing a generic `MppSession`, an OpenAI key, or ChatGPT host auth does not
774
799
  initialize Mercator. Pass `mcp: false` to opt out explicitly.
775
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
+
776
817
  Remote Streamable HTTP MCP servers are configured directly on the agent. The
777
818
  JavaScript binding uses the official MCP SDK transport, keeps remote tools
778
819
  deferred, and mirrors native Nanocodex exposure: the initial Responses request
@@ -789,6 +830,9 @@ supports Tempo charge and session challenges, so paid services composed behind
789
830
  Mercator use the same signer and spending policy as the model:
790
831
 
791
832
  ```js
833
+ import { tempo } from "mppx/client";
834
+ import { createTempoProvider } from "nanocodex/tempo";
835
+
792
836
  const mcpMethod = tempo({
793
837
  account,
794
838
  channelStore,
@@ -809,6 +853,17 @@ const agent = await Agent.create({
809
853
 
810
854
  Explicit `mcp` entries are merged over the Tempo defaults, so an application
811
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
+ ```
812
867
 
813
868
  Each server also accepts `headers`, `fetch`, allow/deny tool lists, a timeout,
814
869
  or an already initialized MCP SDK-compatible `client`. Nanocodex closes clients
@@ -824,6 +879,80 @@ session and are invalidated when the host shuts down; a persisted `wait` never
824
879
  restarts missing work. Embedded cells retain ownership of all nested tool calls
825
880
  until they finish or are cancelled.
826
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
+
827
956
  Custom evaluators receive `audio`, `notify`, `yield_control`, `setTimeout`, and
828
957
  `clearTimeout` alongside the existing globals in `CodeEvaluatorEnvironment`.
829
958
  Forward those helpers into the guest environment to preserve the model-visible
@@ -838,7 +967,7 @@ keeps deferred MCP plus Code Mode functional in Cloudflare Workers:
838
967
 
839
968
  ```js
840
969
  import asyncVariant from "@jitl/quickjs-wasmfile-release-asyncify";
841
- import { Agent, createQuickJsEvaluator, createTempoProvider, Transport } from "nanocodex/host";
970
+ import { Agent, createQuickJsEvaluator, Transport } from "nanocodex/host";
842
971
  import { newQuickJSAsyncWASMModuleFromVariant } from "quickjs-emscripten-core";
843
972
 
844
973
  const quickJs = await newQuickJSAsyncWASMModuleFromVariant(asyncVariant);
@@ -1181,7 +1310,7 @@ package manager or build step:
1181
1310
 
1182
1311
  ```html
1183
1312
  <script type="module">
1184
- import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.6/host/index.mjs";
1313
+ import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.6.7/host/index.mjs";
1185
1314
  const agent = await Agent.create({
1186
1315
  transport: Transport.hostManaged({
1187
1316
  websocketUrl: "/api/responses",
@@ -1361,3 +1490,165 @@ against the Connect Worker with fixture provider responses, covering reads,
1361
1490
  writes, pagination, rate limits, and revoked grants. Worker tests cover routing
1362
1491
  and service denial for all 13 API connectors. SDKs that hardcode their API host need a custom transport instead of only
1363
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,
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  CodeEvaluator,
3
+ CodeEffectJournal,
3
4
  McpServers,
4
5
  MppSession,
5
6
  SubagentToolContext,
@@ -51,12 +52,60 @@ export type BrowserWebSocketConnection = {
51
52
  socket: WebSocket;
52
53
  status?: number | undefined;
53
54
  requestId?: string | undefined;
55
+ /** @internal Correlation with the separately deployed credential egress. */
56
+ egressRequestId?: string | undefined;
54
57
  serverModel?: string | undefined;
55
58
  reasoningIncluded?: boolean | undefined;
56
59
  turnState?: string | undefined;
57
60
  };
58
61
 
62
+ /** @internal Passive lifecycle metadata; never provider frames or request content. */
63
+ export type BrowserSocketObservation = {
64
+ event: "socket.connecting" | "socket.connect_waiting" | "socket.opened" | "socket.closed" | "socket.error"
65
+ | "request.send_started" | "request.send_waiting" | "request.sent" | "request.waiting" | "request.first_message"
66
+ | "request.first_output" | "request.first_reasoning_delta" | "request.first_answer_delta" | "request.first_tool_delta" | "request.finished" | "provider.timing";
67
+ socket_id: string;
68
+ request_id: string;
69
+ egress_request_id?: string;
70
+ provider_request_id?: string;
71
+ response_id?: string;
72
+ socket_request_index?: number;
73
+ model_call_index?: number;
74
+ phase?: "generation" | "compaction" | "warmup";
75
+ outcome?: "completed" | "failed" | "send_failed" | "superseded";
76
+ elapsed_ms?: number;
77
+ send_wait_ms?: number;
78
+ first_message_ms?: number;
79
+ first_output_ms?: number;
80
+ first_reasoning_delta_ms?: number;
81
+ first_answer_delta_ms?: number;
82
+ first_tool_delta_ms?: number;
83
+ /** Fixed protocol name, or unclassified for oversized/unknown envelopes. */
84
+ provider_event_type?: string;
85
+ output_kind?: "item" | "reasoning" | "answer" | "tool";
86
+ last_message_age_ms?: number;
87
+ received_message_count?: number;
88
+ queued_message_count?: number;
89
+ socket_delivered_message_count?: number;
90
+ socket_queue_residence_max_ms?: number;
91
+ buffered_send_bytes?: number;
92
+ close_code?: number;
93
+ close_clean?: boolean;
94
+ intentional?: boolean;
95
+ pre_inference_ms?: number;
96
+ engine_queue_max_ms?: number;
97
+ engine_service_ttft_total_ms?: number;
98
+ };
99
+
59
100
  export function createBrowserHost(options?: {
101
+ /** @internal Trusted runtime configuration carried by nanocodex.browser.internalRuntime. */
102
+ [key: symbol]: {
103
+ traceTool?: <T>(
104
+ name: string,
105
+ context: { sessionId: string; callId: string; parentCallId?: string; turnId?: string },
106
+ run: () => Promise<T>,
107
+ ) => Promise<T>;
108
+ } | undefined;
60
109
  WebSocketImpl?: typeof WebSocket;
61
110
  hostAuth?: boolean;
62
111
  hostManagedProtocol?: boolean;
@@ -74,7 +123,9 @@ export function createBrowserHost(options?: {
74
123
  /** Remote MCP servers exposed through native and Code Mode tool_search plus deferred tools. */
75
124
  mcp?: McpServers;
76
125
  codeEvaluator?: CodeEvaluator;
77
- toolMode?: "code" | "direct";
126
+ /** @internal Trusted durable effect receipts, not available inside guest code. */
127
+ codeEffectJournal?: CodeEffectJournal;
128
+ toolMode?: "code" | "code-only" | "direct";
78
129
  /** @internal Live host lifecycle for ephemeral Rust-owned subagents. */
79
130
  subagentRouting?: Pick<import('../runtime/subagent-routing.mjs').SubagentRouting, 'resolve' | 'bind'>;
80
131
  subagentSessions?: {
@@ -97,6 +148,8 @@ export function createBrowserHost(options?: {
97
148
  engine_service_ttft_total_ms?: number;
98
149
  }>;
99
150
  }) => void;
151
+ /** @internal Live observations while an owned request waits; does not cancel it. */
152
+ onSocketEvent?: (event: BrowserSocketObservation) => void;
100
153
  maxQueuedMessages?: number;
101
154
  maxQueuedBytes?: number;
102
155
  maxBufferedSendBytes?: number;