@cocorograph/hub-agent 0.6.2 → 0.6.3
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 +1 -1
- package/src/claude-history.mjs +57 -13
- package/src/main.mjs +18 -1
package/package.json
CHANGED
package/src/claude-history.mjs
CHANGED
|
@@ -127,12 +127,55 @@ export async function fetchSessionHistory({ cwd, session_id, maxLines, projectsR
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
130
|
+
* jsonl ファイルから最初の user メッセージ本文 (preview 用) を抽出する。
|
|
131
|
+
* 大きいファイルでも先頭付近で見つかるので、先頭 64KB だけ読んで探す。
|
|
132
132
|
*
|
|
133
|
-
* @param {
|
|
133
|
+
* @param {string} filePath
|
|
134
|
+
* @returns {Promise<string>} 先頭 user メッセージの冒頭 (最大 80 文字)、無ければ ""
|
|
134
135
|
*/
|
|
135
|
-
|
|
136
|
+
async function extractPreview(filePath) {
|
|
137
|
+
let text
|
|
138
|
+
try {
|
|
139
|
+
const buf = await readFile(filePath, "utf-8")
|
|
140
|
+
// 先頭 64KB だけ見る (preview には十分)
|
|
141
|
+
text = buf.length > 65536 ? buf.slice(0, 65536) : buf
|
|
142
|
+
} catch {
|
|
143
|
+
return ""
|
|
144
|
+
}
|
|
145
|
+
for (const line of text.split("\n")) {
|
|
146
|
+
if (!line) continue
|
|
147
|
+
let obj
|
|
148
|
+
try {
|
|
149
|
+
obj = JSON.parse(line)
|
|
150
|
+
} catch {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
if (obj?.type === "user" && obj.message) {
|
|
154
|
+
const content = obj.message.content
|
|
155
|
+
let str = ""
|
|
156
|
+
if (typeof content === "string") {
|
|
157
|
+
str = content
|
|
158
|
+
} else if (Array.isArray(content)) {
|
|
159
|
+
const textBlock = content.find((b) => b?.type === "text" && typeof b.text === "string")
|
|
160
|
+
if (textBlock) str = textBlock.text
|
|
161
|
+
}
|
|
162
|
+
str = str.trim().replace(/\s+/g, " ")
|
|
163
|
+
if (str) return str.slice(0, 80)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return ""
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* cwd 配下の全 jsonl ファイルから session 一覧を返す。
|
|
171
|
+
* 各ファイルから session_id / 最終更新時刻 / サイズ / 最初の user message preview を抽出。
|
|
172
|
+
*
|
|
173
|
+
* Cockpit ChatView の「過去セッション一覧」ドロップダウン用 (tmux で作業した
|
|
174
|
+
* セッションも同じ cwd の project dir に並ぶため、Chat 側から読み返せる)。
|
|
175
|
+
*
|
|
176
|
+
* @param {{cwd: string, projectsRoot?: string, limit?: number, logger?: import('pino').Logger}} args
|
|
177
|
+
*/
|
|
178
|
+
export async function listSessions({ cwd, projectsRoot, limit = 30, logger }) {
|
|
136
179
|
if (!cwd) return { sessions: [] }
|
|
137
180
|
const dir = path.join(
|
|
138
181
|
projectsRoot || path.join(os.homedir(), ".claude", "projects"),
|
|
@@ -146,24 +189,25 @@ export async function listSessions({ cwd, projectsRoot, logger }) {
|
|
|
146
189
|
logger?.warn({ err: err.message, dir }, "claude history list failed")
|
|
147
190
|
return { sessions: [], error: err.message }
|
|
148
191
|
}
|
|
149
|
-
const
|
|
192
|
+
const stats = []
|
|
150
193
|
for (const f of files) {
|
|
151
194
|
if (!f.endsWith(".jsonl")) continue
|
|
152
195
|
const session_id = f.slice(0, -".jsonl".length)
|
|
153
196
|
const filePath = path.join(dir, f)
|
|
154
197
|
try {
|
|
155
198
|
const st = await stat(filePath)
|
|
156
|
-
|
|
157
|
-
session_id,
|
|
158
|
-
file_path: filePath,
|
|
159
|
-
mtime: st.mtimeMs,
|
|
160
|
-
size_bytes: st.size,
|
|
161
|
-
})
|
|
199
|
+
stats.push({ session_id, file_path: filePath, mtime: st.mtimeMs, size_bytes: st.size })
|
|
162
200
|
} catch {
|
|
163
201
|
// ignore individual file stat errors
|
|
164
202
|
}
|
|
165
203
|
}
|
|
166
|
-
// 最新順
|
|
167
|
-
|
|
204
|
+
// 最新順 + limit 件に絞ってから preview 抽出 (全ファイル読みを避ける)
|
|
205
|
+
stats.sort((a, b) => b.mtime - a.mtime)
|
|
206
|
+
const top = stats.slice(0, limit)
|
|
207
|
+
const sessions = []
|
|
208
|
+
for (const s of top) {
|
|
209
|
+
const preview = await extractPreview(s.file_path)
|
|
210
|
+
sessions.push({ ...s, preview })
|
|
211
|
+
}
|
|
168
212
|
return { sessions }
|
|
169
213
|
}
|
package/src/main.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { loadPlugins, runHookBroadcast, runHookChain } from "./plugin-loader.mjs
|
|
|
22
22
|
import { WsClient } from "./ws-client.mjs"
|
|
23
23
|
import { PtyBridge } from "./pty-bridge.mjs"
|
|
24
24
|
import { ClaudeStreamBridge } from "./claude-stream-bridge.mjs"
|
|
25
|
-
import { fetchSessionHistory } from "./claude-history.mjs"
|
|
25
|
+
import { fetchSessionHistory, listSessions } from "./claude-history.mjs"
|
|
26
26
|
import { listAgents } from "./agents.mjs"
|
|
27
27
|
import { listSkills } from "./skills.mjs"
|
|
28
28
|
import { listSessionStates } from "./state.mjs"
|
|
@@ -686,6 +686,23 @@ async function dispatch(msg, ctx) {
|
|
|
686
686
|
})
|
|
687
687
|
return
|
|
688
688
|
}
|
|
689
|
+
case "claude.sessions.request": {
|
|
690
|
+
// Sprint G 0.6.3: cwd 配下の全 jsonl セッション一覧を返す。
|
|
691
|
+
// tmux で作業したセッションも同じ project dir に並ぶため、Chat 側から
|
|
692
|
+
// 「過去セッション」として読み返せる。
|
|
693
|
+
const stream_id = msg.stream_id
|
|
694
|
+
const cwd = msg.cwd || ""
|
|
695
|
+
const limit = typeof msg.limit === "number" ? msg.limit : undefined
|
|
696
|
+
const result = await listSessions({ cwd, limit, logger: ctx.logger })
|
|
697
|
+
ctx.client.send({
|
|
698
|
+
type: "claude.sessions.response",
|
|
699
|
+
stream_id,
|
|
700
|
+
cwd,
|
|
701
|
+
sessions: result.sessions || [],
|
|
702
|
+
error: result.error,
|
|
703
|
+
})
|
|
704
|
+
return
|
|
705
|
+
}
|
|
689
706
|
case "tmux.exec": {
|
|
690
707
|
const args = Array.isArray(msg.args) ? msg.args : []
|
|
691
708
|
const hookResult = await runHookChain(ctx.plugins, "interceptTmuxExec", {
|