@theokit/acp 3.0.0 → 4.0.0

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 CHANGED
@@ -1,5 +1,210 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 2c33d98: `AcpServerOptions.info` is now advertised in the `initialize` handshake, and two inert capability
8
+ fields are removed.
9
+
10
+ `info` was accepted, typed, and documented as advertised — and read by nothing. It is now sent as
11
+ `InitializeResponse.agentInfo`, the protocol slot that matches its shape. It is omitted when you do
12
+ not supply one: defaulting to this package's own metadata would label every agent as the adapter
13
+ serving it, and a name that is confidently wrong is worse for a host to display than one that is
14
+ absent.
15
+
16
+ **Breaking:** `AcpCapabilities.forkSession` and `AcpCapabilities.listSessions` are removed.
17
+ `AgentCapabilities` has no slot for either at `@agentclientprotocol/sdk@0.22.1` — neither name
18
+ appears in its schema — and neither gated anything: `session/fork` is refused unconditionally and
19
+ `session/list` is answered regardless. Setting them never did anything; removing them says so at
20
+ compile time.
21
+
22
+ ### Minor Changes
23
+
24
+ - 92a9d6a: `InvalidAgentError` is now exported from `@theokit/acp`.
25
+
26
+ It is the single startup failure of `serveAcp` and its documented name, but it was not on the
27
+ package's public surface, so a consumer could neither `import` it nor `instanceof` it — leaving
28
+ `err.name` string-matching as the only way to tell a bad `agent` from any other rejection. The
29
+ package's other named error, `PromptTooLargeError`, was already exported "so that text has a named
30
+ origin"; both have the same justification.
31
+
32
+ ### Patch Changes
33
+
34
+ - 2ba468b: `session/cancel` no longer kills the session.
35
+
36
+ The session owned exactly one `AbortController`, created at session creation and never replaced, so
37
+ cancelling one turn handed every later prompt on that session an already-aborted signal. ACP hosts
38
+ routinely cancel a turn and then prompt again: the agent silently stopped answering while the host
39
+ stayed connected and the session stayed listed.
40
+
41
+ The abort scope is now armed per turn, which is what `session/cancel` means — the host stops the
42
+ answer being written, not the conversation. Cancelling the turn in flight still works.
43
+
44
+ - e3f2a82: Public-API documentation reviewed file by file, and corrected wherever it disagreed
45
+ with the code. The docblocks ship in the `.d.ts`, so these read as behaviour changes
46
+ in an editor even though no behaviour changed.
47
+
48
+ The corrections that change what a caller would do:
49
+
50
+ - **`sdk-cache` documented its own premise backwards.** The header example labelled a
51
+ semantic hit as if it avoided the provider call. `asPlugin()` returns the cached
52
+ answer as `recalledContext`, which the agent loop injects as a `<memory-context>`
53
+ block _before_ the prompt — the request still goes to the provider. The two modes
54
+ are now labelled separately, with a table saying which one short-circuits and which
55
+ one seeds.
56
+ - **`sdk-handoff`'s five error classes said "throw".** Under the plugin wiring the
57
+ handler never throws; every failure becomes a tool result `{"ok":false,…}` handed
58
+ back to the model. Each class now says where it is actually observable. The header
59
+ also told readers to `import { Handoff } from "@theokit/sdk"`, from which it was
60
+ extracted.
61
+ - **`sdk-budget`'s `charge()` claimed idempotency across concurrent calls.** The mutex
62
+ serialises, it does not deduplicate: two identical calls record twice. Related, and
63
+ newly documented: with `maxUsd` set, a model missing from the pricing table denies
64
+ every request rather than passing it — and the table matches by exact string, so
65
+ `"openai/gpt-4o"` does not match `"gpt-4o"`.
66
+ - **The three `memory-*` adapters advertised an env-var fallback they do not read**,
67
+ and their peer dependencies are required rather than optional. Their behavioural
68
+ differences are now stated where they break the "interchangeable adapter"
69
+ assumption — honcho ignores `k` and always throws on `delete`; mem0 recalls across
70
+ sessions by design; supermemory ignores `sessionId` entirely.
71
+ - **`sdk-memory`'s `truncated` flag was documented as its own inverse**, and its
72
+ dreaming sweep claimed a mutex it never takes against the writer it names.
73
+ - **`sdk-tools`** corrected `run_vitest`'s unreachable `no_vitest` code, `truncation`'s
74
+ replacement-character claim, and two return shapes missing a live error code.
75
+ - **`acp`/`cli`** corrected sixteen statements including a named error class that is
76
+ not the one raised, a handler documented as calling `fork()` that refuses
77
+ unconditionally, handlers described as pure that mint ids and mutate a store, a
78
+ config loader credited to Zod in a package that does not import it, and a `--force`
79
+ scaffold described as atomic that deletes the destination before the rename.
80
+
81
+ Undocumented public symbols were documented across every package, with each claim
82
+ checked against the implementation rather than inferred from the name.
83
+
84
+ - e368fc1: Every published declaration file now compiles without `skipLibCheck` (#345). The
85
+ DTS rollup emitted symbols as a re-export from a chunk while omitting them from
86
+ that chunk's `import`, and dropped type-only imports from external packages —
87
+ leaving 51 unresolved references across ten of the twelve packages. Nothing broke
88
+ at runtime, and `tsc` stayed green for anyone with `skipLibCheck` on, but a
89
+ consumer running type-aware lint saw every type reached through one degrade to
90
+ `error`.
91
+
92
+ The declarations are repaired at build time from the compiler's own diagnostics.
93
+ No source or API change.
94
+
95
+ - 29ebaa1: Three places where a value was reported that nobody had actually selected.
96
+
97
+ **An empty `POSTHOG_API_KEY` no longer masks a valid `POSTHOG_PROJECT_API_KEY`.** The adapter read
98
+ `POSTHOG_API_KEY ?? POSTHOG_PROJECT_API_KEY`, and `??` treats `""` as present. Leaving a variable
99
+ blank in a `.env` or a CI config is the ordinary way to say "unset", so a blank primary key silently
100
+ disabled telemetry while a working key sat in the sibling variable — and telemetry going quiet is the
101
+ one failure that reports itself as nothing at all. Empty and whitespace-only values now fall through.
102
+ The same trap on `POSTHOG_HOST` is closed with it.
103
+
104
+ **The provider inspector reports the model the route resolves to.** `extractModelName` documented
105
+ itself as surfacing the name from the prefix split and instead returned a hard-coded default, so a
106
+ route configured as `anthropic:claude-opus-4` with no explicit `route.model` reported
107
+ `claude-3-7-sonnet`. That field exists to let a caller confirm which model a route resolves to; a
108
+ wrong answer there is worse than no answer, because it is indistinguishable from a right one. The
109
+ name is now derived from the model id the route actually carries, and the default-model lookup that
110
+ produced the literal is deleted rather than left as a decoy.
111
+
112
+ **An errored ACP run no longer reaches the client as `end_turn`.** The stop-reason mapping fell
113
+ through to `end_turn` for any run status it did not recognise, so a failure was reported over the
114
+ wire as an ordinary completed turn — invisible to every ACP client, which is the swallowed-error
115
+ shape the project's error-handling rules forbid by name. The protocol's `StopReason` has no error
116
+ value, so an unmapped status now surfaces through the JSON-RPC error channel the handler already uses
117
+ for every other failure, with a message naming the status that was not mapped. A dead branch
118
+ returning `end_turn` twice is removed in the same pass.
119
+
120
+ - 1ac974f: **`@theokit/sdk-pty` declares its licence.** Every published version up to now shipped with no `license` field in the manifest. npm reads the field, not the directory, so the tarball was all-rights-reserved to whoever installed it — the terms were sitting in the `LICENSE` file it already shipped, saying nothing. The field now says `Apache-2.0`, which is what that file has always been and what all eleven sibling packages declare.
121
+
122
+ **Four packages now ship the licence they declare.** `@theokit/cli`, `@theokit/memory-honcho`, `@theokit/memory-mem0` and `@theokit/memory-supermemory` declared `Apache-2.0` and listed `LICENSE` in `files`, and no such file existed. npm omits a declared-but-absent path in silence, so every published tarball asserted the licence while carrying none of its terms — and §4(a) requires a copy to travel with the distribution. The file is there now, byte-identical to the one the other packages ship.
123
+
124
+ **Six packages complete the rest of their published metadata.** Each field is here for what its absence costs a consumer:
125
+
126
+ - `homepage` and `bugs` — the npm page renders both; without them someone who hits a defect has no route back to the project.
127
+ - `engines.node` — npm warns on an unsupported runtime only when the range is declared. `@theokit/sdk-pty` declared none, so a Node 18 install failed later and somewhere unrelated.
128
+ - `sideEffects` — a bundler keeps every module of a package that stays silent. Declared only after checking: a clean scan of each built ESM entry found zero top-level statements, the residual hits being closing tokens of declarations. `@theokit/sdk` keeps its path-array form, which is the honest shape for a package whose agent entry registers on import.
129
+ - `publishConfig.access` — a scoped package defaults to `restricted`. Three declared none and reached npm public only because the release flow supplied the flag; the manifest states it now instead of depending on how it is invoked.
130
+ - `@theokit/sdk-pty` also ships its `CHANGELOG.md`, which existed on disk and was absent from `files`.
131
+
132
+ The gate that should have caught any of this covered three packages out of twelve, by way of a hand-written list. It now derives the list from `packages/`, asserts the whole contract, and fails when the sweep discovers nothing rather than passing by having nothing to check.
133
+
134
+ - e699569: **The repository moved to the official `usetheokit` organization.** Every `repository`, `bugs` and `homepage` field now points there, along with the README, `CONTRIBUTING.md`, `SECURITY.md` and the issue templates. Existing clones and any URL already published keep working — GitHub redirects a transferred repository permanently — so this is a correctness fix for the metadata npm renders, not a break.
135
+
136
+ **The Apache-2.0 text every package ships was replaced with the official one.** The copy distributed until now had paragraph 4(d) truncated: it read "except as required for describing the origin of the Work and reproducing the content of the NOTICE file", dropping "reasonable and customary use" from the licensed clause. §4(d) governs what a redistributor must do with attribution notices, and the omission narrowed it.
137
+
138
+ That matters more than a typo would. The manifests declare the SPDX identifier `Apache-2.0`, which is an assertion that the terms are _the_ Apache-2.0 terms — a licence scanner resolves the identifier and never reads the file. A consumer's compliance review, which does read the file, would find a body that no longer matches the identifier and has no name of its own. Every `LICENSE` in this repository is now byte-identical to the canonical text, with the appendix filled in.
139
+
140
+ Nothing else about the terms changed: the licence is the same licence it has always been meant to be, and no package changes what it grants.
141
+
142
+ - f692988: The reference docs no longer ship inside the package. `node_modules/@theokit/sdk/docs/` is gone, along with the `harness-capability-map.md` and `error-codes.md` files it carried — the `docs` entry was removed from the published `files` list and the build step that generated it was removed with it.
143
+
144
+ The exported TypeScript types are now the only reference surface, and they remain the canonical contract: every public primitive carries its import path, signature and JSDoc example, surfaced by your editor. Nothing about the runtime API changed.
145
+
146
+ The scaffolded agent context still ships, unchanged, under `claude-template/`.
147
+
148
+ - c7385d2: Test runs no longer claim every core on the host.
149
+
150
+ None of the package configs capped `maxWorkers`, so vitest's default applied: `os.availableParallelism()`,
151
+ one fork per core, each booting a full test environment. The repo's `test` script is
152
+ `turbo run test --filter='./packages/*'`, so that default is paid once per package _concurrently_ —
153
+ nproc forks times turbo's concurrency, on nproc cores. Measured on a 12-thread machine during an
154
+ unrelated investigation, two vitest pools alone were enough to reach load average 33.89 with the
155
+ desktop unusable; a full fan-out is several times that.
156
+
157
+ `@theokit/sdk` is the interesting case. B-104 recorded on 2026-08-19 that the `poolOptions.forks.*`
158
+ block was 100% dead in Vitest 4, deleted it, and noted that `fileParallelism: false` was forcing
159
+ `maxWorkers` to 1 unconditionally, so a fork-count knob could not act. B-059 then flipped
160
+ `fileParallelism` to `true` on 2026-08-20, which made the knob able to act again — and nothing
161
+ reintroduced one, so the package silently went back to the uncapped default. That comment has been
162
+ corrected along with the config; it claimed no knob existed, which is no longer true.
163
+
164
+ The cap leaves 4 cores free (`Math.max(2, cpus().length - 4)`), scaling with the runner rather than
165
+ hard-coding one machine's core count. It costs no wall-clock: measured in `theokit-ui`, the full
166
+ suite ran 73.96s at 4 workers against 74.36s at 12, so the parallelism above the cap was already
167
+ noise. Verified as resolved config rather than as file contents — `createVitest` reports
168
+ `maxWorkers: 8` on a 12-thread host, which is the formula, not the default.
169
+
170
+ This changes no published behaviour; it is test tooling only. Refs usetheokit/theokit-ui#51.
171
+
172
+ ## 3.0.2
173
+
174
+ ### Patch Changes
175
+
176
+ - a3ae640: Declare `repository` so these packages can publish with provenance.
177
+
178
+ npm cross-checks a manifest's `repository.url` against the repository recorded in the signed
179
+ provenance statement, and an empty value cannot match — the PUT is refused with E422 after the
180
+ statement has been signed and written to the public transparency log. Six of the twelve publishable
181
+ packages carried an empty field; it went unnoticed because nothing needed it until provenance was
182
+ enabled, and because each package publishes independently, so the release run went red while the
183
+ package everyone was watching succeeded.
184
+
185
+ `directory` is set alongside the URL, so the registry links to each package rather than to the
186
+ repository root.
187
+
188
+ ## 3.0.1
189
+
190
+ ### Patch Changes
191
+
192
+ - 8790f70: Refuse a `workspace:` range before it can reach npm.
193
+
194
+ Five of this repo's twelve publishable packages declare internal dependencies as `workspace:^`, which
195
+ is correct on disk and becomes an unrecoverable defect if the publish goes out through a tool that
196
+ does not rewrite it: `pnpm` resolves the protocol while packing, `npm` ships the manifest verbatim.
197
+ A version published that way fails to install for everyone and cannot be corrected — only
198
+ deprecated.
199
+
200
+ Every publishable package now runs the guard in `prepublishOnly`, so it fires whichever way the
201
+ publish is invoked, and `pnpm release` runs it once across the repo before `changeset publish`.
202
+
203
+ Note for anyone reading a published manifest: the `prepublishOnly` entry points at a path inside
204
+ this repository. It never runs for a consumer — the hook only fires when the package itself is
205
+ published — and guarding the entry point that a hand-run `npm publish` actually uses was worth the
206
+ cosmetic wart of shipping the line.
207
+
3
208
  ## 3.0.0
4
209
 
5
210
  ### Patch Changes
package/LICENSE CHANGED
@@ -137,8 +137,8 @@
137
137
 
138
138
  6. Trademarks. This License does not grant permission to use the trade
139
139
  names, trademarks, service marks, or product names of the Licensor,
140
- except as required for describing the origin of the Work and
141
- reproducing the content of the NOTICE file.
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
142
 
143
143
  7. Disclaimer of Warranty. Unless required by applicable law or
144
144
  agreed to in writing, Licensor provides the Work (and each
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent Client Protocol (ACP) server adapter for [`@theokit/sdk`](https://www.npmjs.com/package/@theokit/sdk).
4
4
 
5
- Exposes a `SDKAgent` over stdio JSON-RPC so [Zed](https://zed.dev), Cursor, Claude Desktop, and any [ACP-compatible host](https://agentclientprotocol.com) can drive it as a coding agent — without writing any glue code.
5
+ Exposes a `SDKAgent` over stdio JSON-RPC so any [ACP-compatible host](https://agentclientprotocol.com) can drive it as a coding agent — without writing any glue code.
6
6
 
7
7
  ```bash
8
8
  npm i @theokit/acp @theokit/sdk @agentclientprotocol/sdk
@@ -25,7 +25,7 @@ export default async (sessionId: string) => {
25
25
  ```
26
26
 
27
27
  ```bash
28
- # Launch the server (Zed/Cursor will do this for you)
28
+ # Launch the server (an ACP host will do this for you)
29
29
  npx theokit-acp --entry ./my-agent.ts
30
30
  ```
31
31
 
@@ -66,6 +66,19 @@ await serveAcp({
66
66
 
67
67
  Pre-1.0 (`0.x`). Breaking changes are allowed within `0.x` (D181 pattern) until upstream ACP stabilizes.
68
68
 
69
+ ## API reference
70
+
71
+ Every symbol this package exports, with the exact specifier to import it from, is in the generated
72
+ capability map that ships inside `@theokit/sdk`:
73
+
74
+ ```
75
+ node_modules/@theokit/sdk/docs/harness-capability-map.md # symbol -> import specifier
76
+ node_modules/@theokit/sdk/docs/error-codes.md # every `code` an error can carry
77
+ ```
78
+
79
+ Both are generated from the built type declarations, so they describe the version you installed
80
+ rather than the version someone wrote a page about.
81
+
69
82
  ## License
70
83
 
71
84
  Apache-2.0
package/dist/index.cjs CHANGED
@@ -30,8 +30,6 @@ function _interopNamespace(e) {
30
30
  var acp2__namespace = /*#__PURE__*/_interopNamespace(acp2);
31
31
  var path__default = /*#__PURE__*/_interopDefault(path);
32
32
 
33
- // src/serve.ts
34
-
35
33
  // src/agent-resolver.ts
36
34
  function looksLikeSDKAgent(value) {
37
35
  return typeof value === "object" && value !== null && typeof value.agentId === "string" && typeof value.send === "function";
@@ -73,6 +71,7 @@ function buildInitializeResponse(_params, deps) {
73
71
  embeddedContext: userCaps.prompt?.embeddedContext ?? true
74
72
  }
75
73
  },
74
+ ...deps.info !== void 0 ? { agentInfo: { ...deps.info } } : {},
76
75
  authMethods: []
77
76
  };
78
77
  }
@@ -485,6 +484,39 @@ function extractPrompt(blocks, maxBytes) {
485
484
  return { text: state.textParts.join("\n"), attachments: state.attachments };
486
485
  }
487
486
 
487
+ // src/session-store.ts
488
+ function armTurn(session) {
489
+ session.abortController = new AbortController();
490
+ return session.abortController.signal;
491
+ }
492
+ var SessionStore = class {
493
+ map = /* @__PURE__ */ new Map();
494
+ /** @throws Error on a duplicate id — the caller must have checked `has` first. */
495
+ create(session) {
496
+ if (this.map.has(session.sessionId)) {
497
+ throw new Error(`duplicate sessionId: ${session.sessionId}`);
498
+ }
499
+ this.map.set(session.sessionId, session);
500
+ }
501
+ get(sessionId) {
502
+ return this.map.get(sessionId);
503
+ }
504
+ /** `true` when an entry was removed. Does NOT dispose the agent or abort its controller. */
505
+ delete(sessionId) {
506
+ return this.map.delete(sessionId);
507
+ }
508
+ /** A new array over the live session objects — the array is a copy, the sessions are not. */
509
+ list() {
510
+ return [...this.map.values()];
511
+ }
512
+ size() {
513
+ return this.map.size;
514
+ }
515
+ has(sessionId) {
516
+ return this.map.has(sessionId);
517
+ }
518
+ };
519
+
488
520
  // src/prompt-handler.ts
489
521
  function mapStopReason(runStatus, errorCode) {
490
522
  if (errorCode === "aborted" || runStatus === "cancelled") return "cancelled";
@@ -492,8 +524,16 @@ function mapStopReason(runStatus, errorCode) {
492
524
  if (errorCode === "context_length_exceeded" || errorCode === "max_tokens") return "max_tokens";
493
525
  if (errorCode === "max_iterations") return "max_turn_requests";
494
526
  if (runStatus === "finished") return "end_turn";
495
- return "end_turn";
527
+ return void 0;
496
528
  }
529
+ var UnmappedRunStatusError = class extends Error {
530
+ constructor(runStatus, errorCode, detail) {
531
+ super(
532
+ `run ended in status "${runStatus}" with unmapped error code "${errorCode}"${detail !== void 0 ? `: ${detail}` : ""}`
533
+ );
534
+ this.name = "UnmappedRunStatusError";
535
+ }
536
+ };
497
537
  function isAbortError(err) {
498
538
  if (!(err instanceof Error)) return false;
499
539
  return err.name === "AbortError" || err.message.includes("aborted");
@@ -512,13 +552,14 @@ function extractOrError(params, deps) {
512
552
  }
513
553
  }
514
554
  async function runPrompt(session, text, params, deps) {
515
- const run = await session.agent.send(text, { signal: session.abortController.signal });
555
+ const signal = armTurn(session);
556
+ const run = await session.agent.send(text, { signal });
516
557
  try {
517
558
  await translateStream({
518
559
  messages: run.stream(),
519
560
  conn: deps.conn,
520
561
  sessionId: params.sessionId,
521
- signal: session.abortController.signal,
562
+ signal,
522
563
  log: deps.log
523
564
  });
524
565
  } catch (err) {
@@ -527,7 +568,11 @@ async function runPrompt(session, text, params, deps) {
527
568
  }
528
569
  const result = await run.wait();
529
570
  const errorCode = result.status === "error" && result.error !== void 0 ? result.error.code ?? "" : "";
530
- return mapStopReason(result.status, errorCode);
571
+ const stopReason = mapStopReason(result.status, errorCode);
572
+ if (stopReason === void 0) {
573
+ throw new UnmappedRunStatusError(result.status, errorCode, result.error?.message);
574
+ }
575
+ return stopReason;
531
576
  }
532
577
  async function installPermissionOrError(session, params, deps) {
533
578
  if (deps.permissionMode === "auto") return void 0;
@@ -563,37 +608,12 @@ async function handlePrompt(params, deps) {
563
608
  } catch (err) {
564
609
  if (isAbortError(err)) return { response: { stopReason: "cancelled" } };
565
610
  const msg = err instanceof Error ? err.message : String(err);
566
- deps.log(`[acp] agent.send threw: ${msg}`);
611
+ const label = err instanceof UnmappedRunStatusError ? "run ended unmapped" : "agent.send threw";
612
+ deps.log(`[acp] ${label}: ${msg}`);
567
613
  return { error: { code: ACP_ERR.INTERNAL_ERROR, message: msg } };
568
614
  }
569
615
  }
570
616
 
571
- // src/session-store.ts
572
- var SessionStore = class {
573
- map = /* @__PURE__ */ new Map();
574
- create(session) {
575
- if (this.map.has(session.sessionId)) {
576
- throw new Error(`duplicate sessionId: ${session.sessionId}`);
577
- }
578
- this.map.set(session.sessionId, session);
579
- }
580
- get(sessionId) {
581
- return this.map.get(sessionId);
582
- }
583
- delete(sessionId) {
584
- return this.map.delete(sessionId);
585
- }
586
- list() {
587
- return [...this.map.values()];
588
- }
589
- size() {
590
- return this.map.size;
591
- }
592
- has(sessionId) {
593
- return this.map.has(sessionId);
594
- }
595
- };
596
-
597
617
  // src/serve.ts
598
618
  var DEFAULT_MAX_PROMPT_BYTES = 2 * 1024 * 1024;
599
619
  var DEFAULT_PERMISSION_TIMEOUT_MS = 6e4;
@@ -626,7 +646,10 @@ async function serveAcp(options) {
626
646
  });
627
647
  new acp2__namespace.AgentSideConnection((conn) => {
628
648
  return {
629
- initialize: async (params) => buildInitializeResponse(params, { capabilities: options.capabilities }),
649
+ initialize: async (params) => buildInitializeResponse(params, {
650
+ capabilities: options.capabilities,
651
+ ...options.info !== void 0 ? { info: options.info } : {}
652
+ }),
630
653
  authenticate: async (_params) => {
631
654
  return {};
632
655
  },
@@ -687,6 +710,7 @@ async function serveAcp(options) {
687
710
  );
688
711
  }
689
712
 
713
+ exports.InvalidAgentError = InvalidAgentError;
690
714
  exports.PromptTooLargeError = PromptTooLargeError;
691
715
  exports.serveAcp = serveAcp;
692
716
  //# sourceMappingURL=index.cjs.map