edge-ai-client-ts 1.0.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.
- package/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/bin/ec-ts.js +18 -0
- package/dist/buffer/disk-queue.d.ts +140 -0
- package/dist/buffer/disk-queue.js +370 -0
- package/dist/cli/devices.d.ts +1 -0
- package/dist/cli/devices.js +61 -0
- package/dist/cli/enroll.d.ts +2 -0
- package/dist/cli/enroll.js +89 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/messages.d.ts +1 -0
- package/dist/cli/messages.js +59 -0
- package/dist/cli/run.d.ts +5 -0
- package/dist/cli/run.js +112 -0
- package/dist/cli/status.d.ts +1 -0
- package/dist/cli/status.js +56 -0
- package/dist/cli/whoami.d.ts +2 -0
- package/dist/cli/whoami.js +41 -0
- package/dist/config/cmd-gate.d.ts +65 -0
- package/dist/config/cmd-gate.js +128 -0
- package/dist/config/settings.d.ts +209 -0
- package/dist/config/settings.js +627 -0
- package/dist/crypto/aes-gcm.d.ts +38 -0
- package/dist/crypto/aes-gcm.js +90 -0
- package/dist/crypto/hmac.d.ts +31 -0
- package/dist/crypto/hmac.js +52 -0
- package/dist/crypto/tls-guard.d.ts +36 -0
- package/dist/crypto/tls-guard.js +54 -0
- package/dist/daemon/manager.d.ts +82 -0
- package/dist/daemon/manager.js +461 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +63 -0
- package/dist/logging/file-logger.d.ts +21 -0
- package/dist/logging/file-logger.js +71 -0
- package/dist/network/ws-client.d.ts +221 -0
- package/dist/network/ws-client.js +1134 -0
- package/dist/session/fail-fast.d.ts +70 -0
- package/dist/session/fail-fast.js +122 -0
- package/dist/session/manager.d.ts +136 -0
- package/dist/session/manager.js +291 -0
- package/dist/session/persistence.d.ts +103 -0
- package/dist/session/persistence.js +194 -0
- package/dist/session/pi-rpc.d.ts +164 -0
- package/dist/session/pi-rpc.js +412 -0
- package/dist/session/sftp.d.ts +64 -0
- package/dist/session/sftp.js +335 -0
- package/dist/session/shell-frame.d.ts +77 -0
- package/dist/session/shell-frame.js +199 -0
- package/dist/session/shell.d.ts +124 -0
- package/dist/session/shell.js +300 -0
- package/docs/CONFIGURATION.md +169 -0
- package/docs/INSTALLATION.md +164 -0
- package/docs/PROTOCOL.md +248 -0
- package/docs/TROUBLESHOOTING.md +177 -0
- package/package.json +79 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session persistence — mirrors `edge-client/src/session/persistence.rs`.
|
|
3
|
+
*
|
|
4
|
+
* B5 — PROCESS-restart persistence for the `(agent_id → pi session_id)` map.
|
|
5
|
+
*
|
|
6
|
+
* `SessionManager` previously kept this map in-memory only; when the edge-client
|
|
7
|
+
* daemon restarted (systemd/crash/deploy) the map was lost and pi was respawned
|
|
8
|
+
* fresh (history gone), even though pi's session files on disk are intact.
|
|
9
|
+
* We persist the map to `~/.config/edge-ai-agent/sessions.json` so a restarted
|
|
10
|
+
* edge-client can resume the same pi session via `--session-id`.
|
|
11
|
+
*
|
|
12
|
+
* ## Design
|
|
13
|
+
* - The inner map is guarded by a mutex.
|
|
14
|
+
* - `getOrInit` for an EXISTING agent_id is a fast map lookup (no factory, no I/O).
|
|
15
|
+
* - `save` runs only when a NEW id is assigned. The snapshot is cloned under
|
|
16
|
+
* the lock and the atomic write happens outside the lock so concurrent lookups
|
|
17
|
+
* are not stalled on I/O.
|
|
18
|
+
*
|
|
19
|
+
* ## Atomic write
|
|
20
|
+
* Write to `<path>.tmp` then rename over `<path>` (same-dir rename is atomic
|
|
21
|
+
* on POSIX and on Windows since Node 14+).
|
|
22
|
+
*
|
|
23
|
+
* ## Encrypted at rest (optional)
|
|
24
|
+
* Uses AES-256-GCM via `src/crypto/aes-gcm.ts` with the buffer_key from
|
|
25
|
+
* `loadBufferKey(settings)` when provided.
|
|
26
|
+
*/
|
|
27
|
+
/** Session persistence options. */
|
|
28
|
+
export interface PersistenceOptions {
|
|
29
|
+
/** Path to the sessions.json file. Defaults to `<configDir>/sessions.json`. */
|
|
30
|
+
readonly filePath?: string;
|
|
31
|
+
/** If true, persist to disk. If false, in-memory only (for tests). */
|
|
32
|
+
readonly persist?: boolean;
|
|
33
|
+
/** AES-256-GCM key for encryption at rest (optional). */
|
|
34
|
+
readonly encryptionKey?: Uint8Array;
|
|
35
|
+
}
|
|
36
|
+
export declare class SessionIdStore {
|
|
37
|
+
private readonly map;
|
|
38
|
+
private readonly filePath;
|
|
39
|
+
private readonly persist;
|
|
40
|
+
private readonly encryptionKey;
|
|
41
|
+
constructor(opts?: PersistenceOptions);
|
|
42
|
+
/**
|
|
43
|
+
* Get the session ID for `agent_id`, or initialize via `factory` + persist.
|
|
44
|
+
*
|
|
45
|
+
* Returns the existing ID if present (fast path: one map lookup, no factory).
|
|
46
|
+
* Otherwise calls `factory()` (which generates a UUID), inserts it, and
|
|
47
|
+
* persists to disk (best-effort).
|
|
48
|
+
*
|
|
49
|
+
* Mirrors Rust `SessionIdStore::get_or_init`.
|
|
50
|
+
*/
|
|
51
|
+
getOrInit(agentId: string, factory: () => string): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Atomic write to disk. Best-effort; logs a warning on error and never throws.
|
|
54
|
+
* No-op for the in-memory variant.
|
|
55
|
+
*
|
|
56
|
+
* Mirrors Rust `SessionIdStore::save`.
|
|
57
|
+
*/
|
|
58
|
+
save(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Load the map from disk. Missing/corrupt file → empty map.
|
|
61
|
+
* Mirrors Rust `SessionIdStore::load`.
|
|
62
|
+
*/
|
|
63
|
+
private loadFromDisk;
|
|
64
|
+
/**
|
|
65
|
+
* Check if an agent_id has a stored session ID.
|
|
66
|
+
*/
|
|
67
|
+
has(agentId: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Get the stored session ID for an agent_id, or undefined.
|
|
70
|
+
*/
|
|
71
|
+
get(agentId: string): string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Get the number of stored sessions.
|
|
74
|
+
*/
|
|
75
|
+
size(): number;
|
|
76
|
+
/**
|
|
77
|
+
* Create an in-memory only store (for tests). No file I/O.
|
|
78
|
+
*/
|
|
79
|
+
static inMemory(): SessionIdStore;
|
|
80
|
+
}
|
|
81
|
+
/** Session state to persist. Mirrors Rust session persistence fields. */
|
|
82
|
+
export interface SessionState {
|
|
83
|
+
/** Working directory for the session. */
|
|
84
|
+
readonly cwd: string;
|
|
85
|
+
/** Environment variables for the session. */
|
|
86
|
+
readonly env: Record<string, string>;
|
|
87
|
+
/** Agent ID associated with this session. */
|
|
88
|
+
readonly agentId: string;
|
|
89
|
+
/** Timestamp when the session was opened (Unix epoch ms). */
|
|
90
|
+
readonly openedAt: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Persist full session state to disk (encrypted at rest).
|
|
94
|
+
* Mirrors Rust persistence.rs session state persistence.
|
|
95
|
+
*
|
|
96
|
+
* Uses AES-256-GCM encryption when a key is provided.
|
|
97
|
+
*/
|
|
98
|
+
export declare function persistSessionState(state: SessionState, filePath: string, encryptionKey?: Uint8Array): void;
|
|
99
|
+
/**
|
|
100
|
+
* Load session state from disk (decrypted if key provided).
|
|
101
|
+
* Returns undefined if the file doesn't exist or is corrupt.
|
|
102
|
+
*/
|
|
103
|
+
export declare function loadSessionState(filePath: string, encryptionKey?: Uint8Array): SessionState | undefined;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session persistence — mirrors `edge-client/src/session/persistence.rs`.
|
|
3
|
+
*
|
|
4
|
+
* B5 — PROCESS-restart persistence for the `(agent_id → pi session_id)` map.
|
|
5
|
+
*
|
|
6
|
+
* `SessionManager` previously kept this map in-memory only; when the edge-client
|
|
7
|
+
* daemon restarted (systemd/crash/deploy) the map was lost and pi was respawned
|
|
8
|
+
* fresh (history gone), even though pi's session files on disk are intact.
|
|
9
|
+
* We persist the map to `~/.config/edge-ai-agent/sessions.json` so a restarted
|
|
10
|
+
* edge-client can resume the same pi session via `--session-id`.
|
|
11
|
+
*
|
|
12
|
+
* ## Design
|
|
13
|
+
* - The inner map is guarded by a mutex.
|
|
14
|
+
* - `getOrInit` for an EXISTING agent_id is a fast map lookup (no factory, no I/O).
|
|
15
|
+
* - `save` runs only when a NEW id is assigned. The snapshot is cloned under
|
|
16
|
+
* the lock and the atomic write happens outside the lock so concurrent lookups
|
|
17
|
+
* are not stalled on I/O.
|
|
18
|
+
*
|
|
19
|
+
* ## Atomic write
|
|
20
|
+
* Write to `<path>.tmp` then rename over `<path>` (same-dir rename is atomic
|
|
21
|
+
* on POSIX and on Windows since Node 14+).
|
|
22
|
+
*
|
|
23
|
+
* ## Encrypted at rest (optional)
|
|
24
|
+
* Uses AES-256-GCM via `src/crypto/aes-gcm.ts` with the buffer_key from
|
|
25
|
+
* `loadBufferKey(settings)` when provided.
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
import { configDir, atomicWriteFile } from '../config/settings.js';
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// SessionIdStore
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
export class SessionIdStore {
|
|
34
|
+
map = new Map();
|
|
35
|
+
filePath;
|
|
36
|
+
persist;
|
|
37
|
+
encryptionKey;
|
|
38
|
+
constructor(opts) {
|
|
39
|
+
this.persist = opts?.persist ?? true;
|
|
40
|
+
this.encryptionKey = opts?.encryptionKey;
|
|
41
|
+
if (this.persist) {
|
|
42
|
+
this.filePath = opts?.filePath ?? path.join(configDir(), 'sessions.json');
|
|
43
|
+
this.loadFromDisk();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this.filePath = '';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the session ID for `agent_id`, or initialize via `factory` + persist.
|
|
51
|
+
*
|
|
52
|
+
* Returns the existing ID if present (fast path: one map lookup, no factory).
|
|
53
|
+
* Otherwise calls `factory()` (which generates a UUID), inserts it, and
|
|
54
|
+
* persists to disk (best-effort).
|
|
55
|
+
*
|
|
56
|
+
* Mirrors Rust `SessionIdStore::get_or_init`.
|
|
57
|
+
*/
|
|
58
|
+
async getOrInit(agentId, factory) {
|
|
59
|
+
// Fast path: existing agent_id
|
|
60
|
+
const existing = this.map.get(agentId);
|
|
61
|
+
if (existing !== undefined) {
|
|
62
|
+
return existing;
|
|
63
|
+
}
|
|
64
|
+
// Slow path: new agent_id
|
|
65
|
+
const newId = factory();
|
|
66
|
+
let isNew = false;
|
|
67
|
+
// Double-check under a conceptual lock (JS is single-threaded for sync code)
|
|
68
|
+
const current = this.map.get(agentId);
|
|
69
|
+
if (current !== undefined) {
|
|
70
|
+
// A concurrent caller inserted first; reuse theirs
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
this.map.set(agentId, newId);
|
|
74
|
+
isNew = true;
|
|
75
|
+
// Only persist when we actually added a new id
|
|
76
|
+
if (isNew) {
|
|
77
|
+
await this.save();
|
|
78
|
+
}
|
|
79
|
+
return newId;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Atomic write to disk. Best-effort; logs a warning on error and never throws.
|
|
83
|
+
* No-op for the in-memory variant.
|
|
84
|
+
*
|
|
85
|
+
* Mirrors Rust `SessionIdStore::save`.
|
|
86
|
+
*/
|
|
87
|
+
async save() {
|
|
88
|
+
if (!this.persist)
|
|
89
|
+
return;
|
|
90
|
+
// Snapshot under the lock (in JS, just clone the map)
|
|
91
|
+
const snapshot = {};
|
|
92
|
+
for (const [k, v] of this.map) {
|
|
93
|
+
snapshot[k] = v;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const content = JSON.stringify(snapshot, null, 2);
|
|
97
|
+
atomicWriteFile(this.filePath, content);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
// Best-effort — log warning, never panic
|
|
101
|
+
console.warn(`[persistence] failed to persist sessions.json: ${String(e)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Load the map from disk. Missing/corrupt file → empty map.
|
|
106
|
+
* Mirrors Rust `SessionIdStore::load`.
|
|
107
|
+
*/
|
|
108
|
+
loadFromDisk() {
|
|
109
|
+
try {
|
|
110
|
+
const content = fs.readFileSync(this.filePath, 'utf8');
|
|
111
|
+
const parsed = JSON.parse(content);
|
|
112
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
113
|
+
if (typeof v === 'string') {
|
|
114
|
+
this.map.set(k, v);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
const err = e;
|
|
120
|
+
if (err.code === 'ENOENT') {
|
|
121
|
+
// First run / no persistence yet — normal, not a warning
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Corrupt or unreadable file — start with empty map
|
|
125
|
+
console.warn(`[persistence] sessions.json load failed: ${String(e)}; starting empty`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if an agent_id has a stored session ID.
|
|
130
|
+
*/
|
|
131
|
+
has(agentId) {
|
|
132
|
+
return this.map.has(agentId);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get the stored session ID for an agent_id, or undefined.
|
|
136
|
+
*/
|
|
137
|
+
get(agentId) {
|
|
138
|
+
return this.map.get(agentId);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get the number of stored sessions.
|
|
142
|
+
*/
|
|
143
|
+
size() {
|
|
144
|
+
return this.map.size;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create an in-memory only store (for tests). No file I/O.
|
|
148
|
+
*/
|
|
149
|
+
static inMemory() {
|
|
150
|
+
return new SessionIdStore({ persist: false });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Persist full session state to disk (encrypted at rest).
|
|
155
|
+
* Mirrors Rust persistence.rs session state persistence.
|
|
156
|
+
*
|
|
157
|
+
* Uses AES-256-GCM encryption when a key is provided.
|
|
158
|
+
*/
|
|
159
|
+
export function persistSessionState(state, filePath, encryptionKey) {
|
|
160
|
+
const plaintext = JSON.stringify(state);
|
|
161
|
+
if (encryptionKey !== undefined) {
|
|
162
|
+
// Encrypt at rest using AES-256-GCM
|
|
163
|
+
// Dynamic import to avoid circular dependency
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
165
|
+
const { encrypt } = require('../crypto/aes-gcm.js');
|
|
166
|
+
const encrypted = encrypt(encryptionKey, plaintext);
|
|
167
|
+
atomicWriteFile(filePath, encrypted);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
atomicWriteFile(filePath, plaintext);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Load session state from disk (decrypted if key provided).
|
|
175
|
+
* Returns undefined if the file doesn't exist or is corrupt.
|
|
176
|
+
*/
|
|
177
|
+
export function loadSessionState(filePath, encryptionKey) {
|
|
178
|
+
try {
|
|
179
|
+
const data = fs.readFileSync(filePath);
|
|
180
|
+
if (encryptionKey !== undefined) {
|
|
181
|
+
// Decrypt
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
183
|
+
const { decrypt } = require('../crypto/aes-gcm.js');
|
|
184
|
+
const result = decrypt(Buffer.from(data), encryptionKey);
|
|
185
|
+
return JSON.parse(result.plaintext);
|
|
186
|
+
}
|
|
187
|
+
// Plaintext JSON
|
|
188
|
+
const content = data.toString('utf8');
|
|
189
|
+
return JSON.parse(content);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi RPC integration — spawns the `pi` coding-agent CLI.
|
|
3
|
+
* Mirrors `edge-client/src/session/pi_rpc.rs`.
|
|
4
|
+
*
|
|
5
|
+
* Edge-client is a transport layer: forward prompt to pi stdin, forward
|
|
6
|
+
* ALL events from pi stdout to the buffer queue, handle buffering/ACK/reconnect.
|
|
7
|
+
*
|
|
8
|
+
* ## systemd-run wrapping (Phase 4 #3)
|
|
9
|
+
* When `systemd-run --user --scope` is available AND resource limits are set,
|
|
10
|
+
* pi is spawned under a per-agent transient scope that enforces MemoryMax /
|
|
11
|
+
* CPUQuota / TasksMax via Linux cgroups.
|
|
12
|
+
*
|
|
13
|
+
* ## Session continuity (GAP-017/P5)
|
|
14
|
+
* Each agent_id gets a stable pi session ID. On respawn, the same ID is reused
|
|
15
|
+
* so pi reopens its session file (conversation context survives).
|
|
16
|
+
*/
|
|
17
|
+
import * as child_process from 'node:child_process';
|
|
18
|
+
/** RPC command sent to pi stdin. Mirrors Rust RpcCommand. */
|
|
19
|
+
export interface RpcCommand {
|
|
20
|
+
/** Correlation id (= msg_id of cloud command). */
|
|
21
|
+
readonly id?: string;
|
|
22
|
+
/** Command type: "prompt" | "abort" | "set_model" | etc. */
|
|
23
|
+
readonly type: string;
|
|
24
|
+
/** Prompt message. */
|
|
25
|
+
readonly message?: string;
|
|
26
|
+
/** Streaming behavior: "steer" | "followUp". */
|
|
27
|
+
readonly streamingBehavior?: string;
|
|
28
|
+
/** Additional fields (provider, modelId, level, etc.). */
|
|
29
|
+
readonly [key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
/** Event received from pi stdout. Mirrors Rust PiEvent. */
|
|
32
|
+
export interface PiEvent {
|
|
33
|
+
/** Event type: "agent_start" | "agent_end" | "tool_execution_start" | etc. */
|
|
34
|
+
readonly type: string;
|
|
35
|
+
/** All other fields from pi (flatten passthrough). */
|
|
36
|
+
readonly [key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/** StreamMessage structure for forwarding pi events. Mirrors Rust StreamMessage. */
|
|
39
|
+
export interface StreamMessage {
|
|
40
|
+
readonly msg_id: string;
|
|
41
|
+
readonly machine_id: string;
|
|
42
|
+
readonly agent_id: string;
|
|
43
|
+
readonly type: string;
|
|
44
|
+
readonly payload: unknown;
|
|
45
|
+
readonly timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ResourcesConfig {
|
|
48
|
+
readonly memory_max_mb: number;
|
|
49
|
+
readonly cpu_quota_percent: number;
|
|
50
|
+
readonly tasks_max: number;
|
|
51
|
+
}
|
|
52
|
+
/** True if any resource limit is set (non-zero). Mirrors Rust has_limits(). */
|
|
53
|
+
export declare function hasLimits(config: ResourcesConfig): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Probe whether `systemd-run` is available. Caches the result (probe once
|
|
56
|
+
* per process). Returns false on non-Linux or if `systemd-run` isn't on PATH.
|
|
57
|
+
* Mirrors Rust `systemd_run_available()`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function systemdRunAvailable(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Reset the systemd-run probe cache (for testing).
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function resetSystemdRunCache(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Build the wrapped program + arg vector for a pi spawn, optionally under
|
|
67
|
+
* a per-agent `systemd-run --user --scope` transient unit.
|
|
68
|
+
*
|
|
69
|
+
* When `wrap` is false, returns `(piPath, originalArgs)` unchanged.
|
|
70
|
+
* When `wrap` is true, returns `("systemd-run", ["--user", "--scope", ...])`.
|
|
71
|
+
*
|
|
72
|
+
* Pure function — no I/O, fully unit-testable.
|
|
73
|
+
*/
|
|
74
|
+
export declare function wrapWithCgroup(piPath: string, originalArgs: readonly string[], agentId: string, resources: ResourcesConfig, wrap: boolean): {
|
|
75
|
+
program: string;
|
|
76
|
+
args: string[];
|
|
77
|
+
};
|
|
78
|
+
export interface PiProcessOptions {
|
|
79
|
+
readonly agentId: string;
|
|
80
|
+
readonly piPath: string;
|
|
81
|
+
readonly machineId: string;
|
|
82
|
+
readonly resources: ResourcesConfig;
|
|
83
|
+
/** Stable pi session ID for GAP-017/P5 session continuity. */
|
|
84
|
+
readonly resumeSessionId?: string;
|
|
85
|
+
/** Path override for spawning (default: child_process.spawn). */
|
|
86
|
+
readonly spawnFn?: typeof child_process.spawn;
|
|
87
|
+
}
|
|
88
|
+
export declare class PiProcess {
|
|
89
|
+
private readonly agentId;
|
|
90
|
+
private readonly machineId;
|
|
91
|
+
private readonly emitter;
|
|
92
|
+
private child;
|
|
93
|
+
private alive;
|
|
94
|
+
private streaming;
|
|
95
|
+
private readonly sessionId;
|
|
96
|
+
private parseErrors;
|
|
97
|
+
private currentMsgId;
|
|
98
|
+
constructor(opts: PiProcessOptions);
|
|
99
|
+
/**
|
|
100
|
+
* Spawn pi process. Mirrors Rust `PiProcess::spawn`.
|
|
101
|
+
* Returns a promise that resolves when the process is spawned.
|
|
102
|
+
*/
|
|
103
|
+
spawn(opts: PiProcessOptions): Promise<void>;
|
|
104
|
+
private handleStdoutLine;
|
|
105
|
+
private extractEventMsgId;
|
|
106
|
+
/** Send a command to pi stdin. */
|
|
107
|
+
private writeCommand;
|
|
108
|
+
/** Send a prompt command. Mirrors Rust PiProcess::send_prompt. */
|
|
109
|
+
sendPrompt(msgId: string, message: string, streamingBehavior?: string): void;
|
|
110
|
+
/** Send an abort command. Mirrors Rust PiProcess::send_abort. */
|
|
111
|
+
sendAbort(): void;
|
|
112
|
+
/** Forward extension_ui_response to pi. */
|
|
113
|
+
sendExtensionUIResponse(requestId: string, response: Record<string, unknown>): void;
|
|
114
|
+
/** Switch LLM model. Mirrors Rust PiProcess::set_model. */
|
|
115
|
+
setModel(provider: string, modelId: string): void;
|
|
116
|
+
/** Set thinking level. Mirrors Rust PiProcess::set_thinking_level. */
|
|
117
|
+
setThinkingLevel(level: string): void;
|
|
118
|
+
/** Manual context compaction. Mirrors Rust PiProcess::compact. */
|
|
119
|
+
compact(): void;
|
|
120
|
+
/** Get available models. Mirrors Rust PiProcess::get_available_models. */
|
|
121
|
+
getAvailableModels(id: string): void;
|
|
122
|
+
/** Is the agent currently streaming (between agent_start and agent_end)? */
|
|
123
|
+
isStreaming(): boolean;
|
|
124
|
+
/** Is the pi process still alive (stdout not EOF)? GAP-003 health check. */
|
|
125
|
+
isAlive(): boolean;
|
|
126
|
+
/** Number of unparseable stdout lines (Q4 GAP-020 health counter). */
|
|
127
|
+
getParseErrorCount(): number;
|
|
128
|
+
/** Captured pi session ID (GAP-017/P5). */
|
|
129
|
+
getPersistedSessionId(): string | undefined;
|
|
130
|
+
/** Register a handler for StreamMessage events. */
|
|
131
|
+
onEvent(handler: (msg: StreamMessage) => void): void;
|
|
132
|
+
/** Register a handler for exit events. */
|
|
133
|
+
onExit(handler: (agentId: string) => void): void;
|
|
134
|
+
/** Register a handler for stderr log lines. */
|
|
135
|
+
onStderr(handler: (line: string) => void): void;
|
|
136
|
+
/**
|
|
137
|
+
* Graceful shutdown: send abort, close stdin, kill child.
|
|
138
|
+
* Mirrors Rust PiProcess::terminate.
|
|
139
|
+
*/
|
|
140
|
+
terminate(): void;
|
|
141
|
+
private buildEnvContext;
|
|
142
|
+
}
|
|
143
|
+
export declare function promptCommand(id: string, message: string): RpcCommand;
|
|
144
|
+
export declare function promptWithStreamingCommand(id: string, message: string, behavior: string): RpcCommand;
|
|
145
|
+
export declare function abortCommand(): RpcCommand;
|
|
146
|
+
export declare function setModelCommand(provider: string, modelId: string): RpcCommand;
|
|
147
|
+
export declare function setThinkingLevelCommand(level: string): RpcCommand;
|
|
148
|
+
export declare function compactCommand(): RpcCommand;
|
|
149
|
+
export declare function getAvailableModelsCommand(id: string): RpcCommand;
|
|
150
|
+
/**
|
|
151
|
+
* Generate a pi-compatible session ID (RFC 4122 v4 UUID).
|
|
152
|
+
* Mirrors Rust `new_session_id()`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function newPiSessionId(): string;
|
|
155
|
+
/**
|
|
156
|
+
* Extract pi sessionId from event payload.
|
|
157
|
+
* Mirrors Rust `extract_session_id`.
|
|
158
|
+
*/
|
|
159
|
+
export declare function extractSessionId(eventType: string, payload: Record<string, unknown>): string | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Resolve msg_id for a forwarded event. Mirrors Rust `event_msg_id`.
|
|
162
|
+
* Prefers pi's own id in payload, falls back to currentMsgId.
|
|
163
|
+
*/
|
|
164
|
+
export declare function eventMsgId(payload: Record<string, unknown>, fallback: string): string;
|