pi-app-server 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/command-classification.d.ts +59 -0
  4. package/dist/command-classification.d.ts.map +1 -0
  5. package/dist/command-classification.js +78 -0
  6. package/dist/command-classification.js.map +7 -0
  7. package/dist/command-execution-engine.d.ts +118 -0
  8. package/dist/command-execution-engine.d.ts.map +1 -0
  9. package/dist/command-execution-engine.js +259 -0
  10. package/dist/command-execution-engine.js.map +7 -0
  11. package/dist/command-replay-store.d.ts +241 -0
  12. package/dist/command-replay-store.d.ts.map +1 -0
  13. package/dist/command-replay-store.js +306 -0
  14. package/dist/command-replay-store.js.map +7 -0
  15. package/dist/command-router.d.ts +25 -0
  16. package/dist/command-router.d.ts.map +1 -0
  17. package/dist/command-router.js +353 -0
  18. package/dist/command-router.js.map +7 -0
  19. package/dist/extension-ui.d.ts +139 -0
  20. package/dist/extension-ui.d.ts.map +1 -0
  21. package/dist/extension-ui.js +189 -0
  22. package/dist/extension-ui.js.map +7 -0
  23. package/dist/resource-governor.d.ts +254 -0
  24. package/dist/resource-governor.d.ts.map +1 -0
  25. package/dist/resource-governor.js +603 -0
  26. package/dist/resource-governor.js.map +7 -0
  27. package/dist/server-command-handlers.d.ts +120 -0
  28. package/dist/server-command-handlers.d.ts.map +1 -0
  29. package/dist/server-command-handlers.js +234 -0
  30. package/dist/server-command-handlers.js.map +7 -0
  31. package/dist/server-ui-context.d.ts +22 -0
  32. package/dist/server-ui-context.d.ts.map +1 -0
  33. package/dist/server-ui-context.js +221 -0
  34. package/dist/server-ui-context.js.map +7 -0
  35. package/dist/server.d.ts +82 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +561 -0
  38. package/dist/server.js.map +7 -0
  39. package/dist/session-lock-manager.d.ts +100 -0
  40. package/dist/session-lock-manager.d.ts.map +1 -0
  41. package/dist/session-lock-manager.js +199 -0
  42. package/dist/session-lock-manager.js.map +7 -0
  43. package/dist/session-manager.d.ts +196 -0
  44. package/dist/session-manager.d.ts.map +1 -0
  45. package/dist/session-manager.js +1010 -0
  46. package/dist/session-manager.js.map +7 -0
  47. package/dist/session-store.d.ts +190 -0
  48. package/dist/session-store.d.ts.map +1 -0
  49. package/dist/session-store.js +446 -0
  50. package/dist/session-store.js.map +7 -0
  51. package/dist/session-version-store.d.ts +83 -0
  52. package/dist/session-version-store.d.ts.map +1 -0
  53. package/dist/session-version-store.js +117 -0
  54. package/dist/session-version-store.js.map +7 -0
  55. package/dist/type-guards.d.ts +59 -0
  56. package/dist/type-guards.d.ts.map +1 -0
  57. package/dist/type-guards.js +40 -0
  58. package/dist/type-guards.js.map +7 -0
  59. package/dist/types.d.ts +621 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +23 -0
  62. package/dist/types.js.map +7 -0
  63. package/dist/validation.d.ts +22 -0
  64. package/dist/validation.d.ts.map +1 -0
  65. package/dist/validation.js +323 -0
  66. package/dist/validation.js.map +7 -0
  67. package/package.json +135 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/types.ts"],
4
+ "sourcesContent": ["/**\n * Protocol types for pi-app-server session multiplexer.\n * The protocol IS the architecture.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport type { AgentSessionEvent } from \"@mariozechner/pi-coding-agent\";\nimport type { ImageContent, Model } from \"@mariozechner/pi-ai\";\nimport type { SessionStats } from \"@mariozechner/pi-coding-agent\";\nimport type { CompactionResult } from \"@mariozechner/pi-coding-agent\";\nimport type { CircuitBreakerMetrics } from \"./circuit-breaker.js\";\n\n// ============================================================================\n// SESSION INFO\n// ============================================================================\n\nexport interface SessionInfo {\n sessionId: string;\n sessionName?: string;\n sessionFile?: string;\n model?: Model<any>;\n thinkingLevel: ThinkingLevel;\n isStreaming: boolean;\n messageCount: number;\n createdAt: string;\n}\n\n// ============================================================================\n// SERVER COMMANDS (manage session registry)\n// ============================================================================\n\nexport type ServerCommand =\n | { id?: string; type: \"list_sessions\" }\n | { id?: string; type: \"create_session\"; sessionId?: string; cwd?: string }\n | { id?: string; type: \"delete_session\"; sessionId: string }\n | { id?: string; type: \"switch_session\"; sessionId: string }\n | { id?: string; type: \"get_metrics\" }\n | { id?: string; type: \"health_check\" }\n // ADR-0007: Session persistence\n | { id?: string; type: \"list_stored_sessions\" }\n | { id?: string; type: \"load_session\"; sessionId?: string; sessionPath: string };\n\n// ============================================================================\n// SESSION COMMANDS (pass through to AgentSession)\n// ============================================================================\n\nexport type SessionCommand =\n // Extension UI response (client \u2192 server to complete pending UI request)\n | {\n id?: string;\n sessionId: string;\n type: \"extension_ui_response\";\n requestId: string;\n response:\n | { method: \"select\"; value: string }\n | { method: \"confirm\"; confirmed: boolean }\n | { method: \"input\"; value: string }\n | { method: \"editor\"; value: string }\n | { method: \"interview\"; responses: Record<string, any> }\n | { method: \"cancelled\" };\n }\n // Discovery commands\n | { id?: string; sessionId: string; type: \"get_available_models\" }\n | { id?: string; sessionId: string; type: \"get_commands\" }\n | { id?: string; sessionId: string; type: \"get_skills\" }\n | { id?: string; sessionId: string; type: \"get_tools\" }\n | { id?: string; sessionId: string; type: \"list_session_files\" }\n // Session commands\n | {\n id?: string;\n sessionId: string;\n type: \"prompt\";\n message: string;\n images?: ImageContent[];\n streamingBehavior?: \"steer\" | \"followUp\";\n }\n | { id?: string; sessionId: string; type: \"steer\"; message: string; images?: ImageContent[] }\n | { id?: string; sessionId: string; type: \"follow_up\"; message: string; images?: ImageContent[] }\n | { id?: string; sessionId: string; type: \"abort\" }\n | { id?: string; sessionId: string; type: \"get_state\" }\n | { id?: string; sessionId: string; type: \"get_messages\" }\n | { id?: string; sessionId: string; type: \"set_model\"; provider: string; modelId: string }\n | { id?: string; sessionId: string; type: \"cycle_model\"; direction?: \"forward\" | \"backward\" }\n | { id?: string; sessionId: string; type: \"set_thinking_level\"; level: ThinkingLevel }\n | { id?: string; sessionId: string; type: \"cycle_thinking_level\" }\n | { id?: string; sessionId: string; type: \"compact\"; customInstructions?: string }\n | { id?: string; sessionId: string; type: \"abort_compaction\" }\n | { id?: string; sessionId: string; type: \"set_auto_compaction\"; enabled: boolean }\n | { id?: string; sessionId: string; type: \"set_auto_retry\"; enabled: boolean }\n | { id?: string; sessionId: string; type: \"abort_retry\" }\n | { id?: string; sessionId: string; type: \"bash\"; command: string; excludeFromContext?: boolean }\n | { id?: string; sessionId: string; type: \"abort_bash\" }\n | { id?: string; sessionId: string; type: \"get_session_stats\" }\n | { id?: string; sessionId: string; type: \"set_session_name\"; name: string }\n | { id?: string; sessionId: string; type: \"export_html\"; outputPath?: string }\n | { id?: string; sessionId: string; type: \"new_session\"; parentSession?: string }\n | { id?: string; sessionId: string; type: \"switch_session_file\"; sessionPath: string }\n | { id?: string; sessionId: string; type: \"fork\"; entryId: string }\n | { id?: string; sessionId: string; type: \"get_fork_messages\" }\n | { id?: string; sessionId: string; type: \"get_last_assistant_text\" }\n | { id?: string; sessionId: string; type: \"get_context_usage\" };\n\n// ============================================================================\n// UNION OF ALL COMMANDS\n// ============================================================================\n\nexport interface RpcCommandEnvelope {\n /** Optional causal dependencies for command ordering. */\n dependsOn?: string[];\n /** Optional optimistic concurrency precondition for session-targeted commands. */\n ifSessionVersion?: number;\n /** Optional idempotency key for replay-safe retries. */\n idempotencyKey?: string;\n}\n\nexport type RpcCommand = (ServerCommand | SessionCommand) & RpcCommandEnvelope;\n\n// ============================================================================\n// RESPONSES\n// ============================================================================\n\nexport interface RpcResponseBase {\n id?: string;\n type: \"response\";\n command: string;\n success: boolean;\n error?: string;\n /** Monotonic per-session version after successful command execution. */\n sessionVersion?: number;\n /** True when response was replayed from idempotency/duplicate-command cache. */\n replayed?: boolean;\n /** True when the response is due to a timeout (ADR-0001: timeout IS a response). */\n timedOut?: boolean;\n}\n\n// Server command responses\nexport type ServerResponse =\n | (RpcResponseBase & {\n command: \"list_sessions\";\n success: true;\n data: { sessions: SessionInfo[] };\n })\n | (RpcResponseBase & {\n command: \"create_session\";\n success: true;\n data: { sessionId: string; sessionInfo: SessionInfo };\n })\n | (RpcResponseBase & { command: \"delete_session\"; success: true; data: { deleted: true } })\n | (RpcResponseBase & {\n command: \"switch_session\";\n success: true;\n data: { sessionInfo: SessionInfo };\n })\n | (RpcResponseBase & {\n command: \"get_metrics\";\n success: true;\n data: {\n sessionCount: number;\n connectionCount: number;\n totalCommandsExecuted: number;\n commandsRejected: {\n sessionLimit: number;\n messageSize: number;\n rateLimit: number;\n globalRateLimit: number;\n connectionLimit: number;\n extensionUIResponseRateLimit: number;\n };\n zombieSessionsDetected: number;\n zombieSessionsCleaned: number;\n doubleUnregisterErrors: number;\n rateLimitUsage: {\n globalCount: number;\n globalLimit: number;\n };\n /** Store stats for observability (ADR-0001) */\n stores: {\n replay: {\n inFlightCount: number;\n outcomeCount: number;\n idempotencyCacheSize: number;\n maxInFlightCommands: number;\n maxCommandOutcomes: number;\n inFlightRejections: number;\n };\n version: {\n sessionCount: number;\n };\n execution: {\n laneCount: number;\n };\n lock: {\n activeLocks: number;\n timeoutCount: number;\n waitingCount: number;\n };\n extensionUI: {\n pendingCount: number;\n maxPendingRequests: number;\n rejectedCount: number;\n };\n sessionStore: {\n /** Count of metadata file resets due to corruption/oversize */\n metadataResetCount: number;\n };\n };\n /** Circuit breaker metrics per provider (ADR-0010) */\n circuitBreakers: CircuitBreakerMetrics[];\n /** Bash circuit breaker metrics */\n bashCircuitBreaker: {\n enabled: boolean;\n globalState: string;\n sessionCount: number;\n openSessionCount: number;\n totalCalls: number;\n totalTimeouts: number;\n totalRejected: number;\n };\n /** Metrics system data (ADR-0016) - optional, only if MemorySink is enabled */\n metrics?: Record<string, unknown>;\n };\n })\n | (RpcResponseBase & {\n command: \"health_check\";\n success: true;\n data: {\n healthy: boolean;\n issues: string[];\n /** Whether any LLM provider circuit is open (ADR-0010) */\n hasOpenCircuit: boolean;\n /** Whether bash command circuit is open */\n hasOpenBashCircuit: boolean;\n };\n })\n // ADR-0007: Session persistence\n | (RpcResponseBase & {\n command: \"list_stored_sessions\";\n success: true;\n data: { sessions: StoredSessionInfo[] };\n })\n | (RpcResponseBase & {\n command: \"load_session\";\n success: true;\n data: { sessionId: string; sessionInfo: SessionInfo };\n });\n\n// ADR-0007: Stored session info (extends SessionInfo with persistence metadata)\nexport interface StoredSessionInfo extends SessionInfo {\n sessionFile: string;\n cwd: string;\n fileExists: boolean;\n}\n\n// Session command responses (mirrors RpcCommand types)\nexport type SessionResponse =\n | (RpcResponseBase & { command: \"extension_ui_response\"; success: true })\n // Discovery command responses\n | (RpcResponseBase & {\n command: \"get_available_models\";\n success: true;\n data: { models: Model<any>[] };\n })\n | (RpcResponseBase & {\n command: \"get_commands\";\n success: true;\n data: {\n commands: Array<{\n name: string;\n description?: string;\n source: string;\n location?: string;\n path?: string;\n }>;\n };\n })\n | (RpcResponseBase & {\n command: \"get_skills\";\n success: true;\n data: {\n skills: Array<{ name: string; description: string; filePath: string; source: string }>;\n };\n })\n | (RpcResponseBase & {\n command: \"get_tools\";\n success: true;\n data: { tools: Array<{ name: string; description: string }> };\n })\n | (RpcResponseBase & {\n command: \"list_session_files\";\n success: true;\n data: { files: Array<{ path: string; name: string; modifiedAt?: string }> };\n })\n // Session commands\n | (RpcResponseBase & { command: \"prompt\"; success: true })\n | (RpcResponseBase & { command: \"steer\"; success: true })\n | (RpcResponseBase & { command: \"follow_up\"; success: true })\n | (RpcResponseBase & { command: \"abort\"; success: true })\n | (RpcResponseBase & { command: \"get_state\"; success: true; data: SessionInfo })\n | (RpcResponseBase & {\n command: \"get_messages\";\n success: true;\n data: { messages: AgentMessage[] };\n })\n | (RpcResponseBase & { command: \"set_model\"; success: true; data: { model: Model<any> } })\n | (RpcResponseBase & {\n command: \"cycle_model\";\n success: true;\n data: { model: Model<any>; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;\n })\n | (RpcResponseBase & { command: \"set_thinking_level\"; success: true })\n | (RpcResponseBase & {\n command: \"cycle_thinking_level\";\n success: true;\n data: { level: ThinkingLevel } | null;\n })\n | (RpcResponseBase & { command: \"compact\"; success: true; data: CompactionResult })\n | (RpcResponseBase & { command: \"abort_compaction\"; success: true })\n | (RpcResponseBase & { command: \"set_auto_compaction\"; success: true })\n | (RpcResponseBase & { command: \"set_auto_retry\"; success: true })\n | (RpcResponseBase & { command: \"abort_retry\"; success: true })\n | (RpcResponseBase & {\n command: \"bash\";\n success: true;\n data: { exitCode: number; output: string; cancelled: boolean };\n })\n | (RpcResponseBase & { command: \"abort_bash\"; success: true })\n | (RpcResponseBase & { command: \"get_session_stats\"; success: true; data: SessionStats })\n | (RpcResponseBase & { command: \"set_session_name\"; success: true })\n | (RpcResponseBase & { command: \"export_html\"; success: true; data: { path: string } })\n | (RpcResponseBase & { command: \"new_session\"; success: true; data: { cancelled: boolean } })\n | (RpcResponseBase & {\n command: \"switch_session_file\";\n success: true;\n data: { cancelled: boolean };\n })\n | (RpcResponseBase & {\n command: \"fork\";\n success: true;\n data: { text: string; cancelled: boolean };\n })\n | (RpcResponseBase & {\n command: \"get_fork_messages\";\n success: true;\n data: { messages: Array<{ entryId: string; text: string }> };\n })\n | (RpcResponseBase & {\n command: \"get_last_assistant_text\";\n success: true;\n data: { text: string | null };\n })\n | (RpcResponseBase & {\n command: \"get_context_usage\";\n success: true;\n data: { tokens: number | null; contextWindow: number; percent: number | null } | null;\n });\n\n// Error response\nexport type ErrorResponse = RpcResponseBase & { success: false; error: string };\n\nexport type RpcResponse = ServerResponse | SessionResponse | ErrorResponse;\n\n// ============================================================================\n// EVENTS\n// ============================================================================\n\nexport interface RpcEvent {\n type: \"event\";\n sessionId: string;\n event: AgentSessionEvent;\n}\n\nexport interface ServerEvent {\n type: \"server_ready\";\n data: {\n /** Server software version (semver) */\n serverVersion: string;\n /** Protocol version for wire compatibility (semver) */\n protocolVersion: string;\n /** Available transports */\n transports: string[];\n };\n}\n\nexport interface ServerShutdownEvent {\n type: \"server_shutdown\";\n data: { reason: string; timeoutMs?: number };\n}\n\nexport interface SessionLifecycleEvent {\n type: \"session_created\" | \"session_deleted\";\n data: { sessionId: string; sessionInfo?: SessionInfo };\n}\n\nexport interface CommandLifecycleEvent {\n type: \"command_accepted\" | \"command_started\" | \"command_finished\";\n data: {\n commandId: string;\n commandType: string;\n sessionId?: string;\n dependsOn?: string[];\n ifSessionVersion?: number;\n idempotencyKey?: string;\n success?: boolean;\n error?: string;\n sessionVersion?: number;\n replayed?: boolean;\n };\n}\n\nexport type RpcBroadcast =\n | RpcEvent\n | ServerEvent\n | ServerShutdownEvent\n | SessionLifecycleEvent\n | CommandLifecycleEvent;\n\n// ============================================================================\n// SUBSCRIBER\n// ============================================================================\n\nexport interface Subscriber {\n send: (data: string) => void;\n subscribedSessions: Set<string>;\n}\n\n// ============================================================================\n// SESSION RESOLVER\n// ============================================================================\n\nimport type { AgentSession } from \"@mariozechner/pi-coding-agent\";\n\n/**\n * Interface for resolving sessions by ID.\n *\n * This is the NEXUS abstraction - a clean seam that enables:\n * - Test doubles for unit testing without real AgentSession\n * - Future multi-server clustering (resolver over RPC)\n * - Session migration between servers\n * - Dependency injection for cleaner architecture\n *\n * Implementations must be idempotent: calling getSession multiple times\n * with the same ID returns the same session (or undefined consistently).\n */\nexport interface SessionResolver {\n /**\n * Get a session by ID.\n * @returns The session if it exists, undefined otherwise.\n */\n getSession(sessionId: string): AgentSession | undefined;\n}\n\n// ============================================================================\n// TYPE GUARDS & ACCESSORS\n// Moved to type-guards.ts for separation of concerns\n// ============================================================================\n\n// Re-export from type-guards.ts for backwards compatibility\nexport {\n getCommandId,\n getCommandType,\n getSessionId,\n getCommandDependsOn,\n getCommandIfSessionVersion,\n getCommandIdempotencyKey,\n isSessionCommand,\n isCreateSessionResponse,\n isSwitchSessionResponse,\n} from \"./type-guards.js\";\n"],
5
+ "mappings": "AAycA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
6
+ "names": []
7
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Input validation for RPC commands.
3
+ *
4
+ * Validates command structure before processing to prevent:
5
+ * - Missing required fields
6
+ * - Invalid field types
7
+ * - Malformed requests
8
+ */
9
+ export interface ValidationError {
10
+ field: string;
11
+ message: string;
12
+ }
13
+ /**
14
+ * Validate a command has required fields.
15
+ * Returns array of errors (empty if valid).
16
+ */
17
+ export declare function validateCommand(command: unknown): ValidationError[];
18
+ /**
19
+ * Format validation errors as a human-readable string.
20
+ */
21
+ export declare function formatValidationErrors(errors: ValidationError[]): string;
22
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAiHD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CAyGnE;AAwLD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAExE"}
@@ -0,0 +1,323 @@
1
+ import { SYNTHETIC_ID_PREFIX } from "./command-replay-store.js";
2
+ const MAX_PROMPT_MESSAGE_LENGTH = 2e5;
3
+ const MAX_BASH_COMMAND_LENGTH = 2e4;
4
+ const MAX_SESSION_NAME_LENGTH = 256;
5
+ const MAX_COMMAND_ID_LENGTH = 256;
6
+ const MAX_IDEMPOTENCY_KEY_LENGTH = 256;
7
+ const MAX_REQUEST_ID_LENGTH = 256;
8
+ const MAX_DEPENDENCIES = 32;
9
+ const REQUEST_ID_PATTERN = /^[a-zA-Z0-9:_-]+$/;
10
+ const DANGEROUS_PATH_PATTERNS = [/\.\./, /^~/, /\0/];
11
+ const MAX_PATH_LENGTH = 4096;
12
+ function hasControlCharacters(value) {
13
+ for (let i = 0; i < value.length; i++) {
14
+ const code = value.charCodeAt(i);
15
+ if (code <= 31 || code === 127) return true;
16
+ }
17
+ return false;
18
+ }
19
+ function validatePath(path, fieldName) {
20
+ if (path.length > MAX_PATH_LENGTH) {
21
+ return `${fieldName} too long (max ${MAX_PATH_LENGTH} chars)`;
22
+ }
23
+ for (const pattern of DANGEROUS_PATH_PATTERNS) {
24
+ if (pattern.test(path)) {
25
+ return `${fieldName} contains potentially dangerous path components`;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function isReservedIdPrefix(id) {
31
+ return id.startsWith(SYNTHETIC_ID_PREFIX);
32
+ }
33
+ const EXTENSION_UI_RESPONSE_METHODS = /* @__PURE__ */ new Set([
34
+ "select",
35
+ "confirm",
36
+ "input",
37
+ "editor",
38
+ "interview",
39
+ "cancelled"
40
+ ]);
41
+ const SESSION_COMMANDS = /* @__PURE__ */ new Set([
42
+ "extension_ui_response",
43
+ "get_available_models",
44
+ "get_commands",
45
+ "get_skills",
46
+ "get_tools",
47
+ "list_session_files",
48
+ "prompt",
49
+ "steer",
50
+ "follow_up",
51
+ "abort",
52
+ "get_state",
53
+ "get_messages",
54
+ "set_model",
55
+ "cycle_model",
56
+ "set_thinking_level",
57
+ "cycle_thinking_level",
58
+ "compact",
59
+ "abort_compaction",
60
+ "set_auto_compaction",
61
+ "set_auto_retry",
62
+ "abort_retry",
63
+ "bash",
64
+ "abort_bash",
65
+ "get_session_stats",
66
+ "set_session_name",
67
+ "export_html",
68
+ "new_session",
69
+ "switch_session_file",
70
+ "fork",
71
+ "get_fork_messages",
72
+ "get_last_assistant_text",
73
+ "get_context_usage"
74
+ ]);
75
+ const SERVER_COMMANDS = /* @__PURE__ */ new Set([
76
+ "list_sessions",
77
+ "create_session",
78
+ "delete_session",
79
+ "switch_session",
80
+ "get_metrics",
81
+ "health_check",
82
+ // ADR-0007: Session persistence
83
+ "list_stored_sessions",
84
+ "load_session"
85
+ ]);
86
+ const ALL_COMMANDS = /* @__PURE__ */ new Set([...SESSION_COMMANDS, ...SERVER_COMMANDS]);
87
+ function validateCommand(command) {
88
+ const errors = [];
89
+ if (!command || typeof command !== "object") {
90
+ return [{ field: "root", message: "Command must be an object" }];
91
+ }
92
+ const cmd = command;
93
+ if (!cmd.type || typeof cmd.type !== "string") {
94
+ errors.push({ field: "type", message: "Command must have a string 'type' field" });
95
+ }
96
+ if ("id" in cmd && (typeof cmd.id !== "string" || cmd.id.trim().length === 0)) {
97
+ errors.push({ field: "id", message: "Must be a non-empty string if provided" });
98
+ } else if (typeof cmd.id === "string" && cmd.id.length > MAX_COMMAND_ID_LENGTH) {
99
+ errors.push({ field: "id", message: `Too long (max ${MAX_COMMAND_ID_LENGTH} chars)` });
100
+ } else if (typeof cmd.id === "string" && isReservedIdPrefix(cmd.id)) {
101
+ errors.push({ field: "id", message: `Cannot use reserved prefix '${SYNTHETIC_ID_PREFIX}'` });
102
+ }
103
+ if ("idempotencyKey" in cmd) {
104
+ if (typeof cmd.idempotencyKey !== "string" || cmd.idempotencyKey.trim().length === 0) {
105
+ errors.push({ field: "idempotencyKey", message: "Must be a non-empty string if provided" });
106
+ } else if (cmd.idempotencyKey.length > MAX_IDEMPOTENCY_KEY_LENGTH) {
107
+ errors.push({
108
+ field: "idempotencyKey",
109
+ message: `Too long (max ${MAX_IDEMPOTENCY_KEY_LENGTH} chars)`
110
+ });
111
+ }
112
+ }
113
+ if ("dependsOn" in cmd) {
114
+ if (!Array.isArray(cmd.dependsOn)) {
115
+ errors.push({ field: "dependsOn", message: "Must be an array of command IDs" });
116
+ } else {
117
+ if (cmd.dependsOn.length > MAX_DEPENDENCIES) {
118
+ errors.push({
119
+ field: "dependsOn",
120
+ message: `Too many dependencies (max ${MAX_DEPENDENCIES})`
121
+ });
122
+ }
123
+ for (let i = 0; i < cmd.dependsOn.length; i++) {
124
+ const dep = cmd.dependsOn[i];
125
+ if (typeof dep !== "string" || dep.trim().length === 0) {
126
+ errors.push({
127
+ field: `dependsOn[${i}]`,
128
+ message: "Must be a non-empty command ID string"
129
+ });
130
+ }
131
+ }
132
+ if (cmd.dependsOn.length > 0 && typeof cmd.id !== "string") {
133
+ errors.push({ field: "id", message: "Required when dependsOn is provided" });
134
+ }
135
+ }
136
+ }
137
+ if ("ifSessionVersion" in cmd) {
138
+ if (typeof cmd.ifSessionVersion !== "number" || !Number.isInteger(cmd.ifSessionVersion) || cmd.ifSessionVersion < 0) {
139
+ errors.push({ field: "ifSessionVersion", message: "Must be a non-negative integer" });
140
+ }
141
+ }
142
+ if (typeof cmd.type === "string" && !ALL_COMMANDS.has(cmd.type)) {
143
+ errors.push({ field: "type", message: `Unknown command type '${cmd.type}'` });
144
+ }
145
+ if (typeof cmd.type === "string" && SESSION_COMMANDS.has(cmd.type)) {
146
+ if (!("sessionId" in cmd) || typeof cmd.sessionId !== "string" || cmd.sessionId.trim().length === 0) {
147
+ errors.push({
148
+ field: "sessionId",
149
+ message: "Session commands must have a non-empty string 'sessionId'"
150
+ });
151
+ }
152
+ }
153
+ if ("ifSessionVersion" in cmd && typeof cmd.type === "string" && !SESSION_COMMANDS.has(cmd.type) && cmd.type !== "delete_session") {
154
+ errors.push({
155
+ field: "ifSessionVersion",
156
+ message: "Only supported for session-targeted commands"
157
+ });
158
+ }
159
+ if (typeof cmd.type === "string") {
160
+ errors.push(...validateCommandByType(cmd.type, cmd));
161
+ }
162
+ return errors;
163
+ }
164
+ function validateCommandByType(type, cmd) {
165
+ const errors = [];
166
+ switch (type) {
167
+ case "create_session":
168
+ if ("sessionId" in cmd && (typeof cmd.sessionId !== "string" || cmd.sessionId.trim().length === 0)) {
169
+ errors.push({ field: "sessionId", message: "Must be a non-empty string if provided" });
170
+ }
171
+ if ("cwd" in cmd && typeof cmd.cwd !== "string") {
172
+ errors.push({ field: "cwd", message: "Must be a string if provided" });
173
+ }
174
+ break;
175
+ case "delete_session":
176
+ case "switch_session":
177
+ if (typeof cmd.sessionId !== "string" || cmd.sessionId.trim().length === 0) {
178
+ errors.push({ field: "sessionId", message: "Required non-empty string" });
179
+ }
180
+ break;
181
+ case "prompt":
182
+ if (!cmd.message || typeof cmd.message !== "string") {
183
+ errors.push({ field: "message", message: "Required string" });
184
+ } else if (cmd.message.length > MAX_PROMPT_MESSAGE_LENGTH) {
185
+ errors.push({
186
+ field: "message",
187
+ message: `Too long (max ${MAX_PROMPT_MESSAGE_LENGTH} chars)`
188
+ });
189
+ }
190
+ break;
191
+ case "steer":
192
+ case "follow_up":
193
+ if (!cmd.message || typeof cmd.message !== "string") {
194
+ errors.push({ field: "message", message: "Required string" });
195
+ } else if (cmd.message.length > MAX_PROMPT_MESSAGE_LENGTH) {
196
+ errors.push({
197
+ field: "message",
198
+ message: `Too long (max ${MAX_PROMPT_MESSAGE_LENGTH} chars)`
199
+ });
200
+ }
201
+ break;
202
+ case "set_model":
203
+ if (!cmd.provider || typeof cmd.provider !== "string") {
204
+ errors.push({ field: "provider", message: "Required string" });
205
+ }
206
+ if (!cmd.modelId || typeof cmd.modelId !== "string") {
207
+ errors.push({ field: "modelId", message: "Required string" });
208
+ }
209
+ break;
210
+ case "set_thinking_level":
211
+ if (!cmd.level || !["none", "low", "medium", "high", "xhigh"].includes(cmd.level)) {
212
+ errors.push({ field: "level", message: "Must be one of: none, low, medium, high, xhigh" });
213
+ }
214
+ break;
215
+ case "bash":
216
+ if (!cmd.command || typeof cmd.command !== "string") {
217
+ errors.push({ field: "command", message: "Required string" });
218
+ } else if (cmd.command.length > MAX_BASH_COMMAND_LENGTH) {
219
+ errors.push({
220
+ field: "command",
221
+ message: `Too long (max ${MAX_BASH_COMMAND_LENGTH} chars)`
222
+ });
223
+ }
224
+ break;
225
+ case "compact":
226
+ if ("customInstructions" in cmd && typeof cmd.customInstructions !== "string") {
227
+ errors.push({ field: "customInstructions", message: "Must be a string if provided" });
228
+ }
229
+ break;
230
+ case "extension_ui_response":
231
+ if (!cmd.requestId || typeof cmd.requestId !== "string") {
232
+ errors.push({ field: "requestId", message: "Required string" });
233
+ } else {
234
+ if (cmd.requestId.length > MAX_REQUEST_ID_LENGTH) {
235
+ errors.push({
236
+ field: "requestId",
237
+ message: `Too long (max ${MAX_REQUEST_ID_LENGTH} chars)`
238
+ });
239
+ }
240
+ if (!REQUEST_ID_PATTERN.test(cmd.requestId)) {
241
+ errors.push({
242
+ field: "requestId",
243
+ message: "Must contain only alphanumeric characters, colons, underscores, and dashes"
244
+ });
245
+ }
246
+ }
247
+ if (!cmd.response || typeof cmd.response !== "object") {
248
+ errors.push({ field: "response", message: "Required object" });
249
+ } else {
250
+ const response = cmd.response;
251
+ if (typeof response.method !== "string" || !EXTENSION_UI_RESPONSE_METHODS.has(response.method)) {
252
+ errors.push({
253
+ field: "response.method",
254
+ message: "Must be one of: select, confirm, input, editor, interview, cancelled"
255
+ });
256
+ }
257
+ }
258
+ break;
259
+ case "fork":
260
+ if (!cmd.entryId || typeof cmd.entryId !== "string") {
261
+ errors.push({ field: "entryId", message: "Required string" });
262
+ }
263
+ break;
264
+ case "switch_session_file":
265
+ if (!cmd.sessionPath || typeof cmd.sessionPath !== "string") {
266
+ errors.push({ field: "sessionPath", message: "Required string" });
267
+ } else {
268
+ const pathError = validatePath(cmd.sessionPath, "sessionPath");
269
+ if (pathError) {
270
+ errors.push({ field: "sessionPath", message: pathError });
271
+ }
272
+ }
273
+ break;
274
+ case "set_session_name":
275
+ if (!cmd.name || typeof cmd.name !== "string") {
276
+ errors.push({ field: "name", message: "Required string" });
277
+ } else if (cmd.name.length > MAX_SESSION_NAME_LENGTH) {
278
+ errors.push({ field: "name", message: `Too long (max ${MAX_SESSION_NAME_LENGTH} chars)` });
279
+ } else if (hasControlCharacters(cmd.name)) {
280
+ errors.push({ field: "name", message: "Must not contain control characters" });
281
+ }
282
+ break;
283
+ case "export_html":
284
+ if ("outputPath" in cmd && typeof cmd.outputPath !== "string") {
285
+ errors.push({ field: "outputPath", message: "Must be a string if provided" });
286
+ }
287
+ break;
288
+ case "set_auto_compaction":
289
+ case "set_auto_retry":
290
+ if (typeof cmd.enabled !== "boolean") {
291
+ errors.push({ field: "enabled", message: "Required boolean" });
292
+ }
293
+ break;
294
+ case "cycle_model":
295
+ if ("direction" in cmd && !["forward", "backward"].includes(cmd.direction)) {
296
+ errors.push({ field: "direction", message: "Must be 'forward' or 'backward'" });
297
+ }
298
+ break;
299
+ // ADR-0007: Session persistence
300
+ case "load_session":
301
+ if (!cmd.sessionPath || typeof cmd.sessionPath !== "string") {
302
+ errors.push({ field: "sessionPath", message: "Required string" });
303
+ } else {
304
+ const pathError = validatePath(cmd.sessionPath, "sessionPath");
305
+ if (pathError) {
306
+ errors.push({ field: "sessionPath", message: pathError });
307
+ }
308
+ }
309
+ if ("sessionId" in cmd && (typeof cmd.sessionId !== "string" || cmd.sessionId.trim().length === 0)) {
310
+ errors.push({ field: "sessionId", message: "Must be a non-empty string if provided" });
311
+ }
312
+ break;
313
+ }
314
+ return errors;
315
+ }
316
+ function formatValidationErrors(errors) {
317
+ return errors.map((e) => `${e.field}: ${e.message}`).join("; ");
318
+ }
319
+ export {
320
+ formatValidationErrors,
321
+ validateCommand
322
+ };
323
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/validation.ts"],
4
+ "sourcesContent": ["/**\n * Input validation for RPC commands.\n *\n * Validates command structure before processing to prevent:\n * - Missing required fields\n * - Invalid field types\n * - Malformed requests\n */\n\nexport interface ValidationError {\n field: string;\n message: string;\n}\n\nimport { SYNTHETIC_ID_PREFIX } from \"./command-replay-store.js\";\n\nconst MAX_PROMPT_MESSAGE_LENGTH = 200_000;\nconst MAX_BASH_COMMAND_LENGTH = 20_000;\nconst MAX_SESSION_NAME_LENGTH = 256;\nconst MAX_COMMAND_ID_LENGTH = 256;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REQUEST_ID_LENGTH = 256;\nconst MAX_DEPENDENCIES = 32;\n\n/** Valid characters for requestId (alphanumeric, colon, dash, underscore). */\nconst REQUEST_ID_PATTERN = /^[a-zA-Z0-9:_-]+$/;\n\n/** Dangerous path patterns that could allow traversal or injection. */\nconst DANGEROUS_PATH_PATTERNS = [/\\.\\./, /^~/, /\\0/];\n\n/** Maximum path length to prevent abuse. */\nconst MAX_PATH_LENGTH = 4096;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code <= 31 || code === 127) return true;\n }\n return false;\n}\n\n/**\n * Check if a path contains dangerous components.\n * Returns error message if dangerous, null if safe.\n */\nfunction validatePath(path: string, fieldName: string): string | null {\n if (path.length > MAX_PATH_LENGTH) {\n return `${fieldName} too long (max ${MAX_PATH_LENGTH} chars)`;\n }\n for (const pattern of DANGEROUS_PATH_PATTERNS) {\n if (pattern.test(path)) {\n return `${fieldName} contains potentially dangerous path components`;\n }\n }\n return null;\n}\n\n/**\n * Check if a command ID uses a reserved prefix.\n * Client-provided IDs starting with reserved prefixes are rejected.\n */\nfunction isReservedIdPrefix(id: string): boolean {\n return id.startsWith(SYNTHETIC_ID_PREFIX);\n}\n\nconst EXTENSION_UI_RESPONSE_METHODS = new Set([\n \"select\",\n \"confirm\",\n \"input\",\n \"editor\",\n \"interview\",\n \"cancelled\",\n]);\n\n// Session commands that require sessionId\nconst SESSION_COMMANDS = new Set([\n \"extension_ui_response\",\n \"get_available_models\",\n \"get_commands\",\n \"get_skills\",\n \"get_tools\",\n \"list_session_files\",\n \"prompt\",\n \"steer\",\n \"follow_up\",\n \"abort\",\n \"get_state\",\n \"get_messages\",\n \"set_model\",\n \"cycle_model\",\n \"set_thinking_level\",\n \"cycle_thinking_level\",\n \"compact\",\n \"abort_compaction\",\n \"set_auto_compaction\",\n \"set_auto_retry\",\n \"abort_retry\",\n \"bash\",\n \"abort_bash\",\n \"get_session_stats\",\n \"set_session_name\",\n \"export_html\",\n \"new_session\",\n \"switch_session_file\",\n \"fork\",\n \"get_fork_messages\",\n \"get_last_assistant_text\",\n \"get_context_usage\",\n]);\n\n// Server commands that don't require sessionId\nconst SERVER_COMMANDS = new Set([\n \"list_sessions\",\n \"create_session\",\n \"delete_session\",\n \"switch_session\",\n \"get_metrics\",\n \"health_check\",\n // ADR-0007: Session persistence\n \"list_stored_sessions\",\n \"load_session\",\n]);\n\nconst ALL_COMMANDS = new Set([...SESSION_COMMANDS, ...SERVER_COMMANDS]);\n\n/**\n * Validate a command has required fields.\n * Returns array of errors (empty if valid).\n */\nexport function validateCommand(command: unknown): ValidationError[] {\n const errors: ValidationError[] = [];\n\n // Must be an object\n if (!command || typeof command !== \"object\") {\n return [{ field: \"root\", message: \"Command must be an object\" }];\n }\n\n const cmd = command as Record<string, unknown>;\n\n // Must have type field\n if (!cmd.type || typeof cmd.type !== \"string\") {\n errors.push({ field: \"type\", message: \"Command must have a string 'type' field\" });\n }\n\n if (\"id\" in cmd && (typeof cmd.id !== \"string\" || cmd.id.trim().length === 0)) {\n errors.push({ field: \"id\", message: \"Must be a non-empty string if provided\" });\n } else if (typeof cmd.id === \"string\" && cmd.id.length > MAX_COMMAND_ID_LENGTH) {\n errors.push({ field: \"id\", message: `Too long (max ${MAX_COMMAND_ID_LENGTH} chars)` });\n } else if (typeof cmd.id === \"string\" && isReservedIdPrefix(cmd.id)) {\n errors.push({ field: \"id\", message: `Cannot use reserved prefix '${SYNTHETIC_ID_PREFIX}'` });\n }\n\n if (\"idempotencyKey\" in cmd) {\n if (typeof cmd.idempotencyKey !== \"string\" || cmd.idempotencyKey.trim().length === 0) {\n errors.push({ field: \"idempotencyKey\", message: \"Must be a non-empty string if provided\" });\n } else if (cmd.idempotencyKey.length > MAX_IDEMPOTENCY_KEY_LENGTH) {\n errors.push({\n field: \"idempotencyKey\",\n message: `Too long (max ${MAX_IDEMPOTENCY_KEY_LENGTH} chars)`,\n });\n }\n }\n\n if (\"dependsOn\" in cmd) {\n if (!Array.isArray(cmd.dependsOn)) {\n errors.push({ field: \"dependsOn\", message: \"Must be an array of command IDs\" });\n } else {\n if (cmd.dependsOn.length > MAX_DEPENDENCIES) {\n errors.push({\n field: \"dependsOn\",\n message: `Too many dependencies (max ${MAX_DEPENDENCIES})`,\n });\n }\n for (let i = 0; i < cmd.dependsOn.length; i++) {\n const dep = cmd.dependsOn[i];\n if (typeof dep !== \"string\" || dep.trim().length === 0) {\n errors.push({\n field: `dependsOn[${i}]`,\n message: \"Must be a non-empty command ID string\",\n });\n }\n }\n if (cmd.dependsOn.length > 0 && typeof cmd.id !== \"string\") {\n errors.push({ field: \"id\", message: \"Required when dependsOn is provided\" });\n }\n }\n }\n\n if (\"ifSessionVersion\" in cmd) {\n if (\n typeof cmd.ifSessionVersion !== \"number\" ||\n !Number.isInteger(cmd.ifSessionVersion) ||\n cmd.ifSessionVersion < 0\n ) {\n errors.push({ field: \"ifSessionVersion\", message: \"Must be a non-negative integer\" });\n }\n }\n\n if (typeof cmd.type === \"string\" && !ALL_COMMANDS.has(cmd.type)) {\n errors.push({ field: \"type\", message: `Unknown command type '${cmd.type}'` });\n }\n\n // If it's a session command, sessionId is required\n if (typeof cmd.type === \"string\" && SESSION_COMMANDS.has(cmd.type)) {\n if (\n !(\"sessionId\" in cmd) ||\n typeof cmd.sessionId !== \"string\" ||\n cmd.sessionId.trim().length === 0\n ) {\n errors.push({\n field: \"sessionId\",\n message: \"Session commands must have a non-empty string 'sessionId'\",\n });\n }\n }\n\n if (\n \"ifSessionVersion\" in cmd &&\n typeof cmd.type === \"string\" &&\n !SESSION_COMMANDS.has(cmd.type) &&\n cmd.type !== \"delete_session\"\n ) {\n errors.push({\n field: \"ifSessionVersion\",\n message: \"Only supported for session-targeted commands\",\n });\n }\n\n // Type-specific validation\n if (typeof cmd.type === \"string\") {\n errors.push(...validateCommandByType(cmd.type, cmd));\n }\n\n return errors;\n}\n\n/**\n * Type-specific validation for known command types.\n */\nfunction validateCommandByType(type: string, cmd: Record<string, unknown>): ValidationError[] {\n const errors: ValidationError[] = [];\n\n switch (type) {\n case \"create_session\":\n if (\n \"sessionId\" in cmd &&\n (typeof cmd.sessionId !== \"string\" || cmd.sessionId.trim().length === 0)\n ) {\n errors.push({ field: \"sessionId\", message: \"Must be a non-empty string if provided\" });\n }\n if (\"cwd\" in cmd && typeof cmd.cwd !== \"string\") {\n errors.push({ field: \"cwd\", message: \"Must be a string if provided\" });\n }\n break;\n\n case \"delete_session\":\n case \"switch_session\":\n if (typeof cmd.sessionId !== \"string\" || cmd.sessionId.trim().length === 0) {\n errors.push({ field: \"sessionId\", message: \"Required non-empty string\" });\n }\n break;\n\n case \"prompt\":\n if (!cmd.message || typeof cmd.message !== \"string\") {\n errors.push({ field: \"message\", message: \"Required string\" });\n } else if (cmd.message.length > MAX_PROMPT_MESSAGE_LENGTH) {\n errors.push({\n field: \"message\",\n message: `Too long (max ${MAX_PROMPT_MESSAGE_LENGTH} chars)`,\n });\n }\n break;\n\n case \"steer\":\n case \"follow_up\":\n if (!cmd.message || typeof cmd.message !== \"string\") {\n errors.push({ field: \"message\", message: \"Required string\" });\n } else if (cmd.message.length > MAX_PROMPT_MESSAGE_LENGTH) {\n errors.push({\n field: \"message\",\n message: `Too long (max ${MAX_PROMPT_MESSAGE_LENGTH} chars)`,\n });\n }\n break;\n\n case \"set_model\":\n if (!cmd.provider || typeof cmd.provider !== \"string\") {\n errors.push({ field: \"provider\", message: \"Required string\" });\n }\n if (!cmd.modelId || typeof cmd.modelId !== \"string\") {\n errors.push({ field: \"modelId\", message: \"Required string\" });\n }\n break;\n\n case \"set_thinking_level\":\n if (!cmd.level || ![\"none\", \"low\", \"medium\", \"high\", \"xhigh\"].includes(cmd.level as string)) {\n errors.push({ field: \"level\", message: \"Must be one of: none, low, medium, high, xhigh\" });\n }\n break;\n\n case \"bash\":\n if (!cmd.command || typeof cmd.command !== \"string\") {\n errors.push({ field: \"command\", message: \"Required string\" });\n } else if (cmd.command.length > MAX_BASH_COMMAND_LENGTH) {\n errors.push({\n field: \"command\",\n message: `Too long (max ${MAX_BASH_COMMAND_LENGTH} chars)`,\n });\n }\n break;\n\n case \"compact\":\n if (\"customInstructions\" in cmd && typeof cmd.customInstructions !== \"string\") {\n errors.push({ field: \"customInstructions\", message: \"Must be a string if provided\" });\n }\n break;\n\n case \"extension_ui_response\":\n if (!cmd.requestId || typeof cmd.requestId !== \"string\") {\n errors.push({ field: \"requestId\", message: \"Required string\" });\n } else {\n if (cmd.requestId.length > MAX_REQUEST_ID_LENGTH) {\n errors.push({\n field: \"requestId\",\n message: `Too long (max ${MAX_REQUEST_ID_LENGTH} chars)`,\n });\n }\n if (!REQUEST_ID_PATTERN.test(cmd.requestId)) {\n errors.push({\n field: \"requestId\",\n message: \"Must contain only alphanumeric characters, colons, underscores, and dashes\",\n });\n }\n }\n if (!cmd.response || typeof cmd.response !== \"object\") {\n errors.push({ field: \"response\", message: \"Required object\" });\n } else {\n const response = cmd.response as Record<string, unknown>;\n if (\n typeof response.method !== \"string\" ||\n !EXTENSION_UI_RESPONSE_METHODS.has(response.method)\n ) {\n errors.push({\n field: \"response.method\",\n message: \"Must be one of: select, confirm, input, editor, interview, cancelled\",\n });\n }\n }\n break;\n\n case \"fork\":\n if (!cmd.entryId || typeof cmd.entryId !== \"string\") {\n errors.push({ field: \"entryId\", message: \"Required string\" });\n }\n break;\n\n case \"switch_session_file\":\n if (!cmd.sessionPath || typeof cmd.sessionPath !== \"string\") {\n errors.push({ field: \"sessionPath\", message: \"Required string\" });\n } else {\n const pathError = validatePath(cmd.sessionPath, \"sessionPath\");\n if (pathError) {\n errors.push({ field: \"sessionPath\", message: pathError });\n }\n }\n break;\n\n case \"set_session_name\":\n if (!cmd.name || typeof cmd.name !== \"string\") {\n errors.push({ field: \"name\", message: \"Required string\" });\n } else if (cmd.name.length > MAX_SESSION_NAME_LENGTH) {\n errors.push({ field: \"name\", message: `Too long (max ${MAX_SESSION_NAME_LENGTH} chars)` });\n } else if (hasControlCharacters(cmd.name)) {\n errors.push({ field: \"name\", message: \"Must not contain control characters\" });\n }\n break;\n\n case \"export_html\":\n if (\"outputPath\" in cmd && typeof cmd.outputPath !== \"string\") {\n errors.push({ field: \"outputPath\", message: \"Must be a string if provided\" });\n }\n break;\n\n case \"set_auto_compaction\":\n case \"set_auto_retry\":\n if (typeof cmd.enabled !== \"boolean\") {\n errors.push({ field: \"enabled\", message: \"Required boolean\" });\n }\n break;\n\n case \"cycle_model\":\n if (\"direction\" in cmd && ![\"forward\", \"backward\"].includes(cmd.direction as string)) {\n errors.push({ field: \"direction\", message: \"Must be 'forward' or 'backward'\" });\n }\n break;\n\n // ADR-0007: Session persistence\n case \"load_session\":\n if (!cmd.sessionPath || typeof cmd.sessionPath !== \"string\") {\n errors.push({ field: \"sessionPath\", message: \"Required string\" });\n } else {\n const pathError = validatePath(cmd.sessionPath, \"sessionPath\");\n if (pathError) {\n errors.push({ field: \"sessionPath\", message: pathError });\n }\n }\n if (\n \"sessionId\" in cmd &&\n (typeof cmd.sessionId !== \"string\" || cmd.sessionId.trim().length === 0)\n ) {\n errors.push({ field: \"sessionId\", message: \"Must be a non-empty string if provided\" });\n }\n break;\n }\n\n return errors;\n}\n\n/**\n * Format validation errors as a human-readable string.\n */\nexport function formatValidationErrors(errors: ValidationError[]): string {\n return errors.map((e) => `${e.field}: ${e.message}`).join(\"; \");\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,135 @@
1
+ {
2
+ "name": "pi-app-server",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Session multiplexer for pi-coding-agent",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tryingET/pi-server.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/tryingET/pi-server/issues"
13
+ },
14
+ "homepage": "https://github.com/tryingET/pi-server#readme",
15
+ "keywords": [
16
+ "pi",
17
+ "pi-server",
18
+ "session-multiplexer",
19
+ "websocket",
20
+ "stdio"
21
+ ],
22
+ "author": "tryingET",
23
+ "main": "dist/server.js",
24
+ "types": "dist/server.d.ts",
25
+ "bin": {
26
+ "pi-server": "dist/server.js"
27
+ },
28
+ "files": [
29
+ "dist/server.js",
30
+ "dist/server.js.map",
31
+ "dist/server.d.ts",
32
+ "dist/server.d.ts.map",
33
+ "dist/session-manager.js",
34
+ "dist/session-manager.js.map",
35
+ "dist/session-manager.d.ts",
36
+ "dist/session-manager.d.ts.map",
37
+ "dist/command-router.js",
38
+ "dist/command-router.js.map",
39
+ "dist/command-router.d.ts",
40
+ "dist/command-router.d.ts.map",
41
+ "dist/server-command-handlers.js",
42
+ "dist/server-command-handlers.js.map",
43
+ "dist/server-command-handlers.d.ts",
44
+ "dist/server-command-handlers.d.ts.map",
45
+ "dist/extension-ui.js",
46
+ "dist/extension-ui.js.map",
47
+ "dist/extension-ui.d.ts",
48
+ "dist/extension-ui.d.ts.map",
49
+ "dist/server-ui-context.js",
50
+ "dist/server-ui-context.js.map",
51
+ "dist/server-ui-context.d.ts",
52
+ "dist/server-ui-context.d.ts.map",
53
+ "dist/validation.js",
54
+ "dist/validation.js.map",
55
+ "dist/validation.d.ts",
56
+ "dist/validation.d.ts.map",
57
+ "dist/types.js",
58
+ "dist/types.js.map",
59
+ "dist/types.d.ts",
60
+ "dist/types.d.ts.map",
61
+ "dist/type-guards.js",
62
+ "dist/type-guards.js.map",
63
+ "dist/type-guards.d.ts",
64
+ "dist/type-guards.d.ts.map",
65
+ "dist/resource-governor.js",
66
+ "dist/resource-governor.js.map",
67
+ "dist/resource-governor.d.ts",
68
+ "dist/resource-governor.d.ts.map",
69
+ "dist/command-classification.js",
70
+ "dist/command-classification.js.map",
71
+ "dist/command-classification.d.ts",
72
+ "dist/command-classification.d.ts.map",
73
+ "dist/command-replay-store.js",
74
+ "dist/command-replay-store.js.map",
75
+ "dist/command-replay-store.d.ts",
76
+ "dist/command-replay-store.d.ts.map",
77
+ "dist/session-version-store.js",
78
+ "dist/session-version-store.js.map",
79
+ "dist/session-version-store.d.ts",
80
+ "dist/session-version-store.d.ts.map",
81
+ "dist/command-execution-engine.js",
82
+ "dist/command-execution-engine.js.map",
83
+ "dist/command-execution-engine.d.ts",
84
+ "dist/command-execution-engine.d.ts.map",
85
+ "dist/session-lock-manager.js",
86
+ "dist/session-lock-manager.js.map",
87
+ "dist/session-lock-manager.d.ts",
88
+ "dist/session-lock-manager.d.ts.map",
89
+ "dist/session-store.js",
90
+ "dist/session-store.js.map",
91
+ "dist/session-store.d.ts",
92
+ "dist/session-store.d.ts.map",
93
+ "README.md",
94
+ "LICENSE"
95
+ ],
96
+ "publishConfig": {
97
+ "registry": "https://registry.npmjs.org/",
98
+ "access": "public"
99
+ },
100
+ "scripts": {
101
+ "release:check": "bash ./scripts/release-check.sh",
102
+ "build": "npm run build:js && npm run build:types",
103
+ "build:js": "esbuild src/server.ts src/session-manager.ts src/command-router.ts src/server-command-handlers.ts src/extension-ui.ts src/server-ui-context.ts src/validation.ts src/types.ts src/type-guards.ts src/resource-governor.ts src/command-classification.ts src/command-replay-store.ts src/session-version-store.ts src/command-execution-engine.ts src/session-lock-manager.ts src/session-store.ts src/circuit-breaker.ts src/bash-circuit-breaker.ts src/auth.ts src/bounded-map.ts src/metrics-types.ts src/metrics-emitter.ts src/metrics-index.ts src/threshold-alert-sink.ts src/logger-types.ts src/logger-index.ts src/test.ts src/test-command-classification.ts src/test-session-version-store.ts src/test-command-replay-store.ts src/test-command-execution-engine.ts src/test-threshold-alert-sink.ts src/test-bash-circuit-breaker.ts src/test-fuzz.ts src/test-integration.ts --outdir=dist --format=esm --platform=node --target=node20 --sourcemap",
104
+ "build:types": "tsc --emitDeclarationOnly --declaration --declarationMap",
105
+ "typecheck": "tsgo -config tsconfig.json",
106
+ "lint": "biome lint src",
107
+ "format": "biome format src --write",
108
+ "format:check": "biome format src",
109
+ "check": "npm run typecheck && npm run lint",
110
+ "ci": "npm run typecheck && npm run lint && npm run format:check && npm run build && npm test",
111
+ "start": "node dist/server.js",
112
+ "dev": "esbuild src/server.ts --outdir=dist --format=esm --platform=node --target=node20 --sourcemap=inline --watch",
113
+ "test": "node --experimental-vm-modules dist/test.js",
114
+ "test:integration": "node --experimental-vm-modules dist/test-integration.js",
115
+ "test:fuzz": "node --experimental-vm-modules dist/test-fuzz.js"
116
+ },
117
+ "dependencies": {
118
+ "@mariozechner/pi-agent-core": "^0.54.0",
119
+ "@mariozechner/pi-ai": "^0.54.0",
120
+ "@mariozechner/pi-coding-agent": "^0.54.0",
121
+ "ws": "^8.18.0"
122
+ },
123
+ "devDependencies": {
124
+ "@biomejs/biome": "^2.4.4",
125
+ "@rslint/tsgo": "^0.2.1",
126
+ "@types/node": "^22.0.0",
127
+ "@types/ws": "^8.5.0",
128
+ "esbuild": "^0.27.3",
129
+ "get-port": "^7.1.0",
130
+ "typescript": "^5.7.0"
131
+ },
132
+ "engines": {
133
+ "node": ">=20.0.0"
134
+ }
135
+ }