dsh-tacit 0.4.0 → 0.5.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/README.md +3 -1
- package/client/client.js +292 -45
- package/docs/README.zh.md +2 -1
- package/lib/analyze.js +144 -40
- package/lib/fold.js +11 -5
- package/lib/redact.js +53 -0
- package/lib/routes.js +1 -0
- package/lib/schema.js +71 -12
- package/lib/service.js +210 -51
- package/lib/store.js +2 -2
- package/lib/usage.js +57 -14
- package/lib/workspace.js +62 -0
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -53,13 +53,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
53
53
|
'status.trendCorrections': '你纠正智能体:{a} → {b}',
|
|
54
54
|
'status.trendMessy': '混乱轮次:{a} → {b}',
|
|
55
55
|
'status.trendTokens': '每轮 tokens:{a} → {b}',
|
|
56
|
-
'status.trendHint': '(最早 {n} 轮 vs 最近 {n}
|
|
56
|
+
'status.trendHint': '(最早 {n} 轮 vs 最近 {n} 轮,真实数据——这是你自己轮次的前后对比,不是对照实验)',
|
|
57
|
+
'status.trendDocs': '试用与趋势的判定方式见《How it works》:§6 Trials https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#6-trials · §10 The measured trend https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#10-the-measured-trend',
|
|
57
58
|
'turn.enrichment': '发送前补充的上下文',
|
|
58
59
|
'trigger.auto': '自动',
|
|
59
60
|
'trigger.correction': '纠正',
|
|
60
61
|
'trigger.manual': '手动',
|
|
61
62
|
'trigger.bootstrap': '引导',
|
|
62
63
|
'trigger.good': '做对了',
|
|
64
|
+
'trigger.feedback': '反馈',
|
|
65
|
+
'trigger.send': '发送',
|
|
63
66
|
'report.strengths': '这次做对了什么',
|
|
64
67
|
'report.lesson': '经验',
|
|
65
68
|
'settings.learnGood': '也从混乱之后的顺利提示词中学习',
|
|
@@ -89,6 +92,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
89
92
|
'steer.workspace': '仅 {name}',
|
|
90
93
|
'steer.scope': '适用范围',
|
|
91
94
|
'steer.everywhere': '所有工作区',
|
|
95
|
+
'steer.receipt': '这条指令从哪来',
|
|
96
|
+
'steer.review': '新指令先经我确认再开始试用',
|
|
97
|
+
'steer.startTrial': '开始试用',
|
|
98
|
+
'steer.receiptLoading': '读取中…',
|
|
99
|
+
'steer.receiptCopy': '复制为 JSON',
|
|
100
|
+
'steer.receiptNever': '从未',
|
|
101
|
+
'steer.receiptNoRun': '无记账记录',
|
|
102
|
+
'steer.receiptId': '编号',
|
|
103
|
+
'steer.receiptScope': '适用范围',
|
|
104
|
+
'steer.receiptStatus': '状态',
|
|
105
|
+
'steer.receiptSource': '来源',
|
|
106
|
+
'steer.receiptCreated': '创建于',
|
|
107
|
+
'steer.receiptUpdated': '更新于',
|
|
108
|
+
'steer.receiptEvaluated': '评定于',
|
|
109
|
+
'steer.receiptVersion': '版本',
|
|
110
|
+
'steer.receiptTrial': '试用窗口',
|
|
111
|
+
'steer.receiptTrialValue': '{turns} 轮,始于 {started} · 基线:杂乱 {messy}、纠正 {corrected}',
|
|
112
|
+
'steer.receiptTriggers': '触发类别',
|
|
113
|
+
'steer.receiptEvidence': '证据',
|
|
114
|
+
'steer.receiptEvidenceValue': '{turns} 轮,来自 {conversations} 个会话',
|
|
115
|
+
'steer.receiptCost': '提炼花费',
|
|
116
|
+
'steer.receiptCostValue': '{usd} · {calls} 次调用',
|
|
117
|
+
'steer.moveTo': '移到工作区',
|
|
118
|
+
'steer.notSeen': '自 {date} 起未见',
|
|
92
119
|
'settings.auto': '自动分析混乱轮次',
|
|
93
120
|
'settings.budget': '每日自动分析上限',
|
|
94
121
|
settings: '设置',
|
|
@@ -167,10 +194,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
167
194
|
'usage.last30': '最近 30 天',
|
|
168
195
|
'usage.since': '自 {day} 起',
|
|
169
196
|
'usage.calls': '计费调用(30 天)',
|
|
197
|
+
'usage.tokens': 'Tokens(30 天)',
|
|
170
198
|
'usage.avgAnalysis': '单次分析中位成本',
|
|
171
199
|
'usage.cachedRate': '输入缓存命中率(30 天)',
|
|
172
200
|
'usage.unpriced': '无价目调用(30 天)',
|
|
173
201
|
'usage.unpricedShort': '{n} 次无价目',
|
|
202
|
+
'usage.failedRepair': '失败或修复调用花费(30 天)',
|
|
203
|
+
'usage.failedRepairSplit': '失败 {failed} · 修复 {repair}',
|
|
204
|
+
'usage.autoToday': '今日自动分析 {n} / {budget}',
|
|
174
205
|
'usage.priceUnavailable': '无价目',
|
|
175
206
|
'usage.chartLabel': '每日花费,最近 {n} 天',
|
|
176
207
|
'usage.chartSummary': '最近 {n} 天:合计 {total};最高 {day} {max}',
|
|
@@ -178,6 +209,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
178
209
|
'usage.series7': '7 天',
|
|
179
210
|
'usage.series30': '30 天',
|
|
180
211
|
'usage.breakdown': '按操作分类(最近 30 天)',
|
|
212
|
+
'usage.byRoute': '按线路分类(最近 30 天)',
|
|
213
|
+
'usage.byTrigger': '按触发方式分类(最近 30 天)',
|
|
181
214
|
'usage.filters': '筛选',
|
|
182
215
|
'usage.loading': '正在载入这次运行的调用明细…',
|
|
183
216
|
'usage.page': '第 {page} / {pages} 页',
|
|
@@ -292,6 +325,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
292
325
|
'err.network': '无法连接 Tacit 服务。',
|
|
293
326
|
'err.internal': '内部错误:{detail}',
|
|
294
327
|
'err.continuation': '这是一条纯粹的「继续」类消息(没有可分析的意图)——请分析它之前的那一轮。',
|
|
328
|
+
'err.directive-policy': '指令不能改变工具权限、审批或沙箱设置,请换一种说法。',
|
|
329
|
+
'err.already-analyzed': '这一轮已有报告;点击「重新分析」可再花一次费用分析。',
|
|
330
|
+
'err.unknown-directive': '找不到这条指令。',
|
|
295
331
|
}
|
|
296
332
|
|
|
297
333
|
const DICT_EN = {
|
|
@@ -303,13 +339,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
303
339
|
'status.trendCorrections': 'You corrected the agent: {a} → {b}',
|
|
304
340
|
'status.trendMessy': 'Messy turns: {a} → {b}',
|
|
305
341
|
'status.trendTokens': 'Tokens/turn: {a} → {b}',
|
|
306
|
-
'status.trendHint': '(first {n} turns vs. latest {n}, measured)',
|
|
342
|
+
'status.trendHint': '(first {n} turns vs. latest {n}, measured — before/after on your own turns, not a controlled comparison)',
|
|
343
|
+
'status.trendDocs': 'How these are judged: §6 Trials https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#6-trials · §10 The measured trend https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#10-the-measured-trend',
|
|
307
344
|
'turn.enrichment': 'Context added before the send',
|
|
308
345
|
'trigger.auto': 'auto',
|
|
309
346
|
'trigger.correction': 'correction',
|
|
310
347
|
'trigger.manual': 'manual',
|
|
311
348
|
'trigger.bootstrap': 'bootstrap',
|
|
312
349
|
'trigger.good': 'what worked',
|
|
350
|
+
'trigger.feedback': 'feedback',
|
|
351
|
+
'trigger.send': 'send',
|
|
313
352
|
'report.strengths': 'What this prompt got right',
|
|
314
353
|
'report.lesson': 'Lesson',
|
|
315
354
|
'settings.learnGood': 'Also learn from a clean prompt right after a messy turn',
|
|
@@ -339,6 +378,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
339
378
|
'steer.workspace': 'only {name}',
|
|
340
379
|
'steer.scope': 'Applies to',
|
|
341
380
|
'steer.everywhere': 'Everywhere',
|
|
381
|
+
'steer.receipt': 'Where this directive came from',
|
|
382
|
+
'steer.review': 'Review new directives before their trial',
|
|
383
|
+
'steer.startTrial': 'Start trial',
|
|
384
|
+
'steer.receiptLoading': 'Reading…',
|
|
385
|
+
'steer.receiptCopy': 'Copy as JSON',
|
|
386
|
+
'steer.receiptNever': 'never',
|
|
387
|
+
'steer.receiptNoRun': 'no ledger run',
|
|
388
|
+
'steer.receiptId': 'Id',
|
|
389
|
+
'steer.receiptScope': 'Applies to',
|
|
390
|
+
'steer.receiptStatus': 'Status',
|
|
391
|
+
'steer.receiptSource': 'Source',
|
|
392
|
+
'steer.receiptCreated': 'Created',
|
|
393
|
+
'steer.receiptUpdated': 'Updated',
|
|
394
|
+
'steer.receiptEvaluated': 'Evaluated',
|
|
395
|
+
'steer.receiptVersion': 'Version',
|
|
396
|
+
'steer.receiptTrial': 'Trial window',
|
|
397
|
+
'steer.receiptTrialValue': '{turns} turns from {started} · baseline messy {messy}, corrections {corrected}',
|
|
398
|
+
'steer.receiptTriggers': 'Trigger categories',
|
|
399
|
+
'steer.receiptEvidence': 'Evidence',
|
|
400
|
+
'steer.receiptEvidenceValue': '{turns} turns across {conversations} conversations',
|
|
401
|
+
'steer.receiptCost': 'Distillation cost',
|
|
402
|
+
'steer.receiptCostValue': '{usd} · {calls} calls',
|
|
403
|
+
'steer.moveTo': 'Move to workspace',
|
|
404
|
+
'steer.notSeen': 'not seen since {date}',
|
|
342
405
|
'settings.auto': 'Auto-analyze messy turns',
|
|
343
406
|
'settings.budget': 'Daily cap on automatic analyses',
|
|
344
407
|
settings: 'Settings',
|
|
@@ -417,10 +480,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
417
480
|
'usage.last30': 'Last 30 days',
|
|
418
481
|
'usage.since': 'Since {day}',
|
|
419
482
|
'usage.calls': 'Billed calls (30 d)',
|
|
483
|
+
'usage.tokens': 'Tokens (30 d)',
|
|
420
484
|
'usage.avgAnalysis': 'Median cost per analysis',
|
|
421
485
|
'usage.cachedRate': 'Cached input rate (30 d)',
|
|
422
486
|
'usage.unpriced': 'Unpriced calls (30 d)',
|
|
423
487
|
'usage.unpricedShort': '{n} unpriced',
|
|
488
|
+
'usage.failedRepair': 'Spent on failed or repair calls (30 d)',
|
|
489
|
+
'usage.failedRepairSplit': 'failed {failed} · repair {repair}',
|
|
490
|
+
'usage.autoToday': 'automatic analyses today {n} / {budget}',
|
|
424
491
|
'usage.priceUnavailable': 'No price',
|
|
425
492
|
'usage.chartLabel': 'Daily spend, last {n} days',
|
|
426
493
|
'usage.chartSummary': 'Last {n} days: total {total}; highest {day} {max}',
|
|
@@ -428,6 +495,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
428
495
|
'usage.series7': '7 d',
|
|
429
496
|
'usage.series30': '30 d',
|
|
430
497
|
'usage.breakdown': 'By operation (last 30 days)',
|
|
498
|
+
'usage.byRoute': 'By route (last 30 days)',
|
|
499
|
+
'usage.byTrigger': 'By trigger (last 30 days)',
|
|
431
500
|
'usage.filters': 'Filters',
|
|
432
501
|
'usage.loading': 'Loading this run\u2019s calls…',
|
|
433
502
|
'usage.page': 'Page {page} of {pages}',
|
|
@@ -542,6 +611,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
542
611
|
'err.network': 'Could not reach the Tacit service.',
|
|
543
612
|
'err.internal': 'Internal error: {detail}',
|
|
544
613
|
'err.continuation': 'This is a bare continuation ("continue", "go ahead") with nothing to analyze — analyze the turn before it instead.',
|
|
614
|
+
'err.directive-policy': 'A directive cannot change tool permissions, approvals or the sandbox. Reword it.',
|
|
615
|
+
'err.already-analyzed': 'This turn already has a report; Re-analyze pays for a fresh one.',
|
|
616
|
+
'err.unknown-directive': 'No directive with that id.',
|
|
545
617
|
}
|
|
546
618
|
|
|
547
619
|
// ── API client (the host half's own routes on the harness origin) ──────
|
|
@@ -641,6 +713,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
641
713
|
auto: null,
|
|
642
714
|
steering: null, // {enabled, text}
|
|
643
715
|
workspaces: [], // [{cwd, label}] from /state
|
|
716
|
+
receipts: {}, // directive id → the /directive-receipt receipt, re-read on each expand
|
|
644
717
|
trend: null, // measured early-vs-recent trend
|
|
645
718
|
bootstrap: null, // {running, done, total}
|
|
646
719
|
coached: [], // cross-session coached-prompt entries
|
|
@@ -1029,13 +1102,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1029
1102
|
function closeConfirm() {
|
|
1030
1103
|
rootStore.confirm = null
|
|
1031
1104
|
notifyRoot()
|
|
1105
|
+
restoreConfirmOpener()
|
|
1032
1106
|
}
|
|
1033
1107
|
|
|
1034
1108
|
/** Run the parked destructive action, whichever it is, and close the dialog. */
|
|
1035
1109
|
async function confirmAction() {
|
|
1036
1110
|
const kind = rootStore.confirm !== null && typeof rootStore.confirm === 'object' ? rootStore.confirm.kind : null
|
|
1037
|
-
|
|
1038
|
-
notifyRoot()
|
|
1111
|
+
closeConfirm()
|
|
1039
1112
|
if (kind === 'reports') await clearAllRoot()
|
|
1040
1113
|
else if (kind === 'usage') await clearUsageHistory()
|
|
1041
1114
|
}
|
|
@@ -1151,6 +1224,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1151
1224
|
notifyRoot()
|
|
1152
1225
|
}
|
|
1153
1226
|
|
|
1227
|
+
/** One directive's audit receipt. Re-read on every expand: a trial's counters move while it runs. */
|
|
1228
|
+
async function fetchReceipt(id) {
|
|
1229
|
+
const key = String(id)
|
|
1230
|
+
if (key.length === 0) return
|
|
1231
|
+
rootStore.error = null
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await api('/directive-receipt', { id: key })
|
|
1234
|
+
if (result !== null && typeof result === 'object' && result.ok === true
|
|
1235
|
+
&& result.receipt !== null && typeof result.receipt === 'object') {
|
|
1236
|
+
rootStore.receipts[key] = result.receipt
|
|
1237
|
+
} else {
|
|
1238
|
+
rootStore.error = { code: result !== null && typeof result === 'object' && typeof result.code === 'string' ? result.code : 'bad-request', detail: '' }
|
|
1239
|
+
}
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
rootStore.error = errorOf(error)
|
|
1242
|
+
}
|
|
1243
|
+
notifyRoot()
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1154
1246
|
async function initStore(store) {
|
|
1155
1247
|
if (store === null || store.initStarted) return
|
|
1156
1248
|
store.initStarted = true
|
|
@@ -1181,7 +1273,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1181
1273
|
store.error = null
|
|
1182
1274
|
store.notice = null
|
|
1183
1275
|
notify(store)
|
|
1184
|
-
|
|
1276
|
+
// A turn that already has a report is only re-analyzed on purpose; the button reads "Re-analyze" then.
|
|
1277
|
+
api('/analyze', { sessionId: store.sessionId, turn, ...(store.reports[String(turn)] ? { force: true } : {}) })
|
|
1185
1278
|
.then((result) => {
|
|
1186
1279
|
if (result !== null && typeof result === 'object' && result.ok && result.report !== null && typeof result.report === 'object') {
|
|
1187
1280
|
store.reports[String(turn)] = result.report
|
|
@@ -1245,7 +1338,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1245
1338
|
// `busy` means an analysis of that turn was already running — a
|
|
1246
1339
|
// race with the auto-analyzer, not a failure of this batch.
|
|
1247
1340
|
const code = typeof entry.code === 'string' && entry.code.length > 0 ? entry.code : 'call-failed'
|
|
1248
|
-
if (failure === null && code !== 'busy') failure = { code, detail: '' }
|
|
1341
|
+
if (failure === null && code !== 'busy' && code !== 'already-analyzed') failure = { code, detail: '' }
|
|
1249
1342
|
}
|
|
1250
1343
|
if (failure !== null) store.error = failure
|
|
1251
1344
|
if (result.profile !== null && typeof result.profile === 'object') store.profile = result.profile
|
|
@@ -1575,14 +1668,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1575
1668
|
}
|
|
1576
1669
|
|
|
1577
1670
|
/**
|
|
1578
|
-
* Billed tokens in one bucket set
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1671
|
+
* Billed tokens in one bucket set, split the way they are priced:
|
|
1672
|
+
* everything the prompt cost (uncached input, cache reads, cache writes)
|
|
1673
|
+
* against what came back. `reasoningTokens` is a subset of `outputTokens`,
|
|
1674
|
+
* so adding it would count every reasoning token twice.
|
|
1581
1675
|
*/
|
|
1582
|
-
function
|
|
1583
|
-
if (buckets === null || typeof buckets !== 'object') return 0
|
|
1676
|
+
function tokensInOut(buckets) {
|
|
1677
|
+
if (buckets === null || typeof buckets !== 'object') return { input: 0, output: 0 }
|
|
1584
1678
|
const num = (value) => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0)
|
|
1585
|
-
return
|
|
1679
|
+
return {
|
|
1680
|
+
input: num(buckets.inputTokens) + num(buckets.cacheReadTokens) + num(buckets.cacheWriteTokens),
|
|
1681
|
+
output: num(buckets.outputTokens),
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/** Both halves of `tokensInOut` at once. */
|
|
1686
|
+
function tokensTotal(buckets) {
|
|
1687
|
+
const split = tokensInOut(buckets)
|
|
1688
|
+
return split.input + split.output
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/** `tokensInOut` as the one line the tiles and the run rows both show. */
|
|
1692
|
+
function fmtTokensSplit(buckets) {
|
|
1693
|
+
const split = tokensInOut(buckets)
|
|
1694
|
+
return 'in ' + fmtTokens(split.input) + ' · out ' + fmtTokens(split.output)
|
|
1586
1695
|
}
|
|
1587
1696
|
|
|
1588
1697
|
function usageNum(value) {
|
|
@@ -1600,7 +1709,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1600
1709
|
}
|
|
1601
1710
|
}
|
|
1602
1711
|
|
|
1603
|
-
/**
|
|
1712
|
+
/**
|
|
1713
|
+
* One period card, zero-filled; the two averages stay nullable.
|
|
1714
|
+
* `failedUsd` and `repairUsd` overlap on a failed repair, so they can sum
|
|
1715
|
+
* past `failedOrRepairUsd`, which counts that union once.
|
|
1716
|
+
*/
|
|
1604
1717
|
function usagePeriod(value) {
|
|
1605
1718
|
const source = value !== null && typeof value === 'object' ? value : {}
|
|
1606
1719
|
return {
|
|
@@ -1608,8 +1721,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1608
1721
|
billedCalls: usageNum(source.billedCalls),
|
|
1609
1722
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1610
1723
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1724
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1611
1725
|
tokens: usageTokens(source.tokens),
|
|
1612
1726
|
usdKnown: usageNum(source.usdKnown),
|
|
1727
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1728
|
+
repairUsd: usageNum(source.repairUsd),
|
|
1729
|
+
failedOrRepairUsd: usageNum(source.failedOrRepairUsd),
|
|
1613
1730
|
avgAnalysisUsd: typeof source.avgAnalysisUsd === 'number' && Number.isFinite(source.avgAnalysisUsd) ? source.avgAnalysisUsd : null,
|
|
1614
1731
|
cachedInputRate: typeof source.cachedInputRate === 'number' && Number.isFinite(source.cachedInputRate) ? source.cachedInputRate : null,
|
|
1615
1732
|
}
|
|
@@ -1646,8 +1763,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1646
1763
|
billedCalls: usageNum(source.billedCalls),
|
|
1647
1764
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1648
1765
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1766
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1649
1767
|
tokens: usageTokens(source.tokens),
|
|
1650
1768
|
usdKnown: usageNum(source.usdKnown),
|
|
1769
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1651
1770
|
trigger: text('trigger'),
|
|
1652
1771
|
startedAt: usageNum(source.startedAt),
|
|
1653
1772
|
endedAt: usageNum(source.endedAt),
|
|
@@ -1668,6 +1787,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1668
1787
|
if (value === null || typeof value !== 'object' || value.ok !== true) return null
|
|
1669
1788
|
const runs = value.runs !== null && typeof value.runs === 'object' ? value.runs : {}
|
|
1670
1789
|
const warnings = value.warnings !== null && typeof value.warnings === 'object' ? value.warnings : {}
|
|
1790
|
+
const auto = value.auto !== null && typeof value.auto === 'object' ? value.auto : {}
|
|
1671
1791
|
return {
|
|
1672
1792
|
trackingSince: usageNum(value.trackingSince),
|
|
1673
1793
|
pricing: value.pricing !== null && typeof value.pricing === 'object' ? value.pricing : {},
|
|
@@ -1678,9 +1798,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1678
1798
|
lifetime: usagePeriod(value.lifetime),
|
|
1679
1799
|
byType: usageBuckets(value.byType),
|
|
1680
1800
|
byModel: usageBuckets(value.byModel),
|
|
1801
|
+
byProvider: usageBuckets(value.byProvider),
|
|
1802
|
+
byTrigger: usageBuckets(value.byTrigger),
|
|
1681
1803
|
series7: usageSeries(value.series7),
|
|
1682
1804
|
series30: usageSeries(value.series30),
|
|
1683
1805
|
warnings: { daily: usageWarning(warnings.daily), monthly: usageWarning(warnings.monthly) },
|
|
1806
|
+
auto: { today: usageNum(auto.today), budget: usageNum(auto.budget) },
|
|
1684
1807
|
runs: {
|
|
1685
1808
|
items: (Array.isArray(runs.items) ? runs.items : []).map(usageRunItem),
|
|
1686
1809
|
page: usageNum(runs.page) > 0 ? Math.floor(usageNum(runs.page)) : 1,
|
|
@@ -1758,7 +1881,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1758
1881
|
h('span', { className: 'tacit-progress-count' }, autoOn ? t('status.autoOn', { today: String(today), budget: String(budget) }) : '')),
|
|
1759
1882
|
h('div', { className: 'tacit-progress-text' }, autoOn ? t('status.autoHint') : t('status.autoOff')),
|
|
1760
1883
|
hasTrend
|
|
1761
|
-
? h('div', { className: 'tacit-trend' },
|
|
1884
|
+
? h('div', { className: 'tacit-trend', title: t('status.trendDocs') },
|
|
1762
1885
|
h('span', { className: 'tacit-chip' }, t('status.trendCorrections', { a: pct(trend.early.correctionRate), b: pct(trend.recent.correctionRate) })),
|
|
1763
1886
|
h('span', { className: 'tacit-chip' }, t('status.trendMessy', { a: pct(trend.early.messyRate), b: pct(trend.recent.messyRate) })),
|
|
1764
1887
|
h('span', { className: 'tacit-chip' }, t('status.trendTokens', { a: fmt(trend.early.tokensPerTurn), b: fmt(trend.recent.tokensPerTurn) })),
|
|
@@ -2257,7 +2380,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2257
2380
|
*
|
|
2258
2381
|
* It is captured by the store action that opens the dialog rather than by
|
|
2259
2382
|
* the dialog itself: React focuses the `autoFocus`ed Cancel button while it
|
|
2260
|
-
* commits, so by the time
|
|
2383
|
+
* commits, so by the time the dialog is mounted the opener is already gone.
|
|
2261
2384
|
*/
|
|
2262
2385
|
let confirmOpener = null
|
|
2263
2386
|
|
|
@@ -2272,26 +2395,29 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2272
2395
|
confirmOpener = typeof document !== 'undefined' && document !== null ? document.activeElement : null
|
|
2273
2396
|
}
|
|
2274
2397
|
|
|
2398
|
+
/**
|
|
2399
|
+
* Hand focus back one macrotask after the close. The host's Settings modal
|
|
2400
|
+
* moves focus on the same Escape, so a restore that runs inside the close is
|
|
2401
|
+
* overwritten and the user lands on the host's Close button instead.
|
|
2402
|
+
*/
|
|
2403
|
+
function restoreConfirmOpener() {
|
|
2404
|
+
const opener = confirmOpener
|
|
2405
|
+
confirmOpener = null
|
|
2406
|
+
if (opener === null || typeof opener.focus !== 'function') return
|
|
2407
|
+
setTimeout(() => opener.focus(), 0)
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2275
2410
|
/**
|
|
2276
2411
|
* One small modal confirmation, rendered unconditionally by its owner (and
|
|
2277
2412
|
* `null` when closed) so React's hook count never changes between renders.
|
|
2278
|
-
* Hook-free
|
|
2279
|
-
* with markup alone.
|
|
2413
|
+
* Hook-free: focus is restored by `closeConfirm`, the one path out.
|
|
2280
2414
|
*
|
|
2281
2415
|
* Cancel comes first and takes focus: the safe answer is the one a keyboard
|
|
2282
2416
|
* or screen-reader user reaches without looking.
|
|
2283
2417
|
*/
|
|
2284
2418
|
function ConfirmDialog(kit, { open, title, body, confirmLabel, onConfirm, onCancel }) {
|
|
2285
2419
|
const { t } = kit
|
|
2286
|
-
|
|
2287
|
-
useEffect(() => {
|
|
2288
|
-
if (!isOpen || typeof document === 'undefined') return undefined
|
|
2289
|
-
const opener = confirmOpener
|
|
2290
|
-
return () => {
|
|
2291
|
-
if (opener !== null && typeof opener.focus === 'function') opener.focus()
|
|
2292
|
-
}
|
|
2293
|
-
}, [isOpen])
|
|
2294
|
-
if (!isOpen) return null
|
|
2420
|
+
if (open !== true) return null
|
|
2295
2421
|
const cancel = typeof onCancel === 'function' ? onCancel : () => {}
|
|
2296
2422
|
return h('div', {
|
|
2297
2423
|
className: 'tacit-modal-backdrop',
|
|
@@ -2336,9 +2462,68 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2336
2462
|
|
|
2337
2463
|
function DirectivesEditor(kit) {
|
|
2338
2464
|
const { t } = kit
|
|
2465
|
+
const fmtDay = (ms) => {
|
|
2466
|
+
const date = new Date(Number(ms))
|
|
2467
|
+
return !(Number(ms) > 0) || Number.isNaN(date.getTime()) ? t('steer.receiptNever') : date.toISOString().slice(0, 10)
|
|
2468
|
+
}
|
|
2469
|
+
const pct = (value) => (typeof value === 'number' && value >= 0 ? Math.round(value * 100) + '%' : t('steer.receiptNever'))
|
|
2470
|
+
const triggerName = (name) => {
|
|
2471
|
+
const key = 'trigger.' + name
|
|
2472
|
+
const label = t(key)
|
|
2473
|
+
return label === key ? name : label
|
|
2474
|
+
}
|
|
2475
|
+
const copyReceipt = (receipt) => {
|
|
2476
|
+
if (typeof navigator === 'undefined' || navigator === null || !navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') return
|
|
2477
|
+
navigator.clipboard.writeText(JSON.stringify(receipt, null, 2)).catch(() => {})
|
|
2478
|
+
}
|
|
2479
|
+
const receiptRow = (label, value) => [h('dt', { key: label + '-t' }, label), h('dd', { key: label + '-d' }, value)]
|
|
2480
|
+
/** What the receipt route returned for this directive, as definition rows; never any prompt text. */
|
|
2481
|
+
function Receipt(receipt) {
|
|
2482
|
+
if (receipt === null || typeof receipt === 'object' === false) return h('div', { className: 'tacit-panel-hint' }, t('steer.receiptLoading'))
|
|
2483
|
+
const triggers = Object.entries(receipt.triggers !== null && typeof receipt.triggers === 'object' ? receipt.triggers : {})
|
|
2484
|
+
.map(([name, count]) => triggerName(name) + ' ' + String(count)).join(' · ')
|
|
2485
|
+
const evidence = receipt.evidence !== null && typeof receipt.evidence === 'object' ? receipt.evidence : { turns: 0, conversations: 0 }
|
|
2486
|
+
const cost = receipt.cost !== null && typeof receipt.cost === 'object' ? receipt.cost : { usd: null, calls: 0 }
|
|
2487
|
+
const trial = receipt.trial !== null && typeof receipt.trial === 'object' ? receipt.trial : null
|
|
2488
|
+
return h('div', null,
|
|
2489
|
+
h('dl', { className: 'tacit-receipt' },
|
|
2490
|
+
...receiptRow(t('steer.receiptId'), String(receipt.id)),
|
|
2491
|
+
...receiptRow(t('steer.receiptScope'), typeof receipt.scope === 'string' && receipt.scope.length > 0 ? receipt.scope : t('steer.everywhere')),
|
|
2492
|
+
...receiptRow(t('steer.receiptStatus'), String(receipt.status)),
|
|
2493
|
+
...receiptRow(t('steer.receiptSource'), receipt.source === 'user' ? t('steer.user') : t('steer.distilled')),
|
|
2494
|
+
...receiptRow(t('steer.receiptCreated'), fmtDay(receipt.createdAt)),
|
|
2495
|
+
...receiptRow(t('steer.receiptUpdated'), fmtDay(receipt.updatedAt)),
|
|
2496
|
+
...receiptRow(t('steer.receiptEvaluated'), fmtDay(receipt.evaluatedAt)),
|
|
2497
|
+
...receiptRow(t('steer.receiptVersion'), String(receipt.version)),
|
|
2498
|
+
...receiptRow(t('steer.receiptTrial'), trial === null ? t('steer.receiptNever') : t('steer.receiptTrialValue', {
|
|
2499
|
+
turns: String(trial.turns), started: fmtDay(trial.startedAt), messy: pct(trial.baselineMessyRate), corrected: pct(trial.baselineCorrectionRate) })),
|
|
2500
|
+
...receiptRow(t('steer.receiptTriggers'), triggers.length > 0 ? triggers : t('steer.receiptNever')),
|
|
2501
|
+
...receiptRow(t('steer.receiptEvidence'), t('steer.receiptEvidenceValue', { turns: String(evidence.turns), conversations: String(evidence.conversations) })),
|
|
2502
|
+
...receiptRow(t('steer.receiptCost'), cost.usd === null ? t('steer.receiptNoRun') : t('steer.receiptCostValue', { usd: fmtUsd(cost.usd), calls: String(cost.calls) }))),
|
|
2503
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => copyReceipt(receipt) }, t('steer.receiptCopy')))
|
|
2504
|
+
}
|
|
2339
2505
|
return function DirectivesEditorView(props) {
|
|
2340
|
-
const { config,
|
|
2506
|
+
const { config, steering } = props
|
|
2507
|
+
const directives = (Array.isArray(props.directives) ? props.directives : []).filter((entry) => entry.status !== 'removed')
|
|
2508
|
+
const receipts = rootStore.receipts !== null && typeof rootStore.receipts === 'object' ? rootStore.receipts : {}
|
|
2509
|
+
const reviewOn = config !== null && config.reviewCandidates === true
|
|
2341
2510
|
const workspaces = Array.isArray(props.workspaces) ? props.workspaces : []
|
|
2511
|
+
const seenAt = props.seenAt !== null && typeof props.seenAt === 'object' ? props.seenAt : {}
|
|
2512
|
+
const liveScope = (scope) => workspaces.some((entry) => entry.cwd === scope || String(entry.cwd).startsWith(scope + '/'))
|
|
2513
|
+
const notSeenSince = (entry) => {
|
|
2514
|
+
const stamp = typeof seenAt[entry.workspace] === 'number' ? seenAt[entry.workspace] : entry.createdAt
|
|
2515
|
+
const date = new Date(Number(stamp))
|
|
2516
|
+
return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)
|
|
2517
|
+
}
|
|
2518
|
+
const scopeOptions = (current) => {
|
|
2519
|
+
const options = [
|
|
2520
|
+
h('option', { key: '-', value: '-' }, t('steer.moveTo')),
|
|
2521
|
+
h('option', { key: '', value: '' }, t('steer.everywhere')),
|
|
2522
|
+
]
|
|
2523
|
+
for (const entry of workspaces) options.push(h('option', { key: entry.cwd, value: entry.cwd }, entry.label))
|
|
2524
|
+
if (current.length > 0 && !workspaces.some((entry) => entry.cwd === current)) options.push(h('option', { key: current, value: current }, labelOf(current)))
|
|
2525
|
+
return options
|
|
2526
|
+
}
|
|
2342
2527
|
const [draft, setDraft] = useState('')
|
|
2343
2528
|
const [scope, setScope] = useState('')
|
|
2344
2529
|
const labelOf = (cwd) => {
|
|
@@ -2361,6 +2546,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2361
2546
|
setEnrichOn(next)
|
|
2362
2547
|
updateRootConfig({ enrichPrompts: next })
|
|
2363
2548
|
}
|
|
2549
|
+
const toggleReview = () => updateRootConfig({ reviewCandidates: !reviewOn })
|
|
2364
2550
|
const onAdd = () => {
|
|
2365
2551
|
const text = draft.trim()
|
|
2366
2552
|
if (text.length === 0) return
|
|
@@ -2376,10 +2562,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2376
2562
|
h('div', { className: 'tacit-settings-row' },
|
|
2377
2563
|
h('label', { className: 'tacit-settings-label' }, t('steer.enrich')),
|
|
2378
2564
|
h('input', { type: 'checkbox', checked: enrichOn, onChange: toggleEnrich })),
|
|
2565
|
+
h('div', { className: 'tacit-settings-row' },
|
|
2566
|
+
h('label', { className: 'tacit-settings-label' }, t('steer.review')),
|
|
2567
|
+
h('input', { type: 'checkbox', checked: reviewOn, onChange: toggleReview })),
|
|
2379
2568
|
directives.length === 0
|
|
2380
2569
|
? h('div', { className: 'tacit-empty' }, t('steer.empty'))
|
|
2381
2570
|
: h('div', { className: 'tacit-rules-list' },
|
|
2382
|
-
directives.map((entry) => h('div', { key: entry.id, className: 'tacit-
|
|
2571
|
+
directives.map((entry) => h('div', { key: entry.id, className: 'tacit-directive-block' },
|
|
2572
|
+
h('div', { className: 'tacit-rule tacit-directive' + (entry.enabled === false ? ' tacit-directive-off' : '') },
|
|
2383
2573
|
h('input', {
|
|
2384
2574
|
type: 'checkbox',
|
|
2385
2575
|
className: 'tacit-check',
|
|
@@ -2392,6 +2582,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2392
2582
|
typeof entry.workspace === 'string' && entry.workspace.length > 0
|
|
2393
2583
|
? h('span', { className: 'tacit-chip tacit-chip-scope', title: entry.workspace }, t('steer.workspace', { name: labelOf(entry.workspace) }))
|
|
2394
2584
|
: null,
|
|
2585
|
+
typeof entry.workspace === 'string' && entry.workspace.length > 0 && !liveScope(entry.workspace)
|
|
2586
|
+
? h('span', { className: 'tacit-chip tacit-chip-muted', title: entry.workspace }, t('steer.notSeen', { date: notSeenSince(entry) }))
|
|
2587
|
+
: null,
|
|
2395
2588
|
entry.status === 'queued'
|
|
2396
2589
|
? h('span', { className: 'tacit-chip tacit-chip-muted' }, t('steer.queued'))
|
|
2397
2590
|
: entry.status === 'candidate'
|
|
@@ -2402,7 +2595,27 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2402
2595
|
: entry.status === 'retired'
|
|
2403
2596
|
? h('span', { className: 'tacit-chip tacit-chip-warn' }, t('steer.retired', { reason: String(entry.retiredReason || '') }))
|
|
2404
2597
|
: h('span', { className: 'tacit-chip tacit-chip-ok' }, t('steer.active')),
|
|
2405
|
-
|
|
2598
|
+
reviewOn && entry.status === 'queued' && !(entry.approvedAt > 0)
|
|
2599
|
+
? h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => editDirectives({ action: 'start-trial', id: entry.id }) }, t('steer.startTrial'))
|
|
2600
|
+
: null,
|
|
2601
|
+
h('select', {
|
|
2602
|
+
className: 'tacit-input tacit-select tacit-select-sm',
|
|
2603
|
+
'aria-label': t('steer.moveTo'),
|
|
2604
|
+
onChange: (event) => {
|
|
2605
|
+
if (event.target.value !== '-') editDirectives({ action: 'rescope', id: entry.id, workspace: event.target.value })
|
|
2606
|
+
},
|
|
2607
|
+
}, ...scopeOptions(typeof entry.workspace === 'string' ? entry.workspace : '')),
|
|
2608
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => editDirectives({ action: 'remove', id: entry.id }) }, t('steer.remove'))),
|
|
2609
|
+
h('details', {
|
|
2610
|
+
className: 'tacit-preview',
|
|
2611
|
+
'data-testid': 'tacit-receipt',
|
|
2612
|
+
'data-directive-id': entry.id,
|
|
2613
|
+
onToggle: (event) => {
|
|
2614
|
+
if (event.target.open) fetchReceipt(entry.id)
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
h('summary', null, t('steer.receipt')),
|
|
2618
|
+
Receipt(receipts[entry.id] ?? null))))),
|
|
2406
2619
|
h('div', { className: 'tacit-settings-row' },
|
|
2407
2620
|
h('input', {
|
|
2408
2621
|
className: 'tacit-input tacit-directive-input',
|
|
@@ -2655,7 +2868,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2655
2868
|
]),
|
|
2656
2869
|
card('guidance', [
|
|
2657
2870
|
h(DirectivesEditorView, {
|
|
2658
|
-
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering
|
|
2871
|
+
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering,
|
|
2872
|
+
seenAt: rootStore.profile !== null && typeof rootStore.profile === 'object' ? rootStore.profile.workspaceSeenAt : null }),
|
|
2659
2873
|
]),
|
|
2660
2874
|
card('improve', [
|
|
2661
2875
|
h('div', { className: 'tacit-settings-row' },
|
|
@@ -2792,10 +3006,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2792
3006
|
typeof note === 'string' && note.length > 0 ? h('span', { className: 'tacit-tile-note' }, note) : null)
|
|
2793
3007
|
}
|
|
2794
3008
|
|
|
2795
|
-
/** Money tile: the period's spend
|
|
2796
|
-
function SpendTile(kit, id, label, totals) {
|
|
2797
|
-
const
|
|
2798
|
-
|
|
3009
|
+
/** Money tile: the period's spend, its unpriced-call footnote, and any second note that period carries. */
|
|
3010
|
+
function SpendTile(kit, id, label, totals, extraNote) {
|
|
3011
|
+
const notes = []
|
|
3012
|
+
if (totals.unpricedCalls > 0) notes.push(kit.t('usage.unpricedShort', { n: String(totals.unpricedCalls) }))
|
|
3013
|
+
if (typeof extraNote === 'string' && extraNote.length > 0) notes.push(extraNote)
|
|
3014
|
+
return UsageTile(id, label, usageSpend(kit, totals), notes.join(' · '))
|
|
2799
3015
|
}
|
|
2800
3016
|
|
|
2801
3017
|
/**
|
|
@@ -2858,18 +3074,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2858
3074
|
seriesButton('30', t('usage.series30'))))
|
|
2859
3075
|
}
|
|
2860
3076
|
|
|
2861
|
-
/**
|
|
2862
|
-
|
|
3077
|
+
/**
|
|
3078
|
+
* `trigger` is a free string on the wire, so a host that starts tagging
|
|
3079
|
+
* runs with something this dictionary has never heard of shows the tag
|
|
3080
|
+
* itself rather than a raw `trigger.<key>` lookup miss.
|
|
3081
|
+
*/
|
|
3082
|
+
function triggerLabel(t, trigger) {
|
|
3083
|
+
const key = 'trigger.' + trigger
|
|
3084
|
+
const label = t(key)
|
|
3085
|
+
return label === key ? trigger : label
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
/**
|
|
3089
|
+
* Spend across one bucket record, costliest first, each row ending in a
|
|
3090
|
+
* share bar. `label` turns a bucket key into its display name, which is a
|
|
3091
|
+
* dictionary lookup for the closed sets and the raw key for the open ones.
|
|
3092
|
+
*/
|
|
3093
|
+
function UsageBreakdown(kit, { buckets, title, label }) {
|
|
2863
3094
|
const { t } = kit
|
|
2864
|
-
const rows = Object.entries(
|
|
3095
|
+
const rows = Object.entries(buckets).sort((a, b) => b[1].usdKnown - a[1].usdKnown)
|
|
2865
3096
|
if (rows.length === 0) return null
|
|
2866
3097
|
const top = rows[0][1].usdKnown
|
|
2867
|
-
return h('div', { className: 'tacit-usage-breakdown' },
|
|
2868
|
-
h('div', { className: 'tacit-report-title' }, t(
|
|
3098
|
+
return h('div', { key: title, className: 'tacit-usage-breakdown' },
|
|
3099
|
+
h('div', { className: 'tacit-report-title' }, t(title)),
|
|
2869
3100
|
rows.map(([type, totals]) => {
|
|
2870
3101
|
const share = top > 0 ? Math.round((totals.usdKnown / top) * 100) : 0
|
|
2871
3102
|
return h('div', { key: type, className: 'tacit-breakdown-row' },
|
|
2872
|
-
h('span', { className: 'tacit-breakdown-name' },
|
|
3103
|
+
h('span', { className: 'tacit-breakdown-name' }, label(type)),
|
|
2873
3104
|
h('span', { className: 'tacit-breakdown-calls' }, fmtTokens(totals.billedCalls)),
|
|
2874
3105
|
h('span', { className: 'tacit-breakdown-tokens' }, fmtTokens(tokensTotal(totals.tokens))),
|
|
2875
3106
|
h('span', { className: 'tacit-breakdown-usd' }, usageSpend(kit, totals)),
|
|
@@ -3009,7 +3240,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3009
3240
|
cell('model', item.model.length > 0 ? item.model : '—'),
|
|
3010
3241
|
cell('status', h('span', { className: 'tacit-chip tacit-status-' + item.status }, t('status.' + item.status))),
|
|
3011
3242
|
cell('calls', fmtTokens(item.billedCalls)),
|
|
3012
|
-
cell('tokens',
|
|
3243
|
+
cell('tokens', h('span', { className: 'tacit-usage-tokens' },
|
|
3244
|
+
h('span', null, fmtTokens(tokensTotal(item.tokens))),
|
|
3245
|
+
h('span', { className: 'tacit-usage-split' }, fmtTokensSplit(item.tokens)))),
|
|
3013
3246
|
cell('cost', usageSpend(kit, item)))
|
|
3014
3247
|
}
|
|
3015
3248
|
|
|
@@ -3070,19 +3303,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3070
3303
|
return h('div', { className: 'tacit-usage' },
|
|
3071
3304
|
h('p', { className: 'tacit-panel-hint' }, t('usage.label')),
|
|
3072
3305
|
h('div', { className: 'tacit-tiles' },
|
|
3073
|
-
SpendTile(kit, 'today', t('usage.today'), usage.today
|
|
3306
|
+
SpendTile(kit, 'today', t('usage.today'), usage.today,
|
|
3307
|
+
t('usage.autoToday', { n: String(usage.auto.today), budget: String(usage.auto.budget) })),
|
|
3074
3308
|
SpendTile(kit, 'month', t('usage.month'), usage.month),
|
|
3075
3309
|
SpendTile(kit, 'last30', t('usage.last30'), last30),
|
|
3076
3310
|
SpendTile(kit, 'lifetime', t('usage.since', { day: since }), usage.lifetime)),
|
|
3077
3311
|
h('div', { className: 'tacit-tiles' },
|
|
3078
3312
|
UsageTile('calls', t('usage.calls'), fmtTokens(last30.billedCalls)),
|
|
3313
|
+
UsageTile('tokens', t('usage.tokens'), fmtTokensSplit(last30.tokens)),
|
|
3079
3314
|
UsageTile('avg', t('usage.avgAnalysis'), last30.avgAnalysisUsd === null
|
|
3080
3315
|
? t('usage.priceUnavailable')
|
|
3081
3316
|
: fmtUsd(last30.avgAnalysisUsd)),
|
|
3082
3317
|
UsageTile('cached', t('usage.cachedRate'), fmtPct(last30.cachedInputRate)),
|
|
3083
|
-
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls))
|
|
3318
|
+
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls)),
|
|
3319
|
+
UsageTile('failedRepair', t('usage.failedRepair'), fmtUsd(last30.failedOrRepairUsd),
|
|
3320
|
+
t('usage.failedRepairSplit', { failed: fmtUsd(last30.failedUsd), repair: fmtUsd(last30.repairUsd) }))),
|
|
3084
3321
|
BarStrip(kit, { series: which === '7' ? usage.series7 : usage.series30, which, onSeries: props.onSeries }),
|
|
3085
|
-
UsageBreakdown(kit, {
|
|
3322
|
+
UsageBreakdown(kit, { buckets: usage.byType, title: 'usage.breakdown', label: (type) => t('runtype.' + type) }),
|
|
3323
|
+
UsageBreakdown(kit, { buckets: usage.byProvider, title: 'usage.byRoute', label: (provider) => provider }),
|
|
3324
|
+
UsageBreakdown(kit, { buckets: usage.byTrigger, title: 'usage.byTrigger', label: (trigger) => triggerLabel(t, trigger) }),
|
|
3086
3325
|
WarnBar(kit, { id: 'daily', warning: usage.warnings.daily }),
|
|
3087
3326
|
WarnBar(kit, { id: 'monthly', warning: usage.warnings.monthly }),
|
|
3088
3327
|
UsageFilters(kit, {
|
|
@@ -3325,9 +3564,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3325
3564
|
+ '.tacit-usage-filters{display:flex;flex-direction:column;gap:6px}.tacit-filter-row{display:flex;flex-wrap:wrap;gap:8px}.tacit-filter{display:flex;flex-direction:column;gap:2px;min-width:0}.tacit-filter-label{font-size:10px;color:var(--dsw-alias-label-secondary)}'
|
|
3326
3565
|
+ '.tacit-usage-runs{display:flex;flex-direction:column;gap:6px}'
|
|
3327
3566
|
+ '.tacit-usage-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;overflow:hidden}'
|
|
3328
|
-
+ '.tacit-usage-row{display:grid;grid-template-columns:minmax(90px,1fr) minmax(90px,1.2fr) minmax(80px,1.2fr) minmax(90px,1.2fr) 5rem 4rem
|
|
3567
|
+
+ '.tacit-usage-row{display:grid;grid-template-columns:minmax(90px,1fr) minmax(90px,1.2fr) minmax(80px,1.2fr) minmax(90px,1.2fr) 5rem 4rem 7rem 5rem;gap:6px;align-items:center;padding:5px 8px;border-top:1px solid var(--dsw-alias-border-l1);font-size:11px;font-variant-numeric:tabular-nums}'
|
|
3329
3568
|
+ '.tacit-usage-table>.tacit-usage-row:first-child{border-top:0}.tacit-usage-head{color:var(--dsw-alias-label-secondary);font-weight:600;background:var(--dsw-alias-bg-layer-2)}'
|
|
3330
3569
|
+ '.tacit-usage-cell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}'
|
|
3570
|
+
// The cell is nowrap, so the in/out split needs its own block to sit under the total.
|
|
3571
|
+
+ '.tacit-usage-tokens{display:flex;flex-direction:column}.tacit-usage-split{font-size:10px;color:var(--dsw-alias-label-secondary)}'
|
|
3331
3572
|
+ '.tacit-run-toggle{width:100%;text-align:left}'
|
|
3332
3573
|
+ '.tacit-usage-attempts{border-top:1px dashed var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);padding:6px 8px}.tacit-usage-attempts-cell{display:flex;flex-direction:column;gap:4px}'
|
|
3333
3574
|
+ '.tacit-attempt{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:11px;color:var(--dsw-alias-label-primary)}.tacit-attempt-meta,.tacit-attempt-tokens{color:var(--dsw-alias-label-secondary)}.tacit-attempt-usd{margin-left:auto;font-variant-numeric:tabular-nums}'
|
|
@@ -3438,11 +3679,17 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3438
3679
|
refreshPricing,
|
|
3439
3680
|
openConfirm,
|
|
3440
3681
|
closeConfirm,
|
|
3682
|
+
confirmAction,
|
|
3441
3683
|
fetchBootstrapPreview,
|
|
3684
|
+
fetchReceipt,
|
|
3685
|
+
storeFor,
|
|
3686
|
+
analyzeTurn,
|
|
3687
|
+
coachSelected,
|
|
3442
3688
|
clearAllRoot,
|
|
3443
3689
|
clearUsageHistory,
|
|
3444
3690
|
runNotice,
|
|
3445
3691
|
fmtRate,
|
|
3692
|
+
fmtTokensSplit,
|
|
3446
3693
|
ConfirmDialog,
|
|
3447
3694
|
UsageCard,
|
|
3448
3695
|
PricingCard,
|