@hienlh/ppm 0.9.69 → 0.9.71
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 +19 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/providers/claude-agent-sdk.ts +45 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.71] - 2026-04-09
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **CLI db commands**: `ppm db` subcommands (list, add, remove, test, tables, schema, data, query) were implemented but not registered in CLI entry point — now accessible.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Unit tests for CLI db command registration and option structure.
|
|
10
|
+
- Unit tests for `isReadOnlyQuery` utility (18 cases including CTE attack patterns).
|
|
11
|
+
- Unit tests for database routes: readonly enforcement, validation, edge cases (+30 tests).
|
|
12
|
+
- Unit tests for db.service connection CRUD: insert, resolve, update, delete, encryption round-trip (+17 tests).
|
|
13
|
+
|
|
14
|
+
## [0.9.70] - 2026-04-09
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **SDK subprocess crash on resume**: Resumed sessions with existing JSONL files would crash (exit code 1) because `--session-id` was used instead of `--resume`. Now always uses `--resume` for resumed sessions.
|
|
18
|
+
- **Session lookup**: Use targeted `getSessionInfo()` with correct project dir instead of listing all sessions.
|
|
19
|
+
- **SDK stderr capture**: Subprocess stderr is now logged on crash for diagnostics.
|
|
20
|
+
- **Thinking budget option**: Fixed `thinkingBudgetTokens` → `maxThinkingTokens` (correct SDK option name).
|
|
21
|
+
|
|
3
22
|
## [0.9.69] - 2026-04-09
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -148,6 +148,9 @@ registerCloudCommands(program);
|
|
|
148
148
|
const { registerExtCommands } = await import("./cli/commands/ext-cmd.ts");
|
|
149
149
|
registerExtCommands(program);
|
|
150
150
|
|
|
151
|
+
const { registerDbCommands } = await import("./cli/commands/db-cmd.ts");
|
|
152
|
+
registerDbCommands(program);
|
|
153
|
+
|
|
151
154
|
const { registerBotCommands } = await import("./cli/commands/bot-cmd.ts");
|
|
152
155
|
registerBotCommands(program);
|
|
153
156
|
|
|
@@ -280,18 +280,33 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
280
280
|
// Restore project_path from DB so resumed sessions can find JSONL
|
|
281
281
|
const dbProjectPath = getSessionProjectPath(sessionId) ?? undefined;
|
|
282
282
|
|
|
283
|
+
// Try targeted lookup first (searches all project dirs), then fall back to list scan
|
|
283
284
|
try {
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
const lookupId = mappedSdkId ?? sessionId;
|
|
286
|
+
const info = await sdkGetSessionInfo(lookupId, { dir: dbProjectPath });
|
|
287
|
+
if (!info && mappedSdkId) {
|
|
288
|
+
// Try the original PPM session ID as well
|
|
289
|
+
const info2 = await sdkGetSessionInfo(sessionId, { dir: dbProjectPath });
|
|
290
|
+
if (info2) {
|
|
291
|
+
const meta: Session = {
|
|
292
|
+
id: sessionId,
|
|
293
|
+
providerId: this.id,
|
|
294
|
+
title: info2.customTitle ?? info2.summary ?? "Resumed Chat",
|
|
295
|
+
projectPath: dbProjectPath,
|
|
296
|
+
createdAt: new Date(info2.lastModified).toISOString(),
|
|
297
|
+
};
|
|
298
|
+
this.activeSessions.set(sessionId, meta);
|
|
299
|
+
this.messageCount.set(sessionId, 1);
|
|
300
|
+
return meta;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (info) {
|
|
289
304
|
const meta: Session = {
|
|
290
305
|
id: sessionId,
|
|
291
306
|
providerId: this.id,
|
|
292
|
-
title:
|
|
307
|
+
title: info.customTitle ?? info.summary ?? "Resumed Chat",
|
|
293
308
|
projectPath: dbProjectPath,
|
|
294
|
-
createdAt: new Date(
|
|
309
|
+
createdAt: new Date(info.lastModified).toISOString(),
|
|
295
310
|
};
|
|
296
311
|
this.activeSessions.set(sessionId, meta);
|
|
297
312
|
this.messageCount.set(sessionId, 1);
|
|
@@ -301,8 +316,11 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
301
316
|
// SDK not available
|
|
302
317
|
}
|
|
303
318
|
|
|
304
|
-
// Session not found in SDK
|
|
305
|
-
//
|
|
319
|
+
// Session not found in SDK list — it may still have a JSONL on disk
|
|
320
|
+
// (sdkListSessions may not search the correct project directory).
|
|
321
|
+
// Use messageCount=1 so sendMessage uses --resume instead of --session-id.
|
|
322
|
+
// --resume gracefully fails if no JSONL exists, while --session-id crashes
|
|
323
|
+
// when a JSONL file for the same ID is already present on disk.
|
|
306
324
|
const meta: Session = {
|
|
307
325
|
id: sessionId,
|
|
308
326
|
providerId: this.id,
|
|
@@ -311,7 +329,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
311
329
|
createdAt: new Date().toISOString(),
|
|
312
330
|
};
|
|
313
331
|
this.activeSessions.set(sessionId, meta);
|
|
314
|
-
this.messageCount.set(sessionId,
|
|
332
|
+
this.messageCount.set(sessionId, 1);
|
|
315
333
|
return meta;
|
|
316
334
|
}
|
|
317
335
|
|
|
@@ -677,6 +695,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
677
695
|
const mcpServers = mcpConfigService.list();
|
|
678
696
|
const hasMcp = Object.keys(mcpServers).length > 0;
|
|
679
697
|
|
|
698
|
+
// Buffer subprocess stderr for crash diagnostics
|
|
699
|
+
let stderrBuffer = "";
|
|
700
|
+
const stderrCallback = (chunk: string) => {
|
|
701
|
+
stderrBuffer += chunk;
|
|
702
|
+
// Keep only last 2KB to avoid unbounded growth
|
|
703
|
+
if (stderrBuffer.length > 2048) stderrBuffer = stderrBuffer.slice(-2048);
|
|
704
|
+
};
|
|
705
|
+
|
|
680
706
|
const queryOptions: Record<string, any> = {
|
|
681
707
|
// On Windows, child_process.spawn("bun") fails with ENOENT — force node
|
|
682
708
|
...(process.platform === "win32" && { executable: "node" }),
|
|
@@ -697,9 +723,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
697
723
|
maxTurns: providerConfig.max_turns ?? 1000,
|
|
698
724
|
...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
|
|
699
725
|
...(providerConfig.thinking_budget_tokens != null && {
|
|
700
|
-
|
|
726
|
+
maxThinkingTokens: providerConfig.thinking_budget_tokens,
|
|
701
727
|
}),
|
|
702
728
|
includePartialMessages: true,
|
|
729
|
+
stderr: stderrCallback,
|
|
703
730
|
};
|
|
704
731
|
|
|
705
732
|
// Crash retry: if subprocess exits with non-zero code before producing events,
|
|
@@ -1391,7 +1418,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1391
1418
|
break crashRetryLoop; // Normal completion — exit crash retry loop
|
|
1392
1419
|
} catch (crashErr) {
|
|
1393
1420
|
const crashMsg = (crashErr as Error).message ?? String(crashErr);
|
|
1394
|
-
|
|
1421
|
+
const stderrInfo = stderrBuffer.trim() ? ` stderr: ${stderrBuffer.trim().slice(-500)}` : "";
|
|
1422
|
+
console.error(`[sdk] session=${sessionId} cwd=${meta.projectPath} error: ${crashMsg}${stderrInfo}`);
|
|
1395
1423
|
|
|
1396
1424
|
// Clean up crashed subprocess before retry or error
|
|
1397
1425
|
this.activeQueries.delete(sessionId);
|
|
@@ -1404,12 +1432,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1404
1432
|
} else if (crashMsg.includes("exited with code") && crashRetryCount < MAX_CRASH_RETRIES) {
|
|
1405
1433
|
// Subprocess crashed — auto-retry once before surfacing the error
|
|
1406
1434
|
crashRetryCount++;
|
|
1407
|
-
console.warn(`[sdk] session=${sessionId} subprocess crashed: ${crashMsg} — auto-retrying (attempt ${crashRetryCount}/${MAX_CRASH_RETRIES})`);
|
|
1435
|
+
console.warn(`[sdk] session=${sessionId} subprocess crashed: ${crashMsg} — auto-retrying (attempt ${crashRetryCount}/${MAX_CRASH_RETRIES})${stderrInfo}`);
|
|
1436
|
+
stderrBuffer = ""; // Reset for retry
|
|
1408
1437
|
await new Promise((r) => setTimeout(r, 1000));
|
|
1409
1438
|
continue crashRetryLoop;
|
|
1410
1439
|
} else if (crashMsg.includes("exited with code")) {
|
|
1411
|
-
console.warn(`[sdk] session=${sessionId} subprocess crashed after retry: ${crashMsg}`);
|
|
1412
|
-
|
|
1440
|
+
console.warn(`[sdk] session=${sessionId} subprocess crashed after retry: ${crashMsg}${stderrInfo}`);
|
|
1441
|
+
const userHint = stderrInfo ? ` (${stderrBuffer.trim().slice(-200)})` : "";
|
|
1442
|
+
yield { type: "error", message: `SDK subprocess crashed.${userHint} Send another message to auto-recover.` };
|
|
1413
1443
|
} else {
|
|
1414
1444
|
yield { type: "error", message: `SDK error: ${crashMsg}` };
|
|
1415
1445
|
}
|