chore: snapshot working tree - pty_exited notifications + in-flight inference WIP
feat(booterm): structured pty_exited WS notifications. Plan-validated, impl-validated, code-reviewed green (contracts build clean, contracts test 29/29, booterm + web typecheck clean). wip: in-progress inference/provider refactor (agents.ts, provider.ts, new llama-providers.ts, removed llama-args-validator), plus arena, dispatcher, compaction, schema changes. openspec: pty-exit-notifications complete; x-agent-flags planned (not yet implemented).
This commit is contained in:
@@ -32,6 +32,10 @@
|
||||
"./arena": {
|
||||
"types": "./dist/arena.d.ts",
|
||||
"default": "./dist/arena.js"
|
||||
},
|
||||
"./llama-providers": {
|
||||
"types": "./dist/llama-providers.d.ts",
|
||||
"default": "./dist/llama-providers.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
179
packages/contracts/src/__tests__/llama-providers.test.ts
Normal file
179
packages/contracts/src/__tests__/llama-providers.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
LlamaProviderSchema,
|
||||
LlamaProvidersFileSchema,
|
||||
parseModelRef,
|
||||
formatModelRef,
|
||||
} from '../llama-providers.js';
|
||||
|
||||
const VALID_PROVIDER = {
|
||||
id: 'sam-desktop',
|
||||
label: 'Sam-desktop',
|
||||
baseUrl: 'http://100.101.41.16:8401',
|
||||
kind: 'llama-swap',
|
||||
};
|
||||
|
||||
const VALID_MINIMAL_PROVIDER = {
|
||||
id: 'embedding',
|
||||
label: 'embedding',
|
||||
baseUrl: 'http://100.90.172.55:8411',
|
||||
kind: 'llama-swap',
|
||||
};
|
||||
|
||||
const VALID_FILE = {
|
||||
defaultProvider: 'sam-desktop',
|
||||
providers: [VALID_PROVIDER, VALID_MINIMAL_PROVIDER],
|
||||
};
|
||||
|
||||
describe('LlamaProviderSchema', () => {
|
||||
it('accepts a well-formed provider', () => {
|
||||
const result = LlamaProviderSchema.safeParse(VALID_PROVIDER);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('defaults kind to llama-swap', () => {
|
||||
const result = LlamaProviderSchema.safeParse({
|
||||
id: 'test',
|
||||
label: 'test',
|
||||
baseUrl: 'http://localhost:8080',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.kind).toBe('llama-swap');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects missing id', () => {
|
||||
const result = LlamaProviderSchema.safeParse({
|
||||
label: 'test',
|
||||
baseUrl: 'http://localhost:8080',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects empty id', () => {
|
||||
const result = LlamaProviderSchema.safeParse({
|
||||
id: '',
|
||||
label: 'test',
|
||||
baseUrl: 'http://localhost:8080',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects invalid baseUrl', () => {
|
||||
const result = LlamaProviderSchema.safeParse({
|
||||
id: 'test',
|
||||
label: 'test',
|
||||
baseUrl: 'not-a-url',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('LlamaProvidersFileSchema', () => {
|
||||
it('accepts a well-formed file', () => {
|
||||
const result = LlamaProvidersFileSchema.safeParse(VALID_FILE);
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects missing defaultProvider', () => {
|
||||
const result = LlamaProvidersFileSchema.safeParse({
|
||||
providers: [VALID_PROVIDER],
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects empty providers array', () => {
|
||||
const result = LlamaProvidersFileSchema.safeParse({
|
||||
defaultProvider: 'sam-desktop',
|
||||
providers: [],
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when defaultProvider references nonexistent provider id', () => {
|
||||
// Schema doesn't enforce cross-reference, but the file shape is valid.
|
||||
const result = LlamaProvidersFileSchema.safeParse({
|
||||
defaultProvider: 'nonexistent',
|
||||
providers: [VALID_PROVIDER],
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseModelRef', () => {
|
||||
const defaultProvider = 'sam-desktop';
|
||||
|
||||
it('parses composite provider/model', () => {
|
||||
const result = parseModelRef('sam-desktop/qwen3.6-35b-a3b', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'sam-desktop',
|
||||
wireModelId: 'qwen3.6-35b-a3b',
|
||||
isLegacyBareId: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses composite with model containing slashes', () => {
|
||||
const result = parseModelRef('sam-desktop/deepseek/v4-pro', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'sam-desktop',
|
||||
wireModelId: 'deepseek/v4-pro',
|
||||
isLegacyBareId: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves bare id to default provider', () => {
|
||||
const result = parseModelRef('qwen3.6-35b-a3b', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'sam-desktop',
|
||||
wireModelId: 'qwen3.6-35b-a3b',
|
||||
isLegacyBareId: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves empty string to default provider', () => {
|
||||
const result = parseModelRef('', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'sam-desktop',
|
||||
wireModelId: '',
|
||||
isLegacyBareId: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses embedding provider/model', () => {
|
||||
const result = parseModelRef('embedding/gemma-4-12b', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'embedding',
|
||||
wireModelId: 'gemma-4-12b',
|
||||
isLegacyBareId: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not strip provider prefix when first char is slash', () => {
|
||||
// "/model" has slashIdx=0, so treated as bare id.
|
||||
const result = parseModelRef('/model', defaultProvider);
|
||||
expect(result).toEqual({
|
||||
providerId: 'sam-desktop',
|
||||
wireModelId: '/model',
|
||||
isLegacyBareId: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatModelRef', () => {
|
||||
it('formats provider/model', () => {
|
||||
expect(formatModelRef('sam-desktop', 'qwen3.6-35b-a3b')).toBe('sam-desktop/qwen3.6-35b-a3b');
|
||||
});
|
||||
|
||||
it('formats provider with model containing slashes', () => {
|
||||
expect(formatModelRef('sam-desktop', 'deepseek/v4-pro')).toBe('sam-desktop/deepseek/v4-pro');
|
||||
});
|
||||
|
||||
it('round-trips through parseModelRef', () => {
|
||||
const formatted = formatModelRef('embedding', 'gemma-4-12b');
|
||||
const parsed = parseModelRef(formatted, 'sam-desktop');
|
||||
expect(parsed.providerId).toBe('embedding');
|
||||
expect(parsed.wireModelId).toBe('gemma-4-12b');
|
||||
expect(parsed.isLegacyBareId).toBe(false);
|
||||
});
|
||||
});
|
||||
69
packages/contracts/src/llama-providers.ts
Normal file
69
packages/contracts/src/llama-providers.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Single local inference provider entry in the shared config file.
|
||||
* `kind` distinguishes transport families (llama-swap, deepseek, etc.).
|
||||
*/
|
||||
export const LlamaProviderSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
label: z.string().min(1),
|
||||
baseUrl: z.string().url(),
|
||||
kind: z.string().min(1).default('llama-swap'),
|
||||
});
|
||||
|
||||
export type LlamaProvider = z.infer<typeof LlamaProviderSchema>;
|
||||
|
||||
/**
|
||||
* Shape of `/data/llama-providers.json` (or `LLAMA_PROVIDERS_PATH`).
|
||||
* When the file is absent, app-local loaders synthesize one legacy
|
||||
* provider from `LLAMA_SWAP_URL`.
|
||||
*/
|
||||
export const LlamaProvidersFileSchema = z.object({
|
||||
defaultProvider: z.string().min(1),
|
||||
providers: z.array(LlamaProviderSchema).min(1),
|
||||
});
|
||||
|
||||
export type LlamaProvidersFile = z.infer<typeof LlamaProvidersFileSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pure model-ref helpers (D-2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ParsedModelRef {
|
||||
providerId: string;
|
||||
wireModelId: string;
|
||||
isLegacyBareId: boolean;
|
||||
}
|
||||
|
||||
const SEPARATOR = '/';
|
||||
|
||||
/**
|
||||
* Parse a model reference string.
|
||||
*
|
||||
* - Composite `"provider/model"` → `{ providerId, wireModelId, isLegacyBareId: false }`
|
||||
* - Bare `"model"` (no slash) → resolved against `defaultProvider`,
|
||||
* `{ providerId: defaultProvider, wireModelId: model, isLegacyBareId: true }`
|
||||
*/
|
||||
export function parseModelRef(ref: string, defaultProvider: string): ParsedModelRef {
|
||||
const slashIdx = ref.indexOf('/');
|
||||
if (slashIdx <= 0) {
|
||||
// Bare id or empty — resolve to default provider.
|
||||
return {
|
||||
providerId: defaultProvider,
|
||||
wireModelId: ref,
|
||||
isLegacyBareId: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
providerId: ref.slice(0, slashIdx),
|
||||
wireModelId: ref.slice(slashIdx + 1),
|
||||
isLegacyBareId: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a provider id + wire model id into the composite `"provider/model"` form.
|
||||
*/
|
||||
export function formatModelRef(providerId: string, wireModelId: string): string {
|
||||
return `${providerId}${SEPARATOR}${wireModelId}`;
|
||||
}
|
||||
@@ -473,6 +473,89 @@ export const ToolTraceFinishFrame = z.object({
|
||||
// Published when the BooCoder detects that multiple worktrees/agents are editing
|
||||
// the same file concurrently. Advisory only — writes are not blocked.
|
||||
|
||||
// ---- BooControl fleet frames -----------------------------------------------
|
||||
//
|
||||
// Published by the BooControl host service on the /api/ws/control WS endpoint.
|
||||
// These frames use a 2-location sync pattern: contracts (WsFrameSchema +
|
||||
// KNOWN_FRAME_TYPES) + web strict union only. They skip the server's broker
|
||||
// entirely — control frames relay raw bytes through the proxy, so they never
|
||||
// flow through the server's InferenceFrame union.
|
||||
//
|
||||
// The web strict union is the wire-format gate; missing it silently drops
|
||||
// frames at JSON parse. The server loose union is NOT updated — adding it
|
||||
// would be dead code.
|
||||
|
||||
// Host liveness state.
|
||||
const HostLivenessValue = z.enum(['connected', 'reconnecting', 'down']);
|
||||
|
||||
// Control fleet snapshot/delta: full snapshot on join + seq-stamped state deltas.
|
||||
export const ControlFleetFrame = z.object({
|
||||
type: z.literal('control_fleet'),
|
||||
seq: z.number().int().nonnegative(),
|
||||
hosts: z.array(z.object({
|
||||
providerId: z.string(),
|
||||
liveness: HostLivenessValue,
|
||||
lastSeenAt: z.string().nullable(),
|
||||
seq: z.number().int().nonnegative(),
|
||||
models: z.array(z.object({
|
||||
model: z.string(),
|
||||
state: z.string(),
|
||||
ts: z.string(),
|
||||
ttlDeadline: z.string().nullable(),
|
||||
inflight: z.number().int().nonnegative(),
|
||||
})),
|
||||
})),
|
||||
});
|
||||
|
||||
// Control activity: new request rows (live feed).
|
||||
export const ControlActivityFrame = z.object({
|
||||
type: z.literal('control_activity'),
|
||||
seq: z.number().int().nonnegative(),
|
||||
providerId: z.string(),
|
||||
entry: z.object({
|
||||
id: z.number().int(),
|
||||
ts: z.string(),
|
||||
model: z.string().nullable(),
|
||||
reqPath: z.string().nullable(),
|
||||
statusCode: z.number().nullable(),
|
||||
durationMs: z.number().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Control perf sample: appended samples per host.
|
||||
export const ControlPerfFrame = z.object({
|
||||
type: z.literal('control_perf'),
|
||||
seq: z.number().int().nonnegative(),
|
||||
providerId: z.string(),
|
||||
ts: z.string(),
|
||||
gpu: z.unknown(),
|
||||
sys: z.unknown(),
|
||||
});
|
||||
|
||||
// Control log: {provider_id, source: proxy|upstream, line} batches.
|
||||
export const ControlLogFrame = z.object({
|
||||
type: z.literal('control_log'),
|
||||
seq: z.number().int().nonnegative(),
|
||||
providerId: z.string(),
|
||||
source: z.enum(['proxy', 'upstream', 'model']),
|
||||
line: z.string(),
|
||||
});
|
||||
|
||||
// Control job: bench/eval run progress events.
|
||||
export const ControlJobFrame = z.object({
|
||||
type: z.literal('control_job'),
|
||||
seq: z.number().int().nonnegative(),
|
||||
jobType: z.enum(['bench', 'eval', 'action']),
|
||||
jobId: z.string(),
|
||||
status: z.enum(['queued', 'running', 'completed', 'failed']),
|
||||
detail: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
// ---- collision warning frame (v2.8) ----------------------------------------
|
||||
//
|
||||
// Published when the BooCoder detects that multiple worktrees/agents are editing
|
||||
// the same file concurrently. Advisory only — writes are not blocked.
|
||||
|
||||
const ConflictSeverityValue = z.enum(['same_line', 'adjacent_line', 'different_area']);
|
||||
|
||||
export const CollisionWarningFrame = z.object({
|
||||
@@ -483,6 +566,23 @@ export const CollisionWarningFrame = z.object({
|
||||
severity: ConflictSeverityValue,
|
||||
});
|
||||
|
||||
// ---- pty_exited frame (booterm) ---------------------------------------------
|
||||
//
|
||||
// Published by booterm when a PTY process exits. Carries exit code, last output
|
||||
// lines from the ring buffer, session metadata, and timeout status.
|
||||
|
||||
export const PtyExitedFrame = z.object({
|
||||
type: z.literal('pty_exited'),
|
||||
session_id: z.string().min(1).max(64),
|
||||
pane_id: z.string().min(1).max(64),
|
||||
exit_code: z.number().int(),
|
||||
last_lines: z.array(z.string()),
|
||||
session_title: z.string().nullable().optional(),
|
||||
session_description: z.string().nullable().optional(),
|
||||
parent_agent: z.string().nullable().optional(),
|
||||
timed_out: z.boolean(),
|
||||
});
|
||||
|
||||
// ---- channel-delta frames (streaming v2) ----------------------------------
|
||||
//
|
||||
// Each channel frame carries a monotonic `seq` counter so the client can
|
||||
@@ -594,10 +694,18 @@ export const WsFrameSchema = z.discriminatedUnion('type', [
|
||||
ToolTraceFinishFrame,
|
||||
// collision warning
|
||||
CollisionWarningFrame,
|
||||
// pty_exited (booterm)
|
||||
PtyExitedFrame,
|
||||
// channel-delta (streaming v2)
|
||||
ChannelDeltaFrame,
|
||||
// inter-agent message
|
||||
AgentMessageFrame,
|
||||
// BooControl fleet frames
|
||||
ControlFleetFrame,
|
||||
ControlActivityFrame,
|
||||
ControlPerfFrame,
|
||||
ControlLogFrame,
|
||||
ControlJobFrame,
|
||||
// per-user
|
||||
ChatStatusFrame,
|
||||
SessionUpdatedFrame,
|
||||
@@ -649,6 +757,7 @@ export const KNOWN_FRAME_TYPES: readonly WsFrame['type'][] = [
|
||||
'tool_trace_start',
|
||||
'tool_trace_finish',
|
||||
'collision_warning',
|
||||
'pty_exited',
|
||||
'channel_delta',
|
||||
'agent_message',
|
||||
'chat_status',
|
||||
@@ -668,4 +777,10 @@ export const KNOWN_FRAME_TYPES: readonly WsFrame['type'][] = [
|
||||
'project_unarchived',
|
||||
'project_updated',
|
||||
'project_deleted',
|
||||
// BooControl fleet frames
|
||||
'control_fleet',
|
||||
'control_activity',
|
||||
'control_perf',
|
||||
'control_log',
|
||||
'control_job',
|
||||
] as const;
|
||||
|
||||
Reference in New Issue
Block a user