@pikiloom/kernel 0.1.1 → 0.1.2

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 (3) hide show
  1. package/README.md +242 -65
  2. package/llms.txt +98 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,107 +1,284 @@
1
1
  # @pikiloom/kernel
2
2
 
3
- Heterogeneous coding agents (Claude / Codex / Gemini / ACP) an **interaction-friendly,
4
- accumulating session snapshot + control handle**, exposed over **pluggable surfaces**
5
- (IM, Web, tunnel). Environmental concerns are **injected ports** with working defaults.
3
+ Turn heterogeneous coding agents (**Claude Code · Codex · Gemini · ACP/Hermes**) into one
4
+ **uniform, accumulating session snapshot + a small set of control verbs**, exposed over
5
+ **pluggable surfaces** (IM, Web, tunnel, raw terminal). Environmental concerns (storage,
6
+ credentials, tools, prompts) are **injected ports** with working defaults.
6
7
 
7
- This is the reusable core **pikiloom itself is meant to be built on**. Drop it into any
8
- project and stand up a "pikiloom-like" backend in a few lines.
8
+ This is the reusable core that [pikiloom](https://github.com/xiaotonng/pikiloom) itself is
9
+ built on. Drop it into any project and stand up a "pikiloom-like" backend in a few lines
10
+ **you never parse a CLI's output or learn each agent's wire format**; you read one
11
+ `UniversalSnapshot` and call `prompt / stop / steer / interact`.
9
12
 
10
- ## The model (one package, three rings)
13
+ ```bash
14
+ npm i @pikiloom/kernel
15
+ ```
16
+
17
+ - Runtime dep: `ws`. Optional: `node-pty` (only for raw-TUI passthrough). Node ≥ 20, ESM-only.
18
+ - TypeScript types ship in the package (`dist/**/*.d.ts`). For an LLM-oriented summary, see [`llms.txt`](./llms.txt).
19
+
20
+ ---
21
+
22
+ ## Mental model
11
23
 
12
24
  ```
13
- 上层 (you write): IM bindings · Web UI/skin · Plugins ── implement Surface / Plugin
25
+ 上层 (you write): IM bindings · Web UI · Plugins ── implement Surface / Plugin
14
26
  ▲ createLoom({ surfaces, plugins, ...ports })
15
- @pikiloom/kernel: terminal/ Surface contract + LoomIO + built-in WebSurface
16
- (one import, runtime/ SessionRunner · Hub (multi-session) · snapshot accumulation · control verbs
17
- internally agent/ driver registry + Claude/Echo drivers + spawn/parse Driver
18
- modular) protocol/ UniversalSnapshot + diff (the wire vocabulary) Channel
19
- ports/ SessionStore · ModelResolver · ToolProvider · ... (+ defaults)
20
- spawns external CLIs (unchanged)
21
- 下层 (unchanged): the `claude` / `codex` / binaries, native protocols
27
+ @pikiloom/kernel: contracts/ Driver · Surface · LoomIO · Ports
28
+ (one import, runtime/ SessionRunner · Hub (multi-session) · snapshot accumulation · control verbs
29
+ modular inside) drivers/ Claude / Codex / Gemini / Hermes / Echo the agent axis (下层)
30
+ surfaces/ WebSurface (ws host) · CliSurface
31
+ protocol/ UniversalSnapshot + diff ← the wire vocabulary
32
+ ports/ SessionStore · ModelResolver · ToolProvider · ... (+ defaults)
33
+ spawns the external `claude` / `codex` / `gemini` CLIs (unchanged)
34
+ 下层 (unchanged): the agent binaries, native protocols
22
35
  ```
23
36
 
24
- **IM and Web are not two systems** — they are both just `Surface`s over the same `LoomIO`.
37
+ **IM and Web are not two systems** — both are just `Surface`s over the same `LoomIO`.
38
+
39
+ ### Two rails, same driver registry
40
+
41
+ | Rail | Driver method | Output | Use for |
42
+ |------|---------------|--------|---------|
43
+ | **Structured** | `driver.run(input, ctx)` | streamed `UniversalSnapshot` (text, reasoning, tool activity, plan, usage) | IM, Web dashboards, any UI that renders structured turns |
44
+ | **Raw PTY** | `driver.tui(input)` | a real full-screen interactive process passed through a PTY | a local terminal app (`pikiloom code`-style); needs `node-pty` |
25
45
 
26
- ## Quickstart
46
+ ---
47
+
48
+ ## Quick start
49
+
50
+ ### 1. One turn through one agent — `runTurn` (the bridge primitive)
51
+
52
+ The smallest unit. No persistence, no multi-session — just run a turn and get a streamed
53
+ snapshot + final result. This is exactly what an existing app maps onto its own UI.
27
54
 
28
55
  ```ts
29
- import { createLoom, ClaudeDriver, WebSurface } from '@pikiloom/kernel';
56
+ import { runTurn, ClaudeDriver } from '@pikiloom/kernel';
57
+
58
+ const { result, snapshot } = await runTurn(
59
+ new ClaudeDriver(),
60
+ { prompt: 'Summarize package.json in one line', workdir: process.cwd(), effort: 'high' },
61
+ {
62
+ onSnapshot: (s) => {
63
+ // fires on every event — render live:
64
+ // s.text accumulated assistant output
65
+ // s.reasoning accumulated thinking (when the model/auth exposes it)
66
+ // s.activity human-readable execution trail ("Read foo.ts", "Run shell: npm test")
67
+ // s.toolCalls structured tool calls [{ name, summary, status }]
68
+ // s.plan, s.usage, s.artifacts, s.interactions
69
+ process.stdout.write(`\r${s.activity?.split('\n').at(-1) ?? ''}`);
70
+ },
71
+ onSteer: (steer) => { /* call steer('extra prompt') mid-turn */ },
72
+ signal: undefined, // pass an AbortSignal to stop the turn
73
+ },
74
+ );
75
+ console.log(result.ok, result.text, result.sessionId);
76
+ ```
77
+
78
+ ### 2. A full multi-session backend — `createLoom`
79
+
80
+ Adds persistence, a session hub, per-session queueing, discovery, and surfaces. Every port
81
+ has a default, so this runs with zero wiring.
82
+
83
+ ```ts
84
+ import { createLoom, ClaudeDriver, CodexDriver, WebSurface } from '@pikiloom/kernel';
30
85
 
31
86
  const loom = createLoom({
32
- drivers: [new ClaudeDriver()], // 下层 (unchanged)
33
- surfaces: [new WebSurface({ port: 8787 })], // 上层 (IM / Web / tunnel)
87
+ drivers: [new ClaudeDriver(), new CodexDriver()], // 下层 (unchanged binaries)
88
+ surfaces: [new WebSurface({ port: 8787 })], // 上层 (Web/tunnel; add your IM Surface here)
89
+ defaultAgent: 'claude',
90
+ // optional ports — override any one to swap storage / credentials / tools / prompts:
91
+ // sessionStore, modelResolver, toolProvider, systemPromptBuilder, catalog, interactionHandler
34
92
  });
35
93
  await loom.start();
94
+
95
+ // Drive it from anywhere via loom.io (LoomIO):
96
+ const { sessionKey, taskId } = await loom.io.prompt({ prompt: 'hello', agent: 'claude' });
97
+ const unsub = loom.io.subscribe((key, snapshot, patch, seq) => { /* render */ });
98
+ loom.io.steer(taskId, 'actually, do X instead');
99
+ loom.io.stop(sessionKey);
100
+ ```
101
+
102
+ To add an **IM channel**, implement `Surface.start(io)`: route inbound messages to
103
+ `io.prompt(...)` and render `io.subscribe(...)` snapshots back out. Nothing else changes.
104
+
105
+ ### 3. Raw TUI passthrough — `runTui` / `openTui`
106
+
107
+ ```ts
108
+ const loom = createLoom({ drivers: [new ClaudeDriver()], defaultAgent: 'claude' });
109
+ await loom.runTui({ agent: 'claude', workdir: process.cwd() }); // you're now in the real Claude TUI
36
110
  ```
37
111
 
38
- Add an IM terminal by implementing `Surface.start(io)`: route inbound messages to
39
- `io.prompt(...)` and render `io.subscribe(...)` snapshots back. Everything else (storage,
40
- credentials, tools, prompts) has a default and is overridable via ports.
112
+ `runTui` does full stdin/stdout raw passthrough on the current terminal. `openTui` returns a
113
+ `PtyBridge` (`onData` / `write` / `resize` / `onExit`) so you can drive or tee it yourself.
114
+ Requires the optional `node-pty` dependency (`ptyAvailable()` reports availability).
41
115
 
42
- ### TUI passthrough (`pikiloom code` → Claude/Codex TUI)
116
+ ---
43
117
 
44
- Two orthogonal driver outputs off the same registry: `run()` yields a structured
45
- `UniversalSnapshot` (Web/IM); `tui()` yields a raw, full-screen interactive process that
46
- the kernel spawns in a **PTY** and passes through transparently (with optional tee/mirror).
118
+ ## The data you read: `UniversalSnapshot`
119
+
120
+ One driver-agnostic shape for every agent. A surface renders *this* and never touches a
121
+ CLI's native format. It **accumulates** across a turn (text/reasoning append; structured
122
+ fields replace).
47
123
 
48
124
  ```ts
49
- import { createLoom, ClaudeDriver, CodexDriver } from '@pikiloom/kernel';
50
- const loom = createLoom({ drivers: [new ClaudeDriver(), new CodexDriver()], defaultAgent: 'claude' });
51
- await loom.runTui({ agent: 'claude', workdir: process.cwd() }); // you're now in the real Claude TUI, launched by the kernel
125
+ interface UniversalSnapshot {
126
+ phase: 'idle' | 'queued' | 'streaming' | 'done';
127
+ taskId?: string | null;
128
+ sessionId?: string | null;
129
+ agent?: string | null;
130
+ model?: string | null;
131
+ effort?: string | null;
132
+ prompt?: string | null;
133
+
134
+ text?: string; // accumulated assistant output
135
+ reasoning?: string; // accumulated thinking (only when the model/auth streams it; see note)
136
+ activity?: string; // human-readable execution trail, one line per tool/subagent (kernel-derived)
137
+ plan?: UniversalPlan | null; // { explanation, steps:[{ text, status }] }
138
+ toolCalls?: UniversalToolCall[]; // structured: { id, name, summary, input?, result?, status }
139
+ subAgents?: UniversalSubAgent[]; // spawned sub-agents and their tools
140
+ usage?: UniversalUsage | null; // { inputTokens, outputTokens, cachedInputTokens, contextPercent, ... }
141
+ artifacts?: UniversalArtifact[]; // generated files/images { url|path, fileName, mime, kind }
142
+ interactions?: UniversalInteraction[]; // pending human-in-the-loop questions (answer via interact())
143
+ queued?: UniversalQueuedTask[]; // prompts waiting behind the running turn
144
+
145
+ error?: string | null;
146
+ incomplete?: boolean; // true if the turn was interrupted / errored
147
+ startedAt?: number;
148
+ updatedAt: number;
149
+ }
52
150
  ```
53
151
 
54
- `runTui` does full stdin/stdout raw passthrough on the current terminal; `openTui` returns
55
- a `PtyBridge` (`onData`/`write`/`resize`/`onExit`) so you can drive or tee it yourself.
56
- Requires the optional `node-pty` dependency.
152
+ Sub-shapes:
153
+
154
+ ```ts
155
+ interface UniversalToolCall { id: string; name: string; summary: string; input?: string | null; result?: string | null; status: 'running' | 'done' | 'failed'; }
156
+ interface UniversalPlan { explanation: string | null; steps: { text: string; status: 'pending' | 'inProgress' | 'completed' }[]; }
157
+ interface UniversalUsage { inputTokens: number | null; outputTokens: number | null; cachedInputTokens: number | null; contextUsedTokens?: number | null; contextPercent: number | null; providerName?: string | null; }
158
+ interface UniversalArtifact { url?: string; path?: string; fileName: string; mime?: string; kind: 'photo' | 'document'; caption?: string; }
159
+ interface UniversalInteraction { promptId: string; kind: 'user-input' | 'permission' | 'confirmation'; title: string; questions: { id: string; text: string; type?: 'text' | 'select'; choices?: { label: string; value?: string }[] }[]; }
160
+ ```
161
+
162
+ **Activity projection (kernel-owned).** Every driver emits *structured* tool calls; the
163
+ kernel's `SessionRunner` derives `snapshot.activity` from `toolCalls` + `subAgents` centrally
164
+ (`projectActivity`): one line per call with a status suffix — `summary` while running,
165
+ `summary done` / `summary -> detail` on success, `summary failed: detail` on error. So every
166
+ surface gets a readable execution trail for free, and rich UIs can still use `toolCalls`
167
+ directly. You implement this **nowhere** — it's a property of the snapshot.
168
+
169
+ **Reasoning note.** Plaintext thinking only appears when the agent actually streams it
170
+ (e.g. BYOK Anthropic API keys, Codex `item/reasoning`). Subscription/OAuth Claude withholds
171
+ plaintext extended-thinking (streams only an encrypted signature), so `reasoning` is empty
172
+ there — a platform behavior, not a kernel limitation. The Claude driver also captures
173
+ reasoning delivered as a complete block, not only as streamed deltas.
174
+
175
+ ### Streaming on the wire — `diffSnapshot` / `applySnapshotPatch`
176
+
177
+ `diffSnapshot(prev, next)` produces a compact `SnapshotPatch` (prefix-append for `text` /
178
+ `reasoning`; field replacement otherwise, with `undefined → null` so field-clears survive
179
+ `JSON.stringify`). `applySnapshotPatch(prev, patch)` reassembles it on the client. `WebSurface`
180
+ uses these; reuse them for your own transport.
181
+
182
+ ---
183
+
184
+ ## Control verbs (`LoomIO`)
185
+
186
+ ```ts
187
+ io.prompt({ prompt, agent?, sessionKey?, workdir?, model?, effort?, attachments? }) // → { sessionKey, taskId }
188
+ io.stop(sessionKey) // interrupt the running turn (queued tasks stay & promote)
189
+ io.steer(taskId, prompt, attachments?) // inject a message mid-turn (drivers with capabilities.steer)
190
+ io.interact(promptId, action, value?) // answer a pending interaction: 'select' | 'text' | 'skip' | 'cancel'
191
+ io.subscribe((sessionKey, snapshot, patch, seq) => …) // live snapshots
192
+ io.getSnapshot(sessionKey) · io.getHistory(sessionKey) · io.listSessions()
193
+ io.listAgentInfo() · io.listModels(agent) · io.listEffort(agent, model?) · io.listTools(agent, workdir?) · io.listSkills(agent, workdir?)
194
+ ```
195
+
196
+ Concurrent prompts to one session **queue** by default (`serialPerSession`, no clobber); `stop`
197
+ interrupts only the current turn and the next queued task promotes.
198
+
199
+ ---
200
+
201
+ ## Drivers (the agent axis)
202
+
203
+ | Driver | id | transport | steer | interact | resume | tui |
204
+ |--------|----|-----------|:-----:|:--------:|:------:|:---:|
205
+ | `ClaudeDriver` | `claude` | `claude` CLI, stream-json (+ `--effort`, partial messages) | ✓ | — | ✓ | ✓ |
206
+ | `CodexDriver` | `codex` | `codex app-server` JSON-RPC (HITL via `requestUserInput`) | ✓ | via askUser | ✓ | ✓ |
207
+ | `GeminiDriver` | `gemini` | `gemini --output-format stream-json` | — | — | ✓ | ✓ |
208
+ | `HermesDriver` | `hermes` | ACP `session/update` | — | — | ✓ | — |
209
+ | `EchoDriver` | `echo` | none (hermetic, in-process) | ✓ | ✓ | ✓ | ✓ |
210
+
211
+ Write your own by implementing `AgentDriver` and passing it to `createLoom({ drivers })` (or
212
+ `loom.registerDriver(...)`). A driver normalizes its agent into `DriverEvent`s:
213
+
214
+ ```ts
215
+ type DriverEvent =
216
+ | { type: 'session'; sessionId: string }
217
+ | { type: 'text'; delta: string }
218
+ | { type: 'reasoning'; delta: string }
219
+ | { type: 'tool'; call: UniversalToolCall } // emit on tool start AND completion (status update)
220
+ | { type: 'plan'; plan: UniversalPlan }
221
+ | { type: 'subagent'; subagent: UniversalSubAgent }
222
+ | { type: 'usage'; usage: Partial<UniversalUsage> }
223
+ | { type: 'artifact'; artifact: UniversalArtifact }
224
+ | { type: 'activity'; line: string }; // explicit activity line (drivers that don't emit tool events)
225
+
226
+ interface AgentDriver {
227
+ readonly id: string;
228
+ readonly capabilities?: { steer?: boolean; interact?: boolean; resume?: boolean; tui?: boolean };
229
+ run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>; // ctx: { signal, emit, askUser, registerSteer }
230
+ tui?(input: TuiInput): TuiSpec;
231
+ }
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Surfaces & Ports
237
+
238
+ **Surfaces** (上层) bind to `LoomIO`. Built in: `WebSurface` (a WebSocket host speaking the
239
+ wire protocol — any pikichannel-style client connects), `CliSurface`. Implement `Surface` to
240
+ add an IM channel or your own UI.
57
241
 
58
- ## Contracts
242
+ **Ports** (side) — all optional, each with a default, so `createLoom()` runs with zero config:
59
243
 
60
- | Seam | Interface | Direction |
61
- |------|-----------|-----------|
62
- | Agent (下层) | `AgentDriver.run(input, ctx)` → emits `DriverEvent`s | down, bundled (+ `registerDriver` escape hatch) |
63
- | Surface (上层) | `Surface.start(io: LoomIO)` | up, app-provided (WebSurface built-in) |
64
- | Session API | `LoomIO` = prompt/stop/steer/interact + subscribe/getSnapshot/listSessions | the meeting point |
65
- | Plugin | `Plugin.tools()` / `decorateSnapshot()` | up, app-provided |
66
- | Ports | `SessionStore` · `ModelResolver` · `ToolProvider` · `SystemPromptBuilder` · `InteractionHandler` | side, defaults included |
244
+ | Port | Default | Swap to… |
245
+ |------|---------|----------|
246
+ | `SessionStore` | `FsSessionStore` (`~/.<ns>/sessions`) | your DB / transcript store |
247
+ | `ModelResolver` | `NullModelResolver` (native login) | BYOK credential/provider injection |
248
+ | `ToolProvider` | `NoopToolProvider` | per-session MCP servers |
249
+ | `SystemPromptBuilder` | `PassthroughSystemPromptBuilder` | your system/developer prompt |
250
+ | `Catalog` | `NoopCatalog` | model/effort/tool/skill discovery for composers |
251
+ | `InteractionHandler` | `DeferToTerminalInteractionHandler` | programmatic HITL answers (`AutoCancelInteractionHandler` for one-shots) |
67
252
 
68
- Wire shape: `UniversalSnapshot` (phase/text/reasoning/plan/toolCalls/usage/interactions/artifacts)
69
- + delta `diffSnapshot`/`applySnapshotPatch` (prefix-append text, `undefined→null` field-clears
70
- that survive JSON).
253
+ **Plugins** contribute per-session tools (`tools()`) and/or augment snapshots (`decorateSnapshot()`).
71
254
 
72
- ## What's included
255
+ ---
73
256
 
74
- - Drivers: `EchoDriver` (hermetic), `ClaudeDriver` (real `claude` CLI, stream-json + TUI), `CodexDriver` (app-server JSON-RPC + TUI), `GeminiDriver` (stream-json), `HermesDriver` (ACP).
75
- - Terminals: `WebSurface` (ws host speaking the wire protocol — any pikichannel client connects), `CliSurface`.
76
- - TUI: `PtyBridge` + `attachTui` + `Loom.runTui/openTui` (raw PTY passthrough with tee).
77
- - Ports: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`.
257
+ ## Exports
78
258
 
79
- ## Publishing / CI
259
+ Main entry `@pikiloom/kernel` re-exports everything. Subpaths: `@pikiloom/kernel/drivers`,
260
+ `@pikiloom/kernel/surfaces`, `@pikiloom/kernel/protocol`.
80
261
 
81
- `npm publish ./packages/kernel --access public` (scoped-public). Each pikiloom release bumps
82
- + builds the kernel (`scripts/release.sh`) and the Release workflow publishes it alongside
83
- pikiloom needs the `@pikiloom` npm scope to exist and `NPM_TOKEN` to have publish access.
262
+ - Runtime: `createLoom`, `Loom`, `Hub`, `SessionRunner`, `runTurn`, `PtyBridge`, `ptyAvailable`, `attachTui`
263
+ - Drivers: `EchoDriver`, `ClaudeDriver`, `CodexDriver`, `GeminiDriver`, `HermesDriver`
264
+ - Surfaces: `WebSurface`, `CliSurface`
265
+ - Ports/defaults: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`, `DeferToTerminalInteractionHandler`, `NoopCatalog`, `defaultBaseDir`
266
+ - Protocol: `UniversalSnapshot`, `diffSnapshot`, `applySnapshotPatch`, `emptySnapshot`, `PROTOCOL_VERSION`, all wire/`Client*`/`Server*` message types
267
+ - Types: `AgentDriver`, `AgentTurnInput`, `DriverContext`, `DriverEvent`, `DriverResult`, `LoomIO`, `PromptInput`, `Surface`, `Plugin`, `SessionStore`, `ModelResolver`, `ToolProvider`, `SystemPromptBuilder`, `InteractionHandler`, `Catalog`, …
84
268
 
85
- > Known rough edge: node-pty's prebuilt `spawn-helper` can lose its executable bit on some
86
- > npm installs (`posix_spawnp failed`). Fix: `chmod +x node_modules/node-pty/prebuilds/*/spawn-helper`.
269
+ ---
87
270
 
88
- ## Verification
271
+ ## Verify
89
272
 
90
273
  ```bash
91
274
  npm run typecheck # tsc, clean
92
- npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow
275
+ npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow, driver parsers
93
276
  KERNEL_E2E_REAL=1 npm test # also drives the real `claude` CLI end-to-end
94
277
  node examples/smoke.mjs # smoke against the compiled dist
95
278
  ```
96
279
 
97
- Status: contracts + runtime + Echo/Claude drivers + Web terminal + default ports are
98
- implemented and **E2E-verified** (hermetic + real-claude + compiled-artifact). The kernel
99
- lives beside `src/` and does **not** touch the existing pikiloom app.
280
+ See `examples/` for a runnable web console, a Feishu/Lark terminal, and a Node smoke test.
100
281
 
101
- ## Roadmap (gated cutover)
282
+ ## License
102
283
 
103
- 1. Port the remaining drivers (Codex/Gemini/Hermes-ACP) behind the same `AgentDriver` contract.
104
- 2. Promote pikiloom's concrete IM channels to reference `Surface` adapters (`@pikiloom/kernel/surfaces/*`).
105
- 3. Add the full pikichannel transport (WebRTC + rendezvous + TURN + `/api` tunnel) as a `WebSurface` option.
106
- 4. Re-point the pikiloom app's `main.ts` at `createLoom({...})`; keep multi-session queue / promotion / goal
107
- auto-continuation in the app (not the kernel). Switch over only after parity is proven on DEV.
284
+ MIT
package/llms.txt ADDED
@@ -0,0 +1,98 @@
1
+ # @pikiloom/kernel
2
+
3
+ > Reusable core that turns heterogeneous coding-agent CLIs (Claude Code, Codex, Gemini, ACP/Hermes)
4
+ > into ONE uniform, accumulating `UniversalSnapshot` + control verbs (prompt/stop/steer/interact),
5
+ > exposed over pluggable surfaces (IM, Web, tunnel, raw PTY). You read one snapshot shape and never
6
+ > parse a CLI's native output. ESM-only, Node >= 20. Runtime dep: `ws`; optional: `node-pty` (raw TUI).
7
+
8
+ This file is an LLM-oriented summary. Full prose: README.md. Exact types: the bundled `dist/**/*.d.ts`.
9
+
10
+ ## Install & import
11
+
12
+ ```
13
+ npm i @pikiloom/kernel
14
+ ```
15
+ ESM only. Main export re-exports everything; subpaths: `@pikiloom/kernel/drivers`, `/surfaces`, `/protocol`.
16
+
17
+ ## Three usage tiers
18
+
19
+ 1) ONE turn, no persistence — `runTurn(driver, input, opts?) => Promise<{ result, snapshot }>`:
20
+ ```ts
21
+ import { runTurn, ClaudeDriver } from '@pikiloom/kernel';
22
+ const { result, snapshot } = await runTurn(
23
+ new ClaudeDriver(),
24
+ { prompt: 'hi', workdir: process.cwd(), effort: 'high' }, // AgentTurnInput
25
+ { onSnapshot: (s) => render(s), onSteer: (steer) => {}, signal }, // RunTurnOptions (signal = AbortSignal to stop)
26
+ );
27
+ // result: { ok, text, reasoning?, error?, stopReason?, sessionId?, usage? }
28
+ ```
29
+
30
+ 2) FULL multi-session backend — `createLoom(config) => Loom`:
31
+ ```ts
32
+ import { createLoom, ClaudeDriver, WebSurface } from '@pikiloom/kernel';
33
+ const loom = createLoom({ drivers: [new ClaudeDriver()], surfaces: [new WebSurface({ port: 8787 })], defaultAgent: 'claude' });
34
+ await loom.start();
35
+ const { sessionKey, taskId } = await loom.io.prompt({ prompt: 'hi', agent: 'claude' });
36
+ const unsub = loom.io.subscribe((key, snapshot, patch, seq) => render(snapshot));
37
+ ```
38
+ Every port has a default (zero config). Override any: sessionStore, modelResolver, toolProvider, systemPromptBuilder, catalog, interactionHandler, plugins, serialPerSession, appNamespace, workdir, defaultAgent, log.
39
+
40
+ 3) RAW TUI passthrough — `loom.runTui({ agent, workdir })` (full stdio passthrough) or `loom.openTui(...) => PtyBridge` (onData/write/resize/onExit). Needs optional `node-pty`; `ptyAvailable()` reports it.
41
+
42
+ ## UniversalSnapshot — the shape you render (accumulates across a turn)
43
+
44
+ ```ts
45
+ interface UniversalSnapshot {
46
+ phase: 'idle'|'queued'|'streaming'|'done';
47
+ taskId?; sessionId?; agent?; model?; effort?; prompt?: string|null;
48
+ text?: string; // accumulated assistant output (append)
49
+ reasoning?: string; // accumulated thinking (append; empty if the model/auth withholds it — see gotchas)
50
+ activity?: string; // human-readable execution trail, kernel-derived from toolCalls (one line per tool, status suffix)
51
+ plan?: { explanation: string|null; steps: { text: string; status: 'pending'|'inProgress'|'completed' }[] } | null;
52
+ toolCalls?: { id; name; summary; input?; result?; status: 'running'|'done'|'failed' }[];
53
+ subAgents?: { id; kind; description; model; tools[]; status }[];
54
+ usage?: { inputTokens; outputTokens; cachedInputTokens; contextUsedTokens?; contextPercent; providerName? } | null;
55
+ artifacts?: { url?; path?; fileName; mime?; kind: 'photo'|'document'; caption? }[];
56
+ interactions?: { promptId; kind: 'user-input'|'permission'|'confirmation'; title; questions: { id; text; type?: 'text'|'select'; choices? }[] }[];
57
+ queued?: { taskId; prompt }[];
58
+ error?: string|null; incomplete?: boolean; startedAt?; updatedAt: number;
59
+ }
60
+ ```
61
+ Wire delta helpers: `diffSnapshot(prev,next) => SnapshotPatch` (prefix-append text/reasoning; undefined→null clears) and `applySnapshotPatch(prev,patch)`. `emptySnapshot()`, `PROTOCOL_VERSION`.
62
+
63
+ ## Control verbs (LoomIO, via loom.io)
64
+
65
+ - `prompt({ prompt, agent?, sessionKey?, workdir?, model?, effort?, attachments? }) => Promise<{ sessionKey, taskId }>`
66
+ - `stop(sessionKey) => boolean` (interrupts current turn; queued tasks stay and the next promotes)
67
+ - `steer(taskId, prompt, attachments?) => Promise<boolean>` (mid-turn; only drivers with capabilities.steer)
68
+ - `interact(promptId, 'select'|'text'|'skip'|'cancel', value?) => boolean` (answer a pending interaction)
69
+ - `subscribe((sessionKey, snapshot, patch, seq) => void) => unsubscribe`
70
+ - `getSnapshot(sessionKey)`, `getHistory(sessionKey)`, `listSessions()`, `listAgents()`, `listAgentInfo()`
71
+ - discovery: `listModels(agent)`, `listEffort(agent, model?)`, `listTools(agent, workdir?)`, `listSkills(agent, workdir?)`
72
+ Concurrent prompts to one session queue by default (serialPerSession=true).
73
+
74
+ ## Drivers (pass to createLoom({drivers}) or loom.registerDriver)
75
+
76
+ - ClaudeDriver (id 'claude'): `claude` CLI stream-json, supports --effort; steer ✓, resume ✓, tui ✓
77
+ - CodexDriver (id 'codex'): `codex app-server` JSON-RPC; steer ✓, HITL via askUser, resume ✓, tui ✓
78
+ - GeminiDriver (id 'gemini'): `gemini` stream-json; resume ✓, tui ✓
79
+ - HermesDriver (id 'hermes'): ACP session/update; resume ✓
80
+ - EchoDriver (id 'echo'): hermetic in-process test driver; all caps ✓
81
+
82
+ Write a driver: implement `AgentDriver { id; capabilities?; run(input, ctx); tui?(input) }`. In run(), call `ctx.emit(event)`:
83
+ `DriverEvent = {type:'session',sessionId} | {type:'text',delta} | {type:'reasoning',delta} | {type:'tool',call:UniversalToolCall} | {type:'plan',plan} | {type:'subagent',subagent} | {type:'usage',usage} | {type:'artifact',artifact} | {type:'activity',line}`.
84
+ `ctx = { signal: AbortSignal, emit, askUser(interaction)=>Promise<answers>, registerSteer(fn) }`. Return `DriverResult { ok, text, reasoning?, error?, stopReason?, sessionId?, usage? }`. Emit a `tool` event on BOTH start (status 'running') and completion (status 'done'/'failed'); the kernel projects `activity` from these.
85
+
86
+ ## Surfaces & Ports
87
+
88
+ - Surface (上层, binds LoomIO): built-in `WebSurface` (ws host, wire protocol), `CliSurface`. Implement `Surface { id; capabilities?; start(io, host?); stop() }` for an IM channel.
89
+ - Plugin: `{ id; tools?({agent,workdir}); decorateSnapshot?(snapshot) }`.
90
+ - Ports (defaults in parens): SessionStore (FsSessionStore), ModelResolver (NullModelResolver = native login; override for BYOK), ToolProvider (NoopToolProvider), SystemPromptBuilder (PassthroughSystemPromptBuilder), Catalog (NoopCatalog), InteractionHandler (DeferToTerminalInteractionHandler; AutoCancelInteractionHandler for one-shots). `defaultBaseDir(ns)`.
91
+
92
+ ## Gotchas (read before using)
93
+
94
+ - ESM only; Node >= 20. `node-pty` is OPTIONAL — required only for tui()/runTui()/openTui(); a missing one degrades gracefully (`ptyAvailable()===false`), it never breaks import.
95
+ - `reasoning` is only populated when the agent streams plaintext thinking. Subscription/OAuth Claude withholds it (streams only an encrypted signature) → `reasoning` empty; BYOK Anthropic API and Codex DO stream it. Not a kernel bug.
96
+ - `activity` is derived by the kernel from structured `toolCalls`+`subAgents` (you don't build it). Rich UIs can use `toolCalls` directly; simple/text surfaces can use the `activity` string.
97
+ - The kernel bakes in ZERO model knowledge: model/effort/tool/skill lists come from the Catalog port (your app's SSOT). Per-agent capabilities come from the driver registry (`listAgentInfo()`).
98
+ - `stop` interrupts only the running turn (queued tasks promote); it is not "cancel everything".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikiloom/kernel",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Heterogeneous coding agents (Claude / Codex / Gemini / ACP) -> an interaction-friendly, accumulating session snapshot + control handle, over pluggable surfaces (IM, Web, tunnel, TUI). The reusable core pikiloom itself is built on.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,7 +29,8 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "files": [
31
31
  "dist/",
32
- "README.md"
32
+ "README.md",
33
+ "llms.txt"
33
34
  ],
34
35
  "scripts": {
35
36
  "build": "tsc -p tsconfig.json",