@kodax-ai/kodax 0.7.67 → 0.7.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +209 -3
- package/README.md +61 -9
- package/README_CN.md +58 -9
- package/config-templates/config.example.jsonc +26 -0
- package/config-templates/integrations/a2a.example.jsonc +54 -0
- package/config-templates/integrations/extensions.example.jsonc +7 -0
- package/config-templates/integrations/mcp.example.jsonc +16 -0
- package/dist/chunks/agent-DQRXT6M7.js +2 -0
- package/dist/chunks/argument-completer-RSK6CCGP.js +2 -0
- package/dist/chunks/chunk-4WIODYOH.js +329 -0
- package/dist/chunks/chunk-AWMTNUDS.js +37 -0
- package/dist/chunks/chunk-F7C7J6IM.js +7 -0
- package/dist/chunks/chunk-I4TPQEJN.js +5 -0
- package/dist/chunks/chunk-M7TCFYTO.js +107 -0
- package/dist/chunks/chunk-NGHQIGVW.js +46 -0
- package/dist/chunks/chunk-NGJURAY5.js +306 -0
- package/dist/chunks/{chunk-KBBGDRER.js → chunk-PA76WUBL.js} +2 -2
- package/dist/chunks/chunk-PQ3XUSY2.js +357 -0
- package/dist/chunks/chunk-UJEMSPM5.js +78 -0
- package/dist/chunks/{chunk-DEICMTHO.js → chunk-URC6ZI6P.js} +1 -1
- package/dist/chunks/{chunk-EN3O2C43.js → chunk-VAZ25MDX.js} +122 -122
- package/dist/chunks/chunk-VYKFM3TB.js +751 -0
- package/dist/chunks/chunk-WW2O2DEP.js +2 -0
- package/dist/chunks/chunk-Y4WOTWUC.js +2 -0
- package/dist/chunks/chunk-Z4PPLJF2.js +60 -0
- package/dist/chunks/chunk-ZG4DMYBS.js +422 -0
- package/dist/chunks/compaction-config-ABLL6UH3.js +2 -0
- package/dist/chunks/{construction-bootstrap-GLEHQ2KB.js → construction-bootstrap-GH6RW6ST.js} +1 -1
- package/dist/chunks/dist-DRBKYVHF.js +2 -0
- package/dist/chunks/dist-RWL2RBO4.js +2 -0
- package/dist/chunks/host-7SAB4WEC.js +2 -0
- package/dist/chunks/run-manager-XMDKTONJ.js +2 -0
- package/dist/chunks/utils-QYE5Y73D.js +2 -0
- package/dist/index.d.ts +19 -19
- package/dist/index.js +6 -6
- package/dist/kodax_cli.js +1497 -1209
- package/dist/runtime-worker.js +1371 -1107
- package/dist/sdk-a2a.d.ts +397 -0
- package/dist/sdk-a2a.js +9 -0
- package/dist/sdk-agent.d.ts +172 -33
- package/dist/sdk-agent.js +1 -1
- package/dist/sdk-coding.d.ts +126 -51
- package/dist/sdk-coding.js +1 -1
- package/dist/sdk-experimental-memory.d.ts +618 -0
- package/dist/sdk-experimental-memory.js +2 -0
- package/dist/sdk-llm.d.ts +16 -9
- package/dist/sdk-llm.js +1 -1
- package/dist/sdk-mcp.d.ts +1 -1
- package/dist/sdk-mcp.js +1 -1
- package/dist/sdk-media.d.ts +1 -1
- package/dist/sdk-media.js +1 -1
- package/dist/sdk-repl.d.ts +35 -101
- package/dist/sdk-repl.js +2 -2
- package/dist/sdk-runtime.d.ts +744 -21
- package/dist/sdk-runtime.js +1 -1
- package/dist/sdk-session.d.ts +7 -7
- package/dist/sdk-session.js +1 -1
- package/dist/sdk-skills.js +1 -1
- package/dist/semantic-worker.js +100 -12
- package/dist/types-chunks/{base.d-DalIRhbb.d.ts → base.d-CYjtB68X.d.ts} +1 -1
- package/dist/types-chunks/{bash-prefix-extractor.d-D57Gt6n8.d.ts → bash-prefix-extractor.d-BkIA8Wto.d.ts} +89 -18
- package/dist/types-chunks/{capability-learning.d-BQB0M70w.d.ts → capability-learning.d-WtsyRv3O.d.ts} +1 -1
- package/dist/types-chunks/{capsule.d-BZmqu_HL.d.ts → capsule.d-xJvfh4YR.d.ts} +5 -5
- package/dist/types-chunks/{commands.d-Cg26ud-Y.d.ts → commands.d-DkPfcQWG.d.ts} +4 -4
- package/dist/types-chunks/{cost-tracker.d-CBLk1fiD.d.ts → cost-tracker.d-BeaenODW.d.ts} +4 -1
- package/dist/types-chunks/{guardrail.d-Ds7JHxR1.d.ts → guardrail.d-6ZDbNbHO.d.ts} +1 -1
- package/dist/types-chunks/{guardrail.d-vSaBe64I.d.ts → guardrail.d-R7AiGfrI.d.ts} +4 -4
- package/dist/types-chunks/integration-config.d-ojG4swOP.d.ts +95 -0
- package/dist/types-chunks/{manager.d-Zum9cGHU.d.ts → manager.d-CoEuPRAo.d.ts} +4 -1
- package/dist/types-chunks/{public-api.d-CPBMPDiU.d.ts → public-api.d-7G4--RZE.d.ts} +6 -7
- package/dist/types-chunks/{resolver.d-CGkfRULj.d.ts → resolver.d-CIVoGc97.d.ts} +2 -2
- package/dist/types-chunks/{run-manager.d-lnwnfyNC.d.ts → run-manager.d-BaXtkryp.d.ts} +1 -1
- package/dist/types-chunks/{sdk-session-ByoMLoUS.d.ts → sdk-session-BQccrODn.d.ts} +6 -4
- package/dist/types-chunks/{types.d-DG9B4SMm.d.ts → types.d-BtC4yLYO.d.ts} +170 -13
- package/dist/types-chunks/{types.d-DYv3a1ka.d.ts → types.d-C9YHEAmA.d.ts} +38 -7
- package/dist/types-chunks/{types.d-DU9lLkmE.d.ts → types.d-CJR7t6iW.d.ts} +2 -2
- package/dist/types-chunks/{types.d-Bp4Lm1jv.d.ts → types.d-TTvpAGWf.d.ts} +11 -1
- package/dist/types-chunks/{utils.d-DMI-9GTR.d.ts → utils.d-BbB5jzi1.d.ts} +15 -7
- package/docs/SDK_EMBEDDER_GUIDE.md +732 -14
- package/package.json +14 -2
- package/dist/chunks/agent-E4V5SLX6.js +0 -2
- package/dist/chunks/argument-completer-N6RT5KYT.js +0 -2
- package/dist/chunks/chunk-AKMXJSGM.js +0 -56
- package/dist/chunks/chunk-CW32NQFO.js +0 -2
- package/dist/chunks/chunk-D6QVXQNI.js +0 -1
- package/dist/chunks/chunk-NVDK375I.js +0 -729
- package/dist/chunks/chunk-O7N22GJ3.js +0 -31
- package/dist/chunks/chunk-PZTG2C33.js +0 -7
- package/dist/chunks/chunk-QI6SQ2UQ.js +0 -306
- package/dist/chunks/chunk-QJJKMNZF.js +0 -2
- package/dist/chunks/chunk-RS2GAABJ.js +0 -328
- package/dist/chunks/chunk-W2XMNYLK.js +0 -15
- package/dist/chunks/chunk-WCSSXBHR.js +0 -419
- package/dist/chunks/chunk-YH5BTUQO.js +0 -343
- package/dist/chunks/chunk-YRBNXFHY.js +0 -2
- package/dist/chunks/compaction-config-56NO42ZI.js +0 -2
- package/dist/chunks/dist-4PXB7U22.js +0 -2
- package/dist/chunks/dist-4UIRSSFS.js +0 -2
- package/dist/chunks/host-DMREFNSO.js +0 -2
- package/dist/chunks/paste-C33GZQV5.js +0 -2
- package/dist/chunks/run-manager-P5XMSGZ7.js +0 -2
- package/dist/chunks/utils-JENPFKAE.js +0 -2
|
@@ -28,6 +28,9 @@ are NOT obvious from inspecting the type definitions alone:
|
|
|
28
28
|
18. [External-agent executor plane](#18-external-agent-executor-plane-feature_258-v0767)
|
|
29
29
|
19. [Session surface filtering and cursor pagination](#19-session-surface-filtering-and-cursor-pagination-feature_261-v0767)
|
|
30
30
|
20. [Cost-disciplined workflow routing and telemetry](#20-cost-disciplined-workflow-routing-and-telemetry-feature_259-v0767)
|
|
31
|
+
21. [Experimental governed memory — `/experimental-memory`](#21-experimental-governed-memory--experimental-memory-feature_260-v0768)
|
|
32
|
+
22. [Bidirectional A2A 1.0 — `/a2a`](#22-bidirectional-a2a-10--a2a-feature_267-v0769)
|
|
33
|
+
23. [Shared Coder daemon for Space and IDE hosts](#23-shared-coder-daemon-for-space-and-ide-hosts-feature_269-v0769)
|
|
31
34
|
|
|
32
35
|
§1–§3 (and the Phase-7/8 MCP-popout surface in §1) land in v0.7.42
|
|
33
36
|
under FEATURE_186 (see [ADR-032](ADR.md#adr-032-sdk-embedder-surface-closure-feature_186-v0742)).
|
|
@@ -964,7 +967,7 @@ running your project's test suite against `main` — you can `npm link`
|
|
|
964
967
|
the in-tree KodaX checkout instead of waiting for a published version.
|
|
965
968
|
|
|
966
969
|
As of v0.7.43 the root `package.json` is in **already-published shape**:
|
|
967
|
-
`"name": "@kodax-ai/kodax"` is baked in along with all
|
|
970
|
+
`"name": "@kodax-ai/kodax"` is baked in along with all 10 SDK subpath
|
|
968
971
|
exports. `npm link` "just works" — no need to run `scripts/release.mjs`
|
|
969
972
|
first.
|
|
970
973
|
|
|
@@ -2000,9 +2003,14 @@ By default your `topics` **extend** the KodaX base manual. v0.7.58 adds
|
|
|
2000
2003
|
|
|
2001
2004
|
`KODAX_UNDERLYING_CAPABILITY_TOPICS` (exported) is the recommended mechanism-topic
|
|
2002
2005
|
subset a product built on KodaX should keep even in a full replace — so your users
|
|
2003
|
-
still get correct answers about the underlying engine (providers / config /
|
|
2004
|
-
permissions / tools / skills / extensions / mcp / repo-intelligence / sessions /
|
|
2005
|
-
sdk / custom-providers) without the KodaX brand:
|
|
2006
|
+
still get correct answers about the underlying engine (providers / config /
|
|
2007
|
+
permissions / tools / skills / extensions / mcp / repo-intelligence / sessions /
|
|
2008
|
+
sdk / custom-providers) without the KodaX brand:
|
|
2009
|
+
|
|
2010
|
+
The default full manual also includes the `memory` topic. It is intentionally
|
|
2011
|
+
not part of `KODAX_UNDERLYING_CAPABILITY_TOPICS` because `/experimental-memory`
|
|
2012
|
+
is opt-in; hosts that expose it should add `memory` explicitly or provide a
|
|
2013
|
+
product-specific override.
|
|
2006
2014
|
|
|
2007
2015
|
```ts
|
|
2008
2016
|
import {
|
|
@@ -2733,13 +2741,16 @@ fields requires a protocol version bump.
|
|
|
2733
2741
|
|
|
2734
2742
|
### Current verification status
|
|
2735
2743
|
|
|
2736
|
-
The v0.7.
|
|
2744
|
+
The v0.7.69 release validation covers the runtime migration, the Worker
|
|
2737
2745
|
isolation follow-ups delivered ahead of their original v0.7.71/v0.7.72
|
|
2738
|
-
planning slots,
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2746
|
+
planning slots, the v0.7.67 external-agent/session additions, the v0.7.68
|
|
2747
|
+
experimental Memory Agent surface, and the v0.7.69 A2A/integration/shared-
|
|
2748
|
+
daemon delivery:
|
|
2749
|
+
|
|
2750
|
+
- Node 20 and Node 22 post-review regression for process cleanup, lock
|
|
2751
|
+
ownership, frozen eval inputs, A2A transport, and the built-in manual;
|
|
2752
|
+
- root `tsc --noEmit`, package builds, bundle builds, and all 12 public DTS
|
|
2753
|
+
entries on Node 20 and Node 22;
|
|
2743
2754
|
- runtime/daemon/SDK/ACP/REPL integration tests, including process-distinct SDK
|
|
2744
2755
|
auto-start and multi-client sessions/permissions;
|
|
2745
2756
|
- Worker Runtime identity, service parity, hard close, capability requirements,
|
|
@@ -2751,6 +2762,22 @@ planning slots, and the v0.7.67 external-agent/session additions:
|
|
|
2751
2762
|
recovery, policy/credential/artifact gates, and disabled-plane failure;
|
|
2752
2763
|
- exact session `surface` filtering plus opaque cursor continuation through the
|
|
2753
2764
|
narrow `/session` API, embedded Runtime, and daemon transport;
|
|
2765
|
+
- scoped zero-wait memory recall, read-only deliberate query, trace-only
|
|
2766
|
+
receipts, bounded episode review, governed consult-before-write promotion,
|
|
2767
|
+
and the self-contained `/experimental-memory` bundle/DTS surface;
|
|
2768
|
+
- bidirectional A2A 1.0 discovery/call/serve, durable task replay, no-code
|
|
2769
|
+
configuration, local-Agent binding, exact Skill-script admission, SSRF/
|
|
2770
|
+
credential boundaries, and the self-contained `/a2a` bundle/DTS surface;
|
|
2771
|
+
- split MCP/A2A/Extension configuration, lossless migration, canonical-template
|
|
2772
|
+
drift checks, last-known-good hot reload, draining, and restart-required
|
|
2773
|
+
classification;
|
|
2774
|
+
- atomic shared-daemon observation/resync, durable operations and same-session
|
|
2775
|
+
ordering, settings/grant CAS, AskUser/permission transport, credential/Host
|
|
2776
|
+
Tool reverse bridges, owner fencing, restart outcomes, and process-distinct
|
|
2777
|
+
client/daemon smokes with credential-canary scans;
|
|
2778
|
+
- a fresh `0.7.69` tarball consumer importing all 11 public subpaths, creating a
|
|
2779
|
+
Worker-hosted session, running the packaged CLI, and checking packaged DTS and
|
|
2780
|
+
Worker sidecars; plus a Windows x64 binary/version/sidecar smoke;
|
|
2754
2781
|
- external fresh npm consumer installation of the `0.7.67` tarball, proving
|
|
2755
2782
|
Worker isolation and a distinct daemon PID through the published subpath;
|
|
2756
2783
|
- Ubuntu Node 22 Unix-domain-socket daemon gate, including two clients sharing
|
|
@@ -2763,8 +2790,13 @@ The portable manual gates remain in
|
|
|
2763
2790
|
two filenames retain their original planning slots while their content records
|
|
2764
2791
|
the v0.7.66 delivery. v0.7.67 adds
|
|
2765
2792
|
`FEATURE_258_v0.7.67_TEST_GUIDE.md`, `FEATURE_259_v0.7.67_TEST_GUIDE.md`, and
|
|
2766
|
-
`FEATURE_261_v0.7.67_TEST_GUIDE.md
|
|
2767
|
-
|
|
2793
|
+
`FEATURE_261_v0.7.67_TEST_GUIDE.md`; v0.7.69 adds
|
|
2794
|
+
`FEATURE_267_v0.7.69_TEST_GUIDE.md`, `FEATURE_268_v0.7.69_TEST_GUIDE.md`, and
|
|
2795
|
+
`FEATURE_269_v0.7.69_TEST_GUIDE.md`. The earlier release-preparation Actions
|
|
2796
|
+
run is not reused after the severe-fix review. Fresh GitHub Actions run
|
|
2797
|
+
`29385073422` passes the Node 20/22 build, bundle, DTS, and full-test matrix plus
|
|
2798
|
+
the Node 22 Unix-domain-socket daemon gate. npm publication remains a
|
|
2799
|
+
maintainer-owned manual step.
|
|
2768
2800
|
|
|
2769
2801
|
---
|
|
2770
2802
|
|
|
@@ -3070,8 +3102,694 @@ The report is an audit/optimization artifact; correctness still comes from the
|
|
|
3070
3102
|
workflow's structured findings, verification results, and quality gates.
|
|
3071
3103
|
|
|
3072
3104
|
---
|
|
3073
|
-
|
|
3074
|
-
##
|
|
3105
|
+
|
|
3106
|
+
## 21. Experimental governed memory — `/experimental-memory` (FEATURE_260, v0.7.68)
|
|
3107
|
+
|
|
3108
|
+
KodaX has one durable memory plane: the F228 Memory Control Plane. FEATURE_260
|
|
3109
|
+
adds a thin, opt-in agent/session API over that plane; it does not add a second
|
|
3110
|
+
database, filesystem memory actions, a resident memory specialist, or online
|
|
3111
|
+
self-modification.
|
|
3112
|
+
|
|
3113
|
+
Top-level `runKodaX()` coding runs wire this lifecycle automatically. They build
|
|
3114
|
+
an exact-scoped memory pack at session start, keep passive recall off the
|
|
3115
|
+
blocking hot path, expose `memory_recall` only when the memory session starts,
|
|
3116
|
+
record bounded observations/outcomes, and close the session at the run boundary.
|
|
3117
|
+
Use the direct SDK below when a custom host needs to own those boundaries.
|
|
3118
|
+
|
|
3119
|
+
### Minimal direct session
|
|
3120
|
+
|
|
3121
|
+
```ts
|
|
3122
|
+
import { createMemoryControlPlane } from '@kodax-ai/kodax/agent';
|
|
3123
|
+
import { createMemoryAgent } from '@kodax-ai/kodax/experimental-memory';
|
|
3124
|
+
|
|
3125
|
+
const identity = {
|
|
3126
|
+
tenantId: 'tenant:acme',
|
|
3127
|
+
workspaceId: 'workspace:desktop',
|
|
3128
|
+
userId: 'user:42',
|
|
3129
|
+
agentId: 'agent:reviewer',
|
|
3130
|
+
projectId: 'project:kodax',
|
|
3131
|
+
sessionId: 'session:20260712',
|
|
3132
|
+
};
|
|
3133
|
+
|
|
3134
|
+
const controlPlane = createMemoryControlPlane({
|
|
3135
|
+
cwd: process.cwd(),
|
|
3136
|
+
identity,
|
|
3137
|
+
projectDocs: [],
|
|
3138
|
+
discoverSkills: false,
|
|
3139
|
+
});
|
|
3140
|
+
const memory = createMemoryAgent({ controlPlane });
|
|
3141
|
+
const session = await memory.startSession({
|
|
3142
|
+
identity,
|
|
3143
|
+
objective: 'Review the runtime shutdown change',
|
|
3144
|
+
});
|
|
3145
|
+
|
|
3146
|
+
const immediate = session.recall({
|
|
3147
|
+
decisionRevision: 'decision:1',
|
|
3148
|
+
objective: 'Review the runtime shutdown change',
|
|
3149
|
+
decisionContext: 'Choosing the first verification step',
|
|
3150
|
+
decisionIntent: 'runtime shutdown regression',
|
|
3151
|
+
throughSequence: 0,
|
|
3152
|
+
});
|
|
3153
|
+
const deliberate = await session.query({
|
|
3154
|
+
decisionRevision: 'decision:2',
|
|
3155
|
+
need: 'What uncommon daemon cleanup failure happened before?',
|
|
3156
|
+
throughSequence: 0,
|
|
3157
|
+
});
|
|
3158
|
+
|
|
3159
|
+
await session.complete({
|
|
3160
|
+
status: 'succeeded',
|
|
3161
|
+
summary: 'Shutdown state replacement verified',
|
|
3162
|
+
evidence: [],
|
|
3163
|
+
});
|
|
3164
|
+
await session.close();
|
|
3165
|
+
```
|
|
3166
|
+
|
|
3167
|
+
`recall()` is synchronous: exact observations/hints can be returned immediately,
|
|
3168
|
+
while an optional semantic prefetch finishes for a later matching decision.
|
|
3169
|
+
`query()` is deliberate and read-only; one distinct query is admitted per
|
|
3170
|
+
decision epoch, and the result is bounded to at most three prompt-safe hints and
|
|
3171
|
+
512 estimated tokens. `undefined` means there is no governed reminder to inject.
|
|
3172
|
+
|
|
3173
|
+
### Evidence, tracing, and persistence boundaries
|
|
3174
|
+
|
|
3175
|
+
- Recalled content is low-authority evidence. Current repository/config/runtime
|
|
3176
|
+
facts must still come from current tools or host state.
|
|
3177
|
+
- `observe()` accepts monotonic, evidence-linked observations and rejects secret
|
|
3178
|
+
material. `rewind()` removes observations after a sequence boundary.
|
|
3179
|
+
- `complete()` can emit an Outcome Digest through `persistOutcomeDigest` and run
|
|
3180
|
+
bounded episode review through `reviewEpisode`; cancellation creates neither.
|
|
3181
|
+
- `onTrace` receives policy-versioned `MemoryDecisionReceipt` metadata that links
|
|
3182
|
+
candidates, selection, injection, and later outcome influence. Receipts are
|
|
3183
|
+
trace-only and contain no hidden reasoning.
|
|
3184
|
+
- Durable memory mutation remains owned by F228's
|
|
3185
|
+
proposal/preview/fingerprint/apply flow. `/memory` is the CLI governance
|
|
3186
|
+
surface; direct file/shell writes to managed memory roots are denied.
|
|
3187
|
+
- Identity/applicability matching is exact and fail-closed. Hosts should supply
|
|
3188
|
+
stable tenant/workspace/user/agent/project/session identifiers and must not put
|
|
3189
|
+
credentials into those identifiers.
|
|
3190
|
+
|
|
3191
|
+
The subpath is experimental and ESM-only. Treat its exported types as opt-in
|
|
3192
|
+
v0.7.x contracts; keep persistence and product policy behind your own adapter.
|
|
3193
|
+
|
|
3194
|
+
---
|
|
3195
|
+
|
|
3196
|
+
## 22. Bidirectional A2A 1.0 — `/a2a` (FEATURE_267, v0.7.69)
|
|
3197
|
+
|
|
3198
|
+
`@kodax-ai/kodax/a2a` is the protocol edge for the A2A 1.0 JSON-RPC/SSE
|
|
3199
|
+
profile. It composes the protocol-neutral F258 executor plane with the Runtime
|
|
3200
|
+
facade; `/agent` and `/coding` remain free of A2A wire types and dependencies.
|
|
3201
|
+
|
|
3202
|
+
### Call an A2A Agent through F258
|
|
3203
|
+
|
|
3204
|
+
Discovery is an explicit host action. The URL must match `allowedOrigins`, and
|
|
3205
|
+
the default safe transport pins the validated DNS address for each connection
|
|
3206
|
+
and independently revalidates redirects, rejects public plain HTTP, bounds
|
|
3207
|
+
time/body/redirects, and strips authorization on a cross-origin redirect. A
|
|
3208
|
+
custom `fetch` option is a trusted transport override: the embedder then owns
|
|
3209
|
+
equivalent DNS-to-connection binding in that transport or proxy.
|
|
3210
|
+
|
|
3211
|
+
```ts
|
|
3212
|
+
import {
|
|
3213
|
+
createA2AAgentExecutorFactory,
|
|
3214
|
+
discoverA2ARegistration,
|
|
3215
|
+
} from '@kodax-ai/kodax/a2a';
|
|
3216
|
+
import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
|
|
3217
|
+
|
|
3218
|
+
const client = {
|
|
3219
|
+
networkPolicy: {
|
|
3220
|
+
allowedOrigins: ['https://reviewer.example'],
|
|
3221
|
+
allowPrivateAddresses: false,
|
|
3222
|
+
requestTimeoutMs: 10_000,
|
|
3223
|
+
maxResponseBytes: 1_048_576,
|
|
3224
|
+
maxRedirects: 2,
|
|
3225
|
+
},
|
|
3226
|
+
pollIntervalMs: 500,
|
|
3227
|
+
} as const;
|
|
3228
|
+
|
|
3229
|
+
const discovered = await discoverA2ARegistration({
|
|
3230
|
+
agentId: 'external:a2a-reviewer',
|
|
3231
|
+
agentCardUrl: 'https://reviewer.example/.well-known/agent-card.json',
|
|
3232
|
+
credentialRef: 'a2a/reviewer',
|
|
3233
|
+
effects: { remote: 'read' },
|
|
3234
|
+
}, client);
|
|
3235
|
+
|
|
3236
|
+
const runtime = await createKodaXRuntime({
|
|
3237
|
+
mode: 'embedded',
|
|
3238
|
+
isolation: 'inline',
|
|
3239
|
+
externalAgents: {
|
|
3240
|
+
factories: [createA2AAgentExecutorFactory(client)],
|
|
3241
|
+
credentialBroker: {
|
|
3242
|
+
async withCredential(ref, use) {
|
|
3243
|
+
if (ref !== 'a2a/reviewer') throw new Error('Unknown credential reference.');
|
|
3244
|
+
return use(process.env.A2A_REVIEWER_TOKEN ?? '');
|
|
3245
|
+
},
|
|
3246
|
+
},
|
|
3247
|
+
policy: ({ registration }) => ({ allowed: registration.effects.remote === 'read' }),
|
|
3248
|
+
defaultContext: { actorId: 'a2a-host' },
|
|
3249
|
+
},
|
|
3250
|
+
});
|
|
3251
|
+
|
|
3252
|
+
await runtime.admin.agentRegistrations.upsert(discovered.registration);
|
|
3253
|
+
const started = await runtime.agentTasks.start({
|
|
3254
|
+
agentId: discovered.registration.agentId,
|
|
3255
|
+
objective: 'Review this change and return cited findings.',
|
|
3256
|
+
context: { actorId: 'a2a-host' },
|
|
3257
|
+
readOnly: true,
|
|
3258
|
+
expectedConfigurationRevision: discovered.registration.configurationRevision,
|
|
3259
|
+
});
|
|
3260
|
+
const terminal = await runtime.agentTasks.wait(started.taskId, 60_000);
|
|
3261
|
+
```
|
|
3262
|
+
|
|
3263
|
+
The executor supports durable task start/get, input continuation, cancel,
|
|
3264
|
+
reconcile, SSE events, and polling fallback. An ambiguous start is not retried
|
|
3265
|
+
automatically. A `credentialRef` is resolved just in time by the F258 broker;
|
|
3266
|
+
the registration, task store, and diagnostics never contain the credential.
|
|
3267
|
+
|
|
3268
|
+
### Built-in configured path (no host code)
|
|
3269
|
+
|
|
3270
|
+
The CLI product path stores one user document at
|
|
3271
|
+
`~/.kodax/integrations/a2a.json` and uses the same F258 plane:
|
|
3272
|
+
|
|
3273
|
+
```bash
|
|
3274
|
+
kodax a2a add reviewer https://reviewer.example/.well-known/agent-card.json \
|
|
3275
|
+
--credential-env A2A_REVIEWER_TOKEN --effect read
|
|
3276
|
+
kodax a2a test reviewer
|
|
3277
|
+
kodax a2a call reviewer "Review this document"
|
|
3278
|
+
```
|
|
3279
|
+
|
|
3280
|
+
Embedded CLI Runtimes and the user-owned daemon automatically reconcile these
|
|
3281
|
+
entries as `external:<name>`. Discovery/update failure retains that entry's
|
|
3282
|
+
last-known-good registration; another entry can still update. The environment
|
|
3283
|
+
broker resolves `credentialEnv` only at call time. Automatic Runtime
|
|
3284
|
+
registration accepts public HTTPS and exact loopback targets; explicit private
|
|
3285
|
+
network access remains an operator action on the direct CLI/SDK path.
|
|
3286
|
+
|
|
3287
|
+
Inbound publication is also no-code:
|
|
3288
|
+
|
|
3289
|
+
```bash
|
|
3290
|
+
export KODAX_A2A_TOKEN='replace-with-a-long-random-token'
|
|
3291
|
+
kodax a2a expose # Runtime default Agent
|
|
3292
|
+
kodax a2a expose document-agent # ~/.kodax/agents/document-agent.md
|
|
3293
|
+
kodax a2a serve --port 8765
|
|
3294
|
+
```
|
|
3295
|
+
|
|
3296
|
+
`expose` validates a named user Markdown Agent before writing its reference.
|
|
3297
|
+
`serve` loads configured MCP and Extensions before it resolves the execution
|
|
3298
|
+
binding or opens a socket. Native workspace read tools are admitted by
|
|
3299
|
+
workspace access; writes, narrow Extension Tools, MCP capabilities, subagents,
|
|
3300
|
+
and isolated Skill scripts require their corresponding exact `toolPolicy`
|
|
3301
|
+
authority. Internal Skills come from `~/.kodax/skills`, `~/.agents/skills`,
|
|
3302
|
+
plugins, and built-ins; public Agent Card skills are a separate explicit
|
|
3303
|
+
projection and never reveal the private Skill inventory.
|
|
3304
|
+
|
|
3305
|
+
The running server pins Agent, Skill, workspace, tool registration, process and
|
|
3306
|
+
store revisions. Card/auth/limits can hot reload; execution-authority changes
|
|
3307
|
+
require an explicit restart. Managed contexts live below
|
|
3308
|
+
`~/kodax_a2a_server_workspace/<profile>/contexts/`. Exact Skill scripts require
|
|
3309
|
+
`process: isolated`, an admitted `scripts/...` path, and a passing
|
|
3310
|
+
`kodax sandbox doctor`; KodaX never falls back to an unsandboxed shell.
|
|
3311
|
+
|
|
3312
|
+
### Publish one KodaX Agent
|
|
3313
|
+
|
|
3314
|
+
Publication is host-owned and opt-in. The public card describes only the
|
|
3315
|
+
configured Agent, media types, and skills. Authentication runs before task
|
|
3316
|
+
lookup; authorization runs per operation; task visibility is principal-scoped.
|
|
3317
|
+
|
|
3318
|
+
```ts
|
|
3319
|
+
import { createKodaXA2AServer } from '@kodax-ai/kodax/a2a';
|
|
3320
|
+
import { createKodaXRuntime } from '@kodax-ai/kodax/runtime';
|
|
3321
|
+
|
|
3322
|
+
const runtime = await createKodaXRuntime({ mode: 'embedded', isolation: 'inline' });
|
|
3323
|
+
const server = createKodaXA2AServer({
|
|
3324
|
+
runtime,
|
|
3325
|
+
dataDir: '/var/lib/kodax/a2a',
|
|
3326
|
+
agent: {
|
|
3327
|
+
name: 'KodaX Reviewer',
|
|
3328
|
+
description: 'Reviews bounded code changes.',
|
|
3329
|
+
version: '1.0.0',
|
|
3330
|
+
publicBaseUrl: 'https://kodax.example',
|
|
3331
|
+
skills: [{ id: 'review', name: 'Review', description: 'Review code.', tags: ['code'] }],
|
|
3332
|
+
inputModes: ['text/plain'],
|
|
3333
|
+
outputModes: ['text/plain'],
|
|
3334
|
+
},
|
|
3335
|
+
authentication: {
|
|
3336
|
+
securitySchemes: { bearer: { httpAuthSecurityScheme: { scheme: 'Bearer' } } },
|
|
3337
|
+
securityRequirements: [{ schemes: { bearer: { list: [] } } }],
|
|
3338
|
+
async authenticate(request) {
|
|
3339
|
+
return request.headers.get('authorization') === `Bearer ${process.env.KODAX_A2A_TOKEN}`
|
|
3340
|
+
? { subject: 'trusted-orchestrator', scopes: ['a2a'] }
|
|
3341
|
+
: null;
|
|
3342
|
+
},
|
|
3343
|
+
},
|
|
3344
|
+
async authorize({ principal }) { return principal.scopes.includes('a2a'); },
|
|
3345
|
+
limits: {
|
|
3346
|
+
maxRequestBytes: 1_048_576,
|
|
3347
|
+
maxPartBytes: 524_288,
|
|
3348
|
+
maxConcurrentTasks: 8,
|
|
3349
|
+
maxTaskWaitMs: 30_000,
|
|
3350
|
+
maxActiveTasksPerPrincipal: 8,
|
|
3351
|
+
maxRetainedTasksPerPrincipal: 64,
|
|
3352
|
+
maxEventsPerTask: 1_000,
|
|
3353
|
+
maxEventBytesPerTask: 16_777_216,
|
|
3354
|
+
maxWorkspaceBytesPerContext: 1_073_741_824,
|
|
3355
|
+
},
|
|
3356
|
+
});
|
|
3357
|
+
|
|
3358
|
+
// Development only: the built-in listener refuses non-loopback hosts.
|
|
3359
|
+
const localBaseUrl = await server.listen({ hostname: '127.0.0.1', port: 0 });
|
|
3360
|
+
```
|
|
3361
|
+
|
|
3362
|
+
Production hosts route `GET /.well-known/agent-card.json` and JSON-RPC `POST /`
|
|
3363
|
+
to `server.handle(request)` behind their own TLS terminator. `POST /a2a` remains
|
|
3364
|
+
an accepted compatibility alias. `listen()` waits for durable recovery before
|
|
3365
|
+
it resolves. A host that wires `handle()` directly may explicitly await
|
|
3366
|
+
`server.whenReady()` before it starts accepting traffic; `handle()` also waits
|
|
3367
|
+
for the same recovery promise. The durable edge store supports get/list,
|
|
3368
|
+
continuation, cancellation, ordered SSE subscription, and surviving Runtime-run
|
|
3369
|
+
reattachment after an edge restart. Push notifications, A2A 0.3, gRPC, and
|
|
3370
|
+
HTTP+JSON are not advertised; unsupported push methods return the standard
|
|
3371
|
+
`PushNotificationNotSupportedError`.
|
|
3372
|
+
|
|
3373
|
+
Non-streaming `SendMessage` waits at most `maxTaskWaitMs` (30 seconds by
|
|
3374
|
+
default). When that bound is reached the response contains the current working
|
|
3375
|
+
task; it does not cancel the Runtime run, and clients can continue with
|
|
3376
|
+
`GetTask` or `SubscribeToTask`.
|
|
3377
|
+
|
|
3378
|
+
Remote messages are ordinary user inputs. They cannot select provider, model,
|
|
3379
|
+
profile, tools, working directory, permission mode, or Runtime configuration.
|
|
3380
|
+
URL parts are rejected; inline raw/data parts are bounded and materialized under
|
|
3381
|
+
the server-owned data directory. Responses expose final approved output only,
|
|
3382
|
+
not system prompts, reasoning deltas, tool payloads, credentials, or local paths.
|
|
3383
|
+
|
|
3384
|
+
The normative baseline is A2A repository commit
|
|
3385
|
+
`2183794bfb9b67af4aee1be0a0ef726050642873`, protocol `1.0`, with
|
|
3386
|
+
`specification/a2a.proto` SHA-256
|
|
3387
|
+
`e195bf96ab630c69797851970203e1b2b6b19528f2e9803b7d904b91a5104016`.
|
|
3388
|
+
|
|
3389
|
+
---
|
|
3390
|
+
|
|
3391
|
+
## 23. Shared Coder daemon for Space and IDE hosts (FEATURE_269, v0.7.69)
|
|
3392
|
+
|
|
3393
|
+
FEATURE_269 makes one local daemon the source of truth for a Coder profile.
|
|
3394
|
+
CLI, Space, IDE, and SDK clients can observe and control the same sessions and
|
|
3395
|
+
runs. The transport remains local to the current OS user; it is not a remote
|
|
3396
|
+
collaboration protocol. Closing a client detaches that client and does not stop
|
|
3397
|
+
the daemon or another client's run.
|
|
3398
|
+
|
|
3399
|
+
Partner is deliberately outside this migration. Keep Partner on its existing
|
|
3400
|
+
inline callbacks and give it a distinct product data root and sessions root.
|
|
3401
|
+
Do not point a Partner inline Runtime at the Coder daemon profile or the Coder
|
|
3402
|
+
data root.
|
|
3403
|
+
|
|
3404
|
+
### Connect and fail closed on required capabilities
|
|
3405
|
+
|
|
3406
|
+
Space should own the daemon SDK client in Electron Main. Persist a random,
|
|
3407
|
+
stable `instanceId` and a separate 32+ character `instanceSecret` per Space
|
|
3408
|
+
installation. Store the secret in the OS keychain; never accept either value
|
|
3409
|
+
from renderer or model output. `connectKodaXRuntime()` is attach-only unless `autoStart: true`.
|
|
3410
|
+
An explicit inline rollback policy blocks auto-start until the owner policy is
|
|
3411
|
+
explicitly changed back to daemon.
|
|
3412
|
+
|
|
3413
|
+
```ts
|
|
3414
|
+
import { connectKodaXRuntime } from '@kodax-ai/kodax/runtime';
|
|
3415
|
+
|
|
3416
|
+
const runtime = await connectKodaXRuntime({
|
|
3417
|
+
profile: 'coder',
|
|
3418
|
+
autoStart: true,
|
|
3419
|
+
clientInfo: {
|
|
3420
|
+
name: 'kodax-space',
|
|
3421
|
+
version: '0.1.32',
|
|
3422
|
+
instanceId: spaceInstallationId,
|
|
3423
|
+
instanceSecret: await spaceKeychain.readRuntimeClientSecret(),
|
|
3424
|
+
},
|
|
3425
|
+
capabilities: {
|
|
3426
|
+
richEvents: true,
|
|
3427
|
+
permissionPrompts: true,
|
|
3428
|
+
operationDeduplication: true,
|
|
3429
|
+
},
|
|
3430
|
+
requirements: {
|
|
3431
|
+
operationDeduplication: 1,
|
|
3432
|
+
sessionObservation: 1,
|
|
3433
|
+
afterTurnInput: 1,
|
|
3434
|
+
askUserTransport: 1,
|
|
3435
|
+
permissionCas: 1,
|
|
3436
|
+
providerCredentialBroker: 1,
|
|
3437
|
+
runBoundHostTools: 1,
|
|
3438
|
+
coderOwnerFencing: 1,
|
|
3439
|
+
crashOutcomeModel: 1,
|
|
3440
|
+
coderFeatureMatrix: 1,
|
|
3441
|
+
sessionAdmission: 1,
|
|
3442
|
+
completeObservationSnapshot: 1,
|
|
3443
|
+
connectionLifecycle: 1,
|
|
3444
|
+
typedRuntimeEvents: 1,
|
|
3445
|
+
daemonSafeRunInput: 1,
|
|
3446
|
+
sharedSessionSettings: 1,
|
|
3447
|
+
durableRecoveryQueries: 1,
|
|
3448
|
+
},
|
|
3449
|
+
});
|
|
3450
|
+
```
|
|
3451
|
+
|
|
3452
|
+
Requirements are server facts, not authorization requests. Check
|
|
3453
|
+
`runtime.grantedScopes` before enabling controls. Missing capabilities or
|
|
3454
|
+
scopes must disable the affected UI; Space must not silently start inline
|
|
3455
|
+
Coder. FEATURE_269 does not advertise `interruptInput`, so an interrupt-only
|
|
3456
|
+
product must require `{ interruptInput: 1 }` and fail connection. The supported
|
|
3457
|
+
fallback is `delivery: 'after_turn'` only when that is the user's intent.
|
|
3458
|
+
|
|
3459
|
+
The `coderFeatureMatrix` capability reports daemon availability for managed
|
|
3460
|
+
runs, transcript/session operations, Todo projection, managed tasks, Workflow,
|
|
3461
|
+
MCP, Reference External Agent, Memory, and Runtime artifacts. Reference
|
|
3462
|
+
External Agent is `false` when the daemon owner did not install its executor
|
|
3463
|
+
plane.
|
|
3464
|
+
|
|
3465
|
+
The packaged daemon authenticates one local OS-user/profile trust domain with
|
|
3466
|
+
a random token stored beside daemon state and a user-only local endpoint. It
|
|
3467
|
+
does not issue a different daemon token to each application in v0.7.69. The
|
|
3468
|
+
returned scope set is chosen by the host (the packaged host grants the public
|
|
3469
|
+
local-user set). `clientInfo.instanceId` is stable attribution for origin and
|
|
3470
|
+
operation deduplication. `instanceSecret` proves that a new authenticated
|
|
3471
|
+
connection is the same stable client when it resumes that client's credential
|
|
3472
|
+
or Host Tool leases; only its hash participates in daemon-owned bridge state.
|
|
3473
|
+
Keep all three values in Electron Main. Mutually distrusting processes running
|
|
3474
|
+
as the same OS account remain outside this release's threat model.
|
|
3475
|
+
|
|
3476
|
+
### Join atomically and resync after disconnect
|
|
3477
|
+
|
|
3478
|
+
`sessions.observe()` installs the live subscription before taking the
|
|
3479
|
+
snapshot. Its snapshot contains one authoritative `runtimeId`, cursor,
|
|
3480
|
+
`transcriptRevision`, complete transcript, versioned settings, run/queue state,
|
|
3481
|
+
queued continuation IDs/order/origin/safe previews, pending permission and
|
|
3482
|
+
AskUser requests, and live assistant/thinking/tool/Todo/managed-task
|
|
3483
|
+
projection. Run requirements include the current credential/Host Tool
|
|
3484
|
+
availability. Listener events are strictly after the returned cursor.
|
|
3485
|
+
|
|
3486
|
+
```ts
|
|
3487
|
+
let observedRuntimeId: string | undefined;
|
|
3488
|
+
let lastCursor = 0;
|
|
3489
|
+
|
|
3490
|
+
async function openCoderSession(sessionId: string) {
|
|
3491
|
+
const observation = await runtime.sessions.observe(sessionId, (event) => {
|
|
3492
|
+
if (event.seq <= lastCursor) return;
|
|
3493
|
+
applyRuntimeEvent(event);
|
|
3494
|
+
lastCursor = event.seq;
|
|
3495
|
+
});
|
|
3496
|
+
|
|
3497
|
+
const { snapshot } = observation;
|
|
3498
|
+
const runtimeChanged = observedRuntimeId !== undefined
|
|
3499
|
+
&& observedRuntimeId !== snapshot.runtimeId;
|
|
3500
|
+
observedRuntimeId = snapshot.runtimeId;
|
|
3501
|
+
lastCursor = snapshot.cursor;
|
|
3502
|
+
replaceSessionProjection(snapshot, { runtimeChanged });
|
|
3503
|
+
return observation;
|
|
3504
|
+
}
|
|
3505
|
+
```
|
|
3506
|
+
|
|
3507
|
+
Subscribe to `runtime.connection` to freeze mutation UI immediately rather
|
|
3508
|
+
than waiting for a status poll:
|
|
3509
|
+
|
|
3510
|
+
```ts
|
|
3511
|
+
runtime.connection?.subscribe((state) => {
|
|
3512
|
+
setCoderConnectionState(state.state, state.reason);
|
|
3513
|
+
if (state.state === 'disconnected' && state.reconnectable) {
|
|
3514
|
+
scheduleReconnect();
|
|
3515
|
+
}
|
|
3516
|
+
});
|
|
3517
|
+
```
|
|
3518
|
+
|
|
3519
|
+
The SDK reports the current `connectionId`, `runtimeEpoch`, optional
|
|
3520
|
+
`journalEpoch`, disconnect reason, and whether a new connection may be
|
|
3521
|
+
attempted. It does not transparently replay requests or subscriptions. Space
|
|
3522
|
+
creates a replacement Runtime client, checks its new epochs, resumes eligible
|
|
3523
|
+
leases, and observes the session again.
|
|
3524
|
+
|
|
3525
|
+
On transport failure, Runtime change, expired history, or `resync_required`,
|
|
3526
|
+
discard the local derived projection and call `sessions.observe()` again. Do
|
|
3527
|
+
not merge a new snapshot into the old projection. The handshake buffer is
|
|
3528
|
+
bounded; overflow fails explicitly instead of dropping events. A Runtime
|
|
3529
|
+
restart changes `runtimeId`, marks persisted non-terminal runs with a durable
|
|
3530
|
+
terminal fact, and closes old in-memory AskUser/permission requests through the
|
|
3531
|
+
reset boundary.
|
|
3532
|
+
|
|
3533
|
+
### Durable mutations, stable ordering, and settings CAS
|
|
3534
|
+
|
|
3535
|
+
Every public control mutation uses a durable operation envelope. Credential
|
|
3536
|
+
supply and Host Tool completion are connection-local reverse-result frames and
|
|
3537
|
+
are deliberately excluded from the control journal so secrets/results are not
|
|
3538
|
+
persisted. The SDK creates an operation ID for ordinary one-shot calls. A
|
|
3539
|
+
product-level retry after a lost response must reuse its own stable operation
|
|
3540
|
+
ID; changing its method, payload, resource, or authenticated principal is
|
|
3541
|
+
rejected.
|
|
3542
|
+
|
|
3543
|
+
```ts
|
|
3544
|
+
const session = await runtime.sessions.create({
|
|
3545
|
+
sessionId: stableSpaceSessionId,
|
|
3546
|
+
title: 'Shared session',
|
|
3547
|
+
surface: 'space-desktop',
|
|
3548
|
+
operation: { operationId: loadOrCreatePendingOperationId('space-session-draft-7') },
|
|
3549
|
+
});
|
|
3550
|
+
|
|
3551
|
+
const operationId = loadOrCreatePendingOperationId('space-run-draft-42');
|
|
3552
|
+
const handle = await runtime.runs.start({
|
|
3553
|
+
sessionId: session.id,
|
|
3554
|
+
input: { type: 'text', text: prompt },
|
|
3555
|
+
options: { provider: 'anthropic' },
|
|
3556
|
+
operation: { operationId },
|
|
3557
|
+
});
|
|
3558
|
+
|
|
3559
|
+
const current = await runtime.sessions.getSettingsVersioned(session.id);
|
|
3560
|
+
const updated = await runtime.sessions.updateSettingsVersioned(
|
|
3561
|
+
session.id,
|
|
3562
|
+
{ model: 'claude-sonnet-4-5' },
|
|
3563
|
+
{
|
|
3564
|
+
operationId: loadOrCreatePendingOperationId('space-settings-draft-9'),
|
|
3565
|
+
expectedRevision: current.revision,
|
|
3566
|
+
},
|
|
3567
|
+
);
|
|
3568
|
+
```
|
|
3569
|
+
|
|
3570
|
+
Create retries with the same explicit session and operation IDs cannot overwrite
|
|
3571
|
+
an existing session. Same-session starts and after-turn inputs receive a durable `sessionOrder`.
|
|
3572
|
+
Retries with the same operation ID return the canonical result and do not
|
|
3573
|
+
create another run. Settings use compare-and-swap; a stale revision returns a
|
|
3574
|
+
structured conflict and must be reloaded, never silently overwritten. The
|
|
3575
|
+
shared settings keys are `provider`, `model`, `effort`, `thinking`,
|
|
3576
|
+
`reasoningMode`, `permissionMode`, `executionCwd`, `agentMode`, and
|
|
3577
|
+
`autoModeEngine`.
|
|
3578
|
+
|
|
3579
|
+
```ts
|
|
3580
|
+
const queued = await runtime.runs.submitInput({
|
|
3581
|
+
sessionId: session.id,
|
|
3582
|
+
afterRunId: handle.runId,
|
|
3583
|
+
delivery: 'after_turn',
|
|
3584
|
+
input: { type: 'text', text: 'Also update the tests.' },
|
|
3585
|
+
operation: { operationId: loadOrCreatePendingOperationId('space-input-17') },
|
|
3586
|
+
});
|
|
3587
|
+
|
|
3588
|
+
if (!queued.accepted) {
|
|
3589
|
+
// stale_run or unsupported_capability: show the factual result.
|
|
3590
|
+
}
|
|
3591
|
+
```
|
|
3592
|
+
|
|
3593
|
+
Run status exposes acceptance/start/queue times, authenticated origin,
|
|
3594
|
+
`sessionOrder`, and a single terminal fact. Important terminal codes include
|
|
3595
|
+
`runtime_restarted`, `daemon_crashed`, `credential_unavailable`,
|
|
3596
|
+
`host_not_dispatched`, `host_outcome_unknown`, and
|
|
3597
|
+
`control_history_untrusted`. Respect `effectOutcome`; `unknown` must never be
|
|
3598
|
+
presented as success or automatically retried. After a lost response, query
|
|
3599
|
+
`runtime.operations.get({ operationId, journalEpoch })`; applied receipts
|
|
3600
|
+
include the canonical result. Permission grants remain daemon-owned and
|
|
3601
|
+
revisioned.
|
|
3602
|
+
|
|
3603
|
+
### AskUser and permission from any client
|
|
3604
|
+
|
|
3605
|
+
AskUser is no longer an in-process callback for daemon Coder runs. Any client
|
|
3606
|
+
with the responder scope can list the pending request and answer or dismiss it.
|
|
3607
|
+
The request revision and run binding prevent a stale UI from answering a new
|
|
3608
|
+
request. Exactly one concurrent answer is accepted.
|
|
3609
|
+
|
|
3610
|
+
```ts
|
|
3611
|
+
for (const request of await runtime.userInputs.listPending({ sessionId })) {
|
|
3612
|
+
const resolution = await runtime.userInputs.respond(request.id, answer, {
|
|
3613
|
+
expectedRevision: request.revision,
|
|
3614
|
+
runId: request.runId,
|
|
3615
|
+
});
|
|
3616
|
+
if (!resolution.accepted) refreshPendingInteractions();
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
for (const request of await runtime.permissions.listPending({ sessionId })) {
|
|
3620
|
+
const accepted = await runtime.permissions.respond(
|
|
3621
|
+
request.id,
|
|
3622
|
+
{ type: 'allow_once' },
|
|
3623
|
+
{ runId: request.runId },
|
|
3624
|
+
);
|
|
3625
|
+
if (!accepted) refreshPendingInteractions();
|
|
3626
|
+
}
|
|
3627
|
+
```
|
|
3628
|
+
|
|
3629
|
+
Persistent `allow_always` grants are owned by the daemon and require
|
|
3630
|
+
`permission:grant-admin`. Use `permissions.listGrants()` and
|
|
3631
|
+
`revokeGrant(grantId, expectedRevision)` for revision-safe administration.
|
|
3632
|
+
Clients must not keep separate persistent permission rule stores.
|
|
3633
|
+
|
|
3634
|
+
### Broker a Space keychain credential
|
|
3635
|
+
|
|
3636
|
+
Credentials remain owned by Space's OS keychain. Register a broker in Electron
|
|
3637
|
+
Main, bind the returned lease only to runs that Space starts, and return the
|
|
3638
|
+
key only after checking the daemon-provided provider/session/run context.
|
|
3639
|
+
|
|
3640
|
+
```ts
|
|
3641
|
+
const credentialLease = await runtime.credentials.register(
|
|
3642
|
+
{ providers: ['anthropic'] },
|
|
3643
|
+
async ({ provider, sessionId: requestedSession, runId }) => {
|
|
3644
|
+
authorizeSpaceRunCredential({ provider, sessionId: requestedSession, runId });
|
|
3645
|
+
return spaceKeychain.readProviderCredential(provider);
|
|
3646
|
+
},
|
|
3647
|
+
);
|
|
3648
|
+
|
|
3649
|
+
const run = await runtime.runs.start({
|
|
3650
|
+
sessionId,
|
|
3651
|
+
input: { type: 'text', text: prompt },
|
|
3652
|
+
options: { provider: 'anthropic' },
|
|
3653
|
+
credential: { leaseId: credentialLease.id, provider: 'anthropic' },
|
|
3654
|
+
operation: { operationId: loadOrCreatePendingOperationId('space-run-88') },
|
|
3655
|
+
});
|
|
3656
|
+
```
|
|
3657
|
+
|
|
3658
|
+
The secret crosses only the authenticated reverse frame and an in-memory
|
|
3659
|
+
run/provider scope. It is excluded from events, status, logs, diagnostics,
|
|
3660
|
+
operation records, and Runtime persistence. While such a scope is active,
|
|
3661
|
+
provider mismatch fails closed and never falls back to daemon environment.
|
|
3662
|
+
Without a stable `instanceSecret`, registration ends when the Space connection
|
|
3663
|
+
closes. With it, the daemon keeps the registration owned by that stable client;
|
|
3664
|
+
a replacement Space process reattaches the callback with
|
|
3665
|
+
`runtime.credentials.resume(leaseId, broker)`. An accepted run has already
|
|
3666
|
+
acquired its scoped credential, so it reports `requirements.credential.state`
|
|
3667
|
+
as `ready` and may continue after disconnect. If the broker cannot answer, the
|
|
3668
|
+
start is rejected instead of accepting an indefinitely waiting run. Expiry or
|
|
3669
|
+
Runtime restart is explicit (`expired` or terminal); no provider request is
|
|
3670
|
+
automatically replayed.
|
|
3671
|
+
|
|
3672
|
+
### Bind Space-owned Host Tools to one run
|
|
3673
|
+
|
|
3674
|
+
Register only narrow product capabilities. Descriptors are data; handlers stay
|
|
3675
|
+
in Electron Main. A lease grants nothing until its ID is explicitly bound to a
|
|
3676
|
+
run.
|
|
3677
|
+
|
|
3678
|
+
```ts
|
|
3679
|
+
const hostLease = await runtime.hostTools.register([{
|
|
3680
|
+
name: 'space_artifact_create',
|
|
3681
|
+
description: 'Create a Space-owned artifact for this run.',
|
|
3682
|
+
inputSchema: {
|
|
3683
|
+
type: 'object',
|
|
3684
|
+
properties: { title: { type: 'string' } },
|
|
3685
|
+
required: ['title'],
|
|
3686
|
+
additionalProperties: false,
|
|
3687
|
+
},
|
|
3688
|
+
sideEffect: 'non_idempotent',
|
|
3689
|
+
}], {
|
|
3690
|
+
async space_artifact_create(invocation) {
|
|
3691
|
+
authorizeBoundSpaceInvocation(invocation);
|
|
3692
|
+
const artifact = await createSpaceArtifact(invocation.input);
|
|
3693
|
+
return { content: `Created artifact ${artifact.displayId}` };
|
|
3694
|
+
},
|
|
3695
|
+
});
|
|
3696
|
+
|
|
3697
|
+
await runtime.runs.start({
|
|
3698
|
+
sessionId,
|
|
3699
|
+
input: { type: 'text', text: 'Create the report artifact.' },
|
|
3700
|
+
hostTools: { leaseId: hostLease.id },
|
|
3701
|
+
operation: { operationId: loadOrCreatePendingOperationId('space-artifact-run') },
|
|
3702
|
+
});
|
|
3703
|
+
```
|
|
3704
|
+
|
|
3705
|
+
The daemon injects session/run/lease/invocation identity; renderer, model, and
|
|
3706
|
+
ordinary tool input cannot choose it. CLI runs never inherit a Space lease just
|
|
3707
|
+
because Space later observes their session. The client memoizes one handler
|
|
3708
|
+
promise per invocation ID, and the daemon never replays a dispatched Host Tool.
|
|
3709
|
+
After a stable-client reconnect, call
|
|
3710
|
+
`runtime.hostTools.resume(leaseId, handlers)`. Bound run status reports
|
|
3711
|
+
`ready`, `waiting_host`, `expired`, or `terminal`. Disconnect or timeout after dispatch produces `host_outcome_unknown`; Space
|
|
3712
|
+
must reconcile the product side effect itself before offering a new user
|
|
3713
|
+
action. `runtime.hostTools.getInvocation(invocationId)` returns the durable
|
|
3714
|
+
metadata state `prepared`, `dispatched`, `completed`, `unknown`, or
|
|
3715
|
+
`not_dispatched`; it never returns handler input, result, or credential data.
|
|
3716
|
+
The daemon writes the `dispatched` marker before attempting the reverse frame
|
|
3717
|
+
and never auto-replays an invocation.
|
|
3718
|
+
|
|
3719
|
+
### Coder admission, typed events, and transport-safe inputs
|
|
3720
|
+
|
|
3721
|
+
The daemon enforces Coder session admission on the server for list/create/load,
|
|
3722
|
+
run, settings, delete, rewind, fork, compact, transcript, event, interaction,
|
|
3723
|
+
and diagnostic paths. Coder
|
|
3724
|
+
surfaces are `code`, `cli`, `repl`, `acp`, `a2a`, `sdk`, `ide`, and
|
|
3725
|
+
`space-desktop`. A session marked with
|
|
3726
|
+
Partner surface/profile metadata, or any unknown product surface, fails with
|
|
3727
|
+
typed `session_not_admitted` before mutation. Space must continue marking
|
|
3728
|
+
Partner sessions as `surface: 'partner'` and keep their inline storage root
|
|
3729
|
+
separate. Legacy sessions without a surface remain admitted for existing Coder
|
|
3730
|
+
compatibility, so absence of metadata is not a Partner namespace mechanism.
|
|
3731
|
+
|
|
3732
|
+
`RuntimeEventPayloadMap` and `RuntimeTypedEvent` provide the public
|
|
3733
|
+
discriminated contract for known events. Existing raw listeners remain
|
|
3734
|
+
compatible; consumers can use `parseRuntimeEvent(value)` before exhaustive
|
|
3735
|
+
handling. One unknown or malformed event is diagnosed and dropped without
|
|
3736
|
+
closing the observation stream.
|
|
3737
|
+
|
|
3738
|
+
Daemon clients use `RuntimeDaemonStartRunInput`. Function callbacks,
|
|
3739
|
+
`AbortSignal`, Extension Runtime objects, and guardrail instances are excluded
|
|
3740
|
+
from that type and rejected at runtime with `RuntimeTransportBoundaryError`
|
|
3741
|
+
and an exact value path if an untyped caller supplies them. Host-only values
|
|
3742
|
+
remain valid only for embedded Runtime calls.
|
|
3743
|
+
|
|
3744
|
+
### Recovery queries and stop preflight
|
|
3745
|
+
|
|
3746
|
+
`runtime.status.preflight()` returns connected-client count, active and queued
|
|
3747
|
+
runs, pending AskUser/permission records, blockers, and `canStop`. Treat this as
|
|
3748
|
+
a required check before stop or inline rollback; the query does not stop the
|
|
3749
|
+
daemon by itself. `runtime.operations.get()` reconciles durable mutations,
|
|
3750
|
+
`hostTools.getInvocation()` reconciles Host Tool metadata, and
|
|
3751
|
+
`permissions.listGrants()` returns the daemon-owned persistent grant set.
|
|
3752
|
+
|
|
3753
|
+
Terminal notification read/unread state is intentionally client-owned in
|
|
3754
|
+
v0.7.69 (`durableRecoveryQueries.terminalAcknowledgement === false`): Space
|
|
3755
|
+
persists its own UI acknowledgement cursor against Runtime/run terminal facts.
|
|
3756
|
+
This avoids a false claim that one client's acknowledgement is global daemon
|
|
3757
|
+
truth.
|
|
3758
|
+
|
|
3759
|
+
### Owner policy, rollback, and Electron boundary
|
|
3760
|
+
|
|
3761
|
+
Daemon and inline Coder use one profile fence. For an emergency rollback, stop
|
|
3762
|
+
the daemon, switch the policy with revision CAS, then acquire the inline owner:
|
|
3763
|
+
|
|
3764
|
+
```ts
|
|
3765
|
+
import {
|
|
3766
|
+
acquireKodaXInlineOwner,
|
|
3767
|
+
setKodaXRuntimeOwnerMode,
|
|
3768
|
+
} from '@kodax-ai/kodax/runtime';
|
|
3769
|
+
|
|
3770
|
+
const policy = setKodaXRuntimeOwnerMode({
|
|
3771
|
+
homeDir: kodaxHome,
|
|
3772
|
+
profile: 'coder',
|
|
3773
|
+
mode: 'inline',
|
|
3774
|
+
expectedRevision: currentPolicyRevision,
|
|
3775
|
+
});
|
|
3776
|
+
const inlineOwner = acquireKodaXInlineOwner({ homeDir: kodaxHome, profile: 'coder' });
|
|
3777
|
+
```
|
|
3778
|
+
|
|
3779
|
+
The inline policy is sticky: later CLI auto-start is rejected until an explicit
|
|
3780
|
+
CAS changes the policy back to `daemon`. Stale-owner handling validates the
|
|
3781
|
+
owned lock/state and never kills a process merely because a PID was reused.
|
|
3782
|
+
|
|
3783
|
+
Keep all trusted objects in Electron Main: daemon token/endpoint, stable client
|
|
3784
|
+
identity, operation IDs, owner policy, keychain broker, Host Tool handlers, and
|
|
3785
|
+
permission-grant administration. Renderer IPC should expose product-specific
|
|
3786
|
+
commands and sanitized projections only. Never pass daemon credentials,
|
|
3787
|
+
leases, operation epochs, or trusted session/run context to renderer or model
|
|
3788
|
+
tool arguments.
|
|
3789
|
+
|
|
3790
|
+
---
|
|
3791
|
+
|
|
3792
|
+
## See also
|
|
3075
3793
|
|
|
3076
3794
|
- [README.md](../README.md) — end-user CLI quick start
|
|
3077
3795
|
- [docs/ADR.md ADR-024](ADR.md#adr-024-npm-发布物正名-kodax-aikodax--sdk-subpath-exports-形式化-v0739) — SDK subpath architecture rationale
|