Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c7110df
feat: Pi coding tools integration — adapter + DirectApiRunner (by Wren)
conoremclaughlin May 5, 2026
c9382ee
feat: bash security layer — default timeout + dangerous command block…
conoremclaughlin May 5, 2026
2aa0aa6
refactor: strip hand-rolled bash blocker — rely on Pi's native guards…
conoremclaughlin May 5, 2026
858adfe
feat: sandbox orchestrator — Docker container lifecycle for strategy-…
conoremclaughlin May 7, 2026
3f39880
feat: wire sandbox orchestrator into strategy service — start_strateg…
conoremclaughlin May 7, 2026
d74966d
feat: MCP config patching + live tests for sandbox containers (by Wren)
conoremclaughlin May 7, 2026
b5d834f
feat: wire SandboxOrchestrator singleton into all StrategyService ins…
conoremclaughlin May 7, 2026
71760f6
feat: stage Claude OAuth credentials from macOS keychain for Docker c…
conoremclaughlin May 7, 2026
c8b3324
feat: Codex credential staging for Docker containers
conoremclaughlin May 7, 2026
81a19d6
fix: address review blockers — async staging, git worktree mounts, cr…
conoremclaughlin May 7, 2026
959219f
feat: persistent test fixtures + readiness probe (by Wren)
conoremclaughlin May 7, 2026
2a6a426
feat: bash process guard — dangerous command blocking + agent-scoped …
conoremclaughlin May 7, 2026
35b3b0a
test: bash guard live container tests — verify guard + container inte…
conoremclaughlin May 7, 2026
09379de
fix: address review blockers — fail-closed kill guard, remove stdout …
conoremclaughlin May 7, 2026
1c3c184
feat: sandbox fail-closed policy — abort strategy when required sandb…
conoremclaughlin May 8, 2026
8d0df22
feat: container-aware spawn — route CLI runners through Docker exec t…
conoremclaughlin May 8, 2026
1a4d2a1
feat: wire sandbox container through trigger → session → runner pipeline
conoremclaughlin May 8, 2026
cd6dc63
fix: close sandbox routing gaps from PR review (by Wren)
conoremclaughlin May 8, 2026
2f7abaa
refactor: rename DirectApiRunner → InkRunner, DirectApiBackend → InkB…
conoremclaughlin May 8, 2026
edebc27
fix: container path translation — write runner temp files to bind-mou…
conoremclaughlin May 8, 2026
ab7e633
test: container path translation regression tests (by Wren)
conoremclaughlin May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"minimatch@9.0.3": "9.0.7",
"undici": "6.24.0",
"lodash": "4.18.0",
"brace-expansion": "1.1.13",
"brace-expansion@^1": "1.1.13",
"@slack/bolt/path-to-regexp": "8.4.0",
"handlebars": "4.7.9"
}
Expand Down
2 changes: 2 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"dependencies": {
"@anthropic-ai/sdk": "^0.71.2",
"@inklabs/shared": "workspace:*",
"@mariozechner/pi-agent-core": "0.71.1",
"@mariozechner/pi-coding-agent": "0.71.1",
"@modelcontextprotocol/sdk": "^1.26.0",
"@slack/bolt": "^4.6.0",
"@supabase/supabase-js": "^2.39.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/agent/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function getAdapterRegistry(): AdapterRegistry {
registryInstance.register(getClaudeCodeAdapter());

// Future adapters would be registered here:
// registryInstance.register(getDirectApiAdapter());
// registryInstance.register(getInkAdapter());
// registryInstance.register(getOpenAiAdapter());
}
return registryInstance;
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/agent/adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface BackendPermissionConfig {
* Implement this for each backend to translate PCP permissions
*/
export interface PermissionAdapter {
/** Backend identifier (e.g., 'claude-code', 'direct-api') */
/** Backend identifier (e.g., 'claude-code', 'ink') */
readonly backendId: string;

/**
Expand Down
21 changes: 10 additions & 11 deletions packages/api/src/agent/backend-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import type {
ResponseHandler,
} from './types';
import type { ClaudeCodeConfig } from './backends/claude-code.backend';
import type { DirectApiConfig } from './backends/direct-api.backend';
import type { InkConfig } from './backends/ink.backend';
import { ClaudeCodeBackend, createClaudeCodeBackend } from './backends/claude-code.backend';
import { DirectApiBackend, createDirectApiBackend } from './backends/direct-api.backend';
import { InkBackend, createInkBackend } from './backends/ink.backend';

export interface BackendManagerConfig {
/** Primary backend to use */
Expand All @@ -28,7 +28,7 @@ export interface BackendManagerConfig {
/** Backend-specific configurations */
backends: {
'claude-code'?: Partial<ClaudeCodeConfig>;
'direct-api'?: Partial<DirectApiConfig>;
ink?: Partial<InkConfig>;
};
/** Enable automatic failover */
enableFailover?: boolean;
Expand Down Expand Up @@ -138,10 +138,9 @@ export class BackendManager extends EventEmitter {
setResponseHandler(handler: ResponseHandler): void {
this.responseHandler = handler;

// Pass to direct API backend if it exists
const directApi = this.backends.get('direct-api') as DirectApiBackend | undefined;
if (directApi) {
directApi.setResponseHandler(handler);
const inkBackend = this.backends.get('ink') as InkBackend | undefined;
if (inkBackend) {
inkBackend.setResponseHandler(handler);
}
}

Expand Down Expand Up @@ -228,14 +227,14 @@ export class BackendManager extends EventEmitter {
this.backends.set('claude-code', backend);
}

// Create Direct API backend
if (this.config.backends['direct-api'] || this.config.primaryBackend === 'direct-api') {
const backend = createDirectApiBackend(this.config.backends['direct-api']);
// Create Ink backend
if (this.config.backends.ink || this.config.primaryBackend === 'ink') {
const backend = createInkBackend(this.config.backends.ink);
this.setupBackendEvents(backend);
if (this.responseHandler) {
backend.setResponseHandler(this.responseHandler);
}
this.backends.set('direct-api', backend);
this.backends.set('ink', backend);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Direct API Backend
* Ink Backend
*
* Uses the Anthropic API directly instead of Claude Code CLI.
* Useful for cloud deployments where CLI isn't available.
Expand All @@ -17,8 +17,8 @@ import type {
ResponseHandler,
} from '../types';

export interface DirectApiConfig extends BackendConfig {
type: 'direct-api';
export interface InkConfig extends BackendConfig {
type: 'ink';
apiKey?: string;
model?: string;
maxTokens?: number;
Expand All @@ -30,15 +30,15 @@ interface ConversationMessage {
content: string;
}

const DEFAULT_CONFIG: Partial<DirectApiConfig> = {
const DEFAULT_CONFIG: Partial<InkConfig> = {
model: 'claude-sonnet-4-20250514',
maxTokens: 4096,
};

export class DirectApiBackend extends EventEmitter implements AgentBackend {
readonly type: BackendType = 'direct-api';
export class InkBackend extends EventEmitter implements AgentBackend {
readonly type: BackendType = 'ink';

private config: DirectApiConfig;
private config: InkConfig;
private client: Anthropic | null = null;
private ready = false;
private messageCount = 0;
Expand All @@ -55,9 +55,9 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {
// MCP tools definition (simplified for API calls)
private tools: Anthropic.Tool[] = [];

constructor(config: Partial<DirectApiConfig> = {}) {
constructor(config: Partial<InkConfig> = {}) {
super();
this.config = { ...DEFAULT_CONFIG, ...config, type: 'direct-api' } as DirectApiConfig;
this.config = { ...DEFAULT_CONFIG, ...config, type: 'ink' } as InkConfig;
}

/**
Expand All @@ -76,28 +76,28 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {

async initialize(): Promise<void> {
if (this.client) {
logger.warn('Direct API backend already initialized');
logger.warn('Ink backend already initialized');
return;
}

logger.info('Initializing Direct API backend...');
logger.info('Initializing Ink backend...');

const apiKey = this.config.apiKey || process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY is required for Direct API backend');
throw new Error('ANTHROPIC_API_KEY is required for Ink backend');
}

this.client = new Anthropic({ apiKey });
this.sessionId = `direct-api-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
this.sessionId = `ink-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
this.startTime = new Date();
this.ready = true;

this.emit('ready');
logger.info('Direct API backend ready', { sessionId: this.sessionId });
logger.info('Ink backend ready', { sessionId: this.sessionId });
}

async shutdown(): Promise<void> {
logger.info('Shutting down Direct API backend...');
logger.info('Shutting down Ink backend...');
this.client = null;
this.ready = false;
this.conversationHistory.clear();
Expand All @@ -106,7 +106,7 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {

async sendMessage(message: AgentMessage): Promise<void> {
if (!this.client || !this.ready) {
throw new Error('Direct API backend not ready');
throw new Error('Ink backend not ready');
}

this.messageCount++;
Expand All @@ -121,7 +121,7 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {
});

logger.info(
`Sending message via Direct API [${message.channel}]: ${message.content.substring(0, 100)}...`
`Sending message via Ink [${message.channel}]: ${message.content.substring(0, 100)}...`
);

try {
Expand All @@ -138,7 +138,7 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {
await this.processResponse(response, message);
} catch (error) {
this.lastError = error instanceof Error ? error.message : 'Unknown error';
logger.error('Direct API error:', error);
logger.error('Ink error:', error);
this.emit('error', error);
throw error;
}
Expand All @@ -164,7 +164,7 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {
}

async resumeSession(sessionId: string): Promise<boolean> {
// Direct API doesn't have true session resumption
// Ink doesn't have true session resumption
// but we can set the session ID for tracking
this.sessionId = sessionId;
return true;
Expand Down Expand Up @@ -293,8 +293,8 @@ export class DirectApiBackend extends EventEmitter implements AgentBackend {
}

/**
* Create a Direct API backend instance
* Create a Ink backend instance
*/
export function createDirectApiBackend(config?: Partial<DirectApiConfig>): DirectApiBackend {
return new DirectApiBackend(config);
export function createInkBackend(config?: Partial<InkConfig>): InkBackend {
return new InkBackend(config);
}
12 changes: 10 additions & 2 deletions packages/api/src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ export * from './types';
export { ClaudeCodeBackend, createClaudeCodeBackend } from './backends/claude-code.backend';
export type { ClaudeCodeConfig } from './backends/claude-code.backend';

export { DirectApiBackend, createDirectApiBackend } from './backends/direct-api.backend';
export type { DirectApiConfig } from './backends/direct-api.backend';
export { InkBackend, createInkBackend } from './backends/ink.backend';
export type { InkConfig } from './backends/ink.backend';

// Tools
export {
createInkCodingTools,
getPiToolSchemas,
createPiToolExecutor,
} from './tools/pi-coding-tools';
export type { InkToolDefinition, PiCodingToolsConfig } from './tools/pi-coding-tools';

// Manager
export { BackendManager, createBackendManager } from './backend-manager';
Expand Down
Loading
Loading