@oh-my-pi/pi-coding-agent 10.6.2 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +80 -79
  3. package/docs/compaction.md +182 -149
  4. package/docs/config-usage.md +141 -78
  5. package/docs/custom-tools.md +45 -16
  6. package/docs/extension-loading.md +56 -954
  7. package/docs/extensions.md +192 -51
  8. package/docs/hooks.md +109 -70
  9. package/docs/python-repl.md +52 -19
  10. package/docs/rpc.md +43 -19
  11. package/docs/sdk.md +270 -211
  12. package/docs/session-tree-plan.md +60 -417
  13. package/docs/session.md +104 -39
  14. package/docs/skills.md +59 -95
  15. package/docs/theme.md +139 -110
  16. package/docs/tree.md +42 -33
  17. package/docs/tui.md +226 -80
  18. package/package.json +8 -9
  19. package/src/capability/index.ts +3 -4
  20. package/src/cli/args.ts +4 -4
  21. package/src/cli/grep-cli.ts +1 -1
  22. package/src/commit/agentic/index.ts +4 -3
  23. package/src/commit/git/index.ts +2 -3
  24. package/src/commit/map-reduce/index.ts +2 -1
  25. package/src/config/prompt-templates.ts +2 -0
  26. package/src/config/settings-schema.ts +30 -7
  27. package/src/config/settings.ts +0 -14
  28. package/src/config.ts +2 -2
  29. package/src/discovery/agents.ts +36 -0
  30. package/src/discovery/index.ts +1 -0
  31. package/src/exa/mcp-client.ts +3 -3
  32. package/src/ipy/executor.ts +5 -7
  33. package/src/ipy/gateway-coordinator.ts +1 -1
  34. package/src/ipy/kernel.ts +20 -15
  35. package/src/ipy/prelude.py +1 -1
  36. package/src/ipy/runtime.ts +7 -6
  37. package/src/lsp/lspmux.ts +3 -3
  38. package/src/main.ts +6 -8
  39. package/src/mcp/tool-bridge.ts +19 -9
  40. package/src/modes/components/assistant-message.ts +2 -2
  41. package/src/modes/components/hook-editor.ts +4 -4
  42. package/src/modes/components/settings-defs.ts +37 -2
  43. package/src/modes/components/tool-execution.ts +7 -7
  44. package/src/modes/controllers/command-controller.ts +2 -2
  45. package/src/modes/controllers/event-controller.ts +4 -7
  46. package/src/modes/controllers/input-controller.ts +4 -4
  47. package/src/modes/controllers/selector-controller.ts +1 -0
  48. package/src/modes/interactive-mode.ts +3 -5
  49. package/src/modes/rpc/rpc-mode.ts +8 -9
  50. package/src/patch/index.ts +6 -6
  51. package/src/prompts/agents/explore.md +2 -2
  52. package/src/prompts/agents/frontmatter.md +5 -5
  53. package/src/prompts/agents/plan.md +3 -2
  54. package/src/prompts/agents/reviewer.md +1 -1
  55. package/src/prompts/system/system-prompt.md +1 -3
  56. package/src/sdk.ts +13 -9
  57. package/src/session/agent-session.ts +6 -4
  58. package/src/session/compaction/compaction.ts +3 -3
  59. package/src/session/session-manager.ts +8 -9
  60. package/src/ssh/connection-manager.ts +4 -4
  61. package/src/system-prompt.ts +2 -6
  62. package/src/task/agents.ts +1 -1
  63. package/src/task/executor.ts +31 -8
  64. package/src/task/index.ts +14 -35
  65. package/src/task/omp-command.ts +3 -1
  66. package/src/task/output-manager.ts +20 -6
  67. package/src/task/parallel.ts +3 -3
  68. package/src/task/render.ts +16 -2
  69. package/src/task/types.ts +13 -20
  70. package/src/task/worktree.ts +3 -3
  71. package/src/tools/ask.ts +3 -8
  72. package/src/tools/fetch.ts +2 -2
  73. package/src/tools/gemini-image.ts +5 -6
  74. package/src/tools/grep.ts +5 -5
  75. package/src/tools/index.ts +12 -5
  76. package/src/tools/read.ts +1 -1
  77. package/src/tools/todo-write.ts +2 -3
  78. package/src/utils/frontmatter.ts +1 -1
  79. package/src/utils/image-resize.ts +1 -1
  80. package/src/utils/timings.ts +3 -2
  81. package/src/web/scrapers/github.ts +2 -2
  82. package/src/web/scrapers/utils.ts +2 -3
  83. package/src/web/scrapers/youtube.ts +2 -3
  84. package/src/web/search/auth.ts +5 -6
  85. package/src/web/search/providers/anthropic.ts +3 -2
  86. package/src/utils/terminal-notify.ts +0 -37
package/docs/sdk.md CHANGED
@@ -21,14 +21,18 @@ import { createAgentSession, discoverAuthStorage, discoverModels, SessionManager
21
21
 
22
22
  // Set up credential storage and model registry
23
23
  const authStorage = await discoverAuthStorage();
24
- const modelRegistry = await discoverModels(authStorage);
24
+ const modelRegistry = discoverModels(authStorage);
25
25
 
26
- const { session } = await createAgentSession({
26
+ const { session, modelFallbackMessage } = await createAgentSession({
27
27
  sessionManager: SessionManager.inMemory(),
28
28
  authStorage,
29
29
  modelRegistry,
30
30
  });
31
31
 
32
+ if (modelFallbackMessage) {
33
+ process.stderr.write(`${modelFallbackMessage}\n`);
34
+ }
35
+
32
36
  session.subscribe((event) => {
33
37
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
34
38
  process.stdout.write(event.assistantMessageEvent.delta);
@@ -41,7 +45,7 @@ await session.prompt("What files are in the current directory?");
41
45
  ## Installation
42
46
 
43
47
  ```bash
44
- npm install @oh-my-pi/pi-coding-agent
48
+ bun add @oh-my-pi/pi-coding-agent
45
49
  ```
46
50
 
47
51
  The SDK is included in the main package. No separate installation needed.
@@ -59,15 +63,16 @@ The main factory function. Creates an `AgentSession` with configurable options.
59
63
 
60
64
  ```typescript
61
65
  import { createAgentSession } from "@oh-my-pi/pi-coding-agent";
66
+ import systemPrompt from "./SYSTEM.md" with { type: "text" };
62
67
 
63
- // Minimal: all defaults (discovers everything from cwd and ~/.omp/agent)
68
+ // Minimal: all defaults (discovers from cwd + config dirs and ~/.omp/agent)
64
69
  const { session } = await createAgentSession();
65
70
 
66
71
  // Custom: override specific options
67
72
  const { session } = await createAgentSession({
68
73
  model: myModel,
69
- systemPrompt: "You are helpful.",
70
- tools: [readTool, bashTool],
74
+ systemPrompt,
75
+ toolNames: ["read", "bash", "edit"], // Filter to specific tools
71
76
  sessionManager: SessionManager.inMemory(),
72
77
  });
73
78
  ```
@@ -78,8 +83,14 @@ The session manages the agent lifecycle, message history, and event streaming.
78
83
 
79
84
  ```typescript
80
85
  interface AgentSession {
81
- // Send a prompt and wait for completion
86
+ // Prompting
82
87
  prompt(text: string, options?: PromptOptions): Promise<void>;
88
+ sendUserMessage(
89
+ content: string | (TextContent | ImageContent)[],
90
+ options?: { deliverAs?: "steer" | "followUp" },
91
+ ): Promise<void>;
92
+ steer(text: string): void;
93
+ followUp(text: string): void;
83
94
 
84
95
  // Subscribe to events (returns unsubscribe function)
85
96
  subscribe(listener: (event: AgentSessionEvent) => void): () => void;
@@ -87,43 +98,65 @@ interface AgentSession {
87
98
  // Session info
88
99
  sessionFile: string | undefined; // undefined for in-memory
89
100
  sessionId: string;
101
+ sessionName: string | undefined;
90
102
 
91
103
  // Model control
92
- setModel(model: Model): Promise<void>;
104
+ setModel(model: Model, role?: ModelRole): Promise<void>;
105
+ setModelTemporary(model: Model): Promise<void>;
93
106
  setThinkingLevel(level: ThinkingLevel): void;
94
- cycleModel(): Promise<ModelCycleResult | undefined>;
107
+ cycleModel(direction?: "forward" | "backward"): Promise<ModelCycleResult | undefined>;
108
+ cycleRoleModels(
109
+ direction?: "forward" | "backward",
110
+ ): Promise<{ model: Model; thinkingLevel: ThinkingLevel; role: ModelRole } | undefined>;
95
111
  cycleThinkingLevel(): ThinkingLevel | undefined;
96
112
 
97
113
  // State access
98
114
  agent: Agent;
115
+ sessionManager: SessionManager;
116
+ settings: Settings;
99
117
  model: Model | undefined;
100
118
  thinkingLevel: ThinkingLevel;
101
119
  messages: AgentMessage[];
102
120
  isStreaming: boolean;
121
+ isCompacting: boolean;
122
+ isRetrying: boolean;
103
123
 
104
124
  // Session management
105
- newSession(options?: { parentSession?: string }): Promise<boolean>; // Returns false if cancelled by hook
106
- switchSession(sessionPath: string): Promise<boolean>;
125
+ newSession(options?: NewSessionOptions): Promise<boolean>; // Returns false if cancelled by extension
126
+ fork(): Promise<boolean>; // Creates a new session file
107
127
 
108
128
  // Branching
109
- branch(entryId: string): Promise<{ selectedText: string; cancelled: boolean }>; // Creates new session file
129
+ branch(entryId: string): Promise<{ selectedText: string; cancelled: boolean }>;
110
130
  navigateTree(
111
131
  targetId: string,
112
- options?: { summarize?: boolean }
113
- ): Promise<{ editorText?: string; cancelled: boolean }>; // In-place navigation
132
+ options?: { summarize?: boolean; customInstructions?: string }
133
+ ): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry }>;
114
134
 
115
- // Hook message injection
116
- sendHookMessage(message: HookMessage, triggerTurn?: boolean): Promise<void>;
135
+ // Custom message injection
136
+ sendCustomMessage<T>(
137
+ message: { customType: string; content: T; display?: boolean; details?: unknown },
138
+ options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
139
+ ): Promise<void>;
117
140
 
118
141
  // Compaction
119
- compact(customInstructions?: string): Promise<CompactionResult>;
142
+ compact(
143
+ customInstructions?: string,
144
+ options?: { onComplete?: (result: CompactionResult) => void; onError?: (error: Error) => void },
145
+ ): Promise<CompactionResult>;
120
146
  abortCompaction(): void;
121
147
 
148
+ // Utilities
149
+ getSessionStats(): SessionStats;
150
+ formatSessionAsText(): string;
151
+ formatCompactContext(): string;
152
+ exportToHtml(outputPath?: string): Promise<string>;
153
+ handoff(customInstructions?: string): Promise<{ document: string } | undefined>;
154
+
122
155
  // Abort current operation
123
156
  abort(): Promise<void>;
124
157
 
125
158
  // Cleanup
126
- dispose(): void;
159
+ dispose(): Promise<void>;
127
160
  }
128
161
  ```
129
162
 
@@ -200,11 +233,16 @@ session.subscribe((event) => {
200
233
  // event.toolResults: tool results from this turn
201
234
  break;
202
235
 
203
- // Session events (auto-compaction, retry)
236
+ // Session events (auto-compaction, retry, TTSR, todo reminders)
204
237
  case "auto_compaction_start":
205
238
  case "auto_compaction_end":
206
239
  case "auto_retry_start":
207
240
  case "auto_retry_end":
241
+ case "ttsr_triggered":
242
+ // event.rules
243
+ break;
244
+ case "todo_reminder":
245
+ // event.todos
208
246
  break;
209
247
  }
210
248
  });
@@ -226,24 +264,17 @@ const { session } = await createAgentSession({
226
264
 
227
265
  `cwd` is used for:
228
266
 
229
- - Project hooks (`.omp/hooks/`)
230
- - Project tools (`.omp/tools/`)
231
- - Project skills (`.omp/skills/`)
232
- - Project commands (`.omp/commands/`)
267
+ - Project config discovery (`.omp/`, `.pi/`, `.claude/`, `.codex/`, `.gemini/`)
268
+ - Project extensions/tools/skills/commands (via config dirs)
233
269
  - Context files (`AGENTS.md` walking up from cwd)
234
- - Session directory naming
270
+ - Session directory naming (via `SessionManager.create(cwd)`)
235
271
 
236
272
  `agentDir` is used for:
237
273
 
238
- - Global hooks (`hooks/`)
239
- - Global tools (`tools/`)
240
- - Global skills (`skills/`)
241
- - Global commands (`commands/`)
242
- - Global context file (`AGENTS.md`)
243
- - Settings (`settings.json`)
244
- - Custom models (`models.json`)
245
- - Credentials (`auth.json`)
246
- - Sessions (`sessions/`)
274
+ - Global settings (`config.yml` + `agent.db`)
275
+ - Primary auth/models locations (`auth.json`, `models.yml`, `models.json`)
276
+ - Prompt templates (`prompts/`)
277
+ - Custom TS commands (`commands/`)
247
278
 
248
279
  ### Model
249
280
 
@@ -252,18 +283,18 @@ import { getModel } from "@oh-my-pi/pi-ai";
252
283
  import { discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
253
284
 
254
285
  const authStorage = await discoverAuthStorage();
255
- const modelRegistry = await discoverModels(authStorage);
286
+ const modelRegistry = discoverModels(authStorage);
256
287
 
257
288
  // Find specific built-in model (doesn't check if API key exists)
258
289
  const opus = getModel("anthropic", "claude-opus-4-5");
259
290
  if (!opus) throw new Error("Model not found");
260
291
 
261
- // Find any model by provider/id, including custom models from models.json
292
+ // Find any model by provider/id, including custom models from models.yml
262
293
  // (doesn't check if API key exists)
263
294
  const customModel = modelRegistry.find("my-provider", "my-model");
264
295
 
265
- // Get only models that have valid API keys configured
266
- const available = await modelRegistry.getAvailable();
296
+ // Get all models that have valid API keys configured
297
+ const available = modelRegistry.getAvailable();
267
298
 
268
299
  const { session } = await createAgentSession({
269
300
  model: opus,
@@ -293,16 +324,18 @@ If no model is provided:
293
324
  API key resolution priority (handled by AuthStorage):
294
325
 
295
326
  1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
296
- 2. Stored credentials in `auth.json` (API keys or OAuth tokens)
327
+ 2. Stored credentials in `agent.db` (API keys or OAuth tokens)
297
328
  3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
298
- 4. Fallback resolver (for custom provider keys from `models.json`)
329
+ 4. Fallback resolver (for custom provider keys from `models.yml`)
330
+
331
+ `discoverAuthStorage` also migrates legacy `auth.json` from user config directories (`.pi`, `.claude`, `.codex`, `.gemini`) into `agent.db`.
299
332
 
300
333
  ```typescript
301
334
  import { AuthStorage, ModelRegistry, discoverAuthStorage, discoverModels } from "@oh-my-pi/pi-coding-agent";
302
335
 
303
- // Default: uses ~/.omp/agent/auth.json and ~/.omp/agent/models.json
336
+ // Default: uses agentDir/auth.json → agent.db and agentDir/models.yml (with legacy fallbacks)
304
337
  const authStorage = await discoverAuthStorage();
305
- const modelRegistry = await discoverModels(authStorage);
338
+ const modelRegistry = discoverModels(authStorage);
306
339
 
307
340
  const { session } = await createAgentSession({
308
341
  sessionManager: SessionManager.inMemory(),
@@ -313,9 +346,9 @@ const { session } = await createAgentSession({
313
346
  // Runtime API key override (not persisted to disk)
314
347
  authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
315
348
 
316
- // Custom auth storage location
317
- const customAuth = new AuthStorage("/my/app/auth.json");
318
- const customRegistry = new ModelRegistry(customAuth, "/my/app/models.json");
349
+ // Custom auth storage location (use create(), constructor is private)
350
+ const customAuth = await AuthStorage.create("/my/app/auth.json");
351
+ const customRegistry = new ModelRegistry(customAuth, "/my/app/models.yml");
319
352
 
320
353
  const { session } = await createAgentSession({
321
354
  sessionManager: SessionManager.inMemory(),
@@ -323,7 +356,7 @@ const { session } = await createAgentSession({
323
356
  modelRegistry: customRegistry,
324
357
  });
325
358
 
326
- // No custom models.json (built-in models only)
359
+ // No custom models.yml (built-in models only)
327
360
  const simpleRegistry = new ModelRegistry(authStorage);
328
361
  ```
329
362
 
@@ -332,10 +365,14 @@ const simpleRegistry = new ModelRegistry(authStorage);
332
365
  ### System Prompt
333
366
 
334
367
  ```typescript
368
+ import systemPrompt from "./SYSTEM.md" with { type: "text" };
369
+
335
370
  const { session } = await createAgentSession({
336
- // Replace entirely
337
- systemPrompt: "You are a helpful assistant.",
371
+ // Replace entirely with a static prompt
372
+ systemPrompt,
373
+ });
338
374
 
375
+ const { session: modified } = await createAgentSession({
339
376
  // Or modify default (receives default, returns modified)
340
377
  systemPrompt: (defaultPrompt) => {
341
378
  return `${defaultPrompt}\n\n## Additional Rules\n- Be concise`;
@@ -355,8 +392,10 @@ const { session } = await createAgentSession();
355
392
 
356
393
  // Filter to specific tools
357
394
  const { session } = await createAgentSession({
358
- toolNames: ["read", "grep", "find", "ls"], // Read-only tools
395
+ toolNames: ["read", "grep", "find"], // Read-only tools
359
396
  });
397
+
398
+ `toolNames` is an allowlist for built-ins; custom tools are always included even if not listed.
360
399
  ```
361
400
 
362
401
  #### Available Built-in Tools
@@ -365,32 +404,43 @@ All tools are defined in `BUILTIN_TOOLS`:
365
404
 
366
405
  - `ask` - Interactive user prompts (requires UI)
367
406
  - `bash` - Shell command execution
407
+ - `python` - Python REPL execution
408
+ - `calc` - Calculator
409
+ - `ssh` - Remote SSH execution
368
410
  - `edit` - Surgical file editing
369
411
  - `find` - File search by glob patterns
370
- - `git` - Git operations (can be disabled via settings)
371
412
  - `grep` - Content search with regex
372
- - `ls` - Directory listing
373
413
  - `lsp` - Language server protocol integration
374
414
  - `notebook` - Jupyter notebook editing
375
- - `output` - Task output retrieval
376
415
  - `read` - File reading (text and images)
416
+ - `browser` - Puppeteer-based web browser
377
417
  - `task` - Subagent spawning
418
+ - `todo_write` - Todo file management
378
419
  - `fetch` - URL fetching
379
420
  - `web_search` - Web search
380
421
  - `write` - File writing
381
422
 
423
+ Hidden tools (not in `BUILTIN_TOOLS`) are available but excluded unless requested:
424
+
425
+ - `submit_result` - Required for subagent structured output (use `requireSubmitResultTool` or include in `toolNames`)
426
+ - `report_finding` - Security review reporting
427
+ - `exit_plan_mode` - Plan mode control
428
+
382
429
  #### Creating Tools Manually
383
430
 
384
431
  For advanced use cases, you can create tools directly using `createTools`:
385
432
 
386
433
  ```typescript
387
- import { BUILTIN_TOOLS, createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent";
434
+ import { createTools, Settings, type ToolSession } from "@oh-my-pi/pi-coding-agent";
435
+
436
+ const settingsInstance = await Settings.init({ cwd: "/path/to/project" });
388
437
 
389
438
  const session: ToolSession = {
390
439
  cwd: "/path/to/project",
391
440
  hasUI: false,
392
441
  getSessionFile: () => null,
393
442
  getSessionSpawns: () => "*",
443
+ settings: settingsInstance,
394
444
  };
395
445
 
396
446
  const tools = await createTools(session);
@@ -398,20 +448,18 @@ const tools = await createTools(session);
398
448
 
399
449
  **When you don't need factories:**
400
450
 
401
- - If you omit `tools`, omp automatically creates them with the correct `cwd`
451
+ - If you omit `toolNames`, omp automatically creates them with the correct `cwd`
402
452
  - If you use `process.cwd()` as your `cwd`, the pre-built instances work fine
403
453
 
404
454
  **When you must use factories:**
405
455
 
406
- - When you specify both `cwd` (different from `process.cwd()`) AND `tools`
407
-
408
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
456
+ - When you specify both `cwd` (different from `process.cwd()`) AND custom tools
409
457
 
410
458
  ### Custom Tools
411
459
 
412
460
  ```typescript
413
461
  import { Type } from "@sinclair/typebox";
414
- import { createAgentSession, discoverCustomTools, type CustomTool } from "@oh-my-pi/pi-coding-agent";
462
+ import { createAgentSession, type CustomTool } from "@oh-my-pi/pi-coding-agent";
415
463
 
416
464
  // Inline custom tool
417
465
  const myTool: CustomTool = {
@@ -421,38 +469,36 @@ const myTool: CustomTool = {
421
469
  parameters: Type.Object({
422
470
  input: Type.String({ description: "Input value" }),
423
471
  }),
424
- execute: async (toolCallId, params) => ({
472
+ execute: async (toolCallId, params, onUpdate, ctx, signal) => ({
425
473
  content: [{ type: "text", text: `Result: ${params.input}` }],
426
- details: {},
427
474
  }),
475
+ // Optional session lifecycle handler
476
+ onSession: async (event, ctx) => {
477
+ if (event.reason === "shutdown") {
478
+ // cleanup
479
+ }
480
+ },
428
481
  };
429
482
 
430
- // Replace discovery with inline tools
431
- const { session } = await createAgentSession({
432
- customTools: [{ tool: myTool }],
433
- });
434
-
435
- // Merge with discovered tools
436
- const discovered = await discoverCustomTools();
437
- const { session } = await createAgentSession({
438
- customTools: [...discovered, { tool: myTool }],
439
- });
440
-
441
- // Add paths without replacing discovery
483
+ // Add custom tools (merged with built-in tools)
442
484
  const { session } = await createAgentSession({
443
- additionalCustomToolPaths: ["/extra/tools"],
485
+ customTools: [myTool],
444
486
  });
445
487
  ```
446
488
 
447
- > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
489
+ ### Extensions
448
490
 
449
- ### Hooks
491
+ Extensions intercept agent events and can register custom tools/commands. Hooks remain for legacy compatibility.
450
492
 
451
493
  ```typescript
452
- import { createAgentSession, discoverHooks, type HookFactory } from "@oh-my-pi/pi-coding-agent";
494
+ import {
495
+ createAgentSession,
496
+ discoverExtensions,
497
+ type ExtensionFactory,
498
+ } from "@oh-my-pi/pi-coding-agent";
453
499
 
454
- // Inline hook
455
- const loggingHook: HookFactory = (api) => {
500
+ // Inline extension
501
+ const loggingExtension: ExtensionFactory = (api) => {
456
502
  // Log tool calls
457
503
  api.on("tool_call", async (event) => {
458
504
  console.log(`Tool: ${event.toolName}`);
@@ -470,58 +516,52 @@ const loggingHook: HookFactory = (api) => {
470
516
  // Register custom slash command
471
517
  api.registerCommand("stats", {
472
518
  description: "Show session stats",
473
- handler: async (ctx) => {
519
+ handler: async (args, ctx) => {
474
520
  const entries = ctx.sessionManager.getEntries();
475
521
  ctx.ui.notify(`${entries.length} entries`, "info");
476
522
  },
477
523
  });
478
-
479
- // Inject messages
480
- api.sendMessage(
481
- {
482
- customType: "my-hook",
483
- content: "Hook initialized",
484
- display: false, // Hidden from TUI
485
- },
486
- false
487
- ); // Don't trigger agent turn
488
-
489
- // Persist hook state
490
- api.appendEntry("my-hook", { initialized: true });
491
524
  };
492
525
 
526
+ // Merge with discovery (default behavior)
527
+ const { session } = await createAgentSession({
528
+ extensions: [loggingExtension],
529
+ });
530
+
493
531
  // Replace discovery
494
532
  const { session } = await createAgentSession({
495
- hooks: [{ factory: loggingHook }],
533
+ extensions: [loggingExtension],
534
+ disableExtensionDiscovery: true,
496
535
  });
497
536
 
498
- // Disable all hooks
537
+ // Disable all extensions
499
538
  const { session } = await createAgentSession({
500
- hooks: [],
539
+ extensions: [],
540
+ disableExtensionDiscovery: true,
501
541
  });
502
542
 
503
- // Merge with discovered
504
- const discovered = await discoverHooks();
543
+ // Use preloaded extensions (skip discovery I/O)
544
+ const discovered = await discoverExtensions();
505
545
  const { session } = await createAgentSession({
506
- hooks: [...discovered, { factory: loggingHook }],
546
+ preloadedExtensions: discovered,
547
+ extensions: [loggingExtension],
507
548
  });
508
549
 
509
- // Add paths without replacing
550
+ // Add paths without replacing discovery
510
551
  const { session } = await createAgentSession({
511
- additionalHookPaths: ["/extra/hooks"],
552
+ additionalExtensionPaths: ["/extra/extensions"],
512
553
  });
513
554
  ```
514
555
 
515
- Hook API methods:
556
+ Extension API methods:
516
557
 
517
558
  - `api.on(event, handler)` - Subscribe to events
518
- - `api.sendMessage(message, triggerTurn?)` - Inject message (creates `CustomMessageEntry`)
519
- - `api.appendEntry(customType, data?)` - Persist hook state (not in LLM context)
559
+ - `api.registerTool(definition)` - Register a custom tool
520
560
  - `api.registerCommand(name, options)` - Register custom slash command
521
561
  - `api.registerMessageRenderer(customType, renderer)` - Custom TUI rendering
522
562
  - `api.exec(command, args, options?)` - Execute shell commands
523
563
 
524
- > See [examples/sdk/06-hooks.ts](../examples/sdk/06-hooks.ts) and [docs/hooks.md](hooks.md)
564
+ > See [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) and [docs/extensions.md](extensions.md)
525
565
 
526
566
  ### Skills
527
567
 
@@ -529,7 +569,7 @@ Hook API methods:
529
569
  import { createAgentSession, discoverSkills, type Skill } from "@oh-my-pi/pi-coding-agent";
530
570
 
531
571
  // Discover and filter
532
- const { skills: allSkills, warnings } = discoverSkills();
572
+ const { skills: allSkills, warnings } = await discoverSkills();
533
573
  const filtered = allSkills.filter((s) => s.name.includes("search"));
534
574
 
535
575
  // Custom skill
@@ -549,12 +589,6 @@ const { session } = await createAgentSession({
549
589
  const { session } = await createAgentSession({
550
590
  skills: [],
551
591
  });
552
-
553
- // Discovery with settings filter
554
- const { skills } = discoverSkills(process.cwd(), undefined, {
555
- ignoredSkills: ["browser-*"], // glob patterns to exclude
556
- includeSkills: ["search-*"], // glob patterns to include (empty = all)
557
- });
558
592
  ```
559
593
 
560
594
  > See [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)
@@ -565,7 +599,7 @@ const { skills } = discoverSkills(process.cwd(), undefined, {
565
599
  import { createAgentSession, discoverContextFiles } from "@oh-my-pi/pi-coding-agent";
566
600
 
567
601
  // Discover AGENTS.md files
568
- const discovered = discoverContextFiles();
602
+ const discovered = await discoverContextFiles();
569
603
 
570
604
  // Add custom context
571
605
  const { session } = await createAgentSession({
@@ -591,7 +625,7 @@ const { session } = await createAgentSession({
591
625
  ```typescript
592
626
  import { createAgentSession, discoverSlashCommands, type FileSlashCommand } from "@oh-my-pi/pi-coding-agent";
593
627
 
594
- const discovered = discoverSlashCommands();
628
+ const discovered = await discoverSlashCommands();
595
629
 
596
630
  const customCommand: FileSlashCommand = {
597
631
  name: "deploy",
@@ -624,21 +658,21 @@ const { session } = await createAgentSession({
624
658
  sessionManager: SessionManager.create(process.cwd()),
625
659
  });
626
660
 
627
- // Continue most recent
661
+ // Continue most recent (async)
628
662
  const { session, modelFallbackMessage } = await createAgentSession({
629
- sessionManager: SessionManager.continueRecent(process.cwd()),
663
+ sessionManager: await SessionManager.continueRecent(process.cwd()),
630
664
  });
631
665
  if (modelFallbackMessage) {
632
666
  console.log("Note:", modelFallbackMessage);
633
667
  }
634
668
 
635
- // Open specific file
669
+ // Open specific file (async)
636
670
  const { session } = await createAgentSession({
637
- sessionManager: SessionManager.open("/path/to/session.jsonl"),
671
+ sessionManager: await SessionManager.open("/path/to/session.jsonl"),
638
672
  });
639
673
 
640
- // List available sessions
641
- const sessions = SessionManager.list(process.cwd());
674
+ // List available sessions (async)
675
+ const sessions = await SessionManager.list(process.cwd());
642
676
  for (const info of sessions) {
643
677
  console.log(`${info.id}: ${info.firstMessage} (${info.messageCount} messages)`);
644
678
  }
@@ -650,15 +684,25 @@ const { session } = await createAgentSession({
650
684
  });
651
685
  ```
652
686
 
687
+ **SessionManager static factories:**
688
+
689
+ - `SessionManager.create(cwd, sessionDir?)` - New persistent session (sync)
690
+ - `SessionManager.inMemory(cwd?)` - In-memory session (sync)
691
+ - `SessionManager.open(filePath, sessionDir?)` - Open existing file (async)
692
+ - `SessionManager.continueRecent(cwd, sessionDir?)` - Most recent session (async)
693
+ - `SessionManager.list(cwd, sessionDir?)` - List sessions (async)
694
+ - `SessionManager.listAll()` - List all sessions across cwds (async)
695
+ - `SessionManager.forkFrom(sourcePath, cwd, sessionDir?)` - Fork from existing (async)
696
+
653
697
  **SessionManager tree API:**
654
698
 
655
699
  ```typescript
656
- const sm = SessionManager.open("/path/to/session.jsonl");
700
+ const sm = await SessionManager.open("/path/to/session.jsonl");
657
701
 
658
702
  // Tree traversal
659
703
  const entries = sm.getEntries(); // All entries (excludes header)
660
704
  const tree = sm.getTree(); // Full tree structure
661
- const path = sm.getPath(); // Path from root to current leaf
705
+ const branch = sm.getBranch(); // Path from root to current leaf
662
706
  const leaf = sm.getLeafEntry(); // Current leaf entry
663
707
  const entry = sm.getEntry(id); // Get entry by ID
664
708
  const children = sm.getChildren(id); // Direct children of entry
@@ -678,100 +722,112 @@ sm.createBranchedSession(leafId); // Extract path to new file
678
722
  ### Settings Management
679
723
 
680
724
  ```typescript
681
- import { createAgentSession, SettingsManager, SessionManager } from "@oh-my-pi/pi-coding-agent";
725
+ import { createAgentSession, Settings, SessionManager } from "@oh-my-pi/pi-coding-agent";
726
+
727
+ // Default: loads from files (global config.yml + project settings.json merged)
728
+ const settingsInstance = await Settings.init();
682
729
 
683
- // Default: loads from files (global + project merged)
684
730
  const { session } = await createAgentSession({
685
- settingsManager: await SettingsManager.create(),
731
+ settingsInstance,
686
732
  });
687
733
 
688
- // With overrides
689
- const settingsManager = await SettingsManager.create();
690
- settingsManager.applyOverrides({
691
- compaction: { enabled: false },
692
- retry: { enabled: true, maxRetries: 5 },
693
- });
694
- const { session } = await createAgentSession({ settingsManager });
734
+ // Read/write settings
735
+ const enabled = settingsInstance.get("compaction.enabled");
736
+ settingsInstance.set("compaction.enabled", false);
695
737
 
696
738
  // In-memory (no file I/O, for testing)
739
+ const isolated = Settings.isolated({
740
+ "compaction.enabled": false,
741
+ "retry.enabled": true,
742
+ });
743
+
697
744
  const { session } = await createAgentSession({
698
- settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),
745
+ settingsInstance: isolated,
699
746
  sessionManager: SessionManager.inMemory(),
700
747
  });
701
748
 
702
749
  // Custom directories
703
750
  const { session } = await createAgentSession({
704
- settingsManager: await SettingsManager.create("/custom/cwd", "/custom/agent"),
751
+ cwd: "/custom/cwd",
752
+ agentDir: "/custom/agent",
705
753
  });
706
754
  ```
707
755
 
708
- **Static factories:**
756
+ **Settings static factories:**
709
757
 
710
- - `SettingsManager.create(cwd?, agentDir?)` - Load from files (async)
711
- - `SettingsManager.inMemory(settings?)` - No file I/O
758
+ - `Settings.init(options?)` - Load from files (async)
759
+ - `Settings.isolated(overrides?)` - In-memory, no file I/O (sync)
760
+ - `Settings.instance` - Global singleton (throws if not initialized)
712
761
 
713
- **Project-specific settings:**
762
+ **Settings file locations:**
714
763
 
715
764
  Settings load from two locations and merge:
716
765
 
717
- 1. Global: `~/.omp/agent/settings.json`
718
- 2. Project: `<cwd>/.omp/settings.json`
766
+ 1. Global: `<agentDir>/config.yml` (default `~/.omp/agent/config.yml`)
767
+ 2. Project: `settings.json` from the first matching config dir (`.omp/`, `.pi/`, `.claude/`, `.codex/`, `.gemini/`)
719
768
 
720
- Project overrides global. Nested objects merge keys. Setters only modify global (project is read-only for version control).
721
-
722
- > See [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)
769
+ Project overrides global. Nested objects merge keys.
723
770
 
724
771
  ## Discovery Functions
725
772
 
726
- All discovery functions accept optional `cwd` and `agentDir` parameters.
773
+ Discovery functions accept optional `cwd` and `agentDir` parameters where applicable.
727
774
 
728
775
  ```typescript
729
776
  import { getModel } from "@oh-my-pi/pi-ai";
777
+ import appendPrompt from "./APPEND_SYSTEM.md" with { type: "text" };
730
778
  import {
731
779
  AuthStorage,
732
780
  ModelRegistry,
733
781
  discoverAuthStorage,
734
782
  discoverModels,
735
783
  discoverSkills,
736
- discoverHooks,
737
- discoverCustomTools,
784
+ discoverExtensions,
738
785
  discoverContextFiles,
739
786
  discoverSlashCommands,
740
- loadSettings,
787
+ discoverPromptTemplates,
788
+ discoverCustomTSCommands,
789
+ discoverMCPServers,
741
790
  buildSystemPrompt,
791
+ Settings,
742
792
  } from "@oh-my-pi/pi-coding-agent";
743
793
 
744
794
  // Auth and Models
745
- const authStorage = await discoverAuthStorage(); // ~/.omp/agent/agent.db
746
- const modelRegistry = await discoverModels(authStorage); // + ~/.omp/agent/models.json
795
+ const authStorage = await discoverAuthStorage(); // <agentDir>/auth.json → agent.db (with fallbacks)
796
+ const modelRegistry = discoverModels(authStorage); // + <agentDir>/models.yml (or models.json)
747
797
  const allModels = modelRegistry.getAll(); // All models (built-in + custom)
748
- const available = await modelRegistry.getAvailable(); // Only models with API keys
798
+ const available = modelRegistry.getAvailable(); // Only models with API keys
749
799
  const model = modelRegistry.find("provider", "id"); // Find specific model
750
800
  const builtIn = getModel("anthropic", "claude-opus-4-5"); // Built-in only
751
801
 
752
- // Skills
753
- const { skills, warnings } = discoverSkills(cwd, agentDir, skillsSettings);
802
+ // Skills (async)
803
+ const { skills, warnings } = await discoverSkills(cwd, agentDir, skillsSettings);
804
+
805
+ // Extensions (async - loads TypeScript)
806
+ const extensionsResult = await discoverExtensions(cwd);
754
807
 
755
- // Hooks (async - loads TypeScript)
756
- const hooks = await discoverHooks(cwd, agentDir);
808
+ // Custom TS commands (async - loads TypeScript)
809
+ const customCommands = await discoverCustomTSCommands(cwd, agentDir);
757
810
 
758
- // Custom tools (async - loads TypeScript)
759
- const tools = await discoverCustomTools(cwd, agentDir);
811
+ // Context files (async)
812
+ const contextFiles = await discoverContextFiles(cwd, agentDir);
760
813
 
761
- // Context files
762
- const contextFiles = discoverContextFiles(cwd, agentDir);
814
+ // Slash commands (async)
815
+ const commands = await discoverSlashCommands(cwd);
763
816
 
764
- // Slash commands
765
- const commands = discoverSlashCommands(cwd, agentDir);
817
+ // Prompt templates (async)
818
+ const promptTemplates = await discoverPromptTemplates(cwd, agentDir);
766
819
 
767
- // Settings (global + project merged)
768
- const settings = await loadSettings(cwd, agentDir);
820
+ // MCP servers (async)
821
+ const mcp = await discoverMCPServers(cwd);
822
+
823
+ // Settings (async - global + project merged)
824
+ const settings = await Settings.init({ cwd, agentDir });
769
825
 
770
826
  // Build system prompt manually
771
- const prompt = buildSystemPrompt({
827
+ const prompt = await buildSystemPrompt({
772
828
  skills,
773
829
  contextFiles,
774
- appendPrompt: "Additional instructions",
830
+ appendPrompt,
775
831
  cwd,
776
832
  });
777
833
  ```
@@ -785,14 +841,20 @@ interface CreateAgentSessionResult {
785
841
  // The session
786
842
  session: AgentSession;
787
843
 
788
- // Custom tools (for UI setup)
789
- customToolsResult: {
790
- tools: LoadedCustomTool[];
791
- setUIContext: (ctx, hasUI) => void;
792
- };
844
+ // Extensions result (loaded extensions + runtime)
845
+ extensionsResult: LoadExtensionsResult;
846
+
847
+ // Update tool UI context (interactive mode)
848
+ setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
849
+
850
+ // MCP manager for server lifecycle management (undefined if MCP disabled)
851
+ mcpManager?: MCPManager;
793
852
 
794
853
  // Warning if session model couldn't be restored
795
854
  modelFallbackMessage?: string;
855
+
856
+ // LSP servers that were warmed up at startup
857
+ lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>;
796
858
  }
797
859
  ```
798
860
 
@@ -802,30 +864,29 @@ interface CreateAgentSessionResult {
802
864
  import { getModel } from "@oh-my-pi/pi-ai";
803
865
  import { Type } from "@sinclair/typebox";
804
866
  import {
805
- AuthStorage,
806
867
  createAgentSession,
807
- ModelRegistry,
868
+ discoverAuthStorage,
869
+ discoverModels,
808
870
  SessionManager,
809
- SettingsManager,
810
- readTool,
811
- bashTool,
812
- type HookFactory,
871
+ Settings,
872
+ type ExtensionFactory,
813
873
  type CustomTool,
814
874
  } from "@oh-my-pi/pi-coding-agent";
875
+ import systemPrompt from "./SYSTEM.md" with { type: "text" };
815
876
 
816
- // Set up auth storage (custom location)
817
- const authStorage = new AuthStorage("/custom/agent/auth.json");
877
+ // Set up auth storage
878
+ const authStorage = await discoverAuthStorage();
818
879
 
819
880
  // Runtime API key override (not persisted)
820
881
  if (process.env.MY_KEY) {
821
882
  authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
822
883
  }
823
884
 
824
- // Model registry (no custom models.json)
825
- const modelRegistry = new ModelRegistry(authStorage);
885
+ // Model registry
886
+ const modelRegistry = discoverModels(authStorage);
826
887
 
827
- // Inline hook
828
- const auditHook: HookFactory = (api) => {
888
+ // Inline extension
889
+ const auditExtension: ExtensionFactory = (api) => {
829
890
  api.on("tool_call", async (event) => {
830
891
  console.log(`[Audit] ${event.toolName}`);
831
892
  return undefined;
@@ -838,9 +899,8 @@ const statusTool: CustomTool = {
838
899
  label: "Status",
839
900
  description: "Get system status",
840
901
  parameters: Type.Object({}),
841
- execute: async () => ({
902
+ execute: async (toolCallId, params, onUpdate, ctx, signal) => ({
842
903
  content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
843
- details: {},
844
904
  }),
845
905
  };
846
906
 
@@ -848,9 +908,9 @@ const model = getModel("anthropic", "claude-opus-4-5");
848
908
  if (!model) throw new Error("Model not found");
849
909
 
850
910
  // In-memory settings with overrides
851
- const settingsManager = SettingsManager.inMemory({
852
- compaction: { enabled: false },
853
- retry: { enabled: true, maxRetries: 2 },
911
+ const settingsInstance = Settings.isolated({
912
+ "compaction.enabled": false,
913
+ "retry.enabled": true,
854
914
  });
855
915
 
856
916
  const { session } = await createAgentSession({
@@ -862,17 +922,17 @@ const { session } = await createAgentSession({
862
922
  authStorage,
863
923
  modelRegistry,
864
924
 
865
- systemPrompt: "You are a minimal assistant. Be concise.",
925
+ systemPrompt,
866
926
 
867
- tools: [readTool, bashTool],
868
- customTools: [{ tool: statusTool }],
869
- hooks: [{ factory: auditHook }],
927
+ toolNames: ["read", "bash"],
928
+ customTools: [statusTool],
929
+ extensions: [auditExtension],
870
930
  skills: [],
871
931
  contextFiles: [],
872
932
  slashCommands: [],
873
933
 
874
934
  sessionManager: SessionManager.inMemory(),
875
- settingsManager,
935
+ settingsInstance,
876
936
  });
877
937
 
878
938
  session.subscribe((event) => {
@@ -899,7 +959,7 @@ The SDK is preferred when:
899
959
  - You want type safety
900
960
  - You're in the same Node.js process
901
961
  - You need direct access to agent state
902
- - You want to customize tools/hooks programmatically
962
+ - You want to customize tools/extensions programmatically
903
963
 
904
964
  RPC mode is preferred when:
905
965
 
@@ -923,62 +983,61 @@ discoverModels
923
983
 
924
984
  // Discovery
925
985
  discoverSkills
926
- discoverHooks
927
- discoverCustomTools
986
+ discoverExtensions
987
+ discoverCustomTSCommands
928
988
  discoverContextFiles
929
989
  discoverSlashCommands
990
+ discoverPromptTemplates
991
+ discoverMCPServers
930
992
 
931
993
  // Helpers
932
- loadSettings
933
994
  buildSystemPrompt
995
+ Settings
934
996
 
935
997
  // Session management
936
998
  SessionManager
937
- SettingsManager
938
999
 
939
1000
  // Tool registry and factory
940
1001
  BUILTIN_TOOLS // Map of tool name to factory
941
1002
  createTools // Create all tools from ToolSession
942
1003
  type ToolSession // Session context for tool creation
943
1004
 
944
- // Individual tool factories
945
- createReadTool, createBashTool, EditTool, createWriteTool
946
- createGrepTool, createFindTool, createLsTool, createGitTool
1005
+ // Individual tool classes
1006
+ ReadTool, BashTool, EditTool, WriteTool
1007
+ GrepTool, FindTool, PythonTool
1008
+ loadSshTool
947
1009
 
948
1010
  // Types
949
1011
  type CreateAgentSessionOptions
950
1012
  type CreateAgentSessionResult
951
1013
  type CustomTool
952
- type HookFactory
1014
+ type ExtensionFactory
953
1015
  type Skill
954
1016
  type FileSlashCommand
955
- type Settings
956
1017
  type SkillsSettings
957
1018
  type Tool
958
1019
  ```
959
1020
 
960
- For hook types, import from the hooks subpath:
1021
+ For extension types, import from the main package:
961
1022
 
962
1023
  ```typescript
963
1024
  import type {
964
- HookAPI,
965
- HookMessage,
966
- HookFactory,
967
- HookEventContext,
968
- HookCommandContext,
969
- ToolCallEvent,
970
- ToolResultEvent,
971
- } from "@oh-my-pi/pi-coding-agent/hooks";
1025
+ ExtensionAPI,
1026
+ ExtensionFactory,
1027
+ ExtensionContext,
1028
+ ExtensionCommandContext,
1029
+ ToolDefinition,
1030
+ } from "@oh-my-pi/pi-coding-agent";
972
1031
  ```
973
1032
 
974
- For message utilities:
1033
+ For legacy hook types (deprecated, use extensions instead):
975
1034
 
976
1035
  ```typescript
977
- import { isHookMessage, createHookMessage } from "@oh-my-pi/pi-coding-agent";
1036
+ import type { HookAPI, HookFactory, HookContext, HookCommandContext } from "@oh-my-pi/pi-coding-agent/hooks";
978
1037
  ```
979
1038
 
980
1039
  For config utilities:
981
1040
 
982
1041
  ```typescript
983
- import { getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
1042
+ import { getAgentDir } from "@oh-my-pi/pi-coding-agent";
984
1043
  ```