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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # pi-server
2
+
3
+ Session multiplexer for [pi-coding-agent](https://www.npmjs.com/package/@mariozechner/pi-coding-agent). Exposes N independent `AgentSession` instances through WebSocket and stdio transports.
4
+
5
+ [![CI](https://github.com/tryingET/pi-server/actions/workflows/ci.yml/badge.svg)](https://github.com/tryingET/pi-server/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/pi-app-server.svg)](https://www.npmjs.com/package/pi-app-server)
7
+
8
+ > Note: This is a standalone pi server package, not an extension/skills/themes bundle.
9
+
10
+ ## Features
11
+
12
+ - **Dual transport**: WebSocket (port 3141) + stdio (JSON lines)
13
+ - **Session lifecycle**: Create, delete, list, switch sessions
14
+ - **Command execution**: Deterministic lane serialization per session
15
+ - **Idempotent replay**: Atomic outcome storage with free replay lookups
16
+ - **Optimistic concurrency**: Session versioning for conflict detection
17
+ - **Extension UI**: Full round-trip support for `select`, `confirm`, `input`, `editor`, `interview`
18
+ - **Resource governance**: Rate limiting, session limits, message size limits
19
+ - **Graceful shutdown**: Drain in-flight commands, notify clients
20
+ - **Protocol versioning**: `serverVersion` + `protocolVersion` for compatibility checks
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install pi-app-server
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### WebSocket
31
+
32
+ ```bash
33
+ # Start server
34
+ npx pi-server
35
+
36
+ # Connect with wscat
37
+ wscat -c ws://localhost:3141
38
+ ```
39
+
40
+ ```js
41
+ // Create and use a session
42
+ ws> {"type":"create_session","sessionId":"my-session"}
43
+ ws> {"type":"switch_session","sessionId":"my-session"}
44
+ ws> {"id":"cmd-1","type":"prompt","sessionId":"my-session","message":"Hello!"}
45
+ ```
46
+
47
+ ### stdio
48
+
49
+ ```bash
50
+ echo '{"type":"create_session","sessionId":"test"}
51
+ {"type":"switch_session","sessionId":"test"}
52
+ {"id":"cmd-1","type":"prompt","sessionId":"test","message":"Hello!"}' | npx pi-server
53
+ ```
54
+
55
+ ## Architecture
56
+
57
+ ```
58
+ src/
59
+ ├── server.ts # transports, connection lifecycle, routing glue
60
+ ├── session-manager.ts # orchestration: coordinates stores, engines, sessions
61
+ ├── command-router.ts # session command handlers, routing
62
+ ├── command-classification.ts # pure command classification (timeout, mutation)
63
+ ├── command-replay-store.ts # idempotency, duplicate detection, outcome history
64
+ ├── session-version-store.ts # monotonic version counters per session
65
+ ├── command-execution-engine.ts # lane serialization, dependency waits, timeouts
66
+ ├── resource-governor.ts # limits, rate controls, health/metrics
67
+ ├── extension-ui.ts # pending UI request tracking
68
+ ├── server-ui-context.ts # ExtensionUIContext for remote clients
69
+ ├── validation.ts # command validation
70
+ └── types.ts # wire protocol types + SessionResolver interface
71
+ ```
72
+
73
+ ### Core invariants
74
+
75
+ - For each admitted command, there is exactly one terminal response.
76
+ - For each session ID, there is at most one live `AgentSession`.
77
+ - Subscriber session sets are always a subset of active sessions.
78
+ - Session version is monotonic and mutation-sensitive.
79
+ - Fingerprint excludes retry identity (`id`, `idempotencyKey`) for semantic equivalence.
80
+
81
+ ### Key abstractions
82
+
83
+ - **`SessionResolver`** — Interface for session access (enables test doubles, future clustering)
84
+ - **`CommandReplayStore`** — Idempotency and duplicate detection
85
+ - **`SessionVersionStore`** — Optimistic concurrency via version counters
86
+ - **`CommandExecutionEngine`** — Deterministic lane serialization and timeout management
87
+
88
+ ## Protocol
89
+
90
+ See [PROTOCOL.md](./PROTOCOL.md) for the normative wire contract.
91
+
92
+ ### Command → Response
93
+
94
+ Every command receives exactly one response:
95
+
96
+ ```json
97
+ {"id": "cmd-1", "type": "prompt", "sessionId": "s1", "message": "hello"}
98
+ {"id": "cmd-1", "type": "response", "command": "prompt", "success": true}
99
+ ```
100
+
101
+ ### Event Broadcast
102
+
103
+ Events flow session → subscribers:
104
+
105
+ ```json
106
+ {"type": "event", "sessionId": "s1", "event": {"type": "agent_start", ...}}
107
+ ```
108
+
109
+ ### Extension UI Round-Trip
110
+
111
+ 1. Extension calls `ui.select()` → server creates pending request
112
+ 2. Server broadcasts `extension_ui_request` event with `requestId`
113
+ 3. Client sends `extension_ui_response` command with same `requestId`
114
+ 4. Server resolves pending promise → extension continues
115
+
116
+ ### Idempotency & Replay
117
+
118
+ ```json
119
+ // First request with idempotency key
120
+ {"id": "cmd-1", "type": "list_sessions", "idempotencyKey": "key-1"}
121
+ {"id": "cmd-1", "type": "response", "command": "list_sessions", "success": true, ...}
122
+
123
+ // Retry with same key → replayed (free, no rate limit charge)
124
+ {"id": "cmd-2", "type": "list_sessions", "idempotencyKey": "key-1"}
125
+ {"id": "cmd-2", "type": "response", "command": "list_sessions", "success": true, "replayed": true, ...}
126
+ ```
127
+
128
+ ### Timeout semantics (ADR-0001)
129
+
130
+ - Timeout is a **terminal stored outcome** (`timedOut: true`), not an indeterminate placeholder.
131
+ - Replay of the same command identity returns the **same timeout response**.
132
+ - Late underlying completion does **not** overwrite the stored timeout outcome.
133
+
134
+ ## Development
135
+
136
+ ```bash
137
+ # Install dependencies
138
+ npm install
139
+
140
+ # Build
141
+ npm run build
142
+
143
+ # Run tests
144
+ npm test # Unit tests (83)
145
+ npm run test:integration # Integration tests (26)
146
+ npm run test:fuzz # Fuzz tests (17)
147
+
148
+ # Module tests (141)
149
+ node --experimental-vm-modules dist/test-command-classification.js
150
+ node --experimental-vm-modules dist/test-session-version-store.js
151
+ node --experimental-vm-modules dist/test-command-replay-store.js
152
+ node --experimental-vm-modules dist/test-command-execution-engine.js
153
+
154
+ # Type check + lint
155
+ npm run check
156
+
157
+ # Full CI
158
+ npm run ci
159
+ ```
160
+
161
+ ## Release Process
162
+
163
+ This project uses [release-please](https://github.com/googleapis/release-please) for automated versioning.
164
+
165
+ ### Automated Flow
166
+
167
+ 1. Push to `main` → release-please creates/updates a release PR
168
+ 2. Merge the release PR → Creates GitHub release + git tag
169
+ 3. Release published → GitHub Action publishes to npm with provenance
170
+
171
+ ### Manual Release Check
172
+
173
+ ```bash
174
+ npm run release:check
175
+ ```
176
+
177
+ This validates:
178
+ - `package.json` has required fields
179
+ - `dist/` exists with compiled files
180
+ - Entry point has correct shebang
181
+ - `npm pack` produces expected files
182
+ - Full CI passes
183
+
184
+ ## Documentation
185
+
186
+ | Document | Purpose |
187
+ |----------|---------|
188
+ | [AGENTS.md](./AGENTS.md) | Crystallized learnings, patterns, anti-patterns |
189
+ | [PROTOCOL.md](./PROTOCOL.md) | Normative wire contract |
190
+ | [ADR-0001](./docs/adr/0001-atomic-outcome-storage.md) | Atomic outcome storage decision |
191
+ | [ROADMAP.md](./ROADMAP.md) | Phase tracking and milestones |
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Command Classification - unified source of truth for command behavior.
3
+ *
4
+ * This module consolidates all command classification logic that was
5
+ * previously scattered across multiple modules. Single source of truth
6
+ * prevents drift and makes adding new commands easier.
7
+ *
8
+ * Classification dimensions:
9
+ * - Timeout policy: short (30s), long (5min), or none (uncancellable)
10
+ * - Mutation: does the command change session state?
11
+ */
12
+ /**
13
+ * Get the timeout policy for a command type.
14
+ * @returns Timeout in ms, or null for uncancellable commands
15
+ */
16
+ export declare function getCommandTimeoutPolicy(commandType: string, options?: {
17
+ defaultTimeoutMs?: number;
18
+ shortTimeoutMs?: number;
19
+ }): number | null;
20
+ /**
21
+ * Check if a command has a short timeout.
22
+ */
23
+ export declare function isShortTimeoutCommand(commandType: string): boolean;
24
+ /**
25
+ * Check if a command cannot be timed out.
26
+ */
27
+ export declare function isNoTimeoutCommand(commandType: string): boolean;
28
+ /**
29
+ * Check if a command type mutates session state.
30
+ * Mutating commands advance the session version.
31
+ */
32
+ export declare function isMutationCommand(commandType: string): boolean;
33
+ /**
34
+ * Check if a command is read-only.
35
+ */
36
+ export declare function isReadOnlyCommand(commandType: string): boolean;
37
+ /**
38
+ * Full classification of a command type.
39
+ */
40
+ export interface CommandClassification {
41
+ /** Timeout in milliseconds, or null for uncancellable */
42
+ timeoutMs: number | null;
43
+ /** Whether this is a short timeout command */
44
+ isShortTimeout: boolean;
45
+ /** Whether this command can be timed out */
46
+ isCancellable: boolean;
47
+ /** Whether this command mutates session state */
48
+ isMutation: boolean;
49
+ /** Whether this command is read-only */
50
+ isReadOnly: boolean;
51
+ }
52
+ /**
53
+ * Get full classification for a command type.
54
+ */
55
+ export declare function classifyCommand(commandType: string, options?: {
56
+ defaultTimeoutMs?: number;
57
+ shortTimeoutMs?: number;
58
+ }): CommandClassification;
59
+ //# sourceMappingURL=command-classification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-classification.d.ts","sourceRoot":"","sources":["../src/command-classification.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiCH;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,MAAM,GAAG,IAAI,CASf;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE/D;AAiCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE9D;AAMD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,8CAA8C;IAC9C,cAAc,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,iDAAiD;IACjD,UAAU,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,qBAAqB,CASvB"}
@@ -0,0 +1,78 @@
1
+ const SHORT_TIMEOUT_COMMANDS = /* @__PURE__ */ new Set([
2
+ "get_state",
3
+ "get_messages",
4
+ "get_available_models",
5
+ "get_commands",
6
+ "get_skills",
7
+ "get_tools",
8
+ "list_session_files",
9
+ "get_session_stats",
10
+ "get_fork_messages",
11
+ "get_last_assistant_text",
12
+ "get_context_usage",
13
+ "set_session_name"
14
+ ]);
15
+ const NO_TIMEOUT_COMMANDS = /* @__PURE__ */ new Set([
16
+ "create_session"
17
+ // Session creation is atomic
18
+ ]);
19
+ function getCommandTimeoutPolicy(commandType, options) {
20
+ if (NO_TIMEOUT_COMMANDS.has(commandType)) {
21
+ return null;
22
+ }
23
+ const defaultTimeout = options?.defaultTimeoutMs ?? 5 * 60 * 1e3;
24
+ const shortTimeout = options?.shortTimeoutMs ?? 30 * 1e3;
25
+ return SHORT_TIMEOUT_COMMANDS.has(commandType) ? shortTimeout : defaultTimeout;
26
+ }
27
+ function isShortTimeoutCommand(commandType) {
28
+ return SHORT_TIMEOUT_COMMANDS.has(commandType);
29
+ }
30
+ function isNoTimeoutCommand(commandType) {
31
+ return NO_TIMEOUT_COMMANDS.has(commandType);
32
+ }
33
+ const READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
34
+ "get_state",
35
+ "get_messages",
36
+ "get_available_models",
37
+ "get_commands",
38
+ "get_skills",
39
+ "get_tools",
40
+ "list_session_files",
41
+ "get_session_stats",
42
+ "get_fork_messages",
43
+ "get_last_assistant_text",
44
+ "get_context_usage",
45
+ "switch_session"
46
+ // Switches client focus, doesn't change session
47
+ ]);
48
+ const SPECIAL_SESSION_COMMANDS = /* @__PURE__ */ new Set([
49
+ "extension_ui_response"
50
+ // Handled by ExtensionUIManager, not session
51
+ ]);
52
+ function isMutationCommand(commandType) {
53
+ if (READ_ONLY_COMMANDS.has(commandType)) return false;
54
+ if (SPECIAL_SESSION_COMMANDS.has(commandType)) return false;
55
+ return true;
56
+ }
57
+ function isReadOnlyCommand(commandType) {
58
+ return READ_ONLY_COMMANDS.has(commandType);
59
+ }
60
+ function classifyCommand(commandType, options) {
61
+ const timeoutMs = getCommandTimeoutPolicy(commandType, options);
62
+ return {
63
+ timeoutMs,
64
+ isShortTimeout: isShortTimeoutCommand(commandType),
65
+ isCancellable: timeoutMs !== null,
66
+ isMutation: isMutationCommand(commandType),
67
+ isReadOnly: isReadOnlyCommand(commandType)
68
+ };
69
+ }
70
+ export {
71
+ classifyCommand,
72
+ getCommandTimeoutPolicy,
73
+ isMutationCommand,
74
+ isNoTimeoutCommand,
75
+ isReadOnlyCommand,
76
+ isShortTimeoutCommand
77
+ };
78
+ //# sourceMappingURL=command-classification.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/command-classification.ts"],
4
+ "sourcesContent": ["/**\n * Command Classification - unified source of truth for command behavior.\n *\n * This module consolidates all command classification logic that was\n * previously scattered across multiple modules. Single source of truth\n * prevents drift and makes adding new commands easier.\n *\n * Classification dimensions:\n * - Timeout policy: short (30s), long (5min), or none (uncancellable)\n * - Mutation: does the command change session state?\n */\n\n// =============================================================================\n// TIMEOUT CLASSIFICATION\n// =============================================================================\n\n/**\n * Commands that should have shorter timeout (30 seconds).\n * These are fast operations that don't involve LLM calls.\n */\nconst SHORT_TIMEOUT_COMMANDS = new Set([\n \"get_state\",\n \"get_messages\",\n \"get_available_models\",\n \"get_commands\",\n \"get_skills\",\n \"get_tools\",\n \"list_session_files\",\n \"get_session_stats\",\n \"get_fork_messages\",\n \"get_last_assistant_text\",\n \"get_context_usage\",\n \"set_session_name\",\n]);\n\n/**\n * Commands that should not use command timeout (cannot be safely cancelled).\n * These have side effects that must complete once started.\n */\nconst NO_TIMEOUT_COMMANDS = new Set([\n \"create_session\", // Session creation is atomic\n]);\n\n/**\n * Get the timeout policy for a command type.\n * @returns Timeout in ms, or null for uncancellable commands\n */\nexport function getCommandTimeoutPolicy(\n commandType: string,\n options?: {\n defaultTimeoutMs?: number;\n shortTimeoutMs?: number;\n }\n): number | null {\n if (NO_TIMEOUT_COMMANDS.has(commandType)) {\n return null;\n }\n\n const defaultTimeout = options?.defaultTimeoutMs ?? 5 * 60 * 1000;\n const shortTimeout = options?.shortTimeoutMs ?? 30 * 1000;\n\n return SHORT_TIMEOUT_COMMANDS.has(commandType) ? shortTimeout : defaultTimeout;\n}\n\n/**\n * Check if a command has a short timeout.\n */\nexport function isShortTimeoutCommand(commandType: string): boolean {\n return SHORT_TIMEOUT_COMMANDS.has(commandType);\n}\n\n/**\n * Check if a command cannot be timed out.\n */\nexport function isNoTimeoutCommand(commandType: string): boolean {\n return NO_TIMEOUT_COMMANDS.has(commandType);\n}\n\n// =============================================================================\n// MUTATION CLASSIFICATION\n// =============================================================================\n\n/**\n * Commands that don't mutate session state (read-only).\n * These don't advance the session version on success.\n */\nconst READ_ONLY_COMMANDS = new Set([\n \"get_state\",\n \"get_messages\",\n \"get_available_models\",\n \"get_commands\",\n \"get_skills\",\n \"get_tools\",\n \"list_session_files\",\n \"get_session_stats\",\n \"get_fork_messages\",\n \"get_last_assistant_text\",\n \"get_context_usage\",\n \"switch_session\", // Switches client focus, doesn't change session\n]);\n\n/**\n * Commands that appear to target a session but are handled specially.\n * These don't count as session mutations for version purposes.\n */\nconst SPECIAL_SESSION_COMMANDS = new Set([\n \"extension_ui_response\", // Handled by ExtensionUIManager, not session\n]);\n\n/**\n * Check if a command type mutates session state.\n * Mutating commands advance the session version.\n */\nexport function isMutationCommand(commandType: string): boolean {\n if (READ_ONLY_COMMANDS.has(commandType)) return false;\n if (SPECIAL_SESSION_COMMANDS.has(commandType)) return false;\n return true;\n}\n\n/**\n * Check if a command is read-only.\n */\nexport function isReadOnlyCommand(commandType: string): boolean {\n return READ_ONLY_COMMANDS.has(commandType);\n}\n\n// =============================================================================\n// COMBINED QUERIES\n// =============================================================================\n\n/**\n * Full classification of a command type.\n */\nexport interface CommandClassification {\n /** Timeout in milliseconds, or null for uncancellable */\n timeoutMs: number | null;\n /** Whether this is a short timeout command */\n isShortTimeout: boolean;\n /** Whether this command can be timed out */\n isCancellable: boolean;\n /** Whether this command mutates session state */\n isMutation: boolean;\n /** Whether this command is read-only */\n isReadOnly: boolean;\n}\n\n/**\n * Get full classification for a command type.\n */\nexport function classifyCommand(\n commandType: string,\n options?: {\n defaultTimeoutMs?: number;\n shortTimeoutMs?: number;\n }\n): CommandClassification {\n const timeoutMs = getCommandTimeoutPolicy(commandType, options);\n return {\n timeoutMs,\n isShortTimeout: isShortTimeoutCommand(commandType),\n isCancellable: timeoutMs !== null,\n isMutation: isMutationCommand(commandType),\n isReadOnly: isReadOnlyCommand(commandType),\n };\n}\n"],
5
+ "mappings": "AAoBA,MAAM,yBAAyB,oBAAI,IAAI;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,MAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA;AACF,CAAC;AAMM,SAAS,wBACd,aACA,SAIe;AACf,MAAI,oBAAoB,IAAI,WAAW,GAAG;AACxC,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,SAAS,oBAAoB,IAAI,KAAK;AAC7D,QAAM,eAAe,SAAS,kBAAkB,KAAK;AAErD,SAAO,uBAAuB,IAAI,WAAW,IAAI,eAAe;AAClE;AAKO,SAAS,sBAAsB,aAA8B;AAClE,SAAO,uBAAuB,IAAI,WAAW;AAC/C;AAKO,SAAS,mBAAmB,aAA8B;AAC/D,SAAO,oBAAoB,IAAI,WAAW;AAC5C;AAUA,MAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AACF,CAAC;AAMD,MAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA;AACF,CAAC;AAMM,SAAS,kBAAkB,aAA8B;AAC9D,MAAI,mBAAmB,IAAI,WAAW,EAAG,QAAO;AAChD,MAAI,yBAAyB,IAAI,WAAW,EAAG,QAAO;AACtD,SAAO;AACT;AAKO,SAAS,kBAAkB,aAA8B;AAC9D,SAAO,mBAAmB,IAAI,WAAW;AAC3C;AAyBO,SAAS,gBACd,aACA,SAIuB;AACvB,QAAM,YAAY,wBAAwB,aAAa,OAAO;AAC9D,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,sBAAsB,WAAW;AAAA,IACjD,eAAe,cAAc;AAAA,IAC7B,YAAY,kBAAkB,WAAW;AAAA,IACzC,YAAY,kBAAkB,WAAW;AAAA,EAC3C;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Command Execution Engine - manages lane serialization, dependency waits, and timeouts.
3
+ *
4
+ * Responsibilities:
5
+ * - Deterministic per-lane command serialization
6
+ * - Dependency resolution with timeout
7
+ * - Command timeout orchestration with abort hooks
8
+ * - Lifecycle event emission
9
+ */
10
+ import type { AgentSession } from "@mariozechner/pi-coding-agent";
11
+ import type { RpcCommand, RpcResponse, SessionResolver } from "./types.js";
12
+ import type { CommandReplayStore } from "./command-replay-store.js";
13
+ import type { SessionVersionStore } from "./session-version-store.js";
14
+ /**
15
+ * Abort handler for a specific command type.
16
+ * Called when a command times out to attempt cancellation.
17
+ */
18
+ export type AbortHandler = (session: AgentSession) => void | Promise<void>;
19
+ /**
20
+ * Wrap a promise with a timeout.
21
+ * Returns the promise result or throws on timeout.
22
+ */
23
+ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, commandType: string, onTimeout?: () => void | Promise<void>): Promise<T>;
24
+ /**
25
+ * Configuration options for the execution engine.
26
+ */
27
+ export interface ExecutionEngineOptions {
28
+ defaultCommandTimeoutMs?: number;
29
+ shortCommandTimeoutMs?: number;
30
+ dependencyWaitTimeoutMs?: number;
31
+ /** Custom abort handlers for command types (extends defaults) */
32
+ abortHandlers?: Partial<Record<string, AbortHandler>>;
33
+ }
34
+ /**
35
+ * Command Execution Engine - manages lane serialization and dependency waits.
36
+ *
37
+ * Extracted from PiSessionManager to isolate:
38
+ * - Per-lane command serialization
39
+ * - Dependency resolution with timeout
40
+ * - Command timeout with abort hooks
41
+ */
42
+ export declare class CommandExecutionEngine {
43
+ /** Deterministic per-lane command serialization tails. */
44
+ private laneTails;
45
+ private readonly replayStore;
46
+ private readonly versionStore;
47
+ private readonly sessionResolver;
48
+ private readonly abortHandlers;
49
+ private readonly defaultCommandTimeoutMs;
50
+ private readonly shortCommandTimeoutMs;
51
+ private readonly dependencyWaitTimeoutMs;
52
+ constructor(replayStore: CommandReplayStore, versionStore: SessionVersionStore, sessionResolver: SessionResolver, options?: ExecutionEngineOptions);
53
+ /**
54
+ * Get statistics about the execution engine state.
55
+ */
56
+ getStats(): {
57
+ laneCount: number;
58
+ };
59
+ /**
60
+ * Get the lane key for a command.
61
+ * Session commands serialize per-session; server commands serialize together.
62
+ */
63
+ getLaneKey(command: RpcCommand): string;
64
+ /**
65
+ * Run a task in a deterministic serialized lane.
66
+ * Commands in the same lane execute sequentially.
67
+ */
68
+ runOnLane<T>(laneKey: string, task: () => Promise<T>): Promise<T>;
69
+ /**
70
+ * Wait for dependency commands to complete.
71
+ * Returns error if any dependency fails or times out.
72
+ *
73
+ * Note: Cross-lane dependency cycles (A→B, B→A) are detected by timeout
74
+ * rather than explicit cycle detection. This is acceptable because:
75
+ * 1. Cross-lane dependencies are rare
76
+ * 2. The dependencyWaitTimeoutMs (default 30s) prevents indefinite deadlock
77
+ * 3. Same-lane cycles are explicitly detected below
78
+ */
79
+ awaitDependencies(dependsOn: string[], laneKey: string): Promise<{
80
+ ok: true;
81
+ } | {
82
+ ok: false;
83
+ error: string;
84
+ }>;
85
+ /**
86
+ * Resolve timeout policy for a command.
87
+ * Returns null for commands that cannot be safely cancelled.
88
+ */
89
+ getCommandTimeoutMs(commandType: string): number | null;
90
+ /**
91
+ * Best-effort cancellation for timed-out commands.
92
+ * Uses configured abort handlers (defaults + custom overrides).
93
+ */
94
+ abortTimedOutCommand(command: RpcCommand): Promise<void>;
95
+ /**
96
+ * Execute a command with timeout.
97
+ */
98
+ executeWithTimeout(commandType: string, promise: Promise<RpcResponse>, command: RpcCommand): Promise<RpcResponse>;
99
+ /**
100
+ * Check if a session version matches the expected version.
101
+ * Returns error object if mismatch, undefined if OK.
102
+ *
103
+ * @param sessionId - The session to check
104
+ * @param ifSessionVersion - The expected version
105
+ * @param commandType - The command type for error context (preserves caller's context)
106
+ */
107
+ checkSessionVersion(sessionId: string, ifSessionVersion: number, commandType: string): {
108
+ type: "response";
109
+ command: string;
110
+ success: false;
111
+ error: string;
112
+ } | undefined;
113
+ /**
114
+ * Clear all lane state (used during disposal).
115
+ */
116
+ clear(): void;
117
+ }
118
+ //# sourceMappingURL=command-execution-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-execution-engine.d.ts","sourceRoot":"","sources":["../src/command-execution-engine.ts"],"names":[],"mappings":"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"}