@pikiloom/kernel 0.1.1

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 (44) hide show
  1. package/README.md +107 -0
  2. package/dist/contracts/driver.d.ts +95 -0
  3. package/dist/contracts/driver.js +1 -0
  4. package/dist/contracts/ports.d.ts +84 -0
  5. package/dist/contracts/ports.js +1 -0
  6. package/dist/contracts/surface.d.ts +75 -0
  7. package/dist/contracts/surface.js +1 -0
  8. package/dist/drivers/claude.d.ts +23 -0
  9. package/dist/drivers/claude.js +453 -0
  10. package/dist/drivers/codex.d.ts +14 -0
  11. package/dist/drivers/codex.js +346 -0
  12. package/dist/drivers/echo.d.ts +20 -0
  13. package/dist/drivers/echo.js +61 -0
  14. package/dist/drivers/gemini.d.ts +18 -0
  15. package/dist/drivers/gemini.js +143 -0
  16. package/dist/drivers/hermes.d.ts +14 -0
  17. package/dist/drivers/hermes.js +194 -0
  18. package/dist/drivers/index.d.ts +5 -0
  19. package/dist/drivers/index.js +5 -0
  20. package/dist/index.d.ts +18 -0
  21. package/dist/index.js +29 -0
  22. package/dist/ports/defaults.d.ts +59 -0
  23. package/dist/ports/defaults.js +137 -0
  24. package/dist/protocol/index.d.ts +309 -0
  25. package/dist/protocol/index.js +59 -0
  26. package/dist/runtime/hub.d.ts +65 -0
  27. package/dist/runtime/hub.js +260 -0
  28. package/dist/runtime/loom.d.ts +44 -0
  29. package/dist/runtime/loom.js +69 -0
  30. package/dist/runtime/pty.d.ts +23 -0
  31. package/dist/runtime/pty.js +69 -0
  32. package/dist/runtime/session-runner.d.ts +27 -0
  33. package/dist/runtime/session-runner.js +210 -0
  34. package/dist/runtime/tui.d.ts +8 -0
  35. package/dist/runtime/tui.js +35 -0
  36. package/dist/runtime/turn.d.ts +17 -0
  37. package/dist/runtime/turn.js +16 -0
  38. package/dist/surfaces/cli.d.ts +19 -0
  39. package/dist/surfaces/cli.js +65 -0
  40. package/dist/surfaces/index.d.ts +2 -0
  41. package/dist/surfaces/index.js +2 -0
  42. package/dist/surfaces/web.d.ts +39 -0
  43. package/dist/surfaces/web.js +244 -0
  44. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @pikiloom/kernel
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.
6
+
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.
9
+
10
+ ## The model (one package, three rings)
11
+
12
+ ```
13
+ 上层 (you write): IM bindings · Web UI/skin · Plugins ── implement Surface / Plugin
14
+ ▲ 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
22
+ ```
23
+
24
+ **IM and Web are not two systems** — they are both just `Surface`s over the same `LoomIO`.
25
+
26
+ ## Quickstart
27
+
28
+ ```ts
29
+ import { createLoom, ClaudeDriver, WebSurface } from '@pikiloom/kernel';
30
+
31
+ const loom = createLoom({
32
+ drivers: [new ClaudeDriver()], // 下层 (unchanged)
33
+ surfaces: [new WebSurface({ port: 8787 })], // 上层 (IM / Web / tunnel)
34
+ });
35
+ await loom.start();
36
+ ```
37
+
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.
41
+
42
+ ### TUI passthrough (`pikiloom code` → Claude/Codex TUI)
43
+
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).
47
+
48
+ ```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
52
+ ```
53
+
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.
57
+
58
+ ## Contracts
59
+
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 |
67
+
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).
71
+
72
+ ## What's included
73
+
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`.
78
+
79
+ ## Publishing / CI
80
+
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.
84
+
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`.
87
+
88
+ ## Verification
89
+
90
+ ```bash
91
+ npm run typecheck # tsc, clean
92
+ npm test # hermetic: snapshot diff, full lifecycle (stop/steer/interact), web ws flow
93
+ KERNEL_E2E_REAL=1 npm test # also drives the real `claude` CLI end-to-end
94
+ node examples/smoke.mjs # smoke against the compiled dist
95
+ ```
96
+
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.
100
+
101
+ ## Roadmap (gated cutover)
102
+
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.
@@ -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,75 @@
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 Plugin {
69
+ readonly id: string;
70
+ tools?(opts: {
71
+ agent: string;
72
+ workdir: string;
73
+ }): McpServerSpec[] | Promise<McpServerSpec[]>;
74
+ decorateSnapshot?(snapshot: UniversalSnapshot): UniversalSnapshot;
75
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { AgentDriver, AgentTurnInput, DriverContext, DriverResult, DriverEvent, TuiInput, TuiSpec } from '../contracts/driver.js';
2
+ import type { 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 declare function handleClaudeEvent(ev: any, s: any, emit: (e: DriverEvent) => void): void;
18
+ export declare function claudeUserMessage(text: string): string;
19
+ export declare function emitClaudeImages(blocks: any[], s: any, emit: (e: DriverEvent) => void): void;
20
+ export declare function todoWriteToPlan(input: any): UniversalPlan | null;
21
+ export declare function shortToolValue(value: unknown, max?: number): string;
22
+ export declare function summarizeToolUse(name: string, input: any): string;
23
+ export declare function firstResultLine(content: any): string;