aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
229 lines • 8.19 kB
TypeScript
/**
* Executor Registry
*
* Manages registered executor instances per the executor contract v1 spec
* (docs/contracts/executor.v1.md). Each executor registers with
* `aiwg serve` via POST /api/v1/executors/register and then pushes
* real-time events over WebSocket at /ws/executors/:executorId.
*
* This is the AIWG-side dispatch surface for the executor contract epic.
* Executor implementations (sandbox adapter, local executor) are in sibling issues.
*
* @issue #1179
* @see #1177 — executor-contract epic
* @see #1178 — JSON Schema + conformance fixtures (schemas/executor-v1.json)
* @see docs/contracts/executor.v1.md — authoritative prose spec
*/
import { EventEmitter } from 'events';
export declare function validateRegisterPayload(data: unknown): {
valid: boolean;
errors: string;
};
export declare function validateEventEnvelope(data: unknown): {
valid: boolean;
errors: string;
};
export declare function validateDispatchPayload(data: unknown): {
valid: boolean;
errors: string;
};
/** Transport endpoints as declared in the schema. */
export interface TransportEndpoints {
rest: string;
ws: string;
grpc?: string;
}
/** One active mission tracked by aiwg serve. */
export interface MissionRecord {
missionId: string;
executorId: string;
state: MissionState;
createdAt: string;
updatedAt: string;
completedAt?: string;
recentEvents: EventEnvelope[];
ptySessionRef?: string;
exitCode?: number;
error?: string;
}
export type MissionState = 'queued' | 'assigned' | 'running' | 'paused' | 'hitl-required' | 'suspended' | 'done' | 'failed' | 'aborted';
export interface EventEnvelope {
event: string;
executor_id: string;
mission_id?: string;
ts: string;
data?: Record<string, unknown>;
}
/** Per-executor registration record (in-memory). */
export interface ExecutorRegistration {
/** Stable executor UUID — declared by the executor at registration time. */
executorId: string;
/** Optional default A2A sandbox instance id. May differ from executorId. */
a2aInstanceId?: string;
name: string;
version: string;
specVersion: string;
transportEndpoints: TransportEndpoints;
capabilities: string[];
/** Bearer token issued at first registration; preserved across re-registers. */
token: string;
connected: boolean;
lastEventTs?: string;
registeredAt: string;
disconnectedAt?: string;
/** Non-terminal missions currently owned by this executor. */
currentMissions: Set<string>;
/** Live WS connection handle (set by serve.ts on WS upgrade). */
wsConn?: WebSocketConn;
}
/** Minimal WS connection interface (duck-typed from the `ws` package). */
export interface WebSocketConn {
readyState: number;
send(data: string): void;
close(code?: number, reason?: string): void;
}
/** Serializable executor summary (used in API responses). */
export interface ExecutorSummary {
executor_id: string;
a2a_instance_id?: string;
name: string;
version: string;
spec_version: string;
transport_endpoints: TransportEndpoints;
capabilities: string[];
connected: boolean;
last_event_ts?: string;
active_mission_count: number;
registered_at: string;
disconnected_at?: string;
}
/** Request body for POST /api/v1/executors/register (wire shape). */
export interface ExecutorRegisterRequest {
executor_id: string;
a2a_instance_id?: string;
name: string;
version: string;
spec_version: string;
transport_endpoints: TransportEndpoints;
capabilities: string[];
}
/** Response body for 201 Created. */
export interface ExecutorRegisterResponse {
executor_id: string;
token: string;
registered_at: string;
}
/** Filter criteria for executor selection (mirrors ExecutorFilter schema). */
export interface ExecutorFilter {
/** Pin to specific executor. null = any. */
executor_id?: string | null;
/** Executor must advertise ALL listed capabilities. */
capabilities?: string[];
}
/** Result of picking an executor via pickByFilter(). */
export interface ExecutorPickResult {
executor: ExecutorRegistration;
reason: string;
rejected: Array<{
executorId: string;
reason: string;
}>;
}
export declare function resolveExecutorIdentityStorePath(projectRootOverride?: string): string;
/**
* ExecutorRegistry — state, auth, and identity store integration for
* registered executor instances. Parallel to SandboxRegistry.
*
* Emits EventEmitter events:
* 'executor:registered' — { executorId, name }
* 'executor:deregistered' — { executorId, reason }
* 'mission:assigned' — { missionId, executorId }
* 'mission:state_change' — { missionId, executorId, state, prevState }
*/
export declare class ExecutorRegistry extends EventEmitter {
private executors;
private missions;
/** Persistent token store — executorId → identity record */
private identityStore;
constructor();
/**
* Register an executor. Validates payload against `register_payload` schema.
* Returns 400-level error string on invalid payload.
* On re-register with same executor_id: reclaims prior token (per ADR §9).
*/
register(req: ExecutorRegisterRequest): ExecutorRegisterResponse | {
error: string;
status: 400;
};
/**
* Deregister an executor by ID. Auth must be checked by the caller.
* Emits 'executor:deregistered'.
*/
deregister(executorId: string, reason?: 'graceful_shutdown' | 'operator_deleted' | 'timeout'): boolean;
/**
* Validate bearer token for an executor.
*/
authenticate(executorId: string, token: string): boolean;
/**
* Mark the WS event stream as connected or disconnected.
*/
setConnected(executorId: string, connected: boolean, wsConn?: WebSocketConn): void;
/**
* Push a message to the executor's live WS connection.
* Returns true if sent, false if the executor has no open connection.
*/
pushToExecutor(executorId: string, event: EventEnvelope): boolean;
/**
* Handle an inbound event from an executor WS connection.
* Validates against event_envelope schema; updates mission state.
*/
handleEvent(envelope: EventEnvelope): void;
private appendMissionEvent;
/**
* Create a mission record and associate it with an executor.
* Called by the dispatch route after forwarding succeeds.
*/
assignMission(missionId: string, executorId: string): MissionRecord;
/**
* Mark a mission as failed (e.g. executor unreachable on forward).
*/
failMission(missionId: string, error: string): void;
/**
* Get a mission record by ID.
*/
getMission(missionId: string): MissionRecord | undefined;
/**
* Transition a mission to a requested operator state.
* Returns false if the mission is in a terminal state or not found.
*/
transitionMission(missionId: string, targetState: 'paused' | 'running' | 'aborted'): boolean;
/**
* List all executors as serializable summaries.
*/
list(): ExecutorSummary[];
/**
* Get a single executor summary.
*/
get(executorId: string): ExecutorSummary | undefined;
/**
* Get the raw registration record (internal use by dispatch).
*/
getRegistration(executorId: string): ExecutorRegistration | undefined;
/**
* Pick the best executor matching the given filter.
*
* Default-selection policy (per ADR §3):
* 1. Sandbox-first: prefer any executor with isolation:vm or isolation:container
* 2. Local fallback: isolation:none or isolation:host
* 3. 503 if no executor available
*
* `long_running: true` requires a 'resumable' capability.
*/
pickByFilter(filter: ExecutorFilter, longRunning?: boolean): ExecutorPickResult | null;
/** Total registered executor count. */
get size(): number;
/** Shut down — clear all state. */
shutdown(): void;
}
export declare const executorRegistry: ExecutorRegistry;
//# sourceMappingURL=executor-registry.d.ts.map