pikiloom 0.4.37 → 0.4.39
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/dashboard/dist/assets/{AgentTab-DbFzaIyZ.js → AgentTab-Ds8D6yrl.js} +1 -1
- package/dashboard/dist/assets/{ConnectionModal-QNYOWHCU.js → ConnectionModal-Buj2d5L5.js} +1 -1
- package/dashboard/dist/assets/{DirBrowser-BGaKHjFT.js → DirBrowser-BTv7rH8E.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-D4Gv9b8z.js → ExtensionsTab-BnGwxS9U.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-DxxoZd84.js → IMAccessTab-DjQnpOmF.js} +1 -1
- package/dashboard/dist/assets/{Modal-Nm2e93s5.js → Modal-DpuinsE3.js} +1 -1
- package/dashboard/dist/assets/{Modals-BwxZmU0U.js → Modals-Bs842H3n.js} +1 -1
- package/dashboard/dist/assets/{Select-SGlII0yx.js → Select-Dvia29HZ.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-CVItEgcH.js +1 -0
- package/dashboard/dist/assets/{SystemTab-BF6lIAYM.js → SystemTab-BzPtwDxq.js} +1 -1
- package/dashboard/dist/assets/index-Bthwt6K_.css +1 -0
- package/dashboard/dist/assets/{index-DZiAiRNt.js → index-S0NmlDEH.js} +14 -14
- package/dashboard/dist/assets/{index-BP8R_bLT.js → index-yZ-iG1qk.js} +2 -2
- package/dashboard/dist/assets/{shared-S0kcs5yP.js → shared-p3kZpiD4.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/kernel-bridge.js +207 -0
- package/dist/agent/mcp/capabilities.js +39 -0
- package/dist/agent/stream.js +19 -1
- package/dist/bot/bot.js +4 -13
- package/dist/cli/kernel-app.js +115 -0
- package/dist/cli/main.js +7 -0
- package/package.json +4 -2
- package/packages/kernel/README.md +305 -0
- package/packages/kernel/dist/contracts/driver.d.ts +95 -0
- package/packages/kernel/dist/contracts/driver.js +1 -0
- package/packages/kernel/dist/contracts/ports.d.ts +84 -0
- package/packages/kernel/dist/contracts/ports.js +1 -0
- package/packages/kernel/dist/contracts/surface.d.ts +92 -0
- package/packages/kernel/dist/contracts/surface.js +1 -0
- package/packages/kernel/dist/drivers/claude.d.ts +34 -0
- package/packages/kernel/dist/drivers/claude.js +500 -0
- package/packages/kernel/dist/drivers/codex.d.ts +24 -0
- package/packages/kernel/dist/drivers/codex.js +415 -0
- package/packages/kernel/dist/drivers/echo.d.ts +20 -0
- package/packages/kernel/dist/drivers/echo.js +61 -0
- package/packages/kernel/dist/drivers/gemini.d.ts +18 -0
- package/packages/kernel/dist/drivers/gemini.js +143 -0
- package/packages/kernel/dist/drivers/hermes.d.ts +14 -0
- package/packages/kernel/dist/drivers/hermes.js +194 -0
- package/packages/kernel/dist/drivers/index.d.ts +5 -0
- package/packages/kernel/dist/drivers/index.js +5 -0
- package/packages/kernel/dist/index.d.ts +18 -0
- package/packages/kernel/dist/index.js +29 -0
- package/packages/kernel/dist/ports/defaults.d.ts +59 -0
- package/packages/kernel/dist/ports/defaults.js +137 -0
- package/packages/kernel/dist/protocol/index.d.ts +309 -0
- package/packages/kernel/dist/protocol/index.js +59 -0
- package/packages/kernel/dist/runtime/hub.d.ts +68 -0
- package/packages/kernel/dist/runtime/hub.js +334 -0
- package/packages/kernel/dist/runtime/loom.d.ts +45 -0
- package/packages/kernel/dist/runtime/loom.js +72 -0
- package/packages/kernel/dist/runtime/pty.d.ts +23 -0
- package/packages/kernel/dist/runtime/pty.js +69 -0
- package/packages/kernel/dist/runtime/session-runner.d.ts +27 -0
- package/packages/kernel/dist/runtime/session-runner.js +210 -0
- package/packages/kernel/dist/runtime/tui.d.ts +8 -0
- package/packages/kernel/dist/runtime/tui.js +35 -0
- package/packages/kernel/dist/runtime/turn.d.ts +17 -0
- package/packages/kernel/dist/runtime/turn.js +16 -0
- package/packages/kernel/dist/surfaces/cli.d.ts +19 -0
- package/packages/kernel/dist/surfaces/cli.js +65 -0
- package/packages/kernel/dist/surfaces/index.d.ts +2 -0
- package/packages/kernel/dist/surfaces/index.js +2 -0
- package/packages/kernel/dist/surfaces/web.d.ts +39 -0
- package/packages/kernel/dist/surfaces/web.js +244 -0
- package/dashboard/dist/assets/SessionPanel-DYfSlreh.js +0 -1
- package/dashboard/dist/assets/index-CtS48Jn-.css +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# @pikiloom/kernel
|
|
2
|
+
|
|
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.
|
|
7
|
+
|
|
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`.
|
|
12
|
+
|
|
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
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
上层 (you write): IM bindings · Web UI · Plugins ── implement Surface / Plugin
|
|
26
|
+
▲ createLoom({ surfaces, plugins, ...ports })
|
|
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
|
|
35
|
+
```
|
|
36
|
+
|
|
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` |
|
|
45
|
+
|
|
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.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
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';
|
|
85
|
+
|
|
86
|
+
const loom = createLoom({
|
|
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
|
|
92
|
+
});
|
|
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
|
|
110
|
+
```
|
|
111
|
+
|
|
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).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
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).
|
|
123
|
+
|
|
124
|
+
```ts
|
|
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
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
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.
|
|
241
|
+
|
|
242
|
+
**Ports** (side) — all optional, each with a default, so `createLoom()` runs with zero config:
|
|
243
|
+
|
|
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) |
|
|
252
|
+
|
|
253
|
+
**Plugins** are the registration unit for everything ONE capability adds to a session —
|
|
254
|
+
register many, composed deterministically (and dynamically via `loom.registerPlugin(...)`):
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
interface Plugin {
|
|
258
|
+
id: string;
|
|
259
|
+
tools?(opts: { agent; workdir }): McpServerSpec[]; // MCP servers
|
|
260
|
+
promptFragment?(opts: { agent; workdir; isFirstTurn }): string | null; // how-to-use / behavior prompt
|
|
261
|
+
contributeSpawn?(opts: { agent; workdir; mode: 'run'|'tui'; sessionId?; model? }): SpawnContribution | null; // { env?, extraArgs?, configOverrides? }
|
|
262
|
+
decorateSnapshot?(snapshot): UniversalSnapshot;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The kernel merges contributions per spawn — **never mutating global `process.env`** — in order
|
|
267
|
+
`[ModelResolver → ToolProvider.env → plugins (registration order)]`, so a plugin can override
|
|
268
|
+
the resolver (e.g. point an agent's `ANTHROPIC_BASE_URL` at a local proxy). `promptFragment`s are
|
|
269
|
+
appended to the `SystemPromptBuilder` base and delivered via each agent's native mechanism. This
|
|
270
|
+
is how a capability registers its tools **and** their usage prompt **and** any env/flags in one
|
|
271
|
+
place — and how a model-traffic interceptor injects a redirect on both the `run()` and `tui()`
|
|
272
|
+
rails without the kernel knowing anything about it. (The singular `ModelResolver` /
|
|
273
|
+
`SystemPromptBuilder` ports remain the one authoritative model-credential / base-prompt source;
|
|
274
|
+
plugins are the composable per-capability layer on top.)
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Exports
|
|
279
|
+
|
|
280
|
+
Main entry `@pikiloom/kernel` re-exports everything. Subpaths: `@pikiloom/kernel/drivers`,
|
|
281
|
+
`@pikiloom/kernel/surfaces`, `@pikiloom/kernel/protocol`.
|
|
282
|
+
|
|
283
|
+
- Runtime: `createLoom`, `Loom`, `Hub`, `SessionRunner`, `runTurn`, `PtyBridge`, `ptyAvailable`, `attachTui`
|
|
284
|
+
- Drivers: `EchoDriver`, `ClaudeDriver`, `CodexDriver`, `GeminiDriver`, `HermesDriver`
|
|
285
|
+
- Surfaces: `WebSurface`, `CliSurface`
|
|
286
|
+
- Ports/defaults: `FsSessionStore`, `NullModelResolver`, `NoopToolProvider`, `PassthroughSystemPromptBuilder`, `AutoCancelInteractionHandler`, `DeferToTerminalInteractionHandler`, `NoopCatalog`, `defaultBaseDir`
|
|
287
|
+
- Protocol: `UniversalSnapshot`, `diffSnapshot`, `applySnapshotPatch`, `emptySnapshot`, `PROTOCOL_VERSION`, all wire/`Client*`/`Server*` message types
|
|
288
|
+
- Types: `AgentDriver`, `AgentTurnInput`, `DriverContext`, `DriverEvent`, `DriverResult`, `LoomIO`, `PromptInput`, `Surface`, `Plugin`, `SpawnContribution`, `SessionStore`, `ModelResolver`, `ToolProvider`, `SystemPromptBuilder`, `InteractionHandler`, `Catalog`, …
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Verify
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npm run typecheck # tsc, clean
|
|
296
|
+
npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow, driver parsers
|
|
297
|
+
KERNEL_E2E_REAL=1 npm test # also drives the real `claude` CLI end-to-end
|
|
298
|
+
node examples/smoke.mjs # smoke against the compiled dist
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
See `examples/` for a runnable web console, a Feishu/Lark terminal, and a Node smoke test.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { UniversalToolCall, UniversalPlan, UniversalUsage, UniversalInteraction, UniversalArtifact, UniversalSubAgent } from '../protocol/index.js';
|
|
2
|
+
export interface AgentTurnInput {
|
|
3
|
+
prompt: string;
|
|
4
|
+
attachments?: string[];
|
|
5
|
+
sessionId?: string | null;
|
|
6
|
+
workdir: string;
|
|
7
|
+
model?: string | null;
|
|
8
|
+
effort?: string | null;
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
extraMcpServers?: McpServerSpec[];
|
|
12
|
+
mcpConfigPath?: string | null;
|
|
13
|
+
permissionMode?: string | null;
|
|
14
|
+
extraArgs?: string[];
|
|
15
|
+
configOverrides?: string[];
|
|
16
|
+
fullAccess?: boolean;
|
|
17
|
+
steerable?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface McpServerSpec {
|
|
20
|
+
name: string;
|
|
21
|
+
type?: 'stdio' | 'http';
|
|
22
|
+
command?: string;
|
|
23
|
+
args?: string[];
|
|
24
|
+
env?: Record<string, string>;
|
|
25
|
+
url?: string;
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
export type DriverEvent = {
|
|
29
|
+
type: 'session';
|
|
30
|
+
sessionId: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'text';
|
|
33
|
+
delta: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'reasoning';
|
|
36
|
+
delta: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'tool';
|
|
39
|
+
call: UniversalToolCall;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'plan';
|
|
42
|
+
plan: UniversalPlan;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'subagent';
|
|
45
|
+
subagent: UniversalSubAgent;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'usage';
|
|
48
|
+
usage: Partial<UniversalUsage>;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'artifact';
|
|
51
|
+
artifact: UniversalArtifact;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'activity';
|
|
54
|
+
line: string;
|
|
55
|
+
};
|
|
56
|
+
export type SteerFn = (prompt: string, attachments?: string[]) => Promise<boolean>;
|
|
57
|
+
export interface DriverContext {
|
|
58
|
+
signal: AbortSignal;
|
|
59
|
+
emit(event: DriverEvent): void;
|
|
60
|
+
askUser(interaction: UniversalInteraction): Promise<Record<string, string[]>>;
|
|
61
|
+
registerSteer(fn: SteerFn): void;
|
|
62
|
+
}
|
|
63
|
+
export interface DriverResult {
|
|
64
|
+
ok: boolean;
|
|
65
|
+
text: string;
|
|
66
|
+
reasoning?: string;
|
|
67
|
+
error?: string | null;
|
|
68
|
+
stopReason?: string | null;
|
|
69
|
+
sessionId?: string | null;
|
|
70
|
+
usage?: UniversalUsage | null;
|
|
71
|
+
}
|
|
72
|
+
export interface TuiInput {
|
|
73
|
+
workdir: string;
|
|
74
|
+
model?: string | null;
|
|
75
|
+
sessionId?: string | null;
|
|
76
|
+
env?: Record<string, string>;
|
|
77
|
+
extraArgs?: string[];
|
|
78
|
+
}
|
|
79
|
+
export interface TuiSpec {
|
|
80
|
+
command: string;
|
|
81
|
+
args: string[];
|
|
82
|
+
cwd: string;
|
|
83
|
+
env?: Record<string, string>;
|
|
84
|
+
}
|
|
85
|
+
export interface AgentDriver {
|
|
86
|
+
readonly id: string;
|
|
87
|
+
readonly capabilities?: {
|
|
88
|
+
steer?: boolean;
|
|
89
|
+
interact?: boolean;
|
|
90
|
+
resume?: boolean;
|
|
91
|
+
tui?: boolean;
|
|
92
|
+
};
|
|
93
|
+
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
94
|
+
tui?(input: TuiInput): TuiSpec;
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { UniversalInteraction, UniversalSnapshot, ModelDescriptor, EffortOption, ToolDescriptor, SkillDescriptor } from '../protocol/index.js';
|
|
2
|
+
import type { McpServerSpec, DriverResult } from './driver.js';
|
|
3
|
+
export interface CoreSessionRecord {
|
|
4
|
+
agent: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
nativeSessionId?: string | null;
|
|
7
|
+
workspacePath: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
title?: string | null;
|
|
11
|
+
model?: string | null;
|
|
12
|
+
effort?: string | null;
|
|
13
|
+
runState?: 'running' | 'completed' | 'incomplete';
|
|
14
|
+
runDetail?: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionStore {
|
|
17
|
+
ensure(agent: string, opts: {
|
|
18
|
+
sessionId?: string | null;
|
|
19
|
+
title?: string | null;
|
|
20
|
+
workdir: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
sessionId: string;
|
|
23
|
+
workspacePath: string;
|
|
24
|
+
}>;
|
|
25
|
+
get(agent: string, sessionId: string): Promise<CoreSessionRecord | null>;
|
|
26
|
+
save(record: CoreSessionRecord): Promise<void>;
|
|
27
|
+
list(agent: string, opts?: {
|
|
28
|
+
limit?: number;
|
|
29
|
+
}): Promise<CoreSessionRecord[]>;
|
|
30
|
+
recordResult(agent: string, sessionId: string, result: DriverResult): Promise<void>;
|
|
31
|
+
appendTurn?(agent: string, sessionId: string, turn: UniversalSnapshot): Promise<void>;
|
|
32
|
+
history?(agent: string, sessionId: string): Promise<UniversalSnapshot[]>;
|
|
33
|
+
}
|
|
34
|
+
export interface ModelInjection {
|
|
35
|
+
model?: string | null;
|
|
36
|
+
env?: Record<string, string>;
|
|
37
|
+
extraArgs?: string[];
|
|
38
|
+
configOverrides?: string[];
|
|
39
|
+
providerName?: string | null;
|
|
40
|
+
contextWindow?: number | null;
|
|
41
|
+
}
|
|
42
|
+
export interface ModelResolver {
|
|
43
|
+
resolve(agent: string, opts: {
|
|
44
|
+
model?: string | null;
|
|
45
|
+
profileId?: string | null;
|
|
46
|
+
}): Promise<ModelInjection | null>;
|
|
47
|
+
}
|
|
48
|
+
export interface ToolProvider {
|
|
49
|
+
provideForSession(opts: {
|
|
50
|
+
agent: string;
|
|
51
|
+
workdir: string;
|
|
52
|
+
workspacePath: string;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
servers: McpServerSpec[];
|
|
55
|
+
env?: Record<string, string>;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
export interface SystemPromptBuilder {
|
|
59
|
+
compose(opts: {
|
|
60
|
+
agent: string;
|
|
61
|
+
base?: string;
|
|
62
|
+
isFirstTurn: boolean;
|
|
63
|
+
}): string | undefined;
|
|
64
|
+
}
|
|
65
|
+
export interface InteractionHandler {
|
|
66
|
+
askUser(interaction: UniversalInteraction): Promise<Record<string, string[]> | null>;
|
|
67
|
+
}
|
|
68
|
+
export interface Catalog {
|
|
69
|
+
listModels(opts: {
|
|
70
|
+
agent: string;
|
|
71
|
+
}): Promise<ModelDescriptor[]>;
|
|
72
|
+
listEffort(opts: {
|
|
73
|
+
agent: string;
|
|
74
|
+
model?: string | null;
|
|
75
|
+
}): Promise<EffortOption[]>;
|
|
76
|
+
listTools(opts: {
|
|
77
|
+
agent: string;
|
|
78
|
+
workdir: string;
|
|
79
|
+
}): Promise<ToolDescriptor[]>;
|
|
80
|
+
listSkills(opts: {
|
|
81
|
+
agent: string;
|
|
82
|
+
workdir: string;
|
|
83
|
+
}): Promise<SkillDescriptor[]>;
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { UniversalSnapshot, SnapshotPatch, SessionMeta, AgentInfo, ModelDescriptor, EffortOption, ToolDescriptor, SkillDescriptor } from '../protocol/index.js';
|
|
2
|
+
import type { McpServerSpec } from './driver.js';
|
|
3
|
+
export interface PromptInput {
|
|
4
|
+
prompt: string;
|
|
5
|
+
agent?: string;
|
|
6
|
+
sessionKey?: string;
|
|
7
|
+
workdir?: string;
|
|
8
|
+
model?: string | null;
|
|
9
|
+
effort?: string | null;
|
|
10
|
+
attachments?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface LoomIO {
|
|
13
|
+
prompt(input: PromptInput): Promise<{
|
|
14
|
+
sessionKey: string;
|
|
15
|
+
taskId: string;
|
|
16
|
+
}>;
|
|
17
|
+
stop(sessionKey: string): boolean;
|
|
18
|
+
steer(taskId: string, prompt: string, attachments?: string[]): Promise<boolean>;
|
|
19
|
+
interact(promptId: string, action: 'select' | 'text' | 'skip' | 'cancel', value?: string): boolean;
|
|
20
|
+
subscribe(cb: (sessionKey: string, snapshot: UniversalSnapshot, patch: SnapshotPatch, seq: number) => void): () => void;
|
|
21
|
+
onSessionsChanged(cb: (sessions: SessionMeta[]) => void): () => void;
|
|
22
|
+
listSessions(): SessionMeta[];
|
|
23
|
+
getSnapshot(sessionKey: string): {
|
|
24
|
+
snapshot: UniversalSnapshot;
|
|
25
|
+
seq: number;
|
|
26
|
+
} | null;
|
|
27
|
+
getHistory(sessionKey: string): Promise<UniversalSnapshot[]>;
|
|
28
|
+
listAgents(): string[];
|
|
29
|
+
listAgentInfo(): AgentInfo[];
|
|
30
|
+
listModels(agent: string): Promise<ModelDescriptor[]>;
|
|
31
|
+
listEffort(agent: string, model?: string | null): Promise<EffortOption[]>;
|
|
32
|
+
listTools(agent: string, workdir?: string): Promise<ToolDescriptor[]>;
|
|
33
|
+
listSkills(agent: string, workdir?: string): Promise<SkillDescriptor[]>;
|
|
34
|
+
}
|
|
35
|
+
export interface SurfaceCapabilities {
|
|
36
|
+
editMessages?: boolean;
|
|
37
|
+
images?: boolean;
|
|
38
|
+
buttons?: boolean;
|
|
39
|
+
tunnel?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface PtyHandle {
|
|
42
|
+
onData(cb: (data: string) => void): () => void;
|
|
43
|
+
write(data: string): void;
|
|
44
|
+
resize(cols: number, rows: number): void;
|
|
45
|
+
kill(signal?: string): void;
|
|
46
|
+
onExit(cb: (e: {
|
|
47
|
+
exitCode: number;
|
|
48
|
+
signal?: number;
|
|
49
|
+
}) => void): () => void;
|
|
50
|
+
readonly pid: number;
|
|
51
|
+
}
|
|
52
|
+
export interface TuiHost {
|
|
53
|
+
openTui(opts: {
|
|
54
|
+
agent?: string;
|
|
55
|
+
workdir?: string;
|
|
56
|
+
model?: string | null;
|
|
57
|
+
sessionId?: string | null;
|
|
58
|
+
cols?: number;
|
|
59
|
+
rows?: number;
|
|
60
|
+
}): Promise<PtyHandle>;
|
|
61
|
+
}
|
|
62
|
+
export interface Surface {
|
|
63
|
+
readonly id: string;
|
|
64
|
+
readonly capabilities?: SurfaceCapabilities;
|
|
65
|
+
start(io: LoomIO, host?: TuiHost): Promise<void>;
|
|
66
|
+
stop(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export interface SpawnContribution {
|
|
69
|
+
env?: Record<string, string>;
|
|
70
|
+
extraArgs?: string[];
|
|
71
|
+
configOverrides?: string[];
|
|
72
|
+
}
|
|
73
|
+
export interface Plugin {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
tools?(opts: {
|
|
76
|
+
agent: string;
|
|
77
|
+
workdir: string;
|
|
78
|
+
}): McpServerSpec[] | Promise<McpServerSpec[]>;
|
|
79
|
+
promptFragment?(opts: {
|
|
80
|
+
agent: string;
|
|
81
|
+
workdir: string;
|
|
82
|
+
isFirstTurn: boolean;
|
|
83
|
+
}): string | null | undefined | Promise<string | null | undefined>;
|
|
84
|
+
contributeSpawn?(opts: {
|
|
85
|
+
agent: string;
|
|
86
|
+
workdir: string;
|
|
87
|
+
mode: 'run' | 'tui';
|
|
88
|
+
sessionId?: string | null;
|
|
89
|
+
model?: string | null;
|
|
90
|
+
}): SpawnContribution | null | undefined | Promise<SpawnContribution | null | undefined>;
|
|
91
|
+
decorateSnapshot?(snapshot: UniversalSnapshot): UniversalSnapshot;
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec } from '../contracts/driver.js';
|
|
2
|
+
import type { UniversalUsage, UniversalPlan } from '../protocol/index.js';
|
|
3
|
+
export declare class ClaudeDriver implements AgentDriver {
|
|
4
|
+
private readonly bin;
|
|
5
|
+
readonly id = "claude";
|
|
6
|
+
readonly capabilities: {
|
|
7
|
+
steer: boolean;
|
|
8
|
+
interact: boolean;
|
|
9
|
+
resume: boolean;
|
|
10
|
+
tui: boolean;
|
|
11
|
+
};
|
|
12
|
+
constructor(bin?: string);
|
|
13
|
+
tui(input: TuiInput): TuiSpec;
|
|
14
|
+
run(input: AgentTurnInput, ctx: DriverContext): Promise<DriverResult>;
|
|
15
|
+
private usage;
|
|
16
|
+
}
|
|
17
|
+
export interface ClaudeUsageState {
|
|
18
|
+
input: number | null;
|
|
19
|
+
output: number | null;
|
|
20
|
+
cached: number | null;
|
|
21
|
+
cacheCreation?: number | null;
|
|
22
|
+
contextWindow?: number | null;
|
|
23
|
+
turnOutputTokensBase?: number | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function claudeUsageOf(s: ClaudeUsageState): UniversalUsage;
|
|
26
|
+
export declare function claudeContextWindowFromModel(model: unknown): number | null;
|
|
27
|
+
export declare function claudeEffectiveContextWindow(advertised: number | null): number | null;
|
|
28
|
+
export declare function handleClaudeEvent(ev: any, s: any, emit: (e: DriverEvent) => void): void;
|
|
29
|
+
export declare function claudeUserMessage(text: string): string;
|
|
30
|
+
export declare function emitClaudeImages(blocks: any[], s: any, emit: (e: DriverEvent) => void): void;
|
|
31
|
+
export declare function todoWriteToPlan(input: any): UniversalPlan | null;
|
|
32
|
+
export declare function shortToolValue(value: unknown, max?: number): string;
|
|
33
|
+
export declare function summarizeToolUse(name: string, input: any): string;
|
|
34
|
+
export declare function firstResultLine(content: any): string;
|