@hienlh/ppm 0.9.34 → 0.9.36
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
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.36] - 2026-04-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Stream timeout during tool execution**: Per-event timeout increased from 60s to 180s. Tool calls (bash, file writes, memory saves) frequently exceed 60s between events, causing premature stream termination.
|
|
7
|
+
|
|
8
|
+
## [0.9.35] - 2026-04-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Stream hangs forever on stuck AI**: Replaced elapsed-time timeout with per-event `Promise.race` (60s per event). If `.next()` hangs, stream is terminated cleanly instead of blocking forever.
|
|
12
|
+
- **"Project not found" when no default configured**: Added `~/.ppm/bot/` fallback project so bot works immediately without project setup.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Identity onboarding**: `/start` now prompts new users for name, role, stack, and language preference when no identity memories exist.
|
|
16
|
+
|
|
3
17
|
## [0.9.34] - 2026-04-06
|
|
4
18
|
|
|
5
19
|
### Changed
|
package/package.json
CHANGED
|
@@ -184,10 +184,25 @@ class PPMBotService {
|
|
|
184
184
|
}
|
|
185
185
|
text += "\nSwitch: /project <name>";
|
|
186
186
|
} else {
|
|
187
|
-
text += "
|
|
187
|
+
text += "No projects configured — I'll use a default workspace.";
|
|
188
188
|
}
|
|
189
189
|
text += "\n\nJust send a message to start chatting, or /help for commands.";
|
|
190
190
|
await this.telegram!.sendMessage(Number(chatId), text);
|
|
191
|
+
|
|
192
|
+
// Identity onboarding: if no identity memories exist, ask user
|
|
193
|
+
const identityMemories = this.memory.recall("_global", "user identity name role");
|
|
194
|
+
if (identityMemories.length === 0) {
|
|
195
|
+
await this.telegram!.sendMessage(
|
|
196
|
+
Number(chatId),
|
|
197
|
+
"📝 <b>Quick intro?</b>\n\n" +
|
|
198
|
+
"I don't know much about you yet! Tell me:\n" +
|
|
199
|
+
"• Your name\n" +
|
|
200
|
+
"• What you work on (language, stack, role)\n" +
|
|
201
|
+
"• Preferred response language (English, Vietnamese, etc.)\n\n" +
|
|
202
|
+
"I'll remember your preferences for future chats.\n" +
|
|
203
|
+
"Or skip this and just start chatting!",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
private async cmdProject(chatId: string, args: string): Promise<void> {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
1
4
|
import { chatService } from "../chat.service.ts";
|
|
2
5
|
import { configService } from "../config.service.ts";
|
|
3
6
|
import {
|
|
@@ -29,9 +32,10 @@ export class PPMBotSessionManager {
|
|
|
29
32
|
return cached;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const input = projectName || this.getDefaultProject();
|
|
36
|
+
const resolvedProject = input
|
|
37
|
+
? this.resolveProject(input)
|
|
38
|
+
: this.getFallbackProject();
|
|
35
39
|
if (!resolvedProject) {
|
|
36
40
|
throw new Error(`Project not found: "${projectName || "(default)"}"`);
|
|
37
41
|
}
|
|
@@ -128,6 +132,13 @@ export class PPMBotSessionManager {
|
|
|
128
132
|
return cfg?.default_project || "";
|
|
129
133
|
}
|
|
130
134
|
|
|
135
|
+
/** Fallback project when nothing is configured: ~/.ppm/bot/ */
|
|
136
|
+
private getFallbackProject(): { name: string; path: string } {
|
|
137
|
+
const botDir = join(homedir(), ".ppm", "bot");
|
|
138
|
+
if (!existsSync(botDir)) mkdirSync(botDir, { recursive: true });
|
|
139
|
+
return { name: "bot", path: botDir };
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
private getDefaultProvider(): string {
|
|
132
143
|
const cfg = configService.get("clawbot") as PPMBotConfig | undefined;
|
|
133
144
|
return cfg?.default_provider || configService.get("ai").default_provider;
|
|
@@ -8,9 +8,37 @@ import {
|
|
|
8
8
|
|
|
9
9
|
const MAX_MSG_LEN = 4096;
|
|
10
10
|
const TYPING_REFRESH_MS = 4000;
|
|
11
|
-
const
|
|
11
|
+
const EVENT_TIMEOUT_MS = 180_000; // 3 min max wait per event (tool execution can be slow)
|
|
12
12
|
const PLACEHOLDER = "\u2026"; // ellipsis
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Wrap an async iterable with per-event timeout.
|
|
16
|
+
* If .next() doesn't resolve within timeoutMs, yields a timeout error.
|
|
17
|
+
*/
|
|
18
|
+
async function* withEventTimeout<T>(
|
|
19
|
+
iterable: AsyncIterable<T>,
|
|
20
|
+
timeoutMs: number,
|
|
21
|
+
): AsyncGenerator<T> {
|
|
22
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
23
|
+
try {
|
|
24
|
+
while (true) {
|
|
25
|
+
const result = await Promise.race([
|
|
26
|
+
iterator.next(),
|
|
27
|
+
new Promise<{ done: true; value: undefined; timedOut: true }>((resolve) =>
|
|
28
|
+
setTimeout(() => resolve({ done: true, value: undefined, timedOut: true }), timeoutMs),
|
|
29
|
+
),
|
|
30
|
+
]);
|
|
31
|
+
if ("timedOut" in result) {
|
|
32
|
+
throw new Error("No response within 3 minutes");
|
|
33
|
+
}
|
|
34
|
+
if (result.done) break;
|
|
35
|
+
yield result.value;
|
|
36
|
+
}
|
|
37
|
+
} finally {
|
|
38
|
+
iterator.return?.();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
export interface StreamConfig {
|
|
15
43
|
showToolCalls: boolean;
|
|
16
44
|
showThinking: boolean;
|
|
@@ -120,14 +148,9 @@ export async function streamToTelegram(
|
|
|
120
148
|
await telegram.editMessage(chatId, currentMsgId, html);
|
|
121
149
|
};
|
|
122
150
|
|
|
123
|
-
// Process event stream with timeout
|
|
124
|
-
const streamStart = Date.now();
|
|
151
|
+
// Process event stream with per-event timeout
|
|
125
152
|
try {
|
|
126
|
-
for await (const event of events) {
|
|
127
|
-
if (Date.now() - streamStart > STREAM_TIMEOUT_MS) {
|
|
128
|
-
appendHtml(segments, "\n\n⏱️ <i>Response timed out.</i>");
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
153
|
+
for await (const event of withEventTimeout(events, EVENT_TIMEOUT_MS)) {
|
|
131
154
|
await refreshTyping();
|
|
132
155
|
|
|
133
156
|
switch (event.type) {
|