@juspay/shooter 1.16.0 → 1.18.0
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/.claude/hooks/codex-hooks.example.json +75 -0
- package/.claude/hooks/notifier.cjs +158 -8
- package/build/client/_app/immutable/assets/{0.DEfoFaGR.css → 0.NV8k8wxG.css} +1 -1
- package/build/client/_app/immutable/assets/0.NV8k8wxG.css.br +0 -0
- package/build/client/_app/immutable/assets/0.NV8k8wxG.css.gz +0 -0
- package/build/client/_app/immutable/chunks/8lO1IL7u.js +1 -0
- package/build/client/_app/immutable/chunks/8lO1IL7u.js.br +0 -0
- package/build/client/_app/immutable/chunks/8lO1IL7u.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B9WQy_3X.js +1 -0
- package/build/client/_app/immutable/chunks/B9WQy_3X.js.br +0 -0
- package/build/client/_app/immutable/chunks/B9WQy_3X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BdtLzPpO.js +1 -0
- package/build/client/_app/immutable/chunks/BdtLzPpO.js.br +0 -0
- package/build/client/_app/immutable/chunks/BdtLzPpO.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DlS3abGJ.js → DJvX78LW.js} +1 -1
- package/build/client/_app/immutable/chunks/DJvX78LW.js.br +0 -0
- package/build/client/_app/immutable/chunks/DJvX78LW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/nWG9RHyB.js +3 -0
- package/build/client/_app/immutable/chunks/nWG9RHyB.js.br +0 -0
- package/build/client/_app/immutable/chunks/nWG9RHyB.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CSJG7N9H.js → app.f46Ko1hu.js} +2 -2
- package/build/client/_app/immutable/entry/app.f46Ko1hu.js.br +0 -0
- package/build/client/_app/immutable/entry/app.f46Ko1hu.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BVDjNnXt.js +1 -0
- package/build/client/_app/immutable/entry/start.BVDjNnXt.js.br +2 -0
- package/build/client/_app/immutable/entry/start.BVDjNnXt.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.qOL7xtFn.js → 0.D_9EwVmq.js} +1 -1
- package/build/client/_app/immutable/nodes/0.D_9EwVmq.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.D_9EwVmq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.Di708Ago.js → 1.C4eFlqSB.js} +1 -1
- package/build/client/_app/immutable/nodes/1.C4eFlqSB.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.C4eFlqSB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.DSM1znqa.js → 2.CdC092Za.js} +1 -1
- package/build/client/_app/immutable/nodes/2.CdC092Za.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CdC092Za.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.BPa5fh75.js → 3.Dhf4ZWW0.js} +1 -1
- package/build/client/_app/immutable/nodes/3.Dhf4ZWW0.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Dhf4ZWW0.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.B3SEB_li.js +1 -0
- package/build/client/_app/immutable/nodes/6.B3SEB_li.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B3SEB_li.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.B7UJd8GQ.js → 7.DV8cJ1lX.js} +3 -3
- package/build/client/_app/immutable/nodes/7.DV8cJ1lX.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DV8cJ1lX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.Bs362gyb.js +2 -0
- package/build/client/_app/immutable/nodes/8.Bs362gyb.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.Bs362gyb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js +2 -0
- package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.Cf7_3uqT.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-D8uPamNd.js → 0-Cd7jY0a7.js} +3 -3
- package/build/server/chunks/{0-D8uPamNd.js.map → 0-Cd7jY0a7.js.map} +1 -1
- package/build/server/chunks/{1-DhtioHbs.js → 1-C4BOGoJY.js} +2 -2
- package/build/server/chunks/{1-DhtioHbs.js.map → 1-C4BOGoJY.js.map} +1 -1
- package/build/server/chunks/{2-Cgh7ZFgE.js → 2-Ba0mNwJ6.js} +2 -2
- package/build/server/chunks/{2-Cgh7ZFgE.js.map → 2-Ba0mNwJ6.js.map} +1 -1
- package/build/server/chunks/{3-I6hnjssH.js → 3-Pg8t1uJU.js} +2 -2
- package/build/server/chunks/{3-I6hnjssH.js.map → 3-Pg8t1uJU.js.map} +1 -1
- package/build/server/chunks/{6-bPDbH1_W.js → 6-D8xbnTSo.js} +2 -2
- package/build/server/chunks/6-D8xbnTSo.js.map +1 -0
- package/build/server/chunks/{7-CpBrOkxQ.js → 7-CkVK06S0.js} +2 -2
- package/build/server/chunks/7-CkVK06S0.js.map +1 -0
- package/build/server/chunks/{8-BRGAVfze.js → 8-C8qVhrds.js} +2 -2
- package/build/server/chunks/8-C8qVhrds.js.map +1 -0
- package/build/server/chunks/{9-C6xuAb_Y.js → 9-fL5zqN0T.js} +2 -2
- package/build/server/chunks/9-fL5zqN0T.js.map +1 -0
- package/build/server/chunks/{_server.ts-BrRZXr-8.js → _server.ts-BA_uWcPw.js} +9 -9
- package/build/server/chunks/_server.ts-BA_uWcPw.js.map +1 -0
- package/build/server/chunks/{_server.ts-C6xbNz6d.js → _server.ts-Bu3s5hfv.js} +3 -3
- package/build/server/chunks/{_server.ts-C6xbNz6d.js.map → _server.ts-Bu3s5hfv.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Cq9_scaV.js → _server.ts-CwAjt91u.js} +18 -18
- package/build/server/chunks/_server.ts-CwAjt91u.js.map +1 -0
- package/build/server/chunks/{_server.ts-CFX-S_8q.js → _server.ts-DZ5naqSL.js} +2 -2
- package/build/server/chunks/{_server.ts-CFX-S_8q.js.map → _server.ts-DZ5naqSL.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Dekgb6Hx.js → _server.ts-DZP2lhaY.js} +3 -3
- package/build/server/chunks/{_server.ts-Dekgb6Hx.js.map → _server.ts-DZP2lhaY.js.map} +1 -1
- package/build/server/chunks/_server.ts-DZgfQKiH.js +81 -0
- package/build/server/chunks/_server.ts-DZgfQKiH.js.map +1 -0
- package/build/server/chunks/{_server.ts-CjK0g9dO.js → _server.ts-MbnroWEF.js} +25 -16
- package/build/server/chunks/_server.ts-MbnroWEF.js.map +1 -0
- package/build/server/chunks/{pty-manager-aFpChJah.js → pty-manager-DmNSCKAr.js} +99 -2
- package/build/server/chunks/pty-manager-DmNSCKAr.js.map +1 -0
- package/build/server/chunks/qwen-reader-DGfUbKaJ.js +2112 -0
- package/build/server/chunks/qwen-reader-DGfUbKaJ.js.map +1 -0
- package/build/server/chunks/{_server.ts-D--_NXt2.js → registry-Kcw2UCMv.js} +132 -106
- package/build/server/chunks/registry-Kcw2UCMv.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +16 -16
- package/build/server/manifest.js.map +1 -1
- package/package.json +2 -2
- package/scripts/e2e-all-features.sh +165 -0
- package/scripts/e2e-cross-terminal.sh +168 -0
- package/server.ts +12 -0
- package/src/lib/modules/client/common/index.ts +1 -1
- package/src/lib/modules/client/common/provider.ts +11 -0
- package/src/lib/modules/client/terminal/ChatView.svelte +9 -2
- package/src/lib/modules/client/terminal/LaunchSheet.svelte +4 -0
- package/src/lib/modules/server/sessions/amp-reader.ts +439 -0
- package/src/lib/modules/server/sessions/codex-reader.ts +34 -33
- package/src/lib/modules/server/sessions/copilot-reader.ts +542 -0
- package/src/lib/modules/server/sessions/cursor-reader.ts +634 -0
- package/src/lib/modules/server/sessions/gemini-reader.ts +594 -0
- package/src/lib/modules/server/sessions/opencode-db-path.ts +19 -10
- package/src/lib/modules/server/sessions/opencode-reader.ts +13 -12
- package/src/lib/modules/server/sessions/process-detector.ts +39 -18
- package/src/lib/modules/server/sessions/provider-paths.ts +173 -0
- package/src/lib/modules/server/sessions/qwen-reader.ts +336 -0
- package/src/lib/modules/server/sessions/registry.ts +178 -0
- package/src/lib/modules/server/terminal/codex-watcher.ts +4 -1
- package/src/lib/modules/server/terminal/generic-session-watcher.ts +163 -0
- package/src/lib/modules/server/terminal/pty-manager.ts +51 -0
- package/src/lib/modules/server/ws/session-handler.ts +34 -20
- package/src/lib/theme.css +32 -0
- package/src/lib/types/gemini.ts +100 -0
- package/src/lib/types/generated/Sessions.ts +17 -1
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/server.ts +23 -6
- package/src/lib/types/sessions.ts +14 -2
- package/src/routes/api/sessions/+server.ts +5 -52
- package/src/routes/api/sessions/connect/+server.ts +18 -11
- package/src/routes/api/terminals/+server.ts +7 -5
- package/src/routes/terminals/+page.svelte +7 -2
- package/src/routes/terminals/[id]/+page.svelte +1 -2
- package/build/client/_app/immutable/assets/0.DEfoFaGR.css.br +0 -0
- package/build/client/_app/immutable/assets/0.DEfoFaGR.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Bkqjn62J.js +0 -1
- package/build/client/_app/immutable/chunks/Bkqjn62J.js.br +0 -1
- package/build/client/_app/immutable/chunks/Bkqjn62J.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DOHhmtDH.js +0 -3
- package/build/client/_app/immutable/chunks/DOHhmtDH.js.br +0 -0
- package/build/client/_app/immutable/chunks/DOHhmtDH.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DlS3abGJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/DlS3abGJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js +0 -1
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js.br +0 -0
- package/build/client/_app/immutable/chunks/Pw0jDB7M.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CSJG7N9H.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CSJG7N9H.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CTt1901T.js +0 -1
- package/build/client/_app/immutable/entry/start.CTt1901T.js.br +0 -2
- package/build/client/_app/immutable/entry/start.CTt1901T.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.qOL7xtFn.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.qOL7xtFn.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Di708Ago.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Di708Ago.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DSM1znqa.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DSM1znqa.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BPa5fh75.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BPa5fh75.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js +0 -1
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B1LwwEF-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.B7UJd8GQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.B7UJd8GQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js +0 -2
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.CG0mrgBU.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js +0 -2
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.KwzWaMHj.js.gz +0 -0
- package/build/server/chunks/6-bPDbH1_W.js.map +0 -1
- package/build/server/chunks/7-CpBrOkxQ.js.map +0 -1
- package/build/server/chunks/8-BRGAVfze.js.map +0 -1
- package/build/server/chunks/9-C6xuAb_Y.js.map +0 -1
- package/build/server/chunks/_server.ts-BrRZXr-8.js.map +0 -1
- package/build/server/chunks/_server.ts-CjK0g9dO.js.map +0 -1
- package/build/server/chunks/_server.ts-Cq9_scaV.js.map +0 -1
- package/build/server/chunks/_server.ts-D--_NXt2.js.map +0 -1
- package/build/server/chunks/opencode-db-path-CRgzBK5U.js +0 -402
- package/build/server/chunks/opencode-db-path-CRgzBK5U.js.map +0 -1
- package/build/server/chunks/pty-manager-aFpChJah.js.map +0 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry — the single place that knows every AI-agent provider.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by wesm/agentsview's AgentDef registry: instead of branching on
|
|
5
|
+
* `command === 'claude'` across ~14 call sites, the session API, the connect
|
|
6
|
+
* route, and the terminal allowlist all derive from this list. Adding a
|
|
7
|
+
* provider is one entry here (+ its reader) rather than edits everywhere.
|
|
8
|
+
*
|
|
9
|
+
* Detection (process-detector) and live watching (server.ts adapter) stay
|
|
10
|
+
* provider-specific because their mechanisms differ too much to unify cheaply.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ConversationMessage, ProjectGroup, ProviderDef } from '$lib/types';
|
|
14
|
+
|
|
15
|
+
import { getAmpConversation, listAmpProjects } from './amp-reader';
|
|
16
|
+
import { getCodexConversation, listCodexProjects } from './codex-reader';
|
|
17
|
+
import { getCopilotConversation, listCopilotProjects } from './copilot-reader';
|
|
18
|
+
import { getCursorConversation, listCursorProjects } from './cursor-reader';
|
|
19
|
+
import { getGeminiConversation, listGeminiProjects } from './gemini-reader';
|
|
20
|
+
import { getSessionConversation, listProjectsWithSessions } from './jsonl-reader';
|
|
21
|
+
import { getOpenCodeConversation, listOpenCodeProjects } from './opencode-reader';
|
|
22
|
+
import { getQwenConversation, listQwenProjects } from './qwen-reader';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Every AI-agent provider, in merge order. `claude-code` MUST stay first: its
|
|
26
|
+
* projects seed the merge map so the canonical (decoded) project path/name wins
|
|
27
|
+
* over other providers' guesses.
|
|
28
|
+
*/
|
|
29
|
+
export const PROVIDERS: ProviderDef[] = [
|
|
30
|
+
{
|
|
31
|
+
command: 'claude',
|
|
32
|
+
getConversation: (id, offset, limit) => getSessionConversation(id, offset, limit),
|
|
33
|
+
isAI: true,
|
|
34
|
+
label: 'Claude Code',
|
|
35
|
+
listProjects: listProjectsWithSessions,
|
|
36
|
+
resumeArgs: (id) => ['--resume', id],
|
|
37
|
+
source: 'claude-code',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
command: 'opencode',
|
|
41
|
+
getConversation: getOpenCodeConversation,
|
|
42
|
+
isAI: true,
|
|
43
|
+
label: 'OpenCode',
|
|
44
|
+
listProjects: listOpenCodeProjects,
|
|
45
|
+
nameSuffix: ' (OpenCode)',
|
|
46
|
+
resumeArgs: (id) => ['--session', id],
|
|
47
|
+
source: 'opencode',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
command: 'codex',
|
|
51
|
+
getConversation: getCodexConversation,
|
|
52
|
+
isAI: true,
|
|
53
|
+
label: 'Codex',
|
|
54
|
+
listProjects: listCodexProjects,
|
|
55
|
+
resumeArgs: (id) => ['resume', id],
|
|
56
|
+
source: 'codex',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
command: 'gemini',
|
|
60
|
+
getConversation: getGeminiConversation,
|
|
61
|
+
isAI: true,
|
|
62
|
+
label: 'Gemini',
|
|
63
|
+
listProjects: listGeminiProjects,
|
|
64
|
+
resumeArgs: () => [], // Gemini CLI has no session-resume flag
|
|
65
|
+
source: 'gemini',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
command: 'qwen',
|
|
69
|
+
getConversation: getQwenConversation,
|
|
70
|
+
isAI: true,
|
|
71
|
+
label: 'Qwen',
|
|
72
|
+
listProjects: listQwenProjects,
|
|
73
|
+
resumeArgs: () => [],
|
|
74
|
+
source: 'qwen',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: 'cursor-agent',
|
|
78
|
+
getConversation: getCursorConversation,
|
|
79
|
+
isAI: true,
|
|
80
|
+
label: 'Cursor',
|
|
81
|
+
listProjects: listCursorProjects,
|
|
82
|
+
resumeArgs: () => [],
|
|
83
|
+
source: 'cursor',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
command: 'copilot',
|
|
87
|
+
getConversation: getCopilotConversation,
|
|
88
|
+
isAI: true,
|
|
89
|
+
label: 'Copilot',
|
|
90
|
+
listProjects: listCopilotProjects,
|
|
91
|
+
resumeArgs: () => [],
|
|
92
|
+
source: 'copilot',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
command: 'amp',
|
|
96
|
+
getConversation: getAmpConversation,
|
|
97
|
+
isAI: true,
|
|
98
|
+
label: 'Amp',
|
|
99
|
+
listProjects: listAmpProjects,
|
|
100
|
+
resumeArgs: () => [],
|
|
101
|
+
source: 'amp',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/** AI-agent binary names (for AI_COMMANDS-style checks). */
|
|
106
|
+
export const AI_COMMANDS: string[] = PROVIDERS.filter((p) => p.isAI).map((p) => p.command);
|
|
107
|
+
|
|
108
|
+
/** All provider binary names (for the terminal allowlist + connect validation). */
|
|
109
|
+
export const PROVIDER_COMMANDS: string[] = PROVIDERS.map((p) => p.command);
|
|
110
|
+
|
|
111
|
+
/** Resolve a session's conversation across providers (Claude first, with its project dir). */
|
|
112
|
+
export function getProviderConversation(
|
|
113
|
+
sessionId: string,
|
|
114
|
+
offset: number,
|
|
115
|
+
limit: number,
|
|
116
|
+
claudeProjectDir?: string
|
|
117
|
+
): ConversationMessage[] {
|
|
118
|
+
const claude = getSessionConversation(sessionId, offset, limit, claudeProjectDir);
|
|
119
|
+
if (claude.length > 0) {
|
|
120
|
+
return claude;
|
|
121
|
+
}
|
|
122
|
+
for (const provider of PROVIDERS) {
|
|
123
|
+
if (provider.source === 'claude-code') {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const messages = provider.getConversation(sessionId, offset, limit);
|
|
128
|
+
if (messages.length > 0) {
|
|
129
|
+
return messages;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// a failing provider reader must not break resolution — try the next
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Merge every provider's projects, deduplicating by absolute path. */
|
|
139
|
+
export function listAllProviderProjects(): ProjectGroup[] {
|
|
140
|
+
const byPath = new Map<string, ProjectGroup>();
|
|
141
|
+
for (const provider of PROVIDERS) {
|
|
142
|
+
let groups: ProjectGroup[];
|
|
143
|
+
try {
|
|
144
|
+
groups = provider.listProjects();
|
|
145
|
+
} catch {
|
|
146
|
+
continue; // a broken provider must not take down the whole listing
|
|
147
|
+
}
|
|
148
|
+
for (const group of groups) {
|
|
149
|
+
try {
|
|
150
|
+
const name =
|
|
151
|
+
provider.nameSuffix && typeof group.name === 'string'
|
|
152
|
+
? group.name.replace(provider.nameSuffix, '')
|
|
153
|
+
: group.name;
|
|
154
|
+
const existing = byPath.get(group.fullPath);
|
|
155
|
+
if (existing) {
|
|
156
|
+
existing.sessions.push(...group.sessions);
|
|
157
|
+
existing.sessions.sort(
|
|
158
|
+
(a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime()
|
|
159
|
+
);
|
|
160
|
+
existing.sessionCount = existing.sessions.length;
|
|
161
|
+
existing.lastModified = existing.sessions[0]?.modified || existing.lastModified;
|
|
162
|
+
} else {
|
|
163
|
+
byPath.set(group.fullPath, { ...group, name });
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// skip a malformed group rather than dropping the rest of the provider
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return [...byPath.values()].sort(
|
|
171
|
+
(a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Resume args for a launched command (e.g. `codex resume <id>`). */
|
|
176
|
+
export function resumeArgsForCommand(command: string, sessionId: string): string[] {
|
|
177
|
+
return PROVIDERS.find((p) => p.command === command)?.resumeArgs(sessionId) ?? [];
|
|
178
|
+
}
|
|
@@ -15,6 +15,7 @@ import { watch as chokidarWatch } from 'chokidar';
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
|
|
17
17
|
import { CodexStreamParser, parseCodexRollout } from '../sessions/codex-parser';
|
|
18
|
+
import { readBoundedRolloutText } from '../sessions/codex-reader';
|
|
18
19
|
|
|
19
20
|
/** Flush the open run after this many ms without a write. */
|
|
20
21
|
const IDLE_FLUSH_MS = 1500;
|
|
@@ -28,7 +29,9 @@ class CodexWatcher {
|
|
|
28
29
|
return [];
|
|
29
30
|
}
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
+
// Bounded read — rollout files can be hundreds of MB; this is called on
|
|
33
|
+
// every WS session connection (mirrors getCodexConversation).
|
|
34
|
+
return parseCodexRollout(readBoundedRolloutText(filePath)).messages;
|
|
32
35
|
} catch (error) {
|
|
33
36
|
console.error(`[codex-watcher] Failed to read history for ${filePath}:`, error);
|
|
34
37
|
return [];
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenericSessionWatcher — live tailing for the five read-only providers
|
|
3
|
+
* (cursor, copilot, qwen, gemini, amp).
|
|
4
|
+
*
|
|
5
|
+
* The byte-offset watchers (claude/codex) and the SQLite poller (opencode)
|
|
6
|
+
* each understand one wire format. These five providers store sessions in
|
|
7
|
+
* heterogeneous shapes — per-turn JSONL for some, a single rewritten JSON
|
|
8
|
+
* document for others — so a byte-incremental reader would need five bespoke
|
|
9
|
+
* parsers. Instead this watcher re-parses the whole file on each change via
|
|
10
|
+
* the shared `parseReadOnlyProviderFile` dispatch and emits only messages
|
|
11
|
+
* whose ID it has not delivered before. That makes it correct for both
|
|
12
|
+
* append-only and whole-document-rewrite formats with one code path.
|
|
13
|
+
*
|
|
14
|
+
* The public surface (getHistory + ref-counted subscribe) matches
|
|
15
|
+
* SessionWatcherLike, so the server's session-watcher adapter can route to it
|
|
16
|
+
* by path without any handler changes.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ConversationMessage, GenericWatchedFile, OnNewEntries } from '$lib/types';
|
|
20
|
+
|
|
21
|
+
import { watch as chokidarWatch } from 'chokidar';
|
|
22
|
+
|
|
23
|
+
import { parseReadOnlyProviderFile } from '../sessions/provider-paths';
|
|
24
|
+
|
|
25
|
+
class GenericSessionWatcher {
|
|
26
|
+
private watched = new Map<string, GenericWatchedFile>();
|
|
27
|
+
|
|
28
|
+
/** Re-read the whole file and return the full parsed conversation. */
|
|
29
|
+
getHistory(filePath: string): ConversationMessage[] {
|
|
30
|
+
return parseReadOnlyProviderFile(filePath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Stop watching a single file and release its chokidar handle. */
|
|
34
|
+
stop(filePath: string): void {
|
|
35
|
+
const watched = this.watched.get(filePath);
|
|
36
|
+
if (!watched) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
void watched.watcher.close();
|
|
40
|
+
this.watched.delete(filePath);
|
|
41
|
+
console.log(`[generic-watcher] Stopped watching: ${filePath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Stop watching every file. */
|
|
45
|
+
stopAll(): void {
|
|
46
|
+
for (const [filePath] of this.watched) {
|
|
47
|
+
this.stop(filePath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Subscribe to new messages for a file. Adds the callback to an existing
|
|
53
|
+
* watcher when one is present (ref-counted), otherwise starts a chokidar
|
|
54
|
+
* watch. Returns an unsubscribe function that tears the watcher down once
|
|
55
|
+
* the last subscriber leaves — identical lifecycle to SessionWatcher.
|
|
56
|
+
*/
|
|
57
|
+
subscribe(filePath: string, onNewEntries: OnNewEntries): () => void {
|
|
58
|
+
const existing = this.watched.get(filePath);
|
|
59
|
+
if (existing) {
|
|
60
|
+
existing.callbacks.add(onNewEntries);
|
|
61
|
+
return () => {
|
|
62
|
+
this.removeCallback(filePath, onNewEntries);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Seed the emitted set with everything already in the file so the first
|
|
67
|
+
// change only surfaces genuinely new messages — history is sent separately
|
|
68
|
+
// via getHistory, mirroring the byte watchers' initial-offset behaviour.
|
|
69
|
+
const emittedMessageIds = new Set<string>();
|
|
70
|
+
try {
|
|
71
|
+
for (const msg of parseReadOnlyProviderFile(filePath)) {
|
|
72
|
+
emittedMessageIds.add(msg.id);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Degrade gracefully: an unreadable/racy file at subscribe time must not
|
|
76
|
+
// prevent watching — the first change will just re-surface what's there.
|
|
77
|
+
console.error(`[generic-watcher] Failed to seed ${filePath}:`, error);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const watcher = chokidarWatch(filePath, {
|
|
81
|
+
awaitWriteFinish: { pollInterval: 100, stabilityThreshold: 200 },
|
|
82
|
+
ignoreInitial: true,
|
|
83
|
+
usePolling: false,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const watched: GenericWatchedFile = {
|
|
87
|
+
callbacks: new Set([onNewEntries]),
|
|
88
|
+
emittedMessageIds,
|
|
89
|
+
filePath,
|
|
90
|
+
watcher,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const onChange = (): void => {
|
|
94
|
+
this.readNew(watched);
|
|
95
|
+
};
|
|
96
|
+
watcher.on('add', onChange);
|
|
97
|
+
watcher.on('change', onChange);
|
|
98
|
+
watcher.on('error', (error) => {
|
|
99
|
+
console.error(`[generic-watcher] Error watching ${filePath}:`, error);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.watched.set(filePath, watched);
|
|
103
|
+
console.log(`[generic-watcher] Watching: ${filePath} (seeded ${emittedMessageIds.size} msgs)`);
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
this.removeCallback(filePath, onNewEntries);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Legacy fire-and-forget API matching SessionWatcher.watch(). */
|
|
111
|
+
watch(filePath: string, onNewEntries: OnNewEntries): void {
|
|
112
|
+
this.subscribe(filePath, onNewEntries);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Re-parse the file and deliver any messages whose ID has not been emitted.
|
|
117
|
+
* If a rewrite drops the count below what we have seen (truncation/reset),
|
|
118
|
+
* the new IDs simply won't match, so nothing spurious is sent.
|
|
119
|
+
*/
|
|
120
|
+
private readNew(watched: GenericWatchedFile): void {
|
|
121
|
+
let messages: ConversationMessage[];
|
|
122
|
+
try {
|
|
123
|
+
messages = parseReadOnlyProviderFile(watched.filePath);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(`[generic-watcher] Failed to re-read ${watched.filePath}:`, error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const fresh = messages.filter((msg) => !watched.emittedMessageIds.has(msg.id));
|
|
130
|
+
if (fresh.length === 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const msg of fresh) {
|
|
134
|
+
watched.emittedMessageIds.add(msg.id);
|
|
135
|
+
}
|
|
136
|
+
for (const cb of watched.callbacks) {
|
|
137
|
+
try {
|
|
138
|
+
cb(fresh);
|
|
139
|
+
} catch (cbError) {
|
|
140
|
+
console.error('[generic-watcher] Callback error:', cbError);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Remove one callback; stop watching when none remain. */
|
|
146
|
+
private removeCallback(filePath: string, onNewEntries: OnNewEntries): void {
|
|
147
|
+
const watched = this.watched.get(filePath);
|
|
148
|
+
if (!watched) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
watched.callbacks.delete(onNewEntries);
|
|
152
|
+
if (watched.callbacks.size === 0) {
|
|
153
|
+
this.stop(filePath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Single shared instance across module loaders (matches sessionWatcher).
|
|
159
|
+
const GW_GLOBAL_KEY = '__shooter_generic_session_watcher';
|
|
160
|
+
export const genericSessionWatcher: GenericSessionWatcher =
|
|
161
|
+
((globalThis as Record<string, unknown>)[GW_GLOBAL_KEY] as GenericSessionWatcher) ||
|
|
162
|
+
new GenericSessionWatcher();
|
|
163
|
+
(globalThis as Record<string, unknown>)[GW_GLOBAL_KEY] = genericSessionWatcher;
|
|
@@ -13,6 +13,10 @@ import path from 'path';
|
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
|
|
15
15
|
import { findCodexRolloutForCwd } from '../sessions/codex-reader';
|
|
16
|
+
import {
|
|
17
|
+
discoverReadOnlyProviderSessionFile,
|
|
18
|
+
readOnlySourceForCommand,
|
|
19
|
+
} from '../sessions/provider-paths';
|
|
16
20
|
import { broadcastEvent } from '../ws/server.js';
|
|
17
21
|
import { HolderClient } from './holder-client';
|
|
18
22
|
import { openCodeWatcher } from './opencode-watcher';
|
|
@@ -1005,6 +1009,53 @@ class PtyManager {
|
|
|
1005
1009
|
5 * 60 * 1000
|
|
1006
1010
|
);
|
|
1007
1011
|
}
|
|
1012
|
+
|
|
1013
|
+
// For the read-only providers (cursor/copilot/qwen/gemini/amp): poll their
|
|
1014
|
+
// store for a session started after launch and matching this cwd, then set
|
|
1015
|
+
// sessionFile to that path. The WS adapter routes it to the generic watcher
|
|
1016
|
+
// by its provider-root path, giving the same live-tail the JSONL watchers
|
|
1017
|
+
// provide for Claude/Codex.
|
|
1018
|
+
const readOnlySource = readOnlySourceForCommand(command);
|
|
1019
|
+
if (readOnlySource) {
|
|
1020
|
+
const launchTime = terminal.createdAt.getTime();
|
|
1021
|
+
terminal.pollTimer = setInterval(() => {
|
|
1022
|
+
if (terminal.status === 'exited' || terminal.sessionFile) {
|
|
1023
|
+
if (terminal.pollTimer) {
|
|
1024
|
+
clearInterval(terminal.pollTimer);
|
|
1025
|
+
terminal.pollTimer = null;
|
|
1026
|
+
}
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
try {
|
|
1030
|
+
const file = discoverReadOnlyProviderSessionFile(
|
|
1031
|
+
readOnlySource,
|
|
1032
|
+
cwd,
|
|
1033
|
+
launchTime,
|
|
1034
|
+
Date.now()
|
|
1035
|
+
);
|
|
1036
|
+
if (file) {
|
|
1037
|
+
terminal.sessionFile = file;
|
|
1038
|
+
if (terminal.pollTimer) {
|
|
1039
|
+
clearInterval(terminal.pollTimer);
|
|
1040
|
+
terminal.pollTimer = null;
|
|
1041
|
+
}
|
|
1042
|
+
terminalStore.update(id, { sessionFile: file });
|
|
1043
|
+
}
|
|
1044
|
+
} catch {
|
|
1045
|
+
// ignore filesystem errors
|
|
1046
|
+
}
|
|
1047
|
+
}, 2000);
|
|
1048
|
+
|
|
1049
|
+
setTimeout(
|
|
1050
|
+
() => {
|
|
1051
|
+
if (terminal.pollTimer) {
|
|
1052
|
+
clearInterval(terminal.pollTimer);
|
|
1053
|
+
terminal.pollTimer = null;
|
|
1054
|
+
}
|
|
1055
|
+
},
|
|
1056
|
+
5 * 60 * 1000
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1008
1059
|
}
|
|
1009
1060
|
|
|
1010
1061
|
/** Wire up all HolderClient callbacks (activity, CWD, output, exit, disconnect). */
|
|
@@ -22,6 +22,8 @@ import type { WebSocket } from 'ws';
|
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
|
|
25
|
+
import { findCodexRolloutById } from '../sessions/codex-reader';
|
|
26
|
+
|
|
25
27
|
// ── Module-level references ──────────────────────────────────────────
|
|
26
28
|
|
|
27
29
|
let _ptyManager: null | PtyManagerLike = null;
|
|
@@ -171,34 +173,36 @@ function conversationToLive(msg: ConversationMessage): ServerMessage[] {
|
|
|
171
173
|
* Returns the absolute path if found, or null.
|
|
172
174
|
*/
|
|
173
175
|
function findJsonlFileForSession(sessionId: string): null | string {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
176
|
+
// Reject anything that could traverse out of the session directories before
|
|
177
|
+
// it is interpolated into a filesystem path.
|
|
178
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
177
179
|
return null;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
const claudeProjectsDir = path.join(process.env.HOME || '', '.claude', 'projects');
|
|
183
|
+
if (fs.existsSync(claudeProjectsDir)) {
|
|
184
|
+
try {
|
|
185
|
+
for (const dir of fs.readdirSync(claudeProjectsDir)) {
|
|
186
|
+
const fullDir = path.join(claudeProjectsDir, dir);
|
|
187
|
+
try {
|
|
188
|
+
if (!fs.statSync(fullDir).isDirectory()) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
186
192
|
continue;
|
|
187
193
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const jsonlPath = path.join(fullDir, `${sessionId}.jsonl`);
|
|
193
|
-
if (fs.existsSync(jsonlPath)) {
|
|
194
|
-
return jsonlPath;
|
|
194
|
+
const jsonlPath = path.join(fullDir, `${sessionId}.jsonl`);
|
|
195
|
+
if (fs.existsSync(jsonlPath)) {
|
|
196
|
+
return jsonlPath;
|
|
197
|
+
}
|
|
195
198
|
}
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore filesystem errors
|
|
196
201
|
}
|
|
197
|
-
} catch {
|
|
198
|
-
// Ignore filesystem errors
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
// Fall back to an external Codex session: ~/.codex/sessions/**/rollout-*-<id>.jsonl
|
|
205
|
+
return findCodexRolloutById(sessionId);
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
@@ -218,7 +222,17 @@ function findTerminalBySessionUuid(uuid: string): ManagedTerminal | undefined {
|
|
|
218
222
|
// module-level _ptyManagerFull reference if available.
|
|
219
223
|
if (_ptyManagerFull) {
|
|
220
224
|
for (const t of _ptyManagerFull.list()) {
|
|
221
|
-
|
|
225
|
+
// Match across every provider's file naming so a running non-Claude
|
|
226
|
+
// agent can be reached (and replied to) by its session UUID:
|
|
227
|
+
// claude/cursor/qwen: <uuid>.jsonl ; codex: -<uuid>.jsonl ;
|
|
228
|
+
// copilot: <uuid>.jsonl OR <uuid>/events.jsonl ; amp: T-<uuid>.json ;
|
|
229
|
+
// opencode: bare session id.
|
|
230
|
+
if (
|
|
231
|
+
t.sessionFile?.includes(`${uuid}.jsonl`) ||
|
|
232
|
+
t.sessionFile?.endsWith(`/${uuid}/events.jsonl`) ||
|
|
233
|
+
t.sessionFile?.endsWith(`/T-${uuid}.json`) ||
|
|
234
|
+
t.openCodeSessionId === uuid
|
|
235
|
+
) {
|
|
222
236
|
return _ptyManager.getTerminal(t.id);
|
|
223
237
|
}
|
|
224
238
|
}
|
package/src/lib/theme.css
CHANGED
|
@@ -526,6 +526,38 @@
|
|
|
526
526
|
--pill-cursor: inherit;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
/* Additional providers reuse the existing colour families (cosmetic only). */
|
|
530
|
+
.pill-source-qwen {
|
|
531
|
+
--pill-background: var(--ds-red-100);
|
|
532
|
+
--pill-color: var(--ds-red-900);
|
|
533
|
+
--pill-font-size: 10px;
|
|
534
|
+
--pill-padding: 2px 8px;
|
|
535
|
+
--pill-hover-background: var(--ds-red-100);
|
|
536
|
+
--pill-hover-color: var(--ds-red-900);
|
|
537
|
+
--pill-cursor: inherit;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.pill-source-cursor {
|
|
541
|
+
--pill-background: var(--ds-blue-100);
|
|
542
|
+
--pill-color: var(--ds-blue-900);
|
|
543
|
+
--pill-font-size: 10px;
|
|
544
|
+
--pill-padding: 2px 8px;
|
|
545
|
+
--pill-hover-background: var(--ds-blue-100);
|
|
546
|
+
--pill-hover-color: var(--ds-blue-900);
|
|
547
|
+
--pill-cursor: inherit;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.pill-source-copilot,
|
|
551
|
+
.pill-source-amp {
|
|
552
|
+
--pill-background: var(--ds-amber-100);
|
|
553
|
+
--pill-color: var(--ds-amber-900);
|
|
554
|
+
--pill-font-size: 10px;
|
|
555
|
+
--pill-padding: 2px 8px;
|
|
556
|
+
--pill-hover-background: var(--ds-amber-100);
|
|
557
|
+
--pill-hover-color: var(--ds-amber-900);
|
|
558
|
+
--pill-cursor: inherit;
|
|
559
|
+
}
|
|
560
|
+
|
|
529
561
|
/* ===== Icon Sizes ===== */
|
|
530
562
|
.icon-14 {
|
|
531
563
|
--icon-width: 14px;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Gemini CLI session types (hand-written: the on-disk formats are
|
|
2
|
+
// provider-specific and not worth round-tripping through the YAML codegen).
|
|
3
|
+
// Gemini CLI stores user messages in ~/.gemini/tmp/<projectHash>/logs.json
|
|
4
|
+
// and full conversation records in
|
|
5
|
+
// ~/.gemini/tmp/<projectHash>/chats/session-*.json (newer versions only).
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// logs.json — user-messages-only format (all versions)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** Union of all part shapes that appear in a ConversationRecord message. */
|
|
12
|
+
export type GeminiContentPart = GeminiFunctionCallPart | GeminiTextPart | GeminiThoughtPart;
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// chats/session-*.json — full ConversationRecord (newer versions)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** Full conversation record stored in chats/session-*.json. */
|
|
19
|
+
export interface GeminiConversationRecord {
|
|
20
|
+
directories?: string[];
|
|
21
|
+
kind?: 'main' | 'subagent';
|
|
22
|
+
lastUpdated: string;
|
|
23
|
+
messages: GeminiMessageRecord[];
|
|
24
|
+
projectHash: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
startTime: string;
|
|
27
|
+
summary?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Inline function-call part from the Google GenAI SDK. */
|
|
31
|
+
export interface GeminiFunctionCallPart {
|
|
32
|
+
functionCall: {
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
id?: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A single entry in ~/.gemini/tmp/<projectHash>/logs.json. */
|
|
40
|
+
export interface GeminiLogEntry {
|
|
41
|
+
message: string;
|
|
42
|
+
messageId: number;
|
|
43
|
+
sessionId: string;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
type: 'user';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A single message record in a full ConversationRecord. */
|
|
49
|
+
export type GeminiMessageRecord =
|
|
50
|
+
| {
|
|
51
|
+
content: GeminiContentPart[] | string;
|
|
52
|
+
id: string;
|
|
53
|
+
thoughts?: GeminiThoughtSummary[];
|
|
54
|
+
timestamp: string;
|
|
55
|
+
toolCalls?: GeminiToolCallRecord[];
|
|
56
|
+
type: 'gemini';
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
content: GeminiContentPart[] | string;
|
|
60
|
+
id: string;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
type: 'error' | 'info' | 'user' | 'warning';
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Contents of ~/.gemini/projects.json (present only in newer gemini-cli). */
|
|
66
|
+
export type GeminiProjectsJson = Record<string, string>;
|
|
67
|
+
|
|
68
|
+
/** Plain-text content part from the Google GenAI SDK. */
|
|
69
|
+
export interface GeminiTextPart {
|
|
70
|
+
text: string;
|
|
71
|
+
thought?: false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Inline thinking/reasoning part from the Google GenAI SDK. */
|
|
75
|
+
export interface GeminiThoughtPart {
|
|
76
|
+
text: string;
|
|
77
|
+
thought: true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A thought-summary entry attached to a 'gemini'-type message. */
|
|
81
|
+
export interface GeminiThoughtSummary {
|
|
82
|
+
summary?: string;
|
|
83
|
+
timestamp: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// projects.json — project slug → absolute path registry (newer versions)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/** A single tool-call record attached to a 'gemini'-type message. */
|
|
91
|
+
export interface GeminiToolCallRecord {
|
|
92
|
+
args: Record<string, unknown>;
|
|
93
|
+
description?: string;
|
|
94
|
+
displayName?: string;
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
result?: unknown;
|
|
98
|
+
status: 'cancelled' | 'error' | 'pending' | 'success';
|
|
99
|
+
timestamp: string;
|
|
100
|
+
}
|
|
@@ -14,7 +14,15 @@ import {
|
|
|
14
14
|
* @type { SessionSource }
|
|
15
15
|
* @description Source tool that produced the session
|
|
16
16
|
*/
|
|
17
|
-
export type SessionSource =
|
|
17
|
+
export type SessionSource =
|
|
18
|
+
| 'claude-code'
|
|
19
|
+
| 'opencode'
|
|
20
|
+
| 'codex'
|
|
21
|
+
| 'gemini'
|
|
22
|
+
| 'qwen'
|
|
23
|
+
| 'cursor'
|
|
24
|
+
| 'copilot'
|
|
25
|
+
| 'amp';
|
|
18
26
|
|
|
19
27
|
export function decodeSessionSource(rawInput: unknown): SessionSource | null {
|
|
20
28
|
switch (rawInput) {
|
|
@@ -22,6 +30,10 @@ export function decodeSessionSource(rawInput: unknown): SessionSource | null {
|
|
|
22
30
|
case 'opencode':
|
|
23
31
|
case 'codex':
|
|
24
32
|
case 'gemini':
|
|
33
|
+
case 'qwen':
|
|
34
|
+
case 'cursor':
|
|
35
|
+
case 'copilot':
|
|
36
|
+
case 'amp':
|
|
25
37
|
return rawInput;
|
|
26
38
|
}
|
|
27
39
|
return null;
|
|
@@ -33,6 +45,10 @@ export function _decodeSessionSource(rawInput: unknown): SessionSource | undefin
|
|
|
33
45
|
case 'opencode':
|
|
34
46
|
case 'codex':
|
|
35
47
|
case 'gemini':
|
|
48
|
+
case 'qwen':
|
|
49
|
+
case 'cursor':
|
|
50
|
+
case 'copilot':
|
|
51
|
+
case 'amp':
|
|
36
52
|
return rawInput;
|
|
37
53
|
}
|
|
38
54
|
return;
|