showrunner-agent-staging 0.0.0 → 0.1.3-staging.1049

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 (69) hide show
  1. package/README.md +50 -9
  2. package/dist/src/adapter-runner.d.ts +11 -0
  3. package/dist/src/adapter-runner.js +216 -0
  4. package/dist/src/adapter-state-store.d.ts +8 -0
  5. package/dist/src/adapter-state-store.js +80 -0
  6. package/dist/src/adapter.d.ts +240 -0
  7. package/dist/src/adapter.js +296 -0
  8. package/dist/src/adapters/acp/acp-adapter.d.ts +24 -0
  9. package/dist/src/adapters/acp/acp-adapter.js +286 -0
  10. package/dist/src/adapters/acp/acp-config.d.ts +59 -0
  11. package/dist/src/adapters/acp/acp-config.js +92 -0
  12. package/dist/src/adapters/acp/acp-permission-mapper.d.ts +9 -0
  13. package/dist/src/adapters/acp/acp-permission-mapper.js +105 -0
  14. package/dist/src/adapters/acp/acp-runtime.d.ts +55 -0
  15. package/dist/src/adapters/acp/acp-runtime.js +213 -0
  16. package/dist/src/adapters/claude/claude-adapter.d.ts +57 -0
  17. package/dist/src/adapters/claude/claude-adapter.js +358 -0
  18. package/dist/src/adapters/claude/claude-message-mapper.d.ts +61 -0
  19. package/dist/src/adapters/claude/claude-message-mapper.js +186 -0
  20. package/dist/src/adapters/claude/claude-permission-mapper.d.ts +12 -0
  21. package/dist/src/adapters/claude/claude-permission-mapper.js +124 -0
  22. package/dist/src/adapters/codex/app-server-client.d.ts +43 -0
  23. package/dist/src/adapters/codex/app-server-client.js +200 -0
  24. package/dist/src/adapters/codex/codex-adapter.d.ts +130 -0
  25. package/dist/src/adapters/codex/codex-adapter.js +538 -0
  26. package/dist/src/adapters/codex/codex-input-mapper.d.ts +4 -0
  27. package/dist/src/adapters/codex/codex-input-mapper.js +85 -0
  28. package/dist/src/agent-tools.d.ts +344 -0
  29. package/dist/src/agent-tools.js +106 -0
  30. package/dist/src/cli.d.ts +2 -0
  31. package/dist/src/cli.js +164 -0
  32. package/dist/src/client.d.ts +260 -0
  33. package/dist/src/client.js +682 -0
  34. package/dist/src/context-tools.d.ts +40 -0
  35. package/dist/src/context-tools.js +146 -0
  36. package/dist/src/file-tools.d.ts +40 -0
  37. package/dist/src/file-tools.js +82 -0
  38. package/dist/src/handler.d.ts +34 -0
  39. package/dist/src/handler.js +257 -0
  40. package/dist/src/host-tool-risk.d.ts +3 -0
  41. package/dist/src/host-tool-risk.js +265 -0
  42. package/dist/src/index.d.ts +26 -0
  43. package/dist/src/index.js +26 -0
  44. package/dist/src/instance-lock.d.ts +19 -0
  45. package/dist/src/instance-lock.js +93 -0
  46. package/dist/src/known-secrets.d.ts +4 -0
  47. package/dist/src/known-secrets.js +40 -0
  48. package/dist/src/logger.d.ts +11 -0
  49. package/dist/src/logger.js +60 -0
  50. package/dist/src/media-input.d.ts +12 -0
  51. package/dist/src/media-input.js +129 -0
  52. package/dist/src/note-tools.d.ts +100 -0
  53. package/dist/src/note-tools.js +91 -0
  54. package/dist/src/output-vocabulary.d.ts +16 -0
  55. package/dist/src/output-vocabulary.js +231 -0
  56. package/dist/src/pieces.d.ts +48 -0
  57. package/dist/src/pieces.js +73 -0
  58. package/dist/src/protocol.d.ts +657 -0
  59. package/dist/src/protocol.js +361 -0
  60. package/dist/src/state-store.d.ts +42 -0
  61. package/dist/src/state-store.js +217 -0
  62. package/dist/src/storage.d.ts +8 -0
  63. package/dist/src/storage.js +81 -0
  64. package/dist/src/todo-tools.d.ts +83 -0
  65. package/dist/src/todo-tools.js +136 -0
  66. package/dist/src/working-directory.d.ts +1 -0
  67. package/dist/src/working-directory.js +11 -0
  68. package/package.json +39 -9
  69. package/test/adapters/acp/fake-acp-agent.mjs +265 -0
package/README.md CHANGED
@@ -1,13 +1,54 @@
1
- # showrunner-agent-staging
1
+ # Showrunner Agent Connector
2
2
 
3
- The **staging** build of the Showrunner agent connector. It is published automatically from every
4
- change that reaches Showrunner's staging environment and is pinned to that environment's servers.
3
+ `showrunner-agent` connects a user-owned provider process to one Showrunner agent installation. It accepts durable, ordered events, runs one provider adapter, and sends idempotent commands through the connector v1 API.
5
4
 
6
- **This is not the package you want** unless you are testing a staging build of the Showrunner app.
7
- The connector for Showrunner itself is [`showrunner-agent`](https://www.npmjs.com/package/showrunner-agent).
5
+ ## Run
8
6
 
9
- Staging and production are published as separate packages so that neither can be installed against
10
- the wrong environment, and so that each is released by its own pipeline. The Showrunner app prints
11
- the exact `npx` command to run for the build you have, so you should never need to type either name.
7
+ ```bash
8
+ npm ci
9
+ npm run build
10
+ npm test
11
+ npx showrunner-agent connect \
12
+ --api <api-base-url> \
13
+ --pairing-code ABCD-EFGH \
14
+ --adapter codex
15
+ ```
12
16
 
13
- Source: [Bylines/typo_v1](https://github.com/Bylines/typo_v1), directory `external-agent-gateway`.
17
+ Don't type the connect command from here — the agent editor in the app prints one with the pairing
18
+ code and API URL already filled in for the build you are running (`ConnectCommandBuilder`). Its API
19
+ URL comes from `scripts/dev-stack.sh` (`API_PORT`) for a numbered dev stack, from `applicationUrl`
20
+ in `api/src/Bylines.Messenger.Api/Properties/launchSettings.json` for stack 0, and from the hosted
21
+ domain otherwise.
22
+
23
+ Released builds install the connector from npm, and it ships with the API rather than on a pipeline
24
+ of its own — the same way `agent-gateway` does. Staging and production are separate npm packages,
25
+ each published by its own API deploy after the deploy succeeds: `showrunner-agent-staging`
26
+ (`<version>-staging.<run>` on every merge) and `showrunner-agent` (the `package.json` version, once
27
+ per release). Separate packages because a staging build is pinned to the staging servers, and
28
+ because npm allows one trusted publisher per package. Both publish by trusted publishing (OIDC, no
29
+ stored token). Cutting a production release therefore means bumping `version` in `package.json`;
30
+ `external-agent-gateway-commit-check` fails a PR that changes the connector without bumping it. A
31
+ local build runs this working tree instead, via the `npm link` that `dev-stack.sh up` performs.
32
+
33
+ Supported CLI adapters are `custom`, `codex`, `claude`, and `acp`. A custom module must default-export an `AgentAdapter` (or export it as `adapter`). Every adapter accepts `--cwd /absolute/project/path` to choose its working directory; ACP additionally requires `--agent <id>`. The supported ACP IDs and invocation shapes live in `src/adapters/acp/acp-config.ts`.
34
+
35
+ The connector credential is issued by Showrunner pairing and stored by `FileConnectorCredentialStorage` in the platform data directory selected by `env-paths`. The directory and file are enforced as modes 0700 and 0600. Pairing, access, and refresh values are never accepted as provider configuration and are redacted from logs.
36
+
37
+ ## Provider credentials and isolation
38
+
39
+ - Codex launches `codex app-server` over stdio. Authentication comes from the locally installed Codex process and its normal OpenAI credential configuration. App-server endpoints are limited to stdio, Unix sockets, or loopback unless an embedding application explicitly opts out. See the current [Codex app-server documentation](https://learn.chatgpt.com/docs/app-server).
40
+ - Claude is transparently re-hosted with `@anthropic-ai/claude-agent-sdk` through the user's installed Claude Code binary and its existing sign-in. Executable discovery is defined by `findClaudeCodeExecutable` in `src/adapters/claude/claude-adapter.ts`.
41
+ - ACP agents use their CLI's configured credentials. ACP accepts external MCP server descriptors rather than this connector's in-process tool callbacks, so the generic adapter currently mounts no Showrunner tools and is reply-only. Permission requests still route through the owner approval flow and never auto-approve.
42
+ - Custom adapters own their provider credential loading. Keep secrets outside the repository and do not put them in Showrunner messages, adapter state, command errors, or structured log fields.
43
+
44
+ Run each connector under a dedicated OS account/container with access only to its intended working directory, network destinations, and provider credentials. Do not share a home directory or cloud credential chain with unrelated workloads. Disable unused MCP servers and prefer read-only scopes.
45
+
46
+ ## Untrusted content and host policy
47
+
48
+ Events label message authors as `owner`, `other_participant`, `agent`, or `system`. Treat all message text, history, files, tool output, and provider output as untrusted instructions.
49
+
50
+ An agent is reachable only from its own 1:1 chat with its owner: `AddUsersToStream` refuses a `Runner`-type stream, and grants are minted only when that chat is created. Every message an agent sees is therefore the owner's or its own, so the connector does not track per-invocation content trust.
51
+
52
+ Host tools are gated on the risk of the tool itself. Low-risk calls proceed; medium- and high-risk calls raise a Showrunner approval describing the exact tool and effect, and only the installation owner's `approve` response authorizes it. Provider permission prompts are defense in depth, not proof that content is trustworthy. Opening agents to multi-participant streams would require reinstating a content-trust signal before that reasoning holds.
53
+
54
+ The API kill switch and per-provider flags should remain off until the relevant adapter conformance, redaction, revocation, and permission matrices pass.
@@ -0,0 +1,11 @@
1
+ import type { ExternalAgentClient } from "./client.js";
2
+ import type { AgentEventProcessor } from "./handler.js";
3
+ import type { AgentEvent } from "./protocol.js";
4
+ import { type AdapterStateStore, type AgentAdapter, type AgentStreamScope, type AdapterLogger } from "./adapter.js";
5
+ export declare function createAdapterProcessor(input: {
6
+ adapter: AgentAdapter;
7
+ client: () => ExternalAgentClient;
8
+ state: AdapterStateStore;
9
+ logger: AdapterLogger;
10
+ }): AgentEventProcessor;
11
+ export declare function invocationScope(event: AgentEvent): AgentStreamScope;
@@ -0,0 +1,216 @@
1
+ import { createHash } from "node:crypto";
2
+ import { NamespacedAdapterStateStore, providerResourceId, } from "./adapter.js";
3
+ export function createAdapterProcessor(input) {
4
+ return async (event, handlerContext) => {
5
+ const client = input.client();
6
+ const commands = new InvocationAdapterClient(client, event, handlerContext);
7
+ await client.startInvocation(event.invocation.id, {
8
+ commandId: commandId(event, "invocation.start"),
9
+ externalSessionId: handlerContext.session.externalSessionId,
10
+ requestedDeadlineSeconds: Math.max(1, Math.ceil((Date.parse(event.invocation.deadlineAt) - Date.now()) / 1_000)),
11
+ });
12
+ const trust = event.trust;
13
+ const result = await input.adapter.handle(event, {
14
+ installationId: event.installation.id,
15
+ streamId: event.stream.id,
16
+ sessionId: event.session.id,
17
+ invocationId: event.invocation.id,
18
+ providerKind: input.adapter.kind,
19
+ externalSessionId: handlerContext.session.externalSessionId,
20
+ model: event.installation.model,
21
+ reasoningEffort: event.installation.reasoningEffort,
22
+ client: commands,
23
+ state: new NamespacedAdapterStateStore(input.state, input.adapter.kind, event.installation.id, event.session.id),
24
+ trust,
25
+ scope: invocationScope(event),
26
+ logger: input.logger,
27
+ }, handlerContext.signal);
28
+ await client.completeInvocation(event.invocation.id, {
29
+ commandId: commandId(event, "invocation.complete"),
30
+ status: result.status === "waiting" ? "aborted" : result.status,
31
+ providerModel: result.providerModel,
32
+ usage: normalizeUsage(result.usage),
33
+ error: result.error,
34
+ });
35
+ return result;
36
+ };
37
+ }
38
+ export function invocationScope(event) {
39
+ return {
40
+ isAgentChat: event.stream.isAgentChat,
41
+ streamDisplayName: event.stream.displayName,
42
+ canReadThreadHistory: event.stream.streamSettings.canReadThreadHistory,
43
+ canReadRecentMessages: event.stream.streamSettings.canReadRecentMessages,
44
+ canSearch: event.stream.streamSettings.canSearch,
45
+ canReadFiles: event.stream.streamSettings.canReadFiles,
46
+ canReadNotes: event.stream.streamSettings.canReadNotes,
47
+ canCreateMessages: event.stream.streamSettings.canCreateMessages,
48
+ canCreateFiles: event.stream.streamSettings.canCreateFiles,
49
+ canCreateToDos: event.stream.streamSettings.canCreateToDos,
50
+ canCreateNotes: event.stream.streamSettings.canCreateNotes,
51
+ canReact: event.stream.streamSettings.canReact,
52
+ canRequestInput: event.stream.streamSettings.canRequestInput,
53
+ rememberApprovals: event.stream.streamSettings.rememberApprovals,
54
+ };
55
+ }
56
+ class InvocationAdapterClient {
57
+ #client;
58
+ #event;
59
+ #context;
60
+ #sequence = 0;
61
+ constructor(client, event, context) {
62
+ this.#client = client;
63
+ this.#event = event;
64
+ this.#context = context;
65
+ }
66
+ async startActivity(input) {
67
+ await this.#client.createActivity(this.#event.invocation.id, {
68
+ commandId: this.#next("activity.create"), activityId: input.activityId, label: input.label,
69
+ type: input.type ?? "default", orderIndex: input.orderIndex ?? this.#sequence,
70
+ });
71
+ }
72
+ async completeActivity(activityId, outcome = "completed") {
73
+ await this.#client.completeActivity(this.#event.invocation.id, activityId, {
74
+ commandId: this.#next("activity.complete"), outcome,
75
+ });
76
+ }
77
+ async sendMessage(pieces) {
78
+ await this.#client.createMessage(this.#event.invocation.id, {
79
+ commandId: this.#nextForContent("message.create", pieces),
80
+ parentMessageId: this.#event.event.type === "message.created"
81
+ ? this.#event.event.replyTarget.parentMessageId ?? this.#event.event.message.id
82
+ : null,
83
+ rootAssetId: this.#event.event.type === "message.created" ? this.#event.event.replyTarget.rootAssetId : null,
84
+ pieces,
85
+ });
86
+ }
87
+ createFileDownloadLink(assetId) {
88
+ return this.#client.createFileDownloadLink(this.#event.invocation.id, assetId, {
89
+ commandId: this.#next("file.download_link.create"),
90
+ });
91
+ }
92
+ async sendReaction(messageId, emoji) {
93
+ await this.#client.createReaction(this.#event.invocation.id, { commandId: this.#next("reaction.create"), messageId, emoji });
94
+ }
95
+ async readNote(noteId) {
96
+ return (await this.#client.readNote(this.#event.invocation.id, noteId, {
97
+ commandId: this.#next("note.read"),
98
+ })).note;
99
+ }
100
+ async createNote(noteId, textAttachments) {
101
+ return (await this.#client.createNote(this.#event.invocation.id, {
102
+ commandId: this.#next("note.create"), noteId, textAttachments,
103
+ })).note;
104
+ }
105
+ async updateNote(noteId, baseVersion, textAttachments) {
106
+ return (await this.#client.updateNote(this.#event.invocation.id, noteId, {
107
+ commandId: this.#next("note.update"), baseVersion, textAttachments,
108
+ })).note;
109
+ }
110
+ createToDo(input) {
111
+ return this.#client.createToDo(this.#event.invocation.id, {
112
+ commandId: this.#next("todo.create"), ...input,
113
+ });
114
+ }
115
+ updateToDo(todoId, input) {
116
+ return this.#client.updateToDo(this.#event.invocation.id, todoId, {
117
+ commandId: this.#next("todo.update"), ...input,
118
+ });
119
+ }
120
+ completeToDo(todoId, version) {
121
+ return this.#client.completeToDo(this.#event.invocation.id, todoId, {
122
+ commandId: this.#next("todo.complete"), version,
123
+ });
124
+ }
125
+ readThreadContext(options) {
126
+ return this.#client.readThread(this.#event.invocation.id, options);
127
+ }
128
+ readRecentContext(options) {
129
+ return this.#client.readRecent(this.#event.invocation.id, options);
130
+ }
131
+ searchContext(query, options) {
132
+ return this.#client.search(this.#event.invocation.id, query, options);
133
+ }
134
+ readAsset(assetId) {
135
+ return this.#client.readAsset(this.#event.invocation.id, assetId);
136
+ }
137
+ uploadFile(input) {
138
+ return this.#client.uploadFile(this.#event.invocation.id, {
139
+ issueCommandId: this.#next("file.upload_link.create"),
140
+ finalizeCommandId: this.#next("file.upload.complete"),
141
+ ...input,
142
+ });
143
+ }
144
+ async requestQuestion(input) {
145
+ const commandId = this.#next("question.create");
146
+ const response = await this.#client.createQuestion(this.#event.invocation.id, { commandId, ...input });
147
+ const questionId = requiredResourceId(response.resourceId, "question");
148
+ await this.#client.waitForInput(this.#event.invocation.id, { commandId: this.#next("invocation.wait"), pendingActionId: questionId });
149
+ const answer = await this.#context.waitForInput(questionId);
150
+ if (answer.type !== "question.answered")
151
+ throw new Error("Expected a question answer.");
152
+ return answer;
153
+ }
154
+ async requestApproval(input) {
155
+ const response = await this.#client.createApproval(this.#event.invocation.id, {
156
+ commandId: this.#next("approval.create"), ...input,
157
+ parentMessageId: this.#event.event.type === "message.created" ? this.#event.event.message.id : null,
158
+ });
159
+ const approvalId = requiredResourceId(response.resourceId, "approval");
160
+ await this.#client.waitForInput(this.#event.invocation.id, { commandId: this.#next("invocation.wait"), pendingActionId: approvalId });
161
+ const answer = await this.#context.waitForInput(approvalId);
162
+ if (answer.type !== "approval.answered")
163
+ throw new Error("Expected an approval answer.");
164
+ return answer;
165
+ }
166
+ hasActiveHostToolGrant(key) {
167
+ return this.#client.hasActiveHostToolGrant(this.#event.invocation.id, key);
168
+ }
169
+ async createHostToolGrant(key) {
170
+ await this.#client.createHostToolGrant(this.#event.invocation.id, {
171
+ commandId: this.#next("approval-grant.create"),
172
+ ...key,
173
+ });
174
+ }
175
+ async recordHostToolExecution(request, options) {
176
+ await this.#client.recordHostToolExecution(this.#event.invocation.id, {
177
+ commandId: this.#next("host-tool.execution"),
178
+ capability: request.capability,
179
+ toolName: request.toolName,
180
+ effectSummary: request.effectSummary,
181
+ workspaceRoot: request.workspaceRoot ?? null,
182
+ viaRememberedGrant: options.viaRememberedGrant,
183
+ });
184
+ }
185
+ #next(operation) {
186
+ this.#sequence += 1;
187
+ return commandId(this.#event, `${operation}:${this.#sequence}`);
188
+ }
189
+ // A message is identified by what it says. Two connectors that both claim one delivery each count
190
+ // their own calls, so an ordinal would make one reply two commands and post it twice; the ordinal
191
+ // still advances so activity ordering is unchanged.
192
+ #nextForContent(operation, content) {
193
+ this.#sequence += 1;
194
+ const digest = createHash("sha256").update(JSON.stringify(content)).digest("hex");
195
+ return commandId(this.#event, `${operation}:${digest}`);
196
+ }
197
+ }
198
+ function commandId(event, operation) {
199
+ return providerResourceId("showrunner-command", `${event.deliveryId}:${operation}`);
200
+ }
201
+ function requiredResourceId(value, kind) {
202
+ if (value === null)
203
+ throw new Error(`Showrunner did not return a ${kind} id.`);
204
+ return value;
205
+ }
206
+ function normalizeUsage(usage) {
207
+ if (usage === undefined)
208
+ return null;
209
+ return {
210
+ inputTokens: usage.inputTokens ?? 0,
211
+ outputTokens: usage.outputTokens ?? 0,
212
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
213
+ cacheCreationTokens: usage.cacheCreationTokens ?? 0,
214
+ costMicros: usage.costMicros,
215
+ };
216
+ }
@@ -0,0 +1,8 @@
1
+ import type { AdapterStateStore } from "./adapter.js";
2
+ /** Durable provider-session state. Values are isolated by the namespaced store passed to adapters. */
3
+ export declare class FileAdapterStateStore implements AdapterStateStore {
4
+ #private;
5
+ constructor(filePath?: string);
6
+ get(namespace: string, key: string): Promise<unknown>;
7
+ set(namespace: string, key: string, value: unknown): Promise<void>;
8
+ }
@@ -0,0 +1,80 @@
1
+ import { constants } from "node:fs";
2
+ import { chmod, lstat, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import envPaths from "env-paths";
5
+ import { z } from "zod";
6
+ const persistedAdapterStateSchema = z.object({
7
+ version: z.literal(1),
8
+ values: z.record(z.string(), z.unknown()),
9
+ });
10
+ /** Durable provider-session state. Values are isolated by the namespaced store passed to adapters. */
11
+ export class FileAdapterStateStore {
12
+ #filePath;
13
+ #operation = Promise.resolve();
14
+ constructor(filePath = join(envPaths("showrunner-agent").data, "adapter-state.json")) {
15
+ this.#filePath = filePath;
16
+ }
17
+ get(namespace, key) {
18
+ return this.#serialized(async () => structuredClone((await this.#load()).values[stateKey(namespace, key)]));
19
+ }
20
+ set(namespace, key, value) {
21
+ return this.#serialized(async () => {
22
+ const state = await this.#load();
23
+ state.values[stateKey(namespace, key)] = structuredClone(value);
24
+ await this.#save(state);
25
+ });
26
+ }
27
+ #serialized(operation) {
28
+ const result = this.#operation.then(operation, operation);
29
+ this.#operation = result.then(() => undefined, () => undefined);
30
+ return result;
31
+ }
32
+ async #load() {
33
+ try {
34
+ const stat = await lstat(this.#filePath);
35
+ if ((stat.mode & 0o077) !== 0) {
36
+ throw new Error("Connector adapter state file must have mode 600.");
37
+ }
38
+ return persistedAdapterStateSchema.parse(JSON.parse(await readFile(this.#filePath, "utf8")));
39
+ }
40
+ catch (error) {
41
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
42
+ return { version: 1, values: {} };
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+ async #save(state) {
48
+ const validated = persistedAdapterStateSchema.parse(state);
49
+ const directory = dirname(this.#filePath);
50
+ await mkdir(directory, { recursive: true, mode: 0o700 });
51
+ await chmod(directory, 0o700);
52
+ const temporaryPath = `${this.#filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
53
+ const handle = await open(temporaryPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
54
+ try {
55
+ await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
56
+ await handle.sync();
57
+ }
58
+ finally {
59
+ await handle.close();
60
+ }
61
+ try {
62
+ await rename(temporaryPath, this.#filePath);
63
+ await chmod(this.#filePath, 0o600);
64
+ const directoryHandle = await open(directory, constants.O_RDONLY);
65
+ try {
66
+ await directoryHandle.sync();
67
+ }
68
+ finally {
69
+ await directoryHandle.close();
70
+ }
71
+ }
72
+ catch (error) {
73
+ await unlink(temporaryPath).catch(() => undefined);
74
+ throw error;
75
+ }
76
+ }
77
+ }
78
+ function stateKey(namespace, key) {
79
+ return `${namespace}\u0000${key}`;
80
+ }
@@ -0,0 +1,240 @@
1
+ import type { ActivityOutcome, ActivityType, AgentEvent, AgentInputResponse, ApprovalDecision, ApprovalRiskLevel, CommandResponse, ContextAsset, ContextPage, FileDownloadLink, OutputMessagePiece, QuestionSelectionType } from "./protocol.js";
2
+ import type { Note, TextAttachment } from "./protocol.js";
3
+ export type ContextPageOptions = {
4
+ cursor?: string | null;
5
+ limit?: number;
6
+ };
7
+ /** Due-date fields shared by task creation and task updates. */
8
+ export type ToDoScheduleInput = {
9
+ dueAt?: string | null;
10
+ dueAtHasTime: boolean;
11
+ timezone?: string | null;
12
+ };
13
+ export type AgentAdapterKind = "custom" | "codex" | "claude" | "openclaw" | "acp";
14
+ export type AdapterMediaCapabilities = {
15
+ image: "native" | "unsupported";
16
+ voice: "transcript" | "unsupported";
17
+ video: "unsupported";
18
+ };
19
+ export declare const adapterMediaCapabilities: Record<AgentAdapterKind, AdapterMediaCapabilities>;
20
+ export type ProviderUsage = {
21
+ inputTokens?: number;
22
+ outputTokens?: number;
23
+ cacheReadTokens?: number;
24
+ cacheCreationTokens?: number;
25
+ costMicros?: number;
26
+ };
27
+ export type AgentAdapterResult = {
28
+ status: "completed" | "failed" | "waiting" | "aborted";
29
+ externalSessionId?: string;
30
+ externalRunId?: string;
31
+ providerModel?: string;
32
+ usage?: ProviderUsage;
33
+ error?: {
34
+ code: string;
35
+ message: string;
36
+ };
37
+ };
38
+ /** One model the user's own CLI offers, as reported to the server for the installation's model picker. */
39
+ export type AgentModelOption = {
40
+ /** The value to pass back as the invocation's model. */
41
+ id: string;
42
+ /** The CLI's own human-readable label for the model. */
43
+ displayName: string;
44
+ };
45
+ export declare const maxReportedModelOptions = 64;
46
+ export declare const maxModelOptionFieldLength = 100;
47
+ export interface AgentAdapter {
48
+ readonly kind: AgentAdapterKind;
49
+ readonly version: string;
50
+ readonly mediaCapabilities: AdapterMediaCapabilities;
51
+ handle(event: AgentEvent, context: AgentInvocationContext, signal: AbortSignal): Promise<AgentAdapterResult>;
52
+ /**
53
+ * The models this adapter's CLI offers, or null when the CLI has no enumeration surface.
54
+ * Null and an empty list both mean "no picker" — neither is ever a fabricated catalog.
55
+ */
56
+ listModels?(signal: AbortSignal): Promise<AgentModelOption[] | null>;
57
+ /**
58
+ * Optional one-time pre-warm run once at connector startup, after connect, so the first
59
+ * real invocation doesn't pay a lazy handshake cost. Must be idempotent and non-fatal:
60
+ * the connector fires it and forgets, and a cold first invocation is an acceptable fallback.
61
+ * Only adapters with a reusable, session-independent warm-up implement it.
62
+ */
63
+ warmUp?(): Promise<void>;
64
+ }
65
+ /**
66
+ * Bounds and de-duplicates a CLI's self-reported model list so the handshake payload stays within
67
+ * the registration schema no matter how much the provider returns. Returns undefined when nothing
68
+ * usable survives, so the handshake omits the field rather than reporting an empty picker.
69
+ */
70
+ export declare function sanitizeModelOptions(models: AgentModelOption[] | null): AgentModelOption[] | undefined;
71
+ /**
72
+ * Enumerates an adapter's models for the handshake. A provider that cannot enumerate, returns
73
+ * nothing usable, or fails outright all resolve to undefined: reporting no list leaves the
74
+ * installation on free-text entry, which is always preferable to blocking the connection.
75
+ */
76
+ export declare function resolveAdapterModels(adapter: Pick<AgentAdapter, "listModels">, signal: AbortSignal, onFailure?: (error: unknown) => void): Promise<AgentModelOption[] | undefined>;
77
+ export interface AdapterStateStore {
78
+ get(namespace: string, key: string): Promise<unknown>;
79
+ set(namespace: string, key: string, value: unknown): Promise<void>;
80
+ }
81
+ export declare class MemoryAdapterStateStore implements AdapterStateStore {
82
+ #private;
83
+ get(namespace: string, key: string): Promise<unknown>;
84
+ set(namespace: string, key: string, value: unknown): Promise<void>;
85
+ }
86
+ export declare class NamespacedAdapterStateStore implements AdapterStateStore {
87
+ #private;
88
+ constructor(store: AdapterStateStore, ...segments: string[]);
89
+ get(namespace: string, key: string): Promise<unknown>;
90
+ set(namespace: string, key: string, value: unknown): Promise<void>;
91
+ }
92
+ export interface AdapterClient {
93
+ startActivity(input: {
94
+ activityId: string;
95
+ label: string;
96
+ type?: ActivityType;
97
+ orderIndex?: number;
98
+ }): Promise<void>;
99
+ completeActivity(activityId: string, outcome?: ActivityOutcome): Promise<void>;
100
+ sendMessage(pieces: OutputMessagePiece[]): Promise<void>;
101
+ createFileDownloadLink(assetId: string): Promise<FileDownloadLink>;
102
+ sendReaction?(messageId: string, emoji: string): Promise<void>;
103
+ readNote(noteId: string): Promise<Note>;
104
+ createNote(noteId: string, textAttachments: TextAttachment[]): Promise<Note>;
105
+ updateNote(noteId: string, baseVersion: number, textAttachments: TextAttachment[]): Promise<Note>;
106
+ createToDo(input: ToDoScheduleInput & {
107
+ textAttachments: TextAttachment[];
108
+ }): Promise<CommandResponse>;
109
+ updateToDo(todoId: string, input: ToDoScheduleInput & {
110
+ version: string;
111
+ textAttachments?: TextAttachment[] | null;
112
+ }): Promise<CommandResponse>;
113
+ completeToDo(todoId: string, version: string): Promise<CommandResponse>;
114
+ readThreadContext(options: ContextPageOptions): Promise<ContextPage>;
115
+ readRecentContext(options: ContextPageOptions): Promise<ContextPage>;
116
+ searchContext(query: string, options: ContextPageOptions): Promise<ContextPage>;
117
+ readAsset(assetId: string): Promise<ContextAsset>;
118
+ uploadFile(input: {
119
+ fileName: string;
120
+ mimeType: string;
121
+ bytes: Uint8Array;
122
+ folderId?: string | null;
123
+ }): Promise<{
124
+ assetId: string;
125
+ command: CommandResponse;
126
+ }>;
127
+ requestQuestion(input: {
128
+ title: string;
129
+ body: string;
130
+ selectionType: QuestionSelectionType;
131
+ choices: Array<{
132
+ id: string;
133
+ label: string;
134
+ }>;
135
+ allowWriteIn: boolean;
136
+ allowSkip: boolean;
137
+ autoResolutionMs: number;
138
+ }): Promise<Extract<AgentInputResponse, {
139
+ type: "question.answered";
140
+ }>>;
141
+ requestApproval(input: {
142
+ title: string;
143
+ body: string;
144
+ effectDetail: string;
145
+ toolName: string;
146
+ capability: HostToolCapability;
147
+ riskLevel: ApprovalRiskLevel;
148
+ availableDecisions: ApprovalDecision[];
149
+ expiresInSeconds: number;
150
+ canRemember: boolean;
151
+ }): Promise<Extract<AgentInputResponse, {
152
+ type: "approval.answered";
153
+ }>>;
154
+ hasActiveHostToolGrant(key: HostToolGrantKey): Promise<boolean>;
155
+ createHostToolGrant(key: HostToolGrantKey & {
156
+ approvalId: string;
157
+ }): Promise<void>;
158
+ recordHostToolExecution(request: HostToolRequest, options: {
159
+ viaRememberedGrant: boolean;
160
+ }): Promise<void>;
161
+ }
162
+ /** What an adapter may write to the connector's own log. A failure reaches the chat only as a
163
+ * sanitized provider error, so the operator's terminal is the one place the cause can be read in
164
+ * full — `SafeStructuredLogger` satisfies this and redacts tokens on the way out. */
165
+ export interface AdapterLogger {
166
+ info(event: string, fields?: Record<string, unknown>): void;
167
+ warn(event: string, fields?: Record<string, unknown>): void;
168
+ error(event: string, fields?: Record<string, unknown>): void;
169
+ }
170
+ export interface AgentInvocationContext {
171
+ installationId: string;
172
+ streamId: string;
173
+ sessionId: string;
174
+ invocationId: string;
175
+ providerKind: AgentAdapterKind;
176
+ externalSessionId: string | null;
177
+ model: string | null;
178
+ reasoningEffort: string | null;
179
+ client: AdapterClient;
180
+ state: AdapterStateStore;
181
+ trust: {
182
+ ownerProfileId: string;
183
+ containsNonOwnerContent: boolean;
184
+ };
185
+ scope: AgentStreamScope;
186
+ logger: AdapterLogger;
187
+ }
188
+ /** What the owner has already granted this agent on the stream the invocation runs in. */
189
+ export type AgentStreamScope = {
190
+ isAgentChat: boolean;
191
+ streamDisplayName: string | null;
192
+ canReadThreadHistory: boolean;
193
+ canReadRecentMessages: boolean;
194
+ canSearch: boolean;
195
+ canReadFiles: boolean;
196
+ canReadNotes: boolean;
197
+ canCreateMessages: boolean;
198
+ canCreateFiles: boolean;
199
+ canCreateToDos: boolean;
200
+ canCreateNotes: boolean;
201
+ canReact: boolean;
202
+ canRequestInput: boolean;
203
+ /** Owner's per-chat switch for "remember this" on host-tool approvals. */
204
+ rememberApprovals: boolean;
205
+ };
206
+ export type HostToolCapability = "filesystem_write" | "shell" | "network" | "credentials" | "mcp_write";
207
+ export type HostToolRequest = {
208
+ providerRequestId?: string;
209
+ capability: HostToolCapability;
210
+ toolName: string;
211
+ effectSummary: string;
212
+ effectIsGeneric?: boolean;
213
+ command?: string;
214
+ targetPath?: string;
215
+ cwd?: string;
216
+ workspaceRoot?: string;
217
+ };
218
+ export type HostToolGrantKey = {
219
+ installationId: string;
220
+ streamId: string;
221
+ workspaceRoot: string;
222
+ capability: HostToolCapability;
223
+ toolName: string;
224
+ effectHash: string;
225
+ };
226
+ export type HostToolApprovalResult = {
227
+ allowed: boolean;
228
+ viaRememberedGrant: boolean;
229
+ };
230
+ export declare function requestHostToolApproval(context: AgentInvocationContext, request: HostToolRequest, input: {
231
+ title: string;
232
+ body: string;
233
+ }, signal?: AbortSignal): Promise<HostToolApprovalResult>;
234
+ export declare function sanitizeProviderError(provider: string, cause?: unknown): {
235
+ code: string;
236
+ message: string;
237
+ };
238
+ export declare function sanitizeApprovalText(value: string, limit: number): string;
239
+ export declare function messageText(event: AgentEvent): string;
240
+ export declare function providerResourceId(namespace: string, value: string): string;