@hienlh/ppm 0.9.2 → 0.9.4
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 +15 -0
- package/dist/web/assets/{browser-tab-CjUzlPYv.js → browser-tab-B8EBpCT6.js} +1 -1
- package/dist/web/assets/chat-tab-DVuWNtNl.js +8 -0
- package/dist/web/assets/{code-editor-aQQZUc2m.js → code-editor-Bre-_HLS.js} +1 -1
- package/dist/web/assets/{database-viewer-ChyP1N3c.js → database-viewer-As9Pwu58.js} +1 -1
- package/dist/web/assets/{diff-viewer-ktwO5JbX.js → diff-viewer-DTw_Cqpy.js} +1 -1
- package/dist/web/assets/{extension-webview-Bx1TlP6q.js → extension-webview-CgobD-e5.js} +1 -1
- package/dist/web/assets/{git-graph-BIrGMX6e.js → git-graph-CA1TsLfJ.js} +1 -1
- package/dist/web/assets/index-Dx21KBME.css +2 -0
- package/dist/web/assets/{index-C6KLr58u.js → index-iaC9D6YI.js} +4 -4
- package/dist/web/assets/keybindings-store-BzWEhrZs.js +1 -0
- package/dist/web/assets/{markdown-renderer-A7J2gdKT.js → markdown-renderer-zGERfKXC.js} +1 -1
- package/dist/web/assets/{postgres-viewer-C9-Acry_.js → postgres-viewer-q-a11Zs_.js} +1 -1
- package/dist/web/assets/{settings-tab-C17exmRv.js → settings-tab-9ISb6u9A.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Dr5oWCWA.js → sqlite-viewer-CEfqfnpC.js} +1 -1
- package/dist/web/assets/{terminal-tab-CpyKvyfC.js → terminal-tab-BGthbegR.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BjPAik5w.js → use-monaco-theme-0QrlcbfM.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/project-roadmap.md +3 -3
- package/docs/streaming-input-guide.md +267 -0
- package/package.json +1 -1
- package/packages/ext-database/package.json +9 -2
- package/packages/ext-database/src/connection-tree.ts +64 -5
- package/packages/ext-database/src/extension.ts +234 -2
- package/packages/ext-database/src/query-panel.ts +29 -16
- package/packages/ext-database/src/table-viewer-panel.ts +16 -6
- package/snapshot-state.md +1526 -0
- package/src/providers/claude-agent-sdk.ts +16 -11
- package/src/web/components/chat/chat-tab.tsx +1 -0
- package/src/web/components/chat/message-list.tsx +8 -4
- package/src/web/components/layout/mobile-nav.tsx +46 -42
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/.claude.bak/agent-memory/tester/MEMORY.md +0 -3
- package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +0 -32
- package/dist/web/assets/chat-tab-moB4W7-w.js +0 -8
- package/dist/web/assets/index-DpBKDbIW.css +0 -2
- package/dist/web/assets/keybindings-store-D3Y5c5uS.js +0 -1
|
@@ -902,12 +902,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
902
902
|
console.log(`[sdk] session=${sessionId} OAuth token refreshed for ${account.id} (${label}) — retrying`);
|
|
903
903
|
yield { type: "account_retry" as const, reason: "Token refreshed", accountId: refreshedAccount.id, accountLabel: label };
|
|
904
904
|
const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
|
|
905
|
-
// Close failed query and old channel, create new channel + query with refreshed token
|
|
905
|
+
// Close failed query and old channel, create new channel + query with refreshed token.
|
|
906
|
+
// Resume existing SDK session so conversation context is preserved.
|
|
906
907
|
streamCtrl.done();
|
|
907
908
|
q.close();
|
|
908
909
|
const { generator: authRetryGen, controller: authRetryCtrl } = createMessageChannel();
|
|
909
|
-
|
|
910
|
-
const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: retryEnv };
|
|
910
|
+
const retryOpts = { ...queryOptions, sessionId: undefined, resume: sdkId, env: retryEnv };
|
|
911
911
|
const rq = query({
|
|
912
912
|
prompt: authRetryGen,
|
|
913
913
|
options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
|
|
@@ -953,13 +953,13 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
953
953
|
}
|
|
954
954
|
yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
|
|
955
955
|
await new Promise((r) => setTimeout(r, backoff));
|
|
956
|
-
// Close failed query and recreate with (potentially new) account env
|
|
956
|
+
// Close failed query and recreate with (potentially new) account env.
|
|
957
|
+
// Resume existing SDK session so conversation context is preserved.
|
|
957
958
|
streamCtrl.done();
|
|
958
959
|
q.close();
|
|
959
960
|
const rlRetryEnv = this.buildQueryEnv(meta.projectPath, account);
|
|
960
961
|
const { generator: rlRetryGen, controller: rlRetryCtrl } = createMessageChannel();
|
|
961
|
-
|
|
962
|
-
const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: rlRetryEnv };
|
|
962
|
+
const retryOpts = { ...queryOptions, sessionId: undefined, resume: sdkId, env: rlRetryEnv };
|
|
963
963
|
const rq = query({
|
|
964
964
|
prompt: rlRetryGen,
|
|
965
965
|
options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
|
|
@@ -1050,12 +1050,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
|
|
1052
1052
|
await new Promise((r) => setTimeout(r, backoff));
|
|
1053
|
+
// Resume existing SDK session so conversation context is preserved.
|
|
1053
1054
|
streamCtrl.done();
|
|
1054
1055
|
q.close();
|
|
1055
1056
|
const rlRetryEnv = this.buildQueryEnv(meta.projectPath, account);
|
|
1056
1057
|
const { generator: rlRetryGen, controller: rlRetryCtrl } = createMessageChannel();
|
|
1057
|
-
|
|
1058
|
-
const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: rlRetryEnv };
|
|
1058
|
+
const retryOpts = { ...queryOptions, sessionId: undefined, resume: sdkId, env: rlRetryEnv };
|
|
1059
1059
|
const rq = query({
|
|
1060
1060
|
prompt: rlRetryGen,
|
|
1061
1061
|
options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
|
|
@@ -1068,7 +1068,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1068
1068
|
yield { type: "error", message: `Rate limited. Retried ${MAX_RATE_LIMIT_RETRIES} times without success.` };
|
|
1069
1069
|
continue;
|
|
1070
1070
|
} else if (errCode === 401) {
|
|
1071
|
-
// Refresh token and retry
|
|
1071
|
+
// Refresh token and retry — resume existing SDK session to preserve context
|
|
1072
1072
|
if (!authRetried) {
|
|
1073
1073
|
authRetried = true;
|
|
1074
1074
|
try {
|
|
@@ -1078,12 +1078,17 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1078
1078
|
const label = refreshedAccount.label ?? refreshedAccount.email ?? "Unknown";
|
|
1079
1079
|
console.log(`[sdk] 401 in result on account ${account.id} (${label}) — token refreshed, retrying`);
|
|
1080
1080
|
yield { type: "account_retry" as const, reason: "Token refreshed", accountId: refreshedAccount.id, accountLabel: label };
|
|
1081
|
+
// Resume existing SDK session so conversation context is preserved.
|
|
1082
|
+
streamCtrl.done();
|
|
1083
|
+
q.close();
|
|
1081
1084
|
const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
|
|
1082
|
-
const
|
|
1085
|
+
const { generator: authRetryGen2, controller: authRetryCtrl2 } = createMessageChannel();
|
|
1086
|
+
const retryOpts = { ...queryOptions, sessionId: undefined, resume: sdkId, env: retryEnv };
|
|
1083
1087
|
const rq = query({
|
|
1084
|
-
prompt:
|
|
1088
|
+
prompt: authRetryGen2,
|
|
1085
1089
|
options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
|
|
1086
1090
|
});
|
|
1091
|
+
this.streamingSessions.set(sessionId, { meta, query: rq, controller: authRetryCtrl2 });
|
|
1087
1092
|
this.activeQueries.set(sessionId, rq);
|
|
1088
1093
|
eventSource = rq;
|
|
1089
1094
|
continue retryLoop;
|
|
@@ -341,6 +341,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
341
341
|
connectingElapsed={connectingElapsed}
|
|
342
342
|
projectName={projectName}
|
|
343
343
|
onFork={!isStreaming ? handleFork : undefined}
|
|
344
|
+
onSelectSession={handleSelectSession}
|
|
344
345
|
/>
|
|
345
346
|
|
|
346
347
|
{/* Bottom toolbar */}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
XCircle,
|
|
27
27
|
ExternalLink,
|
|
28
28
|
} from "lucide-react";
|
|
29
|
+
import { ChatWelcome } from "./chat-welcome";
|
|
29
30
|
import { QuestionCard } from "./question-card";
|
|
30
31
|
import type { Question } from "./question-card";
|
|
31
32
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -44,6 +45,8 @@ interface MessageListProps {
|
|
|
44
45
|
projectName?: string;
|
|
45
46
|
/** Called when user clicks Fork/Rewind — opens new forked chat tab */
|
|
46
47
|
onFork?: (userMessage: string, messageId?: string) => void;
|
|
48
|
+
/** Called when user selects a recent session from the welcome screen */
|
|
49
|
+
onSelectSession?: (session: import("../../../types/chat").SessionInfo) => void;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export function MessageList({
|
|
@@ -53,6 +56,7 @@ export function MessageList({
|
|
|
53
56
|
onApprovalResponse,
|
|
54
57
|
isStreaming,
|
|
55
58
|
phase,
|
|
59
|
+
onSelectSession,
|
|
56
60
|
connectingElapsed,
|
|
57
61
|
projectName,
|
|
58
62
|
onFork,
|
|
@@ -70,10 +74,10 @@ export function MessageList({
|
|
|
70
74
|
|
|
71
75
|
if (messages.length === 0 && !isStreaming) {
|
|
72
76
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
<ChatWelcome
|
|
78
|
+
projectName={projectName || ""}
|
|
79
|
+
onSelectSession={onSelectSession || (() => {})}
|
|
80
|
+
/>
|
|
77
81
|
);
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Terminal, MessageSquare, GitBranch, Database,
|
|
4
4
|
FileDiff, FileCode, Settings, Menu, X, ArrowLeft, ArrowRight, SplitSquareVertical, MoveVertical, Layers, Plus,
|
|
5
|
-
|
|
5
|
+
ChevronRight, Globe, Puzzle,
|
|
6
6
|
} from "lucide-react";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
8
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
@@ -42,7 +42,7 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
42
42
|
const mobileScrollRef = useRef<HTMLDivElement>(null);
|
|
43
43
|
const prevTabCount = useRef(tabs.length);
|
|
44
44
|
const notifications = useNotificationStore((s) => s.notifications);
|
|
45
|
-
const { canScrollLeft, canScrollRight,
|
|
45
|
+
const { canScrollLeft, canScrollRight, scrollRight: doScrollRight } =
|
|
46
46
|
useTabOverflow(mobileScrollRef);
|
|
47
47
|
const hiddenUnread = getHiddenUnreadDirection(mobileScrollRef.current, tabRefs.current as Map<string, HTMLElement>, tabs, notifications);
|
|
48
48
|
|
|
@@ -114,21 +114,51 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
114
114
|
return (
|
|
115
115
|
<nav className="fixed bottom-0 left-0 right-0 md:hidden bg-background border-t border-border z-40 select-none">
|
|
116
116
|
<div className="flex items-center h-12">
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
{/* Fixed section: Menu + Project + Add — curved right edge */}
|
|
118
|
+
<div className={cn(
|
|
119
|
+
"flex items-center shrink-0 bg-background relative z-10 transition-all duration-200",
|
|
120
|
+
canScrollLeft ? "rounded-r-2xl shadow-[6px_0_12px_-4px_rgba(0,0,0,0.12)]" : "border-r border-border",
|
|
121
|
+
)}>
|
|
122
|
+
<button onClick={onMenuPress} className="flex items-center justify-center size-12 shrink-0 text-text-secondary">
|
|
123
|
+
<Menu className="size-5" />
|
|
124
|
+
</button>
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
<div className="w-px self-stretch bg-border shrink-0" />
|
|
127
|
+
|
|
128
|
+
<button
|
|
129
|
+
onClick={onProjectsPress}
|
|
130
|
+
className="flex items-center justify-center size-12 shrink-0 text-text-secondary"
|
|
131
|
+
title="Switch project"
|
|
132
|
+
>
|
|
133
|
+
{activeInitials ? (
|
|
134
|
+
<div
|
|
135
|
+
className="size-7 rounded-full flex items-center justify-center text-[10px] font-bold text-white"
|
|
136
|
+
style={{ background: activeColor }}
|
|
137
|
+
>
|
|
138
|
+
{activeInitials}
|
|
139
|
+
</div>
|
|
140
|
+
) : (
|
|
141
|
+
<Layers className="size-5" />
|
|
142
|
+
)}
|
|
143
|
+
</button>
|
|
144
|
+
|
|
145
|
+
<div className="w-px self-stretch bg-border shrink-0" />
|
|
146
|
+
|
|
147
|
+
<button
|
|
148
|
+
onClick={() => openCommandPalette()}
|
|
149
|
+
className={cn(
|
|
150
|
+
"flex items-center justify-center shrink-0 text-text-secondary gap-1.5 h-12",
|
|
151
|
+
tabs.length === 0 ? "px-4" : "w-12",
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
<Plus className="size-4" />
|
|
155
|
+
{tabs.length === 0 && <span className="text-xs">New Tab</span>}
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Tab list — overlaps under curved edge so tabs slide beneath it */}
|
|
160
|
+
<div className="flex-1 min-w-0 relative flex items-center h-12 -ml-4">
|
|
161
|
+
<div ref={mobileScrollRef} className="flex-1 min-w-0 flex items-center h-12 overflow-x-auto scrollbar-none pl-4">
|
|
132
162
|
{tabs.map((tab) => {
|
|
133
163
|
const Icon = TAB_ICONS[tab.type];
|
|
134
164
|
const isActive = tab.id === activeTabId;
|
|
@@ -179,32 +209,6 @@ export function MobileNav({ onMenuPress, onProjectsPress }: MobileNavProps) {
|
|
|
179
209
|
</button>
|
|
180
210
|
)}
|
|
181
211
|
</div>
|
|
182
|
-
|
|
183
|
-
{/* Add tab — opens command palette */}
|
|
184
|
-
<button
|
|
185
|
-
onClick={() => openCommandPalette()}
|
|
186
|
-
className="flex items-center justify-center size-12 shrink-0 border-t-2 border-transparent text-text-secondary"
|
|
187
|
-
>
|
|
188
|
-
<Plus className="size-4" />
|
|
189
|
-
</button>
|
|
190
|
-
|
|
191
|
-
{/* Projects button (rightmost) */}
|
|
192
|
-
<button
|
|
193
|
-
onClick={onProjectsPress}
|
|
194
|
-
className="flex items-center justify-center size-12 shrink-0 text-text-secondary border-l border-border"
|
|
195
|
-
title="Switch project"
|
|
196
|
-
>
|
|
197
|
-
{activeInitials ? (
|
|
198
|
-
<div
|
|
199
|
-
className="size-7 rounded-full flex items-center justify-center text-[10px] font-bold text-white"
|
|
200
|
-
style={{ background: activeColor }}
|
|
201
|
-
>
|
|
202
|
-
{activeInitials}
|
|
203
|
-
</div>
|
|
204
|
-
) : (
|
|
205
|
-
<Layers className="size-5" />
|
|
206
|
-
)}
|
|
207
|
-
</button>
|
|
208
212
|
</div>
|
|
209
213
|
|
|
210
214
|
{/* New tab action sheet */}
|