termdock 1.4.38 → 1.4.40
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/dist/client/assets/{abnfDiagram-VRR7QNED-DsRHqRKj.js → abnfDiagram-VRR7QNED-1BbN_Bdc.js} +1 -1
- package/dist/client/assets/{arc-D5aRHB8f.js → arc-B4X31X3m.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-FbRXS-d6.js → architectureDiagram-ZJ3FMSHR-BlauOFgG.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-loWzVfeN.js → blockDiagram-677ZJIJ3-B0mtlYc1.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-z6T_C8bO.js → c4Diagram-LMCZKHZV-B52qGtrK.js} +1 -1
- package/dist/client/assets/channel-D2AA77kQ.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CqG-SvII.js → chunk-2Q5K7J3B-TkkyEbTr.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-0zL-WEd8.js → chunk-32BRIVSS-rmkLGfWN.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CPYKdlUm.js → chunk-5VM5RSS4-CifeRLjF.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-DJ1zpvJe.js → chunk-EX3LRPZG-B_bint1e.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-Ckoj6xXY.js → chunk-JWPE2WC7-DOZwisqR.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-CA3Nm7t1.js → chunk-MOJQB5TN-BEWrO_py.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-C-ifDv4n.js → chunk-RYQCIY6F-DM20-r1x.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-B-FIe5fo.js → chunk-V7JOEXUC-06fOLFGY.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-CmIFAiFd.js → chunk-VR4S4FIN-C9ivxt4F.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CdoSUjQU.js → chunk-XXDRQBXY-BU98Kf52.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-DFpEzsId.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-DFpEzsId.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CHU0yaXN.js → cose-bilkent-JH36ORCC-036xKocd.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-D6-Ma9sC.js → cynefin-VYW2F7L2-Nqf06Kmu.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-90QatrQq.js → cynefinDiagram-TSTJHNR4-Qa8iUoZZ.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-DdKyNy6i.js → dagre-VKFMJZFB-q5f0Dltl.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-9Hb1AzJf.js → diagram-FQU43EPY-CIVowYlH.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-CYvxq8hr.js → diagram-G47NLZAW-Dv1vCJ3c.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-BwOK3wm7.js → diagram-NH7WQ7WH-lLyO_C5q.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-69v6bBie.js → diagram-OA4YK3LP-3cNxfhEy.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-CM-W_1QO.js → diagram-WEI45ONY-BLRqKjFK.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DF49qxpN.js → ebnfDiagram-CCIWWBDH-CsSbfhPV.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-CoLDcoFF.js → erDiagram-Q63AITRT-Dx9mks1A.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-y8NvyxFE.js → flowDiagram-23GEKE2U-CqDiBbzG.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-CJPYo7TO.js → ganttDiagram-NO4QXBWP-Ch-l1-IG.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-Dpd4PkU9.js → gitGraphDiagram-IHSO6WYX-CRhTdG6q.js} +1 -1
- package/dist/client/assets/index-B2_LqlRR.js +430 -0
- package/dist/client/assets/index-BQkNIHH4.css +1 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-B8EUPVad.js → infoDiagram-FWYZ7A6U-B3fKiraO.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CvRk7Maz.js → ishikawaDiagram-FXEZZL3T-DBG_zMtG.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-nBMG1OD3.js → journeyDiagram-5HDEW3XC-Bl1jwFh0.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CgVb7EQi.js → kanban-definition-HUTT4EX6-D0bYnuMX.js} +1 -1
- package/dist/client/assets/{linear-8cbNA5xR.js → linear-Bqm0-Qpp.js} +1 -1
- package/dist/client/assets/{mermaid.core-oQNFhFo1.js → mermaid.core-EBsM-F0c.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-DNjz8EZH.js → mindmap-definition-LN4V7U3C-BQin-jQj.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DYqUqAEf.js → pegDiagram-2B236MQR-HTTycFMY.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-CP_uLGw0.js → pieDiagram-ENE6RG2P-V0Je9c4E.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DFiVbYb8.js → quadrantDiagram-ABIIQ3AL-C3BCyDnb.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-B-2UsGs8.js → railroadDiagram-RFXS5EU6-CFBxVWwn.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-CljBWOcX.js → requirementDiagram-TGXJPOKE-OCuv5jiF.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-Dv1Zufob.js → sankeyDiagram-HTMAVEWB-CJDTF9-0.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-DXKZXUnf.js → sequenceDiagram-DBY2YBRQ-DNsYn886.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-D2ZCuHz7.js → sizeCapture-X5ZJPWSS-UCObVrPF.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-CLxfz-m7.js → stateDiagram-2N3HPSRC-DRVv_Dm9.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BhnNNo6s.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-BYW0DkzS.js → swimlanes-5IMT3BWC-B3pf_PPr.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-By5sWNT_.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-CcTyB2mH.js → timeline-definition-FHXFAJF6-DtXbFtyH.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BkB73IYt.js → vennDiagram-L72KCM5P-D5Ln-8Wo.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-Bj3vIf4c.js → wardleyDiagram-EHGQE667-DBg6Xz6w.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-D0koRTet.js → xychartDiagram-FW5EYKEG-_x4UP6WR.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/cli.js +55 -0
- package/dist/server/quota/QuotaManager.js +142 -0
- package/dist/server/quota/providers/claude.js +125 -0
- package/dist/server/quota/providers/codex.js +175 -0
- package/dist/server/quota/providers/copilot.js +125 -0
- package/dist/server/quota/providers/gemini.js +52 -0
- package/dist/server/quota/providers/grok.js +76 -0
- package/dist/server/quota/providers/kimi.js +113 -0
- package/dist/server/quota/tokenDiscovery.js +239 -0
- package/dist/server/quota/types.js +8 -0
- package/dist/server/routes/terminal.js +76 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-Cxs9-lsF.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-C9M6wlWI.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-C9M6wlWI.js +0 -1
- package/dist/client/assets/index-CN9znI-m.js +0 -430
- package/dist/client/assets/index-d23ibKJx.css +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-qmGgoGYE.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-DGIJpGeD.js +0 -8
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token discovery — reads auth tokens from agent configuration files.
|
|
3
|
+
*
|
|
4
|
+
* Termdock's hook installers already know where each agent stores its config.
|
|
5
|
+
* This module extracts OAuth tokens / API keys so the QuotaManager can call
|
|
6
|
+
* provider usage APIs without asking the user to re-enter credentials.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
const HOME = homedir();
|
|
12
|
+
// ── Claude / Anthropic ──────────────────────────────────────────────
|
|
13
|
+
/** Read ~/.local/share/opencode/auth.json for Anthropic OAuth tokens. */
|
|
14
|
+
function discoverFromOpenCodeAuth() {
|
|
15
|
+
const results = [];
|
|
16
|
+
const authPath = path.join(HOME, '.local/share/opencode/auth.json');
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(authPath, 'utf-8');
|
|
19
|
+
const auth = JSON.parse(raw);
|
|
20
|
+
const oauths = Array.isArray(auth) ? auth : ('oauth' in auth ? auth.oauth : [auth]);
|
|
21
|
+
const entries = Array.isArray(oauths) ? oauths : [oauths];
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.type === 'anthropic' && entry.access) {
|
|
24
|
+
results.push({
|
|
25
|
+
token: entry.access,
|
|
26
|
+
source: 'opencode auth.json',
|
|
27
|
+
accountId: entry.accountId || entry.accountLabel || undefined,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// File missing or unreadable — no Anthropic tokens from this source.
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
/** Read ~/.claude/settings.json for stored credentials. */
|
|
38
|
+
function discoverFromClaudeSettings() {
|
|
39
|
+
const results = [];
|
|
40
|
+
const settingsPath = path.join(HOME, '.claude/settings.json');
|
|
41
|
+
try {
|
|
42
|
+
const raw = fs.readFileSync(settingsPath, 'utf-8');
|
|
43
|
+
const settings = JSON.parse(raw);
|
|
44
|
+
// Claude Code stores OAuth credentials in various shapes.
|
|
45
|
+
// The most reliable source is the oauth account info.
|
|
46
|
+
const oauth = settings?.oauthAccount;
|
|
47
|
+
if (oauth?.accessToken) {
|
|
48
|
+
results.push({
|
|
49
|
+
token: oauth.accessToken,
|
|
50
|
+
source: 'claude settings.json',
|
|
51
|
+
email: oauth.email || undefined,
|
|
52
|
+
accountId: oauth.accountUuid || undefined,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Not found or unreadable.
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
export function discoverClaudeTokens() {
|
|
62
|
+
const fromOpenCode = discoverFromOpenCodeAuth().filter((t) => t.source === 'opencode auth.json');
|
|
63
|
+
// OpenCode auth.json already covers Anthropic — avoid duplicates.
|
|
64
|
+
if (fromOpenCode.length > 0) {
|
|
65
|
+
// Merge with claude settings for email enrichment
|
|
66
|
+
const fromClaude = discoverFromClaudeSettings();
|
|
67
|
+
for (const t of fromOpenCode) {
|
|
68
|
+
const match = fromClaude.find((c) => c.email && c.token !== t.token);
|
|
69
|
+
if (match && !t.email) {
|
|
70
|
+
t.email = match.email;
|
|
71
|
+
t.accountId = match.accountId;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return fromOpenCode;
|
|
75
|
+
}
|
|
76
|
+
return discoverFromClaudeSettings();
|
|
77
|
+
}
|
|
78
|
+
// ── Codex / OpenAI ───────────────────────────────────────────────────
|
|
79
|
+
function discoverCodexFromOpenCodeAuth() {
|
|
80
|
+
const results = [];
|
|
81
|
+
const authPath = path.join(HOME, '.local/share/opencode/auth.json');
|
|
82
|
+
try {
|
|
83
|
+
const raw = fs.readFileSync(authPath, 'utf-8');
|
|
84
|
+
const auth = JSON.parse(raw);
|
|
85
|
+
const oauths = Array.isArray(auth) ? auth : ('oauth' in auth ? auth.oauth : [auth]);
|
|
86
|
+
const entries = Array.isArray(oauths) ? oauths : [oauths];
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
if ((entry.type === 'openai' || entry.type === 'chatgpt' || entry.type === 'codex') && entry.access) {
|
|
89
|
+
results.push({
|
|
90
|
+
token: entry.access,
|
|
91
|
+
source: 'opencode auth.json',
|
|
92
|
+
accountId: entry.accountId || undefined,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Not found.
|
|
99
|
+
}
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
/** Read ~/.codex/auth.json for Codex CLI native OAuth tokens. */
|
|
103
|
+
function discoverCodexFromCodexAuth() {
|
|
104
|
+
const results = [];
|
|
105
|
+
const authPath = path.join(HOME, '.codex', 'auth.json');
|
|
106
|
+
try {
|
|
107
|
+
const raw = fs.readFileSync(authPath, 'utf-8');
|
|
108
|
+
const auth = JSON.parse(raw);
|
|
109
|
+
const tokens = auth?.tokens;
|
|
110
|
+
if (tokens?.access_token) {
|
|
111
|
+
results.push({
|
|
112
|
+
token: tokens.access_token,
|
|
113
|
+
source: 'codex auth.json',
|
|
114
|
+
accountId: tokens.account_id || undefined,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Not found.
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
function discoverCodexFromEnv() {
|
|
124
|
+
const results = [];
|
|
125
|
+
const envVars = ['OPENAI_API_KEY', 'CODEX_API_KEY', 'CHATGPT_TOKEN'];
|
|
126
|
+
for (const v of envVars) {
|
|
127
|
+
const val = process.env[v];
|
|
128
|
+
if (val && val.trim()) {
|
|
129
|
+
results.push({ token: val.trim(), source: `env:${v}` });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
export function discoverCodexTokens() {
|
|
135
|
+
// Priority: Codex CLI auth > OpenCode auth > env
|
|
136
|
+
const fromCodex = discoverCodexFromCodexAuth();
|
|
137
|
+
if (fromCodex.length > 0)
|
|
138
|
+
return fromCodex;
|
|
139
|
+
const fromOpenCode = discoverCodexFromOpenCodeAuth();
|
|
140
|
+
if (fromOpenCode.length > 0)
|
|
141
|
+
return fromOpenCode;
|
|
142
|
+
return discoverCodexFromEnv();
|
|
143
|
+
}
|
|
144
|
+
// ── Kimi ─────────────────────────────────────────────────────────────
|
|
145
|
+
export function discoverKimiTokens() {
|
|
146
|
+
const results = [];
|
|
147
|
+
// Check KIMI_API_KEY env
|
|
148
|
+
const envKey = process.env.KIMI_API_KEY;
|
|
149
|
+
if (envKey?.trim()) {
|
|
150
|
+
results.push({ token: envKey.trim(), source: 'env:KIMI_API_KEY' });
|
|
151
|
+
}
|
|
152
|
+
// Check ~/.kimi-code config
|
|
153
|
+
const configPath = path.join(HOME, '.kimi-code', 'config.json');
|
|
154
|
+
try {
|
|
155
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
156
|
+
const cfg = JSON.parse(raw);
|
|
157
|
+
if (cfg.apiKey) {
|
|
158
|
+
results.push({ token: cfg.apiKey, source: 'kimi-code config.json' });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Not found.
|
|
163
|
+
}
|
|
164
|
+
// Check OpenCode auth.json for kimi type
|
|
165
|
+
const authPath = path.join(HOME, '.local/share/opencode/auth.json');
|
|
166
|
+
try {
|
|
167
|
+
const raw = fs.readFileSync(authPath, 'utf-8');
|
|
168
|
+
const auth = JSON.parse(raw);
|
|
169
|
+
const entries = Array.isArray(auth) ? auth : [auth];
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
if (entry.type === 'kimi' && entry.access) {
|
|
172
|
+
results.push({ token: entry.access, source: 'opencode auth.json' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Not found.
|
|
178
|
+
}
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
// ── GitHub Copilot ───────────────────────────────────────────────────
|
|
182
|
+
export function discoverCopilotTokens() {
|
|
183
|
+
const results = [];
|
|
184
|
+
// OpenCode auth.json
|
|
185
|
+
const authPath = path.join(HOME, '.local/share/opencode/auth.json');
|
|
186
|
+
try {
|
|
187
|
+
const raw = fs.readFileSync(authPath, 'utf-8');
|
|
188
|
+
const auth = JSON.parse(raw);
|
|
189
|
+
const entries = Array.isArray(auth) ? auth : [auth];
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
if ((entry.type === 'github' || entry.type === 'copilot') && entry.access) {
|
|
192
|
+
results.push({
|
|
193
|
+
token: entry.access,
|
|
194
|
+
source: 'opencode auth.json',
|
|
195
|
+
accountId: entry.accountId || entry.accountLabel || undefined,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Not found.
|
|
202
|
+
}
|
|
203
|
+
// Copilot CLI config
|
|
204
|
+
const cliConfigPath = path.join(HOME, '.copilot', 'config.json');
|
|
205
|
+
try {
|
|
206
|
+
const raw = fs.readFileSync(cliConfigPath, 'utf-8');
|
|
207
|
+
const cfg = JSON.parse(raw);
|
|
208
|
+
if (cfg.token) {
|
|
209
|
+
results.push({ token: cfg.token, source: 'copilot config.json' });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Not found.
|
|
214
|
+
}
|
|
215
|
+
// GH_TOKEN env
|
|
216
|
+
const ghToken = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
217
|
+
if (ghToken?.trim()) {
|
|
218
|
+
results.push({ token: ghToken.trim(), source: 'env:GITHUB_TOKEN' });
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
// ── Gemini ────────────────────────────────────────────────────────────
|
|
223
|
+
export function discoverGeminiTokens() {
|
|
224
|
+
const results = [];
|
|
225
|
+
const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
226
|
+
if (envKey?.trim()) {
|
|
227
|
+
results.push({ token: envKey.trim(), source: `env:GEMINI_API_KEY` });
|
|
228
|
+
}
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
// ── Grok ──────────────────────────────────────────────────────────────
|
|
232
|
+
export function discoverGrokTokens() {
|
|
233
|
+
const results = [];
|
|
234
|
+
const envKey = process.env.GROK_API_KEY || process.env.XAI_API_KEY;
|
|
235
|
+
if (envKey?.trim()) {
|
|
236
|
+
results.push({ token: envKey.trim(), source: `env:GROK_API_KEY` });
|
|
237
|
+
}
|
|
238
|
+
return results;
|
|
239
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription quota tracking types.
|
|
3
|
+
*
|
|
4
|
+
* Each provider's fetch() returns a QuotaResult with usage percentage,
|
|
5
|
+
* remaining/entitlement for quota-based plans, and optional per-window
|
|
6
|
+
* breakdowns (e.g. Claude's 5h/7d windows, Codex's primary/secondary).
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -24,6 +24,7 @@ import { listAllHookAgents, refreshStaleHooksAtLaunch, installHooksForSlug, unin
|
|
|
24
24
|
import { loadPlugins, savePlugin, removePlugin, readPluginIcon, } from '../agent/plugins.js';
|
|
25
25
|
import { registerPluginAgents } from '../agent/registry.js';
|
|
26
26
|
import { notifyAgentTransition } from '../notifications/pushService.js';
|
|
27
|
+
import { getQuotaStatus, refreshQuota, startQuotaManager, } from '../quota/QuotaManager.js';
|
|
27
28
|
const router = express.Router();
|
|
28
29
|
const execFileAsync = promisify(execFile);
|
|
29
30
|
const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
|
|
@@ -2145,6 +2146,11 @@ function persistAgentResumeBinding(backendSessionId, session) {
|
|
|
2145
2146
|
upsertGlobalSessionRecord({ ...record, agentResume: next });
|
|
2146
2147
|
schedulePersistGlobalState();
|
|
2147
2148
|
}
|
|
2149
|
+
/** Debounce window before treating an agent disappearance as a real exit.
|
|
2150
|
+
* Subcommands that briefly occupy the foreground group cause momentary poll
|
|
2151
|
+
* blips — waiting 2–3 poll cycles (1200 ms each) before clearing the session
|
|
2152
|
+
* avoids false "agent exited" notifications while keeping real exits responsive. */
|
|
2153
|
+
const AGENT_EXIT_DEBOUNCE_MS = 3000;
|
|
2148
2154
|
/**
|
|
2149
2155
|
* React to activeProgram changes: detect agent identity, end the rich session
|
|
2150
2156
|
* when the agent leaves the foreground, and stamp the launch argv (for resume
|
|
@@ -2157,15 +2163,33 @@ function syncAgentIdentity(sessionId, session) {
|
|
|
2157
2163
|
// The agent leaving the foreground ends its session: clear the rich state
|
|
2158
2164
|
// so a stale "waiting" dot can't outlive the process. The poll can blip
|
|
2159
2165
|
// momentarily (an agent-spawned subcommand takes the foreground group),
|
|
2160
|
-
//
|
|
2166
|
+
// so we debounce: only clear after AGENT_EXIT_DEBOUNCE_MS of continuous
|
|
2167
|
+
// absence. A real agent swap (agent A → agent B) clears immediately.
|
|
2161
2168
|
if (session.agent && session.agentSession) {
|
|
2169
|
+
if (detected === null) {
|
|
2170
|
+
// Agent disappeared — could be a poll blip (subcommand) or real exit.
|
|
2171
|
+
const now = Date.now();
|
|
2172
|
+
if (session.agentLeftAt === null) {
|
|
2173
|
+
session.agentLeftAt = now;
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (now - session.agentLeftAt < AGENT_EXIT_DEBOUNCE_MS) {
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
// Debounce expired (or agent swapped): clear the session for real.
|
|
2162
2181
|
persistAgentResumeBinding(sessionId, session);
|
|
2163
2182
|
session.lastAgentReviewed = session.agentSession.reviewed;
|
|
2164
2183
|
session.agentSession = null;
|
|
2184
|
+
session.agentLeftAt = null;
|
|
2165
2185
|
}
|
|
2166
2186
|
session.agent = detected;
|
|
2167
2187
|
broadcastAgentStatus(sessionId, session);
|
|
2168
2188
|
}
|
|
2189
|
+
else if (detected && session.agentLeftAt !== null) {
|
|
2190
|
+
// Agent re-detected before debounce expired — false alarm, restore.
|
|
2191
|
+
session.agentLeftAt = null;
|
|
2192
|
+
}
|
|
2169
2193
|
// Stamp the launch argv the identity poll captured — resume gets the flags.
|
|
2170
2194
|
if (detected && session.activeProgram?.rawArgs) {
|
|
2171
2195
|
const argv = splitCommandToArgv(session.activeProgram.rawArgs);
|
|
@@ -2196,6 +2220,9 @@ function syncAgentIdentity(sessionId, session) {
|
|
|
2196
2220
|
function applyAgentSignals(sessionId, session, events, notification) {
|
|
2197
2221
|
if (events.length === 0 && !notification)
|
|
2198
2222
|
return;
|
|
2223
|
+
// Hook events prove the agent process is alive — cancel any pending-exit timer
|
|
2224
|
+
// that a poll blip may have started in syncAgentIdentity.
|
|
2225
|
+
session.agentLeftAt = null;
|
|
2199
2226
|
let identityChanged = false;
|
|
2200
2227
|
for (const event of events) {
|
|
2201
2228
|
// An event naming an agent brands the pane even when the process poll
|
|
@@ -3507,6 +3534,7 @@ async function spawnTerminalSession(req, input) {
|
|
|
3507
3534
|
agent: null,
|
|
3508
3535
|
agentSession: null,
|
|
3509
3536
|
lastAgentReviewed: null,
|
|
3537
|
+
agentLeftAt: null,
|
|
3510
3538
|
gitStatus: null,
|
|
3511
3539
|
gitStatusKey: null,
|
|
3512
3540
|
gitAgentActivitySeen: 0,
|
|
@@ -3587,6 +3615,7 @@ async function adoptPtyHostSessions() {
|
|
|
3587
3615
|
agent: null,
|
|
3588
3616
|
agentSession: null,
|
|
3589
3617
|
lastAgentReviewed: null,
|
|
3618
|
+
agentLeftAt: null,
|
|
3590
3619
|
gitStatus: null,
|
|
3591
3620
|
gitStatusKey: null,
|
|
3592
3621
|
gitAgentActivitySeen: 0,
|
|
@@ -4272,6 +4301,52 @@ router.get('/agent-plugin-icon/:slug', (req, res) => {
|
|
|
4272
4301
|
res.send(svg);
|
|
4273
4302
|
});
|
|
4274
4303
|
// ── end Agent plugins API ──
|
|
4304
|
+
// ── Subscription quota API ──
|
|
4305
|
+
function broadcastQuotaToAll() {
|
|
4306
|
+
const status = getQuotaStatus();
|
|
4307
|
+
const payload = {
|
|
4308
|
+
type: 'quota-status',
|
|
4309
|
+
providers: status.providers,
|
|
4310
|
+
updatedAt: status.updatedAt,
|
|
4311
|
+
};
|
|
4312
|
+
const data = JSON.stringify(payload);
|
|
4313
|
+
// Send to control WS clients
|
|
4314
|
+
for (const [clientId, ws] of controlClients) {
|
|
4315
|
+
if (ws.readyState !== ws.OPEN) {
|
|
4316
|
+
controlClients.delete(clientId);
|
|
4317
|
+
continue;
|
|
4318
|
+
}
|
|
4319
|
+
try {
|
|
4320
|
+
ws.send(data);
|
|
4321
|
+
}
|
|
4322
|
+
catch {
|
|
4323
|
+
controlClients.delete(clientId);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
// Send to per-session WS clients
|
|
4327
|
+
for (const [sessionId] of terminalSessions) {
|
|
4328
|
+
try {
|
|
4329
|
+
broadcastJsonWs(sessionId, payload);
|
|
4330
|
+
}
|
|
4331
|
+
catch { /* best-effort */ }
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
// Start quota polling — broadcast fn hooks into both WS channels
|
|
4335
|
+
startQuotaManager({ broadcast: broadcastQuotaToAll });
|
|
4336
|
+
router.get('/quota', (_req, res) => {
|
|
4337
|
+
res.json(getQuotaStatus());
|
|
4338
|
+
});
|
|
4339
|
+
router.post('/quota/refresh', async (_req, res) => {
|
|
4340
|
+
try {
|
|
4341
|
+
const status = await refreshQuota();
|
|
4342
|
+
broadcastQuotaToAll();
|
|
4343
|
+
res.json(status);
|
|
4344
|
+
}
|
|
4345
|
+
catch (e) {
|
|
4346
|
+
res.status(500).json({ error: e instanceof Error ? e.message : 'Refresh failed' });
|
|
4347
|
+
}
|
|
4348
|
+
});
|
|
4349
|
+
// ── Subscription quota API end ──
|
|
4275
4350
|
// ── Program detection config API ──
|
|
4276
4351
|
router.get('/program-detection', async (_req, res) => {
|
|
4277
4352
|
res.json(await loadProgramDetectionFromDisk());
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-oQNFhFo1.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-B-FIe5fo.js";import{b as i}from"./mermaid.core-oQNFhFo1.js";import"./chunk-5VM5RSS4-CPYKdlUm.js";import"./chunk-XXDRQBXY-CdoSUjQU.js";import"./chunk-VR4S4FIN-CmIFAiFd.js";import"./chunk-32BRIVSS-0zL-WEd8.js";import"./purify.es-CC4Brkr_.js";import"./index-CN9znI-m.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-B-FIe5fo.js";import{b as i}from"./mermaid.core-oQNFhFo1.js";import"./chunk-5VM5RSS4-CPYKdlUm.js";import"./chunk-XXDRQBXY-CdoSUjQU.js";import"./chunk-VR4S4FIN-CmIFAiFd.js";import"./chunk-32BRIVSS-0zL-WEd8.js";import"./purify.es-CC4Brkr_.js";import"./index-CN9znI-m.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|