@shardworks/claude-code-apparatus 0.1.148 → 0.1.150

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/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # `@shardworks/claude-code-apparatus`
2
+
3
+ Claude Code session provider apparatus for Nexus. Implements the `AnimatorSessionProvider` interface for the Claude Code CLI, enabling the Animator to launch and manage AI sessions. Also provides the **Session Babysitter** — a detached process that hosts sessions independently of the guild lifecycle.
4
+
5
+ Depends on `@shardworks/animator-apparatus` (types), `@shardworks/tools-apparatus` (tool definitions and routing), and `@shardworks/nexus-core`.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```json
12
+ {
13
+ "dependencies": {
14
+ "@shardworks/claude-code-apparatus": "workspace:*"
15
+ }
16
+ }
17
+ ```
18
+
19
+ ## API
20
+
21
+ ### Session Provider
22
+
23
+ The default export is a `Plugin` whose apparatus `provides` an `AnimatorSessionProvider`:
24
+
25
+ ```typescript
26
+ import createClaudeCodeProvider from '@shardworks/claude-code-apparatus';
27
+
28
+ // In guild.json:
29
+ // { "animator": { "sessionProvider": "claude-code" } }
30
+ ```
31
+
32
+ The provider implements `launch()` and `cancel()`:
33
+
34
+ - **`launch(config)`** — spawns a **detached babysitter process** that hosts the session independently of the guild. The babysitter spawns `claude` in autonomous mode, streams transcripts to SQLite, and reports lifecycle events via HTTP. Returns `{ chunks, result, processInfo }` where:
35
+ - `chunks` completes immediately (empty) — real-time output is available via the transcripts book
36
+ - `result` polls the sessions book for terminal status (resolves when the babysitter calls `session-record`)
37
+ - `processInfo` polls the SessionDoc for `cancelMetadata.pid` (set by the babysitter via `session-running`)
38
+ - **`cancel(cancelMetadata)`** — sends SIGTERM to the claude process using the PID from `cancelMetadata`. Works cross-process regardless of parent-child relationships.
39
+
40
+ An **attached mode** (`launchAttached()`) is preserved as an internal export for debugging — it spawns claude as a direct child process with in-process MCP server and streaming.
41
+
42
+ ### MCP Server
43
+
44
+ The package exports functions for running MCP tool servers:
45
+
46
+ ```typescript
47
+ import { createMcpServer, startMcpHttpServer } from '@shardworks/claude-code-apparatus';
48
+
49
+ // Create an MCP server with resolved tool definitions
50
+ const mcpServer = await createMcpServer(toolDefinitions);
51
+
52
+ // Or start an HTTP server on an ephemeral port
53
+ const handle = await startMcpHttpServer(toolDefinitions);
54
+ console.log(handle.url); // "http://127.0.0.1:PORT/sse"
55
+ await handle.close(); // cleanup
56
+ ```
57
+
58
+ ### Stream Parsing
59
+
60
+ Exported utilities for parsing Claude's NDJSON output:
61
+
62
+ ```typescript
63
+ import {
64
+ processNdjsonBuffer,
65
+ parseStreamJsonMessage,
66
+ extractFinalAssistantText,
67
+ } from '@shardworks/claude-code-apparatus';
68
+ ```
69
+
70
+ - **`processNdjsonBuffer(buffer, handler)`** — splits NDJSON buffer on newlines, calls handler for each parsed JSON object, returns remaining incomplete buffer.
71
+ - **`parseStreamJsonMessage(msg, acc)`** — processes a single NDJSON message, accumulates transcript/metrics, returns `SessionChunk[]`.
72
+ - **`extractFinalAssistantText(transcript)`** — walks transcript backwards to find the last assistant message's text content.
73
+
74
+ ## Session Babysitter
75
+
76
+ The babysitter is a standalone Node.js script that runs as a detached process, hosting a claude session independently of the guild. It survives guild restarts.
77
+
78
+ ### Entry Point
79
+
80
+ ```bash
81
+ node dist/babysitter.js # reads config from stdin
82
+ ```
83
+
84
+ Or import the module for programmatic use:
85
+
86
+ ```typescript
87
+ import { runBabysitter } from '@shardworks/claude-code-apparatus/babysitter';
88
+ ```
89
+
90
+ ### Config (via stdin)
91
+
92
+ The spawning process writes JSON config to the babysitter's stdin:
93
+
94
+ ```typescript
95
+ interface BabysitterConfig {
96
+ sessionId: string; // Pre-generated session ID
97
+ guildToolUrl: string; // Guild's Tool HTTP API URL (e.g. "http://127.0.0.1:7471")
98
+ dbPath: string; // Path to guild's SQLite database
99
+ claudeArgs: string[]; // CLI args for claude (--model, --system-prompt-file, etc.)
100
+ cwd: string; // Working directory for the claude process
101
+ env: Record<string, string>; // Environment variables for the claude process
102
+ prompt: string; // Initial prompt piped to claude's stdin
103
+ tools: SerializedTool[]; // Tool definitions with JSON Schema params
104
+ startedAt: string; // ISO timestamp of session start
105
+ provider: string; // Provider name (e.g. "claude-code")
106
+ metadata?: Record<string, unknown>; // Optional session metadata
107
+ }
108
+
109
+ interface SerializedTool {
110
+ name: string;
111
+ description: string;
112
+ params: Record<string, unknown>; // JSON Schema
113
+ }
114
+ ```
115
+
116
+ ### Lifecycle
117
+
118
+ 1. **Read config** from stdin, parse JSON, validate required fields
119
+ 2. **Open SQLite** (WAL mode) for real-time transcript streaming
120
+ 3. **Start MCP/SSE proxy server** — registers tools that forward calls to the guild's Tool HTTP API with retry and exponential backoff
121
+ 4. **Prepare session files** — temp directory, mcp-config.json pointing to the proxy server
122
+ 5. **Spawn claude** — pipes prompt to stdin, captures NDJSON stdout
123
+ 6. **Report "running"** — calls `session-running` tool on guild via HTTP (DLQ fallback)
124
+ 7. **Stream transcript** — parses NDJSON, writes to `books_animator_transcripts` table in SQLite after each message batch
125
+ 8. **Report result** — calls `session-record` tool on guild via HTTP (DLQ fallback)
126
+ 9. **Cleanup** — close MCP server, close SQLite, remove temp directory
127
+
128
+ ### Error Handling
129
+
130
+ - **Tool call proxy errors**: retried with exponential backoff (1s initial, 8s max, 60s timeout). If retries exhaust, returns error to claude as MCP tool result — doesn't crash.
131
+ - **Lifecycle reporting errors**: if guild is unreachable, payload is written to `.nexus/dlq/{sessionId}[-running].json` for later drain.
132
+ - **Top-level errors**: attempts to report `status: 'failed'` to guild, falls back to DLQ, then exits non-zero.
133
+
134
+ ## Exports
135
+
136
+ | Entry point | Description |
137
+ |---|---|
138
+ | `.` (`src/index.ts`) | Session provider plugin, MCP server, stream parsing utilities, `launchAttached()` |
139
+ | `./babysitter` (`src/babysitter.ts`) | Babysitter module — `runBabysitter()`, config parsing, proxy server, transcript DB |
140
+
141
+ ### Internal Modules
142
+
143
+ | Module | Description |
144
+ |---|---|
145
+ | `src/detached.ts` | Detached launch — `launchDetached()`, tool serialization (`serializeTools()`), polling helpers |
146
+
147
+ ## Configuration
148
+
149
+ Configured in `guild.json` under the `animator` key:
150
+
151
+ ```json
152
+ {
153
+ "animator": {
154
+ "sessionProvider": "claude-code"
155
+ }
156
+ }
157
+ ```
158
+
159
+ No additional configuration fields. The model is passed per-session via the Animator.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Session Babysitter — detached process that hosts a claude session.
3
+ *
4
+ * A standalone Node.js script that:
5
+ * 1. Reads config from stdin (spawned by the claude-code provider)
6
+ * 2. Opens the guild's SQLite database for transcript streaming
7
+ * 3. Starts an MCP/SSE server that proxies tool calls to the guild
8
+ * 4. Spawns claude with prepared session files
9
+ * 5. Reports session lifecycle events via the guild's HTTP API
10
+ * 6. Streams transcript data to SQLite in real-time
11
+ * 7. Reports the final result and cleans up
12
+ *
13
+ * The babysitter is a detached process: it survives guild restarts.
14
+ * All guild communication is via HTTP (tool server) and SQLite (transcripts).
15
+ *
16
+ * See: docs/architecture/detached-sessions.md
17
+ */
18
+ import { spawn } from 'node:child_process';
19
+ import { type StreamJsonResult } from './index.ts';
20
+ /** A serialized tool definition as received in the babysitter config. */
21
+ export interface SerializedTool {
22
+ /** Tool name (e.g. 'writ-list'). */
23
+ name: string;
24
+ /** Tool description. */
25
+ description: string;
26
+ /** JSON Schema for the tool's input parameters. */
27
+ params: Record<string, unknown>;
28
+ }
29
+ /** Config written to the babysitter's stdin by the spawning process. */
30
+ export interface BabysitterConfig {
31
+ sessionId: string;
32
+ guildToolUrl: string;
33
+ dbPath: string;
34
+ claudeArgs: string[];
35
+ cwd: string;
36
+ env: Record<string, string>;
37
+ prompt: string;
38
+ tools: SerializedTool[];
39
+ startedAt: string;
40
+ provider: string;
41
+ metadata?: Record<string, unknown>;
42
+ }
43
+ export interface McpProxyHandle {
44
+ /** URL for --mcp-config (e.g. "http://127.0.0.1:PORT/sse"). */
45
+ url: string;
46
+ /** Shut down the HTTP server and MCP transport. */
47
+ close(): Promise<void>;
48
+ }
49
+ /**
50
+ * Read the babysitter config from stdin.
51
+ *
52
+ * Reads stdin to completion, parses the JSON, and validates required fields.
53
+ * The spawning process writes config and closes the write end.
54
+ */
55
+ export declare function readConfigFromStdin(stream?: NodeJS.ReadableStream): Promise<BabysitterConfig>;
56
+ /**
57
+ * Call a guild HTTP API endpoint with exponential backoff retry.
58
+ *
59
+ * Retries on connection errors (ECONNREFUSED, ECONNRESET, ETIMEDOUT).
60
+ * Returns the parsed JSON response on success.
61
+ * Throws after RETRY_TIMEOUT_MS of retrying.
62
+ */
63
+ export declare function callGuildHttpApi(url: string, sessionId: string, body: unknown, timeoutMs?: number): Promise<unknown>;
64
+ /**
65
+ * Write a payload to the Dead Letter Queue.
66
+ *
67
+ * Creates the DLQ directory if it doesn't exist. Writes the payload as
68
+ * pretty-printed JSON. Used as a fallback when the guild HTTP API is
69
+ * unreachable for lifecycle calls.
70
+ */
71
+ export declare function writeToDlq(cwd: string, filename: string, payload: unknown): void;
72
+ /**
73
+ * Create an MCP/SSE HTTP server that proxies tool calls to the guild.
74
+ *
75
+ * For each tool in the config, registers an MCP tool whose handler
76
+ * forwards the call to the guild's Tool HTTP API via HTTP POST.
77
+ *
78
+ * Uses the low-level MCP Server class to register tools with raw
79
+ * JSON Schema (the serialized params from the config).
80
+ */
81
+ export declare function createProxyMcpHttpServer(tools: SerializedTool[], guildToolUrl: string, sessionId: string): Promise<McpProxyHandle>;
82
+ /** Minimal interface for the SQLite database used by the babysitter. */
83
+ export interface TranscriptDb {
84
+ /** Write a transcript entry (id, content JSON). */
85
+ writeTranscript(sessionId: string, content: string): void;
86
+ /** Close the database connection. */
87
+ close(): void;
88
+ }
89
+ /**
90
+ * Open the guild's SQLite database for transcript streaming.
91
+ *
92
+ * Creates the database file and table if they don't exist.
93
+ * Enables WAL mode for concurrent read access by other processes
94
+ * (Oculus, CLI queries, other agents).
95
+ *
96
+ * Uses dynamic import() to load better-sqlite3 at runtime. This avoids
97
+ * requiring the native module at import time (beneficial for type-checking
98
+ * and testing).
99
+ */
100
+ export declare function openTranscriptDb(dbPath: string): Promise<TranscriptDb>;
101
+ /**
102
+ * Initialize a TranscriptDb from a Database constructor.
103
+ *
104
+ * Shared logic between openTranscriptDb() and test injection.
105
+ * Exported for testing — allows injecting a mock Database constructor.
106
+ */
107
+ export declare function initTranscriptDb(DatabaseConstructor: new (path: string) => {
108
+ pragma(stmt: string): unknown;
109
+ prepare(sql: string): {
110
+ run(...params: unknown[]): void;
111
+ };
112
+ exec(sql: string): void;
113
+ close(): void;
114
+ }, dbPath: string): TranscriptDb;
115
+ /**
116
+ * Write the current transcript to SQLite.
117
+ */
118
+ export declare function writeTranscript(db: TranscriptDb, sessionId: string, messages: Record<string, unknown>[]): void;
119
+ /**
120
+ * Report "running" status to the guild via the session-running tool.
121
+ *
122
+ * If the guild is unreachable, writes the payload to the DLQ.
123
+ */
124
+ export declare function reportRunning(config: BabysitterConfig, claudePid: number, timeoutMs?: number): Promise<void>;
125
+ /**
126
+ * Report the final session result to the guild via the session-record tool.
127
+ *
128
+ * If the guild is unreachable, writes the payload to the DLQ.
129
+ */
130
+ export declare function reportResult(config: BabysitterConfig, result: StreamJsonResult, transcript: Record<string, unknown>[], timeoutMs?: number): Promise<void>;
131
+ /**
132
+ * Run the session babysitter.
133
+ *
134
+ * This is the main orchestration function. It:
135
+ * 1. Opens SQLite for transcript streaming
136
+ * 2. Starts the MCP proxy server
137
+ * 3. Prepares session files (tmpDir, system prompt, mcp-config)
138
+ * 4. Spawns claude
139
+ * 5. Reports "running" status
140
+ * 6. Streams transcript to SQLite
141
+ * 7. Reports result on exit
142
+ * 8. Cleans up
143
+ */
144
+ export declare function runBabysitter(config: BabysitterConfig, deps?: {
145
+ /** Injected TranscriptDb for testing (avoids loading better-sqlite3). */
146
+ db?: TranscriptDb;
147
+ /** Override spawn for testing. */
148
+ spawnFn?: typeof spawn;
149
+ /** Override retry timeout for testing (default: 60_000ms). */
150
+ retryTimeoutMs?: number;
151
+ }): Promise<void>;
152
+ //# sourceMappingURL=babysitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"babysitter.d.ts","sourceRoot":"","sources":["../src/babysitter.ts"],"names":[],"mappings":"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"}