@hienlh/ppm 0.9.80 → 0.9.82
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/.opencode/.env.example +98 -0
- package/.opencode/skills/ads-management/scripts/.env.example +13 -0
- package/.opencode/skills/ai-multimodal/.env.example +230 -0
- package/.opencode/skills/cip-design/.env.example +6 -0
- package/.opencode/skills/devops/.env.example +76 -0
- package/.opencode/skills/docs-seeker/.env.example +15 -0
- package/.opencode/skills/elevenlabs/.env.example +3 -0
- package/.opencode/skills/marketing-dashboard/.env.example +15 -0
- package/.opencode/skills/marketing-dashboard/app/.env.example +2 -0
- package/.opencode/skills/marketing-dashboard/server/.env.example +2 -0
- package/.opencode/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/.opencode/skills/mcp-management/scripts/dist/cli.js +160 -0
- package/.opencode/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/.opencode/skills/payment-integration/scripts/.env.example +20 -0
- package/.opencode/skills/sequential-thinking/.env.example +8 -0
- package/.repomixignore +22 -0
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +12 -0
- package/assets/skills/ppm-guide/SKILL.md +61 -0
- package/bun.lock +9 -1
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/chat-tab-bS86TsT5.js +10 -0
- package/dist/web/assets/{code-editor-BFe-hnpF.js → code-editor-BaNaQ33b.js} +1 -1
- package/dist/web/assets/{database-viewer-BeY2V5QI.js → database-viewer-C5MVw8cJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-D6xzs8PP.js → diff-viewer-CUbFMWVo.js} +1 -1
- package/dist/web/assets/{extension-webview-Cd1XYFXO.js → extension-webview-CwGufYEP.js} +1 -1
- package/dist/web/assets/{git-graph-D2XXpiMQ.js → git-graph-BD7A7MLo.js} +1 -1
- package/dist/web/assets/index-BYXjCNlK.css +2 -0
- package/dist/web/assets/index-CpzkPHOC.js +30 -0
- package/dist/web/assets/keybindings-store-DsaANvBz.js +1 -0
- package/dist/web/assets/markdown-renderer-C19IsITh.js +326 -0
- package/dist/web/assets/{port-forwarding-tab-B5rj_I66.js → port-forwarding-tab-BF79F1iL.js} +1 -1
- package/dist/web/assets/{postgres-viewer-DnlqzOnm.js → postgres-viewer-_nYiO_wp.js} +1 -1
- package/dist/web/assets/{settings-tab-CNZpuPD3.js → settings-tab-C1SQMbSu.js} +1 -1
- package/dist/web/assets/{sql-query-editor-Df2kzbPj.js → sql-query-editor-6OFvxxuN.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Cj1G70z4.js → sqlite-viewer-SNVYFXvB.js} +1 -1
- package/dist/web/assets/{terminal-tab-Dv9A7Xe2.js → terminal-tab-BJEkmrDt.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CPfIEo8t.js → use-monaco-theme-r8FzlCWr.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +78 -0
- package/docs/project-changelog.md +29 -0
- package/docs/system-architecture.md +2 -0
- package/package.json +5 -2
- package/release-manifest.json +15784 -0
- package/scripts/check-ppm-dir-usage.sh +21 -0
- package/scripts/generate-ppm-guide.ts +92 -0
- package/src/cli/commands/init.ts +2 -1
- package/src/cli/commands/logs.ts +11 -11
- package/src/cli/commands/report.ts +3 -2
- package/src/cli/commands/restart.ts +22 -23
- package/src/cli/commands/skills-cmd.ts +123 -0
- package/src/cli/commands/status.ts +7 -8
- package/src/cli/commands/stop.ts +18 -19
- package/src/index.ts +3 -0
- package/src/lib/account-crypto.ts +12 -7
- package/src/providers/claude-agent-sdk.ts +42 -11
- package/src/server/index.ts +8 -8
- package/src/server/routes/chat.ts +4 -2
- package/src/server/routes/upgrade.ts +3 -5
- package/src/server/ws/chat.ts +31 -0
- package/src/services/cloud-ws.service.ts +6 -3
- package/src/services/cloud.service.ts +20 -19
- package/src/services/cloudflared.service.ts +13 -13
- package/src/services/config.service.ts +5 -7
- package/src/services/db.service.ts +5 -6
- package/src/services/extension-rpc-handlers.ts +2 -2
- package/src/services/extension.service.ts +9 -12
- package/src/services/ppm-dir.ts +14 -0
- package/src/services/slash-discovery/builtin-commands.ts +53 -0
- package/src/services/slash-discovery/builtin-handlers.ts +65 -0
- package/src/services/slash-discovery/definition-source.ts +27 -0
- package/src/services/slash-discovery/discover-skill-roots.ts +128 -0
- package/src/services/slash-discovery/fuzzy-search.ts +76 -0
- package/src/services/slash-discovery/index.ts +42 -0
- package/src/services/slash-discovery/resolve-overrides.ts +41 -0
- package/src/services/slash-discovery/skill-loader.ts +156 -0
- package/src/services/slash-discovery/types.ts +51 -0
- package/src/services/slash-items.service.ts +4 -182
- package/src/services/supervisor-state.ts +14 -15
- package/src/services/supervisor-stopped-page.ts +2 -4
- package/src/services/supervisor.ts +15 -15
- package/src/services/tunnel.service.ts +22 -5
- package/src/services/upgrade.service.ts +2 -3
- package/src/types/chat.ts +3 -1
- package/src/web/components/chat/chat-history-bar.tsx +2 -15
- package/src/web/components/chat/chat-tab.tsx +5 -2
- package/src/web/components/chat/message-input.tsx +48 -6
- package/src/web/components/chat/message-list.tsx +19 -5
- package/src/web/components/chat/slash-command-picker.tsx +21 -12
- package/src/web/components/layout/mobile-nav.tsx +47 -21
- package/src/web/components/layout/panel-layout.tsx +11 -0
- package/src/web/components/layout/upgrade-banner.tsx +48 -2
- package/src/web/components/shared/markdown-renderer.tsx +5 -2
- package/src/web/hooks/use-chat.ts +33 -1
- package/src/web/main.tsx +1 -0
- package/src/web/stores/panel-store.ts +25 -1
- package/src/web/styles/globals.css +14 -0
- package/dist/web/assets/chat-tab-CmSLt4tg.js +0 -10
- package/dist/web/assets/index-BtwsLrdT.css +0 -2
- package/dist/web/assets/index-D6_wwsL_.js +0 -30
- package/dist/web/assets/keybindings-store-C8ryKudw.js +0 -1
- package/dist/web/assets/markdown-renderer-xYMhd9cE.js +0 -69
|
@@ -1,184 +1,6 @@
|
|
|
1
|
-
import { resolve, basename, relative, sep } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
|
|
4
|
-
import yaml from "js-yaml";
|
|
5
|
-
|
|
6
|
-
export interface SlashItem {
|
|
7
|
-
type: "skill" | "command";
|
|
8
|
-
/** Slash name, e.g. "review", "devops/deploy", "ck:research" */
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
argumentHint?: string;
|
|
12
|
-
/** Where the item comes from */
|
|
13
|
-
scope: "project" | "user";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Safely coerce a frontmatter value to string, returns undefined if not a scalar. */
|
|
17
|
-
function str(val: unknown): string | undefined {
|
|
18
|
-
if (typeof val === "string") return val;
|
|
19
|
-
if (typeof val === "number" || typeof val === "boolean") return String(val);
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
1
|
/**
|
|
24
|
-
*
|
|
2
|
+
* Thin re-export wrapper — actual discovery logic lives in slash-discovery/.
|
|
3
|
+
* Kept for backward compatibility with existing imports.
|
|
25
4
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!match || !match[1]) return { meta: {}, body: content };
|
|
29
|
-
try {
|
|
30
|
-
const meta = yaml.load(match[1]) as Record<string, unknown>;
|
|
31
|
-
const body = content.slice(match[0]!.length).trim();
|
|
32
|
-
return { meta: meta ?? {}, body };
|
|
33
|
-
} catch {
|
|
34
|
-
return { meta: {}, body: content };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Recursively walk a directory tree, calling `visitor` for every file.
|
|
40
|
-
* Ignores unreadable dirs/files silently.
|
|
41
|
-
*/
|
|
42
|
-
function walkDir(dir: string, visitor: (filePath: string) => void): void {
|
|
43
|
-
let entries: string[];
|
|
44
|
-
try {
|
|
45
|
-
entries = readdirSync(dir);
|
|
46
|
-
} catch {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
for (const entry of entries) {
|
|
50
|
-
const full = resolve(dir, entry);
|
|
51
|
-
try {
|
|
52
|
-
const stat = statSync(full);
|
|
53
|
-
if (stat.isDirectory()) {
|
|
54
|
-
walkDir(full, visitor);
|
|
55
|
-
} else if (stat.isFile()) {
|
|
56
|
-
visitor(full);
|
|
57
|
-
}
|
|
58
|
-
} catch { /* skip */ }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Collect commands from a commands directory (recursive).
|
|
64
|
-
* `commands/devops/deploy.md` → name `devops/deploy`
|
|
65
|
-
*/
|
|
66
|
-
function collectCommands(commandsDir: string, scope: "project" | "user"): SlashItem[] {
|
|
67
|
-
const items: SlashItem[] = [];
|
|
68
|
-
if (!existsSync(commandsDir)) return items;
|
|
69
|
-
walkDir(commandsDir, (filePath) => {
|
|
70
|
-
if (!filePath.endsWith(".md")) return;
|
|
71
|
-
try {
|
|
72
|
-
const content = readFileSync(filePath, "utf-8");
|
|
73
|
-
const { meta } = parseFrontmatter(content);
|
|
74
|
-
const rel = relative(commandsDir, filePath);
|
|
75
|
-
const name = rel.replace(/\.md$/, "").split(sep).join("/");
|
|
76
|
-
items.push({
|
|
77
|
-
type: "command",
|
|
78
|
-
name: str(meta.name) ?? name,
|
|
79
|
-
description: str(meta.description) ?? "",
|
|
80
|
-
argumentHint: str(meta["argument-hint"]),
|
|
81
|
-
scope,
|
|
82
|
-
});
|
|
83
|
-
} catch { /* skip */ }
|
|
84
|
-
});
|
|
85
|
-
return items;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Collect skills from a skills directory.
|
|
90
|
-
*
|
|
91
|
-
* @param skillsDir Root skills directory to scan
|
|
92
|
-
* @param scope "project" or "user"
|
|
93
|
-
* @param strictMode When true, ONLY pick up SKILL.md files (used for user-global
|
|
94
|
-
* which can have many supporting .md files per skill).
|
|
95
|
-
* When false, also pick up loose .md files outside SKILL.md dirs
|
|
96
|
-
* (used for project-local where flat layout is common).
|
|
97
|
-
*/
|
|
98
|
-
function collectSkills(skillsDir: string, scope: "project" | "user", strictMode: boolean): SlashItem[] {
|
|
99
|
-
const items: SlashItem[] = [];
|
|
100
|
-
if (!existsSync(skillsDir)) return items;
|
|
101
|
-
|
|
102
|
-
const dirsWithSkillMd = new Set<string>();
|
|
103
|
-
|
|
104
|
-
// Pass 1: SKILL.md files (directory-based skills)
|
|
105
|
-
walkDir(skillsDir, (filePath) => {
|
|
106
|
-
if (basename(filePath) !== "SKILL.md") return;
|
|
107
|
-
try {
|
|
108
|
-
const content = readFileSync(filePath, "utf-8");
|
|
109
|
-
const { meta } = parseFrontmatter(content);
|
|
110
|
-
const skillDir = resolve(filePath, "..");
|
|
111
|
-
dirsWithSkillMd.add(skillDir);
|
|
112
|
-
const rel = relative(skillsDir, skillDir);
|
|
113
|
-
const pathName = rel.split(sep).join("/");
|
|
114
|
-
const name = str(meta.name) ?? pathName;
|
|
115
|
-
if (!name) return;
|
|
116
|
-
items.push({
|
|
117
|
-
type: "skill",
|
|
118
|
-
name,
|
|
119
|
-
description: str(meta.description) ?? "",
|
|
120
|
-
scope,
|
|
121
|
-
});
|
|
122
|
-
} catch { /* skip */ }
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Pass 2 (only in relaxed mode): loose .md files not inside a SKILL.md directory
|
|
126
|
-
if (!strictMode) {
|
|
127
|
-
walkDir(skillsDir, (filePath) => {
|
|
128
|
-
if (!filePath.endsWith(".md")) return;
|
|
129
|
-
if (basename(filePath) === "SKILL.md") return;
|
|
130
|
-
const dir = resolve(filePath, "..");
|
|
131
|
-
// Skip supporting files inside a skill dir (or any ancestor)
|
|
132
|
-
let ancestor = dir;
|
|
133
|
-
while (ancestor.startsWith(skillsDir) && ancestor !== skillsDir) {
|
|
134
|
-
if (dirsWithSkillMd.has(ancestor)) return;
|
|
135
|
-
ancestor = resolve(ancestor, "..");
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const content = readFileSync(filePath, "utf-8");
|
|
139
|
-
const { meta } = parseFrontmatter(content);
|
|
140
|
-
const rel = relative(skillsDir, filePath);
|
|
141
|
-
const pathName = rel.replace(/\.md$/, "").split(sep).join("/");
|
|
142
|
-
const name = str(meta.name) ?? pathName;
|
|
143
|
-
if (!name) return;
|
|
144
|
-
items.push({
|
|
145
|
-
type: "skill",
|
|
146
|
-
name,
|
|
147
|
-
description: str(meta.description) ?? "",
|
|
148
|
-
scope,
|
|
149
|
-
});
|
|
150
|
-
} catch { /* skip */ }
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return items;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Scan for available slash commands and skills.
|
|
159
|
-
*
|
|
160
|
-
* Sources (merged, project overrides user if same name):
|
|
161
|
-
* 1. User-global: ~/.claude/commands/ and ~/.claude/skills/ (strict: SKILL.md only)
|
|
162
|
-
* 2. Project-local: <projectPath>/.claude/commands/ and .claude/skills/ (relaxed: also loose .md)
|
|
163
|
-
*/
|
|
164
|
-
export function listSlashItems(projectPath: string): SlashItem[] {
|
|
165
|
-
const home = homedir();
|
|
166
|
-
const globalClaude = resolve(home, ".claude");
|
|
167
|
-
|
|
168
|
-
// Collect from both scopes (user-global uses strict mode)
|
|
169
|
-
const userCommands = collectCommands(resolve(globalClaude, "commands"), "user");
|
|
170
|
-
const userSkills = collectSkills(resolve(globalClaude, "skills"), "user", true);
|
|
171
|
-
const projectCommands = collectCommands(resolve(projectPath, ".claude", "commands"), "project");
|
|
172
|
-
const projectSkills = collectSkills(resolve(projectPath, ".claude", "skills"), "project", false);
|
|
173
|
-
|
|
174
|
-
// Merge: project items override user items with the same name
|
|
175
|
-
const map = new Map<string, SlashItem>();
|
|
176
|
-
for (const item of [...userCommands, ...userSkills]) {
|
|
177
|
-
map.set(`${item.type}:${item.name}`, item);
|
|
178
|
-
}
|
|
179
|
-
for (const item of [...projectCommands, ...projectSkills]) {
|
|
180
|
-
map.set(`${item.type}:${item.name}`, item);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return Array.from(map.values());
|
|
184
|
-
}
|
|
5
|
+
export { listSlashItems, searchSlashItems } from "./slash-discovery/index.ts";
|
|
6
|
+
export type { SlashItem } from "./slash-discovery/types.ts";
|
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
* Extracted from supervisor.ts to keep the orchestrator lean.
|
|
4
4
|
*/
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import {
|
|
8
7
|
readFileSync, writeFileSync, existsSync, unlinkSync, renameSync, openSync, closeSync,
|
|
9
8
|
} from "node:fs";
|
|
10
9
|
import { constants } from "node:fs";
|
|
10
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
15
|
-
export const
|
|
16
|
-
export const LOCK_FILE = resolve(PPM_DIR, ".start-lock");
|
|
12
|
+
export const CMD_FILE = () => resolve(getPpmDir(), ".supervisor-cmd");
|
|
13
|
+
export const STATUS_FILE = () => resolve(getPpmDir(), "status.json");
|
|
14
|
+
export const PID_FILE = () => resolve(getPpmDir(), "ppm.pid");
|
|
15
|
+
export const LOCK_FILE = () => resolve(getPpmDir(), ".start-lock");
|
|
17
16
|
|
|
18
17
|
// ─── State ─────────────────────────────────────────────────────────────
|
|
19
18
|
export type SupervisorState = "running" | "paused" | "stopped" | "upgrading";
|
|
@@ -47,7 +46,7 @@ function atomicWriteJson(filePath: string, data: unknown) {
|
|
|
47
46
|
|
|
48
47
|
export function readStatus(): Record<string, unknown> {
|
|
49
48
|
try {
|
|
50
|
-
if (existsSync(STATUS_FILE)) return JSON.parse(readFileSync(STATUS_FILE, "utf-8"));
|
|
49
|
+
if (existsSync(STATUS_FILE())) return JSON.parse(readFileSync(STATUS_FILE(), "utf-8"));
|
|
51
50
|
} catch {}
|
|
52
51
|
return {};
|
|
53
52
|
}
|
|
@@ -55,7 +54,7 @@ export function readStatus(): Record<string, unknown> {
|
|
|
55
54
|
export function updateStatus(patch: Record<string, unknown>) {
|
|
56
55
|
try {
|
|
57
56
|
const data = { ...readStatus(), ...patch };
|
|
58
|
-
atomicWriteJson(STATUS_FILE, data);
|
|
57
|
+
atomicWriteJson(STATUS_FILE(), data);
|
|
59
58
|
} catch {}
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -64,9 +63,9 @@ export type CmdAction = "soft_stop" | "resume";
|
|
|
64
63
|
|
|
65
64
|
/** Atomically claim + read command file (rename to .claimed, read, delete) */
|
|
66
65
|
export function readAndDeleteCmd(): { action: CmdAction } | null {
|
|
67
|
-
const claimed = CMD_FILE + ".claimed";
|
|
66
|
+
const claimed = CMD_FILE() + ".claimed";
|
|
68
67
|
try {
|
|
69
|
-
renameSync(CMD_FILE, claimed); // atomic claim — second caller gets ENOENT
|
|
68
|
+
renameSync(CMD_FILE(), claimed); // atomic claim — second caller gets ENOENT
|
|
70
69
|
const cmd = JSON.parse(readFileSync(claimed, "utf-8"));
|
|
71
70
|
unlinkSync(claimed);
|
|
72
71
|
return cmd;
|
|
@@ -78,31 +77,31 @@ export function readAndDeleteCmd(): { action: CmdAction } | null {
|
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
export function writeCmd(action: CmdAction) {
|
|
81
|
-
writeFileSync(CMD_FILE, JSON.stringify({ action }));
|
|
80
|
+
writeFileSync(CMD_FILE(), JSON.stringify({ action }));
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
// ─── Lockfile ──────────────────────────────────────────────────────────
|
|
85
84
|
export function acquireLock(): boolean {
|
|
86
85
|
try {
|
|
87
86
|
// Try exclusive create — fails if file already exists (atomic)
|
|
88
|
-
const fd = openSync(LOCK_FILE, "wx");
|
|
87
|
+
const fd = openSync(LOCK_FILE(), "wx");
|
|
89
88
|
writeFileSync(fd, String(process.pid));
|
|
90
89
|
closeSync(fd);
|
|
91
90
|
return true;
|
|
92
91
|
} catch {
|
|
93
92
|
// File exists — check if holding process is alive
|
|
94
93
|
try {
|
|
95
|
-
const pid = parseInt(readFileSync(LOCK_FILE, "utf-8").trim(), 10);
|
|
94
|
+
const pid = parseInt(readFileSync(LOCK_FILE(), "utf-8").trim(), 10);
|
|
96
95
|
if (!isNaN(pid)) {
|
|
97
96
|
try { process.kill(pid, 0); return false; } catch {} // stale lock
|
|
98
97
|
}
|
|
99
98
|
// Stale lock — overwrite
|
|
100
|
-
writeFileSync(LOCK_FILE, String(process.pid));
|
|
99
|
+
writeFileSync(LOCK_FILE(), String(process.pid));
|
|
101
100
|
return true;
|
|
102
101
|
} catch { return false; }
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
export function releaseLock() {
|
|
107
|
-
try { unlinkSync(LOCK_FILE); } catch {}
|
|
106
|
+
try { unlinkSync(LOCK_FILE()); } catch {}
|
|
108
107
|
}
|
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { appendFileSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const LOG_FILE = resolve(process.env.PPM_HOME || resolve(homedir(), ".ppm"), "ppm.log");
|
|
7
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
10
8
|
|
|
11
9
|
function log(level: string, msg: string) {
|
|
12
10
|
const ts = new Date().toISOString();
|
|
13
|
-
try { appendFileSync(
|
|
11
|
+
try { appendFileSync(resolve(getPpmDir(), "ppm.log"), `[${ts}] [${level}] [stopped-page] ${msg}\n`); } catch {}
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
const STOPPED_HTML = `<!DOCTYPE html>
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Subprocess } from "bun";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
9
|
import {
|
|
11
10
|
readFileSync, writeFileSync, existsSync, mkdirSync, openSync, appendFileSync,
|
|
12
11
|
unlinkSync,
|
|
13
12
|
} from "node:fs";
|
|
13
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
14
14
|
import { isCompiledBinary } from "./autostart-generator.ts";
|
|
15
15
|
import {
|
|
16
16
|
type SupervisorState,
|
|
@@ -34,9 +34,8 @@ const UPGRADE_CHECK_INTERVAL_MS = 900_000; // 15min
|
|
|
34
34
|
const UPGRADE_SKIP_INITIAL_MS = 300_000; // 5min delay before first check
|
|
35
35
|
const SELF_REPLACE_TIMEOUT_MS = 30_000; // 30s to wait for new supervisor
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const RESTARTING_FLAG = resolve(PPM_DIR, ".restarting");
|
|
37
|
+
const logFile = () => resolve(getPpmDir(), "ppm.log");
|
|
38
|
+
const restartingFlag = () => resolve(getPpmDir(), ".restarting");
|
|
40
39
|
|
|
41
40
|
// ─── State ─────────────────────────────────────────────────────────────
|
|
42
41
|
let serverChild: Subprocess | null = null;
|
|
@@ -75,7 +74,7 @@ let originalArgv: string[] = [];
|
|
|
75
74
|
function log(level: string, msg: string) {
|
|
76
75
|
const ts = new Date().toISOString();
|
|
77
76
|
const line = `[${ts}] [${level}] [supervisor] ${msg}\n`;
|
|
78
|
-
try { appendFileSync(
|
|
77
|
+
try { appendFileSync(logFile(), line); } catch {}
|
|
79
78
|
if (level === "ERROR" || level === "FATAL") {
|
|
80
79
|
process.stderr.write(line);
|
|
81
80
|
}
|
|
@@ -103,7 +102,7 @@ export async function spawnServer(
|
|
|
103
102
|
|
|
104
103
|
const childPid = serverChild.pid;
|
|
105
104
|
updateStatus({ pid: childPid });
|
|
106
|
-
writeFileSync(PID_FILE, String(process.pid)); // supervisor PID for stop
|
|
105
|
+
writeFileSync(PID_FILE(), String(process.pid)); // supervisor PID for stop
|
|
107
106
|
log("INFO", `Server started (PID: ${childPid})`);
|
|
108
107
|
|
|
109
108
|
const exitCode = await serverChild.exited;
|
|
@@ -413,7 +412,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
413
412
|
updateStatus({ state: "upgrading" });
|
|
414
413
|
|
|
415
414
|
// Set restarting flag so server child's stopTunnel() skips killing the tunnel
|
|
416
|
-
try { writeFileSync(
|
|
415
|
+
try { writeFileSync(restartingFlag(), ""); } catch {}
|
|
417
416
|
|
|
418
417
|
// Clear probe timer FIRST to prevent race between flush check and queued callback
|
|
419
418
|
if (tunnelProbeTimer) { clearInterval(tunnelProbeTimer); tunnelProbeTimer = null; }
|
|
@@ -439,7 +438,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
439
438
|
|
|
440
439
|
// Spawn new supervisor using saved argv
|
|
441
440
|
const cmd = originalArgv.slice();
|
|
442
|
-
const logFd = openSync(
|
|
441
|
+
const logFd = openSync(logFile(), "a");
|
|
443
442
|
const child = Bun.spawn({
|
|
444
443
|
cmd,
|
|
445
444
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -452,7 +451,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
452
451
|
while (Date.now() - start < SELF_REPLACE_TIMEOUT_MS) {
|
|
453
452
|
await Bun.sleep(1000);
|
|
454
453
|
try {
|
|
455
|
-
const data = JSON.parse(readFileSync(STATUS_FILE, "utf-8"));
|
|
454
|
+
const data = JSON.parse(readFileSync(STATUS_FILE(), "utf-8"));
|
|
456
455
|
if (data.supervisorPid && data.supervisorPid !== currentSupervisorPid) {
|
|
457
456
|
log("INFO", `New supervisor detected (PID: ${data.supervisorPid}), old exiting`);
|
|
458
457
|
// Children already killed, just clear remaining timers and exit
|
|
@@ -467,7 +466,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
467
466
|
// Timeout — new supervisor didn't start, restore old supervisor
|
|
468
467
|
log("ERROR", "Self-replace timeout: new supervisor did not start");
|
|
469
468
|
try { child.kill(); } catch {}
|
|
470
|
-
try { unlinkSync(
|
|
469
|
+
try { unlinkSync(restartingFlag()); } catch {}
|
|
471
470
|
shuttingDown = false;
|
|
472
471
|
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
473
472
|
setState("running");
|
|
@@ -475,7 +474,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
475
474
|
return { success: false, error: "New supervisor failed to start within 30s" };
|
|
476
475
|
} catch (e) {
|
|
477
476
|
log("ERROR", `Self-replace error: ${e}`);
|
|
478
|
-
try { unlinkSync(
|
|
477
|
+
try { unlinkSync(restartingFlag()); } catch {}
|
|
479
478
|
shuttingDown = false;
|
|
480
479
|
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
481
480
|
setState("running");
|
|
@@ -741,15 +740,16 @@ export async function runSupervisor(opts: {
|
|
|
741
740
|
profile?: string;
|
|
742
741
|
share: boolean;
|
|
743
742
|
}) {
|
|
744
|
-
|
|
743
|
+
const ppmDir = getPpmDir();
|
|
744
|
+
if (!existsSync(ppmDir)) mkdirSync(ppmDir, { recursive: true });
|
|
745
745
|
|
|
746
746
|
// Clean up restarting flag from previous upgrade/restart
|
|
747
|
-
try { unlinkSync(
|
|
747
|
+
try { unlinkSync(restartingFlag()); } catch {}
|
|
748
748
|
|
|
749
749
|
// Save original argv for self-replace
|
|
750
750
|
originalArgv = [...process.argv];
|
|
751
751
|
|
|
752
|
-
const logFd = openSync(
|
|
752
|
+
const logFd = openSync(logFile(), "a");
|
|
753
753
|
log("INFO", `Supervisor started (PID: ${process.pid}, port: ${opts.port}, share: ${opts.share})`);
|
|
754
754
|
|
|
755
755
|
// Global exception handlers — supervisor must never crash
|
|
@@ -761,7 +761,7 @@ export async function runSupervisor(opts: {
|
|
|
761
761
|
});
|
|
762
762
|
|
|
763
763
|
// Write supervisor PID + clear stale availableVersion from previous run
|
|
764
|
-
writeFileSync(PID_FILE, String(process.pid));
|
|
764
|
+
writeFileSync(PID_FILE(), String(process.pid));
|
|
765
765
|
updateStatus({
|
|
766
766
|
supervisorPid: process.pid, port: opts.port, host: opts.host, availableVersion: null,
|
|
767
767
|
state: "running", pausedAt: null, pauseReason: null, lastCrashError: null,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { Subprocess } from "bun";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { existsSync, unlinkSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
5
4
|
import { ensureCloudflared } from "./cloudflared.service.ts";
|
|
5
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
6
6
|
|
|
7
7
|
const TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
|
|
8
8
|
const decoder = new TextDecoder();
|
|
9
|
-
const RESTARTING_FLAG = resolve(homedir(), ".ppm", ".restarting");
|
|
10
9
|
|
|
11
10
|
/** Extract tunnel URL from cloudflared stderr output */
|
|
12
11
|
export function extractTunnelUrl(text: string): string | null {
|
|
@@ -98,7 +97,7 @@ class TunnelService {
|
|
|
98
97
|
this.cleanupHandler = null;
|
|
99
98
|
}
|
|
100
99
|
// If server is restarting, keep tunnel alive
|
|
101
|
-
if (existsSync(
|
|
100
|
+
if (existsSync(resolve(getPpmDir(), ".restarting"))) {
|
|
102
101
|
this.childProcess = null;
|
|
103
102
|
this.externalPid = null;
|
|
104
103
|
this.url = null;
|
|
@@ -123,11 +122,29 @@ class TunnelService {
|
|
|
123
122
|
this.stopCloudSync();
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
/** Get current tunnel URL (null if not running)
|
|
125
|
+
/** Get current tunnel URL (null if not running).
|
|
126
|
+
* Falls back to status.json if the supervisor set the URL after this process started. */
|
|
127
127
|
getTunnelUrl(): string | null {
|
|
128
|
+
if (this.url) return this.url;
|
|
129
|
+
// Lazy sync: supervisor may have written shareUrl after server startup
|
|
130
|
+
this.syncFromStatusFile();
|
|
128
131
|
return this.url;
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
/** Re-read tunnel state from status.json (supervisor may have updated it after server started) */
|
|
135
|
+
private syncFromStatusFile(): void {
|
|
136
|
+
if (this.url) return; // already have a URL
|
|
137
|
+
try {
|
|
138
|
+
const statusFile = resolve(getPpmDir(), "status.json");
|
|
139
|
+
const status = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
140
|
+
if (status.shareUrl) {
|
|
141
|
+
this.url = status.shareUrl;
|
|
142
|
+
this.supervisorManaged = true;
|
|
143
|
+
if (status.tunnelPid) this.externalPid = status.tunnelPid;
|
|
144
|
+
}
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
/** Get cloudflared PID (child process or external) */
|
|
132
149
|
getTunnelPid(): number | null {
|
|
133
150
|
return this.childProcess?.pid ?? this.externalPid;
|
|
@@ -148,7 +165,7 @@ class TunnelService {
|
|
|
148
165
|
|
|
149
166
|
/** Persist shareUrl + tunnelPid to status.json (atomic write to avoid cross-process races) */
|
|
150
167
|
private persistToStatusFile(): void {
|
|
151
|
-
const statusFile = resolve(
|
|
168
|
+
const statusFile = resolve(getPpmDir(), "status.json");
|
|
152
169
|
if (!existsSync(statusFile)) return;
|
|
153
170
|
try {
|
|
154
171
|
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
* detects install method, runs install command.
|
|
4
4
|
*/
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import { readFileSync } from "node:fs";
|
|
8
7
|
import { VERSION } from "../version.ts";
|
|
9
8
|
import { isCompiledBinary } from "./autostart-generator.ts";
|
|
9
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
10
10
|
|
|
11
11
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@hienlh/ppm/latest";
|
|
12
12
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
13
|
-
const STATUS_FILE = resolve(process.env.PPM_HOME || resolve(homedir(), ".ppm"), "status.json");
|
|
14
13
|
|
|
15
14
|
export type InstallMethod = "bun" | "npm" | "binary";
|
|
16
15
|
|
|
@@ -103,7 +102,7 @@ export async function applyUpgrade(): Promise<{
|
|
|
103
102
|
/** Send SIGUSR1 to supervisor to trigger self-replace after upgrade */
|
|
104
103
|
export function signalSupervisorUpgrade(): { sent: boolean; error?: string } {
|
|
105
104
|
try {
|
|
106
|
-
const data = JSON.parse(readFileSync(
|
|
105
|
+
const data = JSON.parse(readFileSync(resolve(getPpmDir(), "status.json"), "utf-8"));
|
|
107
106
|
const pid = data.supervisorPid;
|
|
108
107
|
if (!pid) return { sent: false, error: "No supervisor PID" };
|
|
109
108
|
process.kill(pid, 0); // check alive
|
package/src/types/chat.ts
CHANGED
|
@@ -116,7 +116,7 @@ export type ChatEvent =
|
|
|
116
116
|
| { type: "tool_result"; output: string; isError?: boolean; toolUseId?: string; parentToolUseId?: string }
|
|
117
117
|
| { type: "approval_request"; requestId: string; tool: string; input: unknown }
|
|
118
118
|
| { type: "error"; message: string }
|
|
119
|
-
| { type: "done"; sessionId: string; resultSubtype?: ResultSubtype; numTurns?: number; contextWindowPct?: number }
|
|
119
|
+
| { type: "done"; sessionId: string; resultSubtype?: ResultSubtype; numTurns?: number; contextWindowPct?: number; lastMessageUuid?: string }
|
|
120
120
|
| { type: "account_info"; accountId: string; accountLabel: string }
|
|
121
121
|
| { type: "account_retry"; reason: string; accountId?: string; accountLabel?: string }
|
|
122
122
|
| { type: "status_update"; phase: "routing" | "refreshing" | "switching"; message: string; accountLabel?: string }
|
|
@@ -139,4 +139,6 @@ export interface ChatMessage {
|
|
|
139
139
|
/** Account used to generate this assistant message */
|
|
140
140
|
accountId?: string;
|
|
141
141
|
accountLabel?: string;
|
|
142
|
+
/** SDK message UUID — used for fork/rewind (maps to JSONL message IDs) */
|
|
143
|
+
sdkUuid?: string;
|
|
142
144
|
}
|
|
@@ -24,7 +24,6 @@ interface TeamActivityState {
|
|
|
24
24
|
interface ChatHistoryBarProps {
|
|
25
25
|
projectName: string;
|
|
26
26
|
usageInfo: UsageInfo;
|
|
27
|
-
compactStatus?: "compacting" | null;
|
|
28
27
|
usageLoading?: boolean;
|
|
29
28
|
refreshUsage?: () => void;
|
|
30
29
|
lastFetchedAt?: string | null;
|
|
@@ -95,7 +94,7 @@ function DebugCopyButton({ sessionId, projectName }: { sessionId: string; projec
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
export function ChatHistoryBar({
|
|
98
|
-
projectName, usageInfo,
|
|
97
|
+
projectName, usageInfo, usageLoading, refreshUsage, lastFetchedAt,
|
|
99
98
|
sessionId, providerId, onSelectSession, onBugReport, isConnected, onReconnect,
|
|
100
99
|
teamActivity, teamMessages, onTeamOpen,
|
|
101
100
|
}: ChatHistoryBarProps) {
|
|
@@ -290,20 +289,8 @@ export function ChatHistoryBar({
|
|
|
290
289
|
<span>5h:{fiveHourPct != null ? `${fiveHourPct}%` : "--%"}</span>
|
|
291
290
|
<span className="text-text-subtle">·</span>
|
|
292
291
|
<span>Wk:{sevenDayPct != null ? `${sevenDayPct}%` : "--%"}</span>
|
|
293
|
-
{compactStatus === "compacting" && (
|
|
294
|
-
<>
|
|
295
|
-
<span className="text-text-subtle">·</span>
|
|
296
|
-
<span className="text-blue-400 animate-pulse">compacting...</span>
|
|
297
|
-
</>
|
|
298
|
-
)}
|
|
299
292
|
</button>
|
|
300
|
-
) :
|
|
301
|
-
compactStatus === "compacting" ? (
|
|
302
|
-
<span className="text-[11px] px-1.5 py-0.5 text-blue-400 animate-pulse">
|
|
303
|
-
compacting...
|
|
304
|
-
</span>
|
|
305
|
-
) : null
|
|
306
|
-
)}
|
|
293
|
+
) : null}
|
|
307
294
|
|
|
308
295
|
{/* Team activity */}
|
|
309
296
|
{teamActivity?.hasTeams && (
|
|
@@ -36,6 +36,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
36
36
|
const [slashItems, setSlashItems] = useState<SlashItem[]>([]);
|
|
37
37
|
const [showSlashPicker, setShowSlashPicker] = useState(false);
|
|
38
38
|
const [slashFilter, setSlashFilter] = useState("");
|
|
39
|
+
const [slashRanked, setSlashRanked] = useState(false);
|
|
39
40
|
const [slashSelected, setSlashSelected] = useState<SlashItem | null>(null);
|
|
40
41
|
|
|
41
42
|
// File picker state
|
|
@@ -243,6 +244,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
243
244
|
const handleSlashStateChange = useCallback((visible: boolean, filter: string) => {
|
|
244
245
|
setShowSlashPicker(visible);
|
|
245
246
|
setSlashFilter(filter);
|
|
247
|
+
if (!visible || !filter) setSlashRanked(false);
|
|
246
248
|
}, []);
|
|
247
249
|
|
|
248
250
|
const handleSlashSelect = useCallback((item: SlashItem) => {
|
|
@@ -347,6 +349,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
347
349
|
phase={phase}
|
|
348
350
|
connectingElapsed={connectingElapsed}
|
|
349
351
|
statusMessage={statusMessage}
|
|
352
|
+
compactStatus={compactStatus}
|
|
350
353
|
projectName={projectName}
|
|
351
354
|
onFork={!isStreaming ? handleFork : undefined}
|
|
352
355
|
onSelectSession={handleSelectSession}
|
|
@@ -358,7 +361,6 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
358
361
|
<ChatHistoryBar
|
|
359
362
|
projectName={projectName}
|
|
360
363
|
usageInfo={usageInfo}
|
|
361
|
-
compactStatus={compactStatus}
|
|
362
364
|
usageLoading={usageLoading}
|
|
363
365
|
refreshUsage={refreshUsage}
|
|
364
366
|
lastFetchedAt={lastFetchedAt}
|
|
@@ -383,6 +385,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
383
385
|
onSelect={handleSlashSelect}
|
|
384
386
|
onClose={handleSlashClose}
|
|
385
387
|
visible={showSlashPicker}
|
|
388
|
+
ranked={slashRanked}
|
|
386
389
|
/>
|
|
387
390
|
<FilePicker
|
|
388
391
|
items={fileItems}
|
|
@@ -404,7 +407,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
404
407
|
initialValue={forkDraft}
|
|
405
408
|
projectName={projectName}
|
|
406
409
|
onSlashStateChange={handleSlashStateChange}
|
|
407
|
-
onSlashItemsLoaded={setSlashItems}
|
|
410
|
+
onSlashItemsLoaded={(items, ranked) => { setSlashItems(items); if (ranked !== undefined) setSlashRanked(ranked); }}
|
|
408
411
|
slashSelected={slashSelected}
|
|
409
412
|
onFileStateChange={handleFileStateChange}
|
|
410
413
|
onFileItemsLoaded={setFileItems}
|