@pinixai/core 0.5.1 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinixai/core",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Clip framework for Pinix — define once, run as CLI / MCP / Pinix bridge",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
package/src/clip.ts CHANGED
@@ -25,6 +25,14 @@ export abstract class Clip {
25
25
  dependencies: Record<string, { package: string; version: string }> = {};
26
26
  entities: Record<string, z.ZodObject<any>> = {};
27
27
 
28
+ /**
29
+ * How long (ms) the process stays alive after the last invoke completes.
30
+ * - `30_000` (default): exit after 30s idle
31
+ * - `0`: exit immediately after each invoke
32
+ * - `Infinity`: never exit (persistent)
33
+ */
34
+ idleTimeout: number = 30_000;
35
+
28
36
  protected readonly commands = new Map<string, HandlerDef>();
29
37
  protected readonly commandDescriptions = new Map<string, string>();
30
38
 
package/src/ipc.ts CHANGED
@@ -176,10 +176,32 @@ export async function serveIPC(clip: Clip): Promise<void> {
176
176
  const manifest = createIPCManifest(clip);
177
177
  send({ type: "register", manifest });
178
178
 
179
+ // Idle timeout management
180
+ const idleTimeout = (clip as Clip & { idleTimeout?: number }).idleTimeout ?? 30_000;
181
+ let inflight = 0;
182
+ let idleTimer: ReturnType<typeof setTimeout> | null = null;
183
+
184
+ function clearIdleTimer(): void {
185
+ if (idleTimer !== null) {
186
+ clearTimeout(idleTimer);
187
+ idleTimer = null;
188
+ }
189
+ }
190
+
191
+ function resetIdleTimer(): void {
192
+ clearIdleTimer();
193
+ if (inflight > 0) return;
194
+ if (!Number.isFinite(idleTimeout) || idleTimeout < 0) return;
195
+ idleTimer = setTimeout(() => process.exit(0), idleTimeout);
196
+ }
197
+
179
198
  // Read messages from stdin
180
199
  const reader = createLineReader(process.stdin);
181
200
  const commands = clip.getCommands();
182
201
 
202
+ // Start idle timer after registration
203
+ resetIdleTimer();
204
+
183
205
  for await (const line of reader) {
184
206
  let msg: BaseMessage;
185
207
  try {
@@ -200,8 +222,13 @@ export async function serveIPC(clip: Clip): Promise<void> {
200
222
  break;
201
223
 
202
224
  case "invoke": {
225
+ clearIdleTimer();
226
+ inflight++;
203
227
  const inv = msg as InvokeMessage;
204
- handleInvoke(inv, commands);
228
+ handleInvoke(inv, commands).finally(() => {
229
+ inflight--;
230
+ resetIdleTimer();
231
+ });
205
232
  break;
206
233
  }
207
234
 
@@ -247,6 +274,7 @@ export async function serveIPC(clip: Clip): Promise<void> {
247
274
  }
248
275
 
249
276
  // Clean up pending invokes on EOF
277
+ clearIdleTimer();
250
278
  for (const [id, pending] of pendingInvokes) {
251
279
  pending.reject(new Error("IPC connection closed"));
252
280
  }