@mclawnet/backend-types 0.1.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.
@@ -0,0 +1,278 @@
1
+ /**
2
+ * BackendAdapter — pluggable backend for AI model interaction.
3
+ *
4
+ * Implementations:
5
+ * - ClaudeCodeAdapter (@mclawnet/claude-adapter): spawn `claude` CLI
6
+ * - OpenClawAdapter: spawn `openclaw gateway` (legacy)
7
+ * - CodexAdapter: M3.S3 — `codex app-server --listen stdio://`
8
+ *
9
+ * M3.S1 introduces:
10
+ * - Typed permission flow (PermissionRequest / PermissionDecision)
11
+ * - Typed BackendOutput envelope replacing the historical `onOutput(msg: unknown)`
12
+ * - `backendSessionId` replaces the previous `claudeSessionId` (see plan D7).
13
+ */
14
+ import type { BackendManifestEntry } from "@mclawnet/shared";
15
+ export interface PermissionRequest {
16
+ /** Stable identifier for the pending tool call. */
17
+ callId: string;
18
+ /** Tool name as reported by the backend (e.g. 'shell', 'apply_patch', 'Write'). */
19
+ toolName: string;
20
+ /** Raw tool input as the backend supplied it. */
21
+ input: unknown;
22
+ /** Optional backend-supplied context (sandbox, cwd, reason, ...). */
23
+ meta?: {
24
+ backend?: "claude" | "codex" | string;
25
+ reason?: string;
26
+ cwd?: string;
27
+ [k: string]: unknown;
28
+ };
29
+ }
30
+ /**
31
+ * S0 finding: neither claude `--permission-mode default` (no wire channel)
32
+ * nor codex `ReviewDecision` supports "approve with modifiedInput", so the
33
+ * S1 contract is a 4-state enum. `allow_session` may degrade to `allow` for
34
+ * backends that don't track session-scoped approvals.
35
+ */
36
+ export interface PermissionDecision {
37
+ callId: string;
38
+ decision: "allow" | "allow_session" | "deny" | "abort";
39
+ reason?: string;
40
+ }
41
+ export type BackendOutput = {
42
+ kind: "assistant_text";
43
+ text: string;
44
+ } | {
45
+ kind: "tool_use";
46
+ toolName: string;
47
+ input: unknown;
48
+ callId: string;
49
+ } | {
50
+ kind: "tool_result";
51
+ callId: string;
52
+ output: unknown;
53
+ isError?: boolean;
54
+ } | {
55
+ kind: "system";
56
+ subtype: string;
57
+ data: unknown;
58
+ }
59
+ /**
60
+ * Adapter-internal diagnostic only. Hub and wire layers MUST NOT consume
61
+ * `raw` — promote a new typed variant instead when hub needs to read it.
62
+ */
63
+ | {
64
+ kind: "raw";
65
+ backend: string;
66
+ payload: unknown;
67
+ };
68
+ export interface BackendProcess {
69
+ /** Unique identifier (e.g., Claude session ID) */
70
+ id: string;
71
+ /** Working directory for this process */
72
+ workDir: string;
73
+ /**
74
+ * OS process id of the underlying backend process, if applicable.
75
+ *
76
+ * Optional because not every backend is a child process — a future in-process
77
+ * adapter (mock / stubbed / WASM) wouldn't have one. SessionManager uses pid
78
+ * for the PR-C checkpoint: a restart probes `process.kill(pid, 0)` to decide
79
+ * whether the recorded session is `dead` or an `orphan`. Adapters that omit
80
+ * pid forfeit the dead/orphan classification — checkpointed sessions will
81
+ * silently appear as "no longer here", which is also acceptable behavior.
82
+ */
83
+ pid?: number;
84
+ /** Kill the backend process */
85
+ kill(): Promise<void>;
86
+ /**
87
+ * Whether the backend process is still alive and capable of accepting input.
88
+ * Returns false if the process has exited, is being killed, or its stdin is no
89
+ * longer writable. Used by SessionManager.isHealthy() to detect stale entries.
90
+ */
91
+ isAlive(): boolean;
92
+ }
93
+ export interface SpawnOptions {
94
+ /** ClawNet session ID */
95
+ sessionId: string;
96
+ /** Working directory for the backend process */
97
+ workDir?: string;
98
+ /** Resume a previous session (e.g., Claude --resume UUID) */
99
+ resumeId?: string;
100
+ /** System prompt to append (e.g., role prompt for swarm mode) */
101
+ systemPrompt?: string;
102
+ /** Load BrainCore + CoreSkill via --add-dir */
103
+ useBrainCore?: boolean;
104
+ /** Path to MCP config JSON file for --mcp-config */
105
+ mcpConfigPath?: string;
106
+ /** Memory system role ID. When set, SessionManager auto-injects memory prompt + MCP config */
107
+ roleId?: string;
108
+ /**
109
+ * Optional first user input. When set, SessionManager uses this as the
110
+ * semantic-retrieval query for `buildMemorySection` (Pipeline A) so the
111
+ * injected memory section is relevant to what the user is about to ask.
112
+ * Falls back to an empty query (importance-top-K) when omitted.
113
+ */
114
+ initialUserInput?: string;
115
+ /** Extra directories to mount via --add-dir */
116
+ additionalDirs?: string[];
117
+ /** Optional allowlist of tool names to forward to the backend (PR3.5). */
118
+ allowedTools?: string[];
119
+ /** Optional denylist of tool names to forward to the backend (PR3.5). */
120
+ disallowedTools?: string[];
121
+ /**
122
+ * Override CLAUDE_CODE_MAX_OUTPUT_TOKENS for this spawn. Drives the
123
+ * server-side hard limit (200k − maxOutputTokens) and the CLI's own
124
+ * effective window. Lower = wider prompt budget. SessionManager pulls this
125
+ * from `getRecommendedMaxOutputTokens()` on each spawn so the ladder
126
+ * (32k → 16k → 8k) survives across `--resume` restarts.
127
+ *
128
+ * Optional: when omitted the CLI uses its own default — adapters that
129
+ * don't honour it lose D-lite escalation but never crash.
130
+ */
131
+ maxOutputTokens?: number;
132
+ /**
133
+ * Optional backend kind for this spawn. When set, SessionManager resolves
134
+ * an adapter via its `resolveAdapter` hook (typically `createBackendAdapter`)
135
+ * and uses it for this process instead of the default constructor adapter.
136
+ * Omitted = use the default adapter (back-compat).
137
+ */
138
+ backend?: "claude" | "codex";
139
+ /**
140
+ * Per-role sandbox level. Each adapter maps to its own permission model:
141
+ * - codex-adapter: `-c sandbox_mode="<value>"` (OS-level seatbelt /
142
+ * landlock — strong enforcement)
143
+ * - claude-adapter: `--permission-mode` (LLM-soft approval; UI should
144
+ * tooltip that claude has no real OS sandbox)
145
+ *
146
+ * Values: "read-only" | "workspace-write" | "full-access".
147
+ * Default (undefined): adapter-specific — claude uses constructor
148
+ * permissionMode (typically bypassPermissions), codex uses
149
+ * "workspace-write".
150
+ */
151
+ sandbox?: "read-only" | "workspace-write" | "full-access";
152
+ /**
153
+ * Extra env vars merged onto `process.env` for the spawned subprocess.
154
+ * M6 T7: SwarmCoordinator forwards `CLAWNET_PROJECT_ROOT` here so MCP
155
+ * tools running inside the role can resolve back to the original
156
+ * project root even though the role's cwd points to a worktree.
157
+ * Adapters must merge (not replace) `process.env`.
158
+ */
159
+ env?: Record<string, string>;
160
+ /** Sticky session-level model code. Adapter maps to CLI flag. */
161
+ model?: string;
162
+ /** Sticky session-level mode code (permission-mode / sandbox-mode / operating-mode). */
163
+ mode?: string;
164
+ /**
165
+ * Per-turn override applied ONLY to this spawn; not persisted to DB.
166
+ *
167
+ * Precedence: turnOverride.{model,mode} wins over sticky model/mode
168
+ * field-by-field — omitted fields fall through to the sticky value.
169
+ *
170
+ * Adapters SHOULD NOT read this field directly. SessionManager merges
171
+ * it into the top-level `model`/`mode` before calling spawn(), so the
172
+ * adapter only ever sees the resolved values.
173
+ */
174
+ turnOverride?: {
175
+ model?: string;
176
+ mode?: string;
177
+ };
178
+ }
179
+ export interface BackendAdapter {
180
+ /** Backend type identifier */
181
+ type: string;
182
+ /** Spawn a new backend process */
183
+ spawn(options: SpawnOptions): Promise<BackendProcess>;
184
+ /** Stop a running backend process */
185
+ stop(process: BackendProcess): Promise<void>;
186
+ /** Send user input to the backend process */
187
+ send(process: BackendProcess, input: string): void;
188
+ /**
189
+ * Register output handler. Handler receives `unknown` today (legacy claude
190
+ * stream-json frames); adapters are migrating to emit `BackendOutput`
191
+ * envelopes. Consumers that need typed output should narrow with the
192
+ * `BackendOutput` discriminator.
193
+ */
194
+ onOutput(process: BackendProcess, handler: (msg: unknown) => void): void;
195
+ /**
196
+ * M3.S1 — Optional permission flow.
197
+ * - codex-adapter (S3): implemented; maps from `ReviewDecision` wire format.
198
+ * - claude-adapter: M3 stays on `bypassPermissions` and does NOT implement
199
+ * these. M4+ will add via an MCP permission-prompt tool.
200
+ */
201
+ onPermissionRequest?(process: BackendProcess, handler: (req: PermissionRequest) => void): void;
202
+ respondToPermission?(process: BackendProcess, decision: PermissionDecision): Promise<void>;
203
+ /** Turn-complete handler. */
204
+ onTurnComplete?(process: BackendProcess, handler: (info: {
205
+ backendSessionId?: string;
206
+ cost?: number;
207
+ duration?: number;
208
+ contextUsage?: {
209
+ used: number;
210
+ total: number;
211
+ };
212
+ backendMeta?: Record<string, unknown>;
213
+ }) => void): void;
214
+ /** Register error handler */
215
+ onError?(process: BackendProcess, handler: (error: Error) => void): void;
216
+ /**
217
+ * Register a handler that fires the moment the backend reports its session
218
+ * is initialized (e.g. Claude CLI's `system/init` frame, codex `thread_id`).
219
+ * Used by hub to persist `backendSessionId` early — without this we have to
220
+ * wait for `turn_complete`, and a mid-turn crash would leave db's
221
+ * backend_session_id NULL despite a valid jsonl file existing on disk.
222
+ *
223
+ * Optional: adapters that don't implement this lose the early-backfill
224
+ * capability — db's backend_session_id will only be populated on
225
+ * `turn_complete`, so any session whose first turn crashes will be
226
+ * unrecoverable via `--resume`. Other backends (codex, openclaw) should
227
+ * implement this if they have an analogous "session ready" signal.
228
+ */
229
+ onSessionStarted?(process: BackendProcess, handler: (info: {
230
+ backendSessionId: string;
231
+ }) => void): void;
232
+ /**
233
+ * Register a handler that fires when the underlying process exits on its
234
+ * own (crash, EPIPE, OOM, manual SIGKILL from outside) — *not* when we
235
+ * deliberately call kill() ourselves. Used by SessionManager to auto-evict
236
+ * dead Map entries and notify hub via onSessionError.
237
+ *
238
+ * Optional: adapters that don't implement this lose auto-eviction —
239
+ * SessionManager's Map will keep the dead entry, isHealthy() will still
240
+ * fall back to BackendProcess.isAlive() so the unhealthy-fallback-resume
241
+ * branch in hub-connection.ts still works, but the orphan Map entry will
242
+ * accumulate until the next abortSession/closeSession call.
243
+ */
244
+ onExit?(process: BackendProcess, handler: (code: number | null) => void): void;
245
+ /**
246
+ * Register a handler for token-budget warnings. Fires when the backend
247
+ * estimates the prompt is approaching the server-side hard limit.
248
+ * SessionManager uses this to escalate the max_output_tokens ladder so
249
+ * the next spawn gets a wider budget.
250
+ *
251
+ * Optional: adapters that can't introspect token usage simply skip this —
252
+ * the ladder stays at index 0 forever, falling back to claude-code's own
253
+ * auto-compact (which works most of the time, just less defensively).
254
+ */
255
+ onTokenBudgetWarning?(process: BackendProcess, handler: (info: {
256
+ used: number;
257
+ hardLimit: number;
258
+ threshold: number;
259
+ distanceToHard: number;
260
+ maxOutputTokens: number;
261
+ }) => void): void;
262
+ /**
263
+ * Report this adapter's capability manifest. Called by the agent on
264
+ * startup (and again on manifest-refresh requests) to populate the
265
+ * AgentManifestPayload sent to hub.
266
+ *
267
+ * Implementations must:
268
+ * - Be idempotent — safe to call multiple times.
269
+ * - Catch ALL detection errors internally (missing binary, EACCES,
270
+ * hanging --version, etc.) and surface them via `unavailableReason`
271
+ * with `installed: false`. Never reject.
272
+ *
273
+ * Agents still register the kind even when installed=false so UI can
274
+ * show it greyed-out with the unavailableReason as tooltip.
275
+ */
276
+ getManifest(): Promise<BackendManifestEntry>;
277
+ }
278
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * BackendAdapter — pluggable backend for AI model interaction.
3
+ *
4
+ * Implementations:
5
+ * - ClaudeCodeAdapter (@mclawnet/claude-adapter): spawn `claude` CLI
6
+ * - OpenClawAdapter: spawn `openclaw gateway` (legacy)
7
+ * - CodexAdapter: M3.S3 — `codex app-server --listen stdio://`
8
+ *
9
+ * M3.S1 introduces:
10
+ * - Typed permission flow (PermissionRequest / PermissionDecision)
11
+ * - Typed BackendOutput envelope replacing the historical `onOutput(msg: unknown)`
12
+ * - `backendSessionId` replaces the previous `claudeSessionId` (see plan D7).
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@mclawnet/backend-types",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ }
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "@mclawnet/shared": "0.1.9"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.8.3"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "clean": "rm -rf dist"
26
+ }
27
+ }