mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-22 00:00:27 +00:00
Release v0.27.8 - OAuth takes priority over settings.json API keys
This commit is contained in:
parent
60768b90f3
commit
a965b6f160
14 changed files with 426 additions and 102 deletions
40
package-lock.json
generated
40
package-lock.json
generated
|
|
@ -6447,11 +6447,11 @@
|
||||||
},
|
},
|
||||||
"packages/agent": {
|
"packages/agent": {
|
||||||
"name": "@mariozechner/pi-agent-core",
|
"name": "@mariozechner/pi-agent-core",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7"
|
"@mariozechner/pi-tui": "^0.27.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^24.3.0",
|
||||||
|
|
@ -6481,7 +6481,7 @@
|
||||||
},
|
},
|
||||||
"packages/ai": {
|
"packages/ai": {
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "0.71.2",
|
"@anthropic-ai/sdk": "0.71.2",
|
||||||
|
|
@ -6523,12 +6523,12 @@
|
||||||
},
|
},
|
||||||
"packages/coding-agent": {
|
"packages/coding-agent": {
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7",
|
"@mariozechner/pi-tui": "^0.27.8",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"cli-highlight": "^2.1.11",
|
"cli-highlight": "^2.1.11",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
|
|
@ -6569,13 +6569,13 @@
|
||||||
},
|
},
|
||||||
"packages/mom": {
|
"packages/mom": {
|
||||||
"name": "@mariozechner/pi-mom",
|
"name": "@mariozechner/pi-mom",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-coding-agent": "^0.27.7",
|
"@mariozechner/pi-coding-agent": "^0.27.8",
|
||||||
"@sinclair/typebox": "^0.34.0",
|
"@sinclair/typebox": "^0.34.0",
|
||||||
"@slack/socket-mode": "^2.0.0",
|
"@slack/socket-mode": "^2.0.0",
|
||||||
"@slack/web-api": "^7.0.0",
|
"@slack/web-api": "^7.0.0",
|
||||||
|
|
@ -6614,10 +6614,10 @@
|
||||||
},
|
},
|
||||||
"packages/pods": {
|
"packages/pods": {
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -6630,7 +6630,7 @@
|
||||||
},
|
},
|
||||||
"packages/proxy": {
|
"packages/proxy": {
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.14.0",
|
"@hono/node-server": "^1.14.0",
|
||||||
"hono": "^4.6.16"
|
"hono": "^4.6.16"
|
||||||
|
|
@ -6646,7 +6646,7 @@
|
||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@mariozechner/pi-tui",
|
"name": "@mariozechner/pi-tui",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
|
@ -6690,12 +6690,12 @@
|
||||||
},
|
},
|
||||||
"packages/web-ui": {
|
"packages/web-ui": {
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lmstudio/sdk": "^1.5.0",
|
"@lmstudio/sdk": "^1.5.0",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7",
|
"@mariozechner/pi-tui": "^0.27.8",
|
||||||
"docx-preview": "^0.3.7",
|
"docx-preview": "^0.3.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.544.0",
|
"lucide": "^0.544.0",
|
||||||
|
|
@ -6716,7 +6716,7 @@
|
||||||
},
|
},
|
||||||
"packages/web-ui/example": {
|
"packages/web-ui/example": {
|
||||||
"name": "pi-web-ui-example",
|
"name": "pi-web-ui-example",
|
||||||
"version": "1.15.7",
|
"version": "1.15.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/mini-lit": "^0.2.0",
|
"@mariozechner/mini-lit": "^0.2.0",
|
||||||
"@mariozechner/pi-ai": "file:../../ai",
|
"@mariozechner/pi-ai": "file:../../ai",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-agent-core",
|
"name": "@mariozechner/pi-agent-core",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7"
|
"@mariozechner/pi-tui": "^0.27.8"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -2919,8 +2919,8 @@ export const MODELS = {
|
||||||
cost: {
|
cost: {
|
||||||
input: 0.3,
|
input: 0.3,
|
||||||
output: 1.2,
|
output: 1.2,
|
||||||
cacheRead: 0,
|
cacheRead: 0.03,
|
||||||
cacheWrite: 0,
|
cacheWrite: 0.375,
|
||||||
},
|
},
|
||||||
contextWindow: 204800,
|
contextWindow: 204800,
|
||||||
maxTokens: 131072,
|
maxTokens: 131072,
|
||||||
|
|
@ -6359,23 +6359,6 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 16384,
|
maxTokens: 16384,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"meta-llama/llama-3.1-70b-instruct": {
|
|
||||||
id: "meta-llama/llama-3.1-70b-instruct",
|
|
||||||
name: "Meta: Llama 3.1 70B Instruct",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text"],
|
|
||||||
cost: {
|
|
||||||
input: 0.39999999999999997,
|
|
||||||
output: 0.39999999999999997,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 131072,
|
|
||||||
maxTokens: 4096,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"meta-llama/llama-3.1-8b-instruct": {
|
"meta-llama/llama-3.1-8b-instruct": {
|
||||||
id: "meta-llama/llama-3.1-8b-instruct",
|
id: "meta-llama/llama-3.1-8b-instruct",
|
||||||
name: "Meta: Llama 3.1 8B Instruct",
|
name: "Meta: Llama 3.1 8B Instruct",
|
||||||
|
|
@ -6410,6 +6393,23 @@ export const MODELS = {
|
||||||
contextWindow: 10000,
|
contextWindow: 10000,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"meta-llama/llama-3.1-70b-instruct": {
|
||||||
|
id: "meta-llama/llama-3.1-70b-instruct",
|
||||||
|
name: "Meta: Llama 3.1 70B Instruct",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0.39999999999999997,
|
||||||
|
output: 0.39999999999999997,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"mistralai/mistral-nemo": {
|
"mistralai/mistral-nemo": {
|
||||||
id: "mistralai/mistral-nemo",
|
id: "mistralai/mistral-nemo",
|
||||||
name: "Mistral: Mistral Nemo",
|
name: "Mistral: Mistral Nemo",
|
||||||
|
|
@ -6546,6 +6546,23 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"openai/gpt-4o-2024-05-13": {
|
||||||
|
id: "openai/gpt-4o-2024-05-13",
|
||||||
|
name: "OpenAI: GPT-4o (2024-05-13)",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text", "image"],
|
||||||
|
cost: {
|
||||||
|
input: 5,
|
||||||
|
output: 15,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-4o": {
|
"openai/gpt-4o": {
|
||||||
id: "openai/gpt-4o",
|
id: "openai/gpt-4o",
|
||||||
name: "OpenAI: GPT-4o",
|
name: "OpenAI: GPT-4o",
|
||||||
|
|
@ -6580,23 +6597,6 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-4o-2024-05-13": {
|
|
||||||
id: "openai/gpt-4o-2024-05-13",
|
|
||||||
name: "OpenAI: GPT-4o (2024-05-13)",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text", "image"],
|
|
||||||
cost: {
|
|
||||||
input: 5,
|
|
||||||
output: 15,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 128000,
|
|
||||||
maxTokens: 4096,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"meta-llama/llama-3-70b-instruct": {
|
"meta-llama/llama-3-70b-instruct": {
|
||||||
id: "meta-llama/llama-3-70b-instruct",
|
id: "meta-llama/llama-3-70b-instruct",
|
||||||
name: "Meta: Llama 3 70B Instruct",
|
name: "Meta: Llama 3 70B Instruct",
|
||||||
|
|
@ -6716,23 +6716,6 @@ export const MODELS = {
|
||||||
contextWindow: 128000,
|
contextWindow: 128000,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
"openai/gpt-4-turbo-preview": {
|
|
||||||
id: "openai/gpt-4-turbo-preview",
|
|
||||||
name: "OpenAI: GPT-4 Turbo Preview",
|
|
||||||
api: "openai-completions",
|
|
||||||
provider: "openrouter",
|
|
||||||
baseUrl: "https://openrouter.ai/api/v1",
|
|
||||||
reasoning: false,
|
|
||||||
input: ["text"],
|
|
||||||
cost: {
|
|
||||||
input: 10,
|
|
||||||
output: 30,
|
|
||||||
cacheRead: 0,
|
|
||||||
cacheWrite: 0,
|
|
||||||
},
|
|
||||||
contextWindow: 128000,
|
|
||||||
maxTokens: 4096,
|
|
||||||
} satisfies Model<"openai-completions">,
|
|
||||||
"openai/gpt-3.5-turbo-0613": {
|
"openai/gpt-3.5-turbo-0613": {
|
||||||
id: "openai/gpt-3.5-turbo-0613",
|
id: "openai/gpt-3.5-turbo-0613",
|
||||||
name: "OpenAI: GPT-3.5 Turbo (older v0613)",
|
name: "OpenAI: GPT-3.5 Turbo (older v0613)",
|
||||||
|
|
@ -6750,6 +6733,23 @@ export const MODELS = {
|
||||||
contextWindow: 4095,
|
contextWindow: 4095,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"openai/gpt-4-turbo-preview": {
|
||||||
|
id: "openai/gpt-4-turbo-preview",
|
||||||
|
name: "OpenAI: GPT-4 Turbo Preview",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 10,
|
||||||
|
output: 30,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"mistralai/mistral-tiny": {
|
"mistralai/mistral-tiny": {
|
||||||
id: "mistralai/mistral-tiny",
|
id: "mistralai/mistral-tiny",
|
||||||
name: "Mistral Tiny",
|
name: "Mistral Tiny",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.27.8] - 2025-12-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **API key priority**: OAuth tokens now take priority over settings.json API keys. Previously, an API key in settings.json would trump OAuth, causing users logged in with a plan (unlimited tokens) to be billed via PAYG instead.
|
||||||
|
|
||||||
## [0.27.7] - 2025-12-24
|
## [0.27.7] - 2025-12-24
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
314
packages/coding-agent/docs/hooks-v2.md
Normal file
314
packages/coding-agent/docs/hooks-v2.md
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
# Hooks v2: Commands + Context Control
|
||||||
|
|
||||||
|
Extends hooks with slash commands and context manipulation primitives.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. Hooks can register slash commands (`/pop`, `/pr`, `/test`)
|
||||||
|
2. Hooks can save custom session entries
|
||||||
|
3. Hooks can transform context before it goes to LLM
|
||||||
|
4. All handlers get unified baseline access to state
|
||||||
|
|
||||||
|
Benchmark: `/pop` (session stacking) implementable entirely as a hook.
|
||||||
|
|
||||||
|
## API Extensions
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
pi.command("pop", {
|
||||||
|
description: "Pop to previous turn",
|
||||||
|
handler: async (ctx) => {
|
||||||
|
// ctx has full access (see Unified Context below)
|
||||||
|
const selected = await ctx.ui.select("Pop to:", options);
|
||||||
|
// ...
|
||||||
|
return { status: "Done" }; // show status
|
||||||
|
return "prompt text"; // send to agent
|
||||||
|
return; // do nothing
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Entries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Save arbitrary entry to session
|
||||||
|
await ctx.saveEntry({
|
||||||
|
type: "stack_pop", // custom type, ignored by core
|
||||||
|
backToIndex: 5,
|
||||||
|
summary: "...",
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Transform
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Fires when building context for LLM
|
||||||
|
pi.on("context", (event, ctx) => {
|
||||||
|
// event.entries: all session entries (including custom types)
|
||||||
|
// event.messages: core-computed messages (after compaction)
|
||||||
|
|
||||||
|
// Return modified messages, or undefined to keep default
|
||||||
|
return { messages: transformed };
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple `context` handlers chain: each receives previous handler's output.
|
||||||
|
|
||||||
|
### Rebuild Trigger
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Force context rebuild (after saving entries)
|
||||||
|
await ctx.rebuildContext();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unified Context
|
||||||
|
|
||||||
|
All handlers receive:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HookEventContext {
|
||||||
|
// Existing
|
||||||
|
exec(cmd: string, args: string[], opts?): Promise<ExecResult>;
|
||||||
|
ui: { select, confirm, input, notify };
|
||||||
|
hasUI: boolean;
|
||||||
|
cwd: string;
|
||||||
|
sessionFile: string | null;
|
||||||
|
|
||||||
|
// New: State (read-only)
|
||||||
|
model: Model<any> | null;
|
||||||
|
thinkingLevel: ThinkingLevel;
|
||||||
|
entries: readonly SessionEntry[];
|
||||||
|
messages: readonly AppMessage[];
|
||||||
|
|
||||||
|
// New: Utilities
|
||||||
|
findModel(provider: string, id: string): Model<any> | null;
|
||||||
|
availableModels(): Promise<Model<any>[]>;
|
||||||
|
resolveApiKey(model: Model<any>): Promise<string | undefined>;
|
||||||
|
|
||||||
|
// New: Mutation (commands only? or all?)
|
||||||
|
saveEntry(entry: { type: string; [k: string]: unknown }): Promise<void>;
|
||||||
|
rebuildContext(): Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Commands additionally get:
|
||||||
|
- `args: string[]`, `argsRaw: string`
|
||||||
|
- `setModel()`, `setThinkingLevel()` (state mutation)
|
||||||
|
|
||||||
|
## Benchmark: Stacking as Hook
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default function(pi: HookAPI) {
|
||||||
|
// Command: /pop
|
||||||
|
pi.command("pop", {
|
||||||
|
description: "Pop to previous turn, summarizing substack",
|
||||||
|
handler: async (ctx) => {
|
||||||
|
// 1. Build turn list from entries
|
||||||
|
const turns = ctx.entries
|
||||||
|
.map((e, i) => ({ e, i }))
|
||||||
|
.filter(({ e }) => e.type === "message" && e.message.role === "user")
|
||||||
|
.map(({ e, i }) => ({ index: i, text: e.message.content.slice(0, 50) }));
|
||||||
|
|
||||||
|
if (!turns.length) return { status: "No turns to pop" };
|
||||||
|
|
||||||
|
// 2. User selects
|
||||||
|
const selected = await ctx.ui.select("Pop to:", turns.map(t => t.text));
|
||||||
|
if (!selected) return;
|
||||||
|
const backTo = turns.find(t => t.text === selected)!.index;
|
||||||
|
|
||||||
|
// 3. Summarize entries from backTo to now
|
||||||
|
const toSummarize = ctx.entries.slice(backTo)
|
||||||
|
.filter(e => e.type === "message")
|
||||||
|
.map(e => e.message);
|
||||||
|
const summary = await generateSummary(toSummarize, ctx);
|
||||||
|
|
||||||
|
// 4. Save custom entry
|
||||||
|
await ctx.saveEntry({
|
||||||
|
type: "stack_pop",
|
||||||
|
backToIndex: backTo,
|
||||||
|
summary,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. Rebuild
|
||||||
|
await ctx.rebuildContext();
|
||||||
|
return { status: "Popped stack" };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Context transform: apply stack pops
|
||||||
|
pi.on("context", (event, ctx) => {
|
||||||
|
const pops = event.entries.filter(e => e.type === "stack_pop");
|
||||||
|
if (!pops.length) return; // use default
|
||||||
|
|
||||||
|
// Build exclusion set
|
||||||
|
const excluded = new Set<number>();
|
||||||
|
const summaryAt = new Map<number, string>();
|
||||||
|
|
||||||
|
for (const pop of pops) {
|
||||||
|
const popIdx = event.entries.indexOf(pop);
|
||||||
|
for (let i = pop.backToIndex; i <= popIdx; i++) excluded.add(i);
|
||||||
|
summaryAt.set(pop.backToIndex, pop.summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build filtered messages
|
||||||
|
const messages: AppMessage[] = [];
|
||||||
|
for (let i = 0; i < event.entries.length; i++) {
|
||||||
|
if (excluded.has(i)) continue;
|
||||||
|
|
||||||
|
if (summaryAt.has(i)) {
|
||||||
|
messages.push({
|
||||||
|
role: "user",
|
||||||
|
content: `[Subtask completed]\n\n${summaryAt.get(i)}`,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const e = event.entries[i];
|
||||||
|
if (e.type === "message") messages.push(e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateSummary(messages, ctx) {
|
||||||
|
const apiKey = await ctx.resolveApiKey(ctx.model);
|
||||||
|
// Call LLM for summary...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Changes Required
|
||||||
|
|
||||||
|
### session-manager.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Allow saving arbitrary entries
|
||||||
|
saveEntry(entry: { type: string; [k: string]: unknown }): void {
|
||||||
|
if (!entry.type) throw new Error("Entry must have type");
|
||||||
|
this.inMemoryEntries.push(entry);
|
||||||
|
this._persist(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSessionContext ignores unknown types (existing behavior works)
|
||||||
|
```
|
||||||
|
|
||||||
|
### hooks/types.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// New event
|
||||||
|
interface ContextEvent {
|
||||||
|
type: "context";
|
||||||
|
entries: readonly SessionEntry[];
|
||||||
|
messages: AppMessage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended base context (see Unified Context above)
|
||||||
|
|
||||||
|
// Command types
|
||||||
|
interface CommandOptions {
|
||||||
|
description?: string;
|
||||||
|
handler: (ctx: CommandContext) => Promise<CommandResult | void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandResult =
|
||||||
|
| string
|
||||||
|
| { prompt: string; attachments?: Attachment[] }
|
||||||
|
| { status: string };
|
||||||
|
```
|
||||||
|
|
||||||
|
### hooks/loader.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Track registered commands
|
||||||
|
interface LoadedHook {
|
||||||
|
path: string;
|
||||||
|
handlers: Map<string, Handler[]>;
|
||||||
|
commands: Map<string, CommandOptions>; // NEW
|
||||||
|
}
|
||||||
|
|
||||||
|
// createHookAPI adds command() method
|
||||||
|
```
|
||||||
|
|
||||||
|
### hooks/runner.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class HookRunner {
|
||||||
|
// State callbacks (set by AgentSession)
|
||||||
|
setStateCallbacks(cb: StateCallbacks): void;
|
||||||
|
|
||||||
|
// Command invocation
|
||||||
|
getCommands(): Map<string, CommandOptions>;
|
||||||
|
invokeCommand(name: string, argsRaw: string): Promise<CommandResult | void>;
|
||||||
|
|
||||||
|
// Context event with chaining
|
||||||
|
async emitContext(entries, messages): Promise<AppMessage[]> {
|
||||||
|
let result = messages;
|
||||||
|
for (const hook of this.hooks) {
|
||||||
|
const handlers = hook.handlers.get("context");
|
||||||
|
for (const h of handlers ?? []) {
|
||||||
|
const out = await h({ entries, messages: result }, this.createContext());
|
||||||
|
if (out?.messages) result = out.messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### agent-session.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Expose saveEntry
|
||||||
|
async saveEntry(entry): Promise<void> {
|
||||||
|
this.sessionManager.saveEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild context
|
||||||
|
async rebuildContext(): Promise<void> {
|
||||||
|
const base = this.sessionManager.buildSessionContext();
|
||||||
|
const entries = this.sessionManager.getEntries();
|
||||||
|
const messages = await this._hookRunner.emitContext(entries, base.messages);
|
||||||
|
this.agent.replaceMessages(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire context event during normal context building too
|
||||||
|
```
|
||||||
|
|
||||||
|
### interactive-mode.ts
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In setupEditorSubmitHandler, check hook commands
|
||||||
|
const commands = this.session.hookRunner?.getCommands();
|
||||||
|
if (commands?.has(commandName)) {
|
||||||
|
const result = await this.session.invokeCommand(commandName, argsRaw);
|
||||||
|
// Handle result...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add hook commands to autocomplete
|
||||||
|
```
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. **Mutation in all handlers or commands only?**
|
||||||
|
- `saveEntry`/`rebuildContext` in all handlers = more power, more footguns
|
||||||
|
- Commands only = safer, but limits hook creativity
|
||||||
|
- Recommendation: start with commands only
|
||||||
|
|
||||||
|
2. **Context event timing**
|
||||||
|
- Fire on every prompt? Or only when explicitly rebuilt?
|
||||||
|
- Need to fire on session load too
|
||||||
|
- Recommendation: fire whenever agent.replaceMessages is called
|
||||||
|
|
||||||
|
3. **Compaction interaction**
|
||||||
|
- Core compaction runs first, then `context` event
|
||||||
|
- Hooks can post-process compacted output
|
||||||
|
- Future: compaction itself could become a replaceable hook
|
||||||
|
|
||||||
|
4. **Multiple context handlers**
|
||||||
|
- Chain in load order (global → project)
|
||||||
|
- Each sees previous output
|
||||||
|
- No explicit priority system (KISS)
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"piConfig": {
|
"piConfig": {
|
||||||
|
|
@ -39,9 +39,9 @@
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7",
|
"@mariozechner/pi-tui": "^0.27.8",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"cli-highlight": "^2.1.11",
|
"cli-highlight": "^2.1.11",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
|
|
|
||||||
|
|
@ -328,21 +328,25 @@ export function discoverSlashCommands(cwd?: string, agentDir?: string): FileSlas
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the default API key resolver.
|
* Create the default API key resolver.
|
||||||
* Priority: settings.json apiKeys > custom providers (models.json) > OAuth > environment variables.
|
* Priority: OAuth > custom providers (models.json) > environment variables > settings.json apiKeys.
|
||||||
|
*
|
||||||
|
* OAuth takes priority so users logged in with a plan (e.g. unlimited tokens) aren't
|
||||||
|
* accidentally billed via a PAYG API key sitting in settings.json.
|
||||||
*/
|
*/
|
||||||
export function defaultGetApiKey(
|
export function defaultGetApiKey(
|
||||||
settingsManager?: SettingsManager,
|
settingsManager?: SettingsManager,
|
||||||
): (model: Model<any>) => Promise<string | undefined> {
|
): (model: Model<any>) => Promise<string | undefined> {
|
||||||
return async (model: Model<any>) => {
|
return async (model: Model<any>) => {
|
||||||
// Check settings.json apiKeys first
|
// Check OAuth, custom providers, env vars first
|
||||||
if (settingsManager) {
|
const resolvedKey = await getApiKeyForModel(model);
|
||||||
const settingsKey = settingsManager.getApiKey(model.provider);
|
if (resolvedKey) {
|
||||||
if (settingsKey) {
|
return resolvedKey;
|
||||||
return settingsKey;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Fall back to existing resolution (custom providers, OAuth, env vars)
|
// Fall back to settings.json apiKeys
|
||||||
return getApiKeyForModel(model);
|
if (settingsManager) {
|
||||||
|
return settingsManager.getApiKey(model.provider);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-mom",
|
"name": "@mariozechner/pi-mom",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "Slack bot that delegates messages to the pi coding agent",
|
"description": "Slack bot that delegates messages to the pi coding agent",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -21,9 +21,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-coding-agent": "^0.27.7",
|
"@mariozechner/pi-coding-agent": "^0.27.8",
|
||||||
"@sinclair/typebox": "^0.34.0",
|
"@sinclair/typebox": "^0.34.0",
|
||||||
"@slack/socket-mode": "^2.0.0",
|
"@slack/socket-mode": "^2.0.0",
|
||||||
"@slack/web-api": "^7.0.0",
|
"@slack/web-api": "^7.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "CLI tool for managing vLLM deployments on GPU pods",
|
"description": "CLI tool for managing vLLM deployments on GPU pods",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.27.7",
|
"@mariozechner/pi-agent-core": "^0.27.8",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "CORS and authentication proxy for pi-ai",
|
"description": "CORS and authentication proxy for pi-ai",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-tui",
|
"name": "@mariozechner/pi-tui",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "pi-web-ui-example",
|
"name": "pi-web-ui-example",
|
||||||
"version": "1.15.7",
|
"version": "1.15.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.27.7",
|
"version": "0.27.8",
|
||||||
"description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai",
|
"description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lmstudio/sdk": "^1.5.0",
|
"@lmstudio/sdk": "^1.5.0",
|
||||||
"@mariozechner/pi-ai": "^0.27.7",
|
"@mariozechner/pi-ai": "^0.27.8",
|
||||||
"@mariozechner/pi-tui": "^0.27.7",
|
"@mariozechner/pi-tui": "^0.27.8",
|
||||||
"docx-preview": "^0.3.7",
|
"docx-preview": "^0.3.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.544.0",
|
"lucide": "^0.544.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue