cortico-world-workbuddy 0.3.0 → 0.3.4
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/CHANGELOG.md +127 -2
- package/README.md +71 -54
- package/SECURITY.md +29 -16
- package/describe.json +2 -2
- package/dist/webui.js +58 -72
- package/package.json +3 -3
- package/scripts/clean-legacy-injection.mjs +77 -0
- package/scripts/clean-workbuddy.mjs +101 -0
- package/scripts/contract-regression.mjs +221 -0
- package/scripts/contract.mjs +45 -17
- package/scripts/reinstall.ps1 +15 -4
- package/scripts/selfcheck.mjs +1 -1
- package/scripts/verify-ui.mjs +19 -33
- package/src/ENV_PROMPT.md +4 -10
- package/src/config.js +5 -16
- package/src/credentials.js +11 -3
- package/src/definition.js +3 -3
- package/src/promo.js +64 -12
- package/src/upstream.js +31 -63
- package/src/usage.js +27 -21
- package/src/wbproc.js +0 -7
- package/src/webui/client.js +62 -84
- package/src/world.js +96 -139
package/src/webui/client.js
CHANGED
|
@@ -26,7 +26,6 @@ const TEXT = {
|
|
|
26
26
|
source: '来源',
|
|
27
27
|
token: 'Token(掩码)',
|
|
28
28
|
account: '账号',
|
|
29
|
-
region: '区域',
|
|
30
29
|
upstream: '上游',
|
|
31
30
|
rediscover: '重新检测本机登录',
|
|
32
31
|
pasteToken: '手动粘贴 access token',
|
|
@@ -39,29 +38,26 @@ const TEXT = {
|
|
|
39
38
|
lastError: '最近错误',
|
|
40
39
|
models: '个模型',
|
|
41
40
|
currentModel: '当前模型',
|
|
42
|
-
priorityTitle: '中转站优先',
|
|
43
|
-
priorityLabel: '是否启用Workbuddy',
|
|
44
|
-
priorityDesc:
|
|
45
|
-
'打开后桌宠每轮对话优先调用 WorkBuddy 的模型,关闭则完全跳过 WorkBuddy。需要 WorkBuddy 桌面端主进程在运行。',
|
|
46
41
|
procRunning: 'WorkBuddy 主进程:运行中',
|
|
47
42
|
procStopped: 'WorkBuddy 主进程:未检测到',
|
|
48
|
-
|
|
49
|
-
enabledOn: '已开启:桌宠优先走 WorkBuddy 模型',
|
|
50
|
-
enabledOff: '已关闭:桌宠跳过 WorkBuddy',
|
|
51
|
-
usageTitle: '用量',
|
|
43
|
+
usageTitle: '用量与成本',
|
|
52
44
|
usageDesc:
|
|
53
|
-
'
|
|
45
|
+
'经由 WorkBuddy 消耗的 token 与积分(本地记账):积分取上游每次调用回传的真实扣费(可能为 0),token 取同一段 SSE 的 usage;倍率按目录 + 限时优惠实时计算。',
|
|
54
46
|
usageRefresh: '刷新余额',
|
|
55
47
|
usageClear: '清空本地账本',
|
|
56
48
|
usageCleared: '本地账本已清空',
|
|
57
49
|
metricCalls: '调用',
|
|
50
|
+
metricFailed: '失败',
|
|
58
51
|
metricCredit: '积分',
|
|
59
52
|
metricInput: '输入 tok',
|
|
60
53
|
metricOutput: '输出 tok',
|
|
54
|
+
metricCached: '缓存命中',
|
|
55
|
+
metricTotal: '共 tok',
|
|
61
56
|
spanToday: '今天',
|
|
62
57
|
spanWeek: '近 7 天',
|
|
63
58
|
spanMonth: '近 30 天',
|
|
64
|
-
|
|
59
|
+
spanAll: '全部',
|
|
60
|
+
byModel: '按模型(近 30 天)',
|
|
65
61
|
balanceTitle: '积分包余额',
|
|
66
62
|
colPkgName: '积分包',
|
|
67
63
|
colPkgRemain: '剩余',
|
|
@@ -107,7 +103,6 @@ const TEXT = {
|
|
|
107
103
|
source: 'Source',
|
|
108
104
|
token: 'Token (masked)',
|
|
109
105
|
account: 'Account',
|
|
110
|
-
region: 'Region',
|
|
111
106
|
upstream: 'Upstream',
|
|
112
107
|
rediscover: 'Re-detect local sign-in',
|
|
113
108
|
pasteToken: 'Paste an access token manually',
|
|
@@ -120,29 +115,26 @@ const TEXT = {
|
|
|
120
115
|
lastError: 'Last error',
|
|
121
116
|
models: 'models',
|
|
122
117
|
currentModel: 'Current model',
|
|
123
|
-
priorityTitle: 'Relay first',
|
|
124
|
-
priorityLabel: 'Enable Workbuddy',
|
|
125
|
-
priorityDesc:
|
|
126
|
-
'Same state as the “Relay first” button on the Home page: when on, the pet asks a WorkBuddy model first every turn; when off, WorkBuddy is skipped entirely. Requires the WorkBuddy desktop main process.',
|
|
127
118
|
procRunning: 'WorkBuddy process: running',
|
|
128
119
|
procStopped: 'WorkBuddy process: not detected',
|
|
129
|
-
|
|
130
|
-
enabledOn: 'On: the pet asks WorkBuddy first',
|
|
131
|
-
enabledOff: 'Off: the pet skips WorkBuddy',
|
|
132
|
-
usageTitle: 'Usage',
|
|
120
|
+
usageTitle: 'Usage & cost',
|
|
133
121
|
usageDesc:
|
|
134
|
-
'
|
|
122
|
+
'Tokens and credits spent through WorkBuddy (locally recorded): credits are the per-call figure the upstream returns (may be 0), tokens come from the same SSE usage block; multipliers combine the catalog with time-limited offers in real time.',
|
|
135
123
|
usageRefresh: 'Refresh balance',
|
|
136
124
|
usageClear: 'Clear local ledger',
|
|
137
125
|
usageCleared: 'Local ledger cleared',
|
|
138
126
|
metricCalls: 'Calls',
|
|
127
|
+
metricFailed: 'Failed',
|
|
139
128
|
metricCredit: 'Credits',
|
|
140
129
|
metricInput: 'Input tok',
|
|
141
130
|
metricOutput: 'Output tok',
|
|
131
|
+
metricCached: 'Cached',
|
|
132
|
+
metricTotal: 'Total tok',
|
|
142
133
|
spanToday: 'Today',
|
|
143
134
|
spanWeek: 'Last 7 days',
|
|
144
135
|
spanMonth: 'Last 30 days',
|
|
145
|
-
|
|
136
|
+
spanAll: 'All',
|
|
137
|
+
byModel: 'By model (last 30 days)',
|
|
146
138
|
balanceTitle: 'Credit packages',
|
|
147
139
|
colPkgName: 'Package',
|
|
148
140
|
colPkgRemain: 'Remaining',
|
|
@@ -215,9 +207,10 @@ function mountModels(ctx) {
|
|
|
215
207
|
const head = ui.rowbar();
|
|
216
208
|
const refreshBtn = ui.button(s.refresh, { variant: 'primary' });
|
|
217
209
|
const stateChip = ui.chip('', 'plain');
|
|
210
|
+
const procChip = ui.chip('', 'plain');
|
|
218
211
|
const currentChip = ui.chip('', 'accent');
|
|
219
212
|
const allBox = ui.checkbox(s.showAllModels, { checked: false });
|
|
220
|
-
head.append(refreshBtn, stateChip, currentChip, allBox.el);
|
|
213
|
+
head.append(refreshBtn, stateChip, procChip, currentChip, allBox.el);
|
|
221
214
|
sheet.body.append(head);
|
|
222
215
|
|
|
223
216
|
const errLine = ui.msgline();
|
|
@@ -238,6 +231,7 @@ function mountModels(ctx) {
|
|
|
238
231
|
state?.safeOnly === false ? s.safeOnlyOff : s.safeOnlyOn
|
|
239
232
|
}`;
|
|
240
233
|
currentChip.textContent = `${s.currentModel}: ${state?.model || '—'}`;
|
|
234
|
+
procChip.textContent = state?.running ? s.procRunning : s.procStopped;
|
|
241
235
|
}
|
|
242
236
|
|
|
243
237
|
/** 当前是否允许选这个模型:safeOnly 开时只放行安全清单。 */
|
|
@@ -397,7 +391,6 @@ function mountAccount(ctx) {
|
|
|
397
391
|
{ k: s.source, v: state?.tokenSource ?? '—' },
|
|
398
392
|
{ k: s.token, v: state?.tokenMask ?? '—' },
|
|
399
393
|
{ k: s.account, v: state?.account?.name ?? '—' },
|
|
400
|
-
{ k: s.region, v: state?.region ?? '—' },
|
|
401
394
|
{ k: s.upstream, v: state?.baseUrl ?? '—' },
|
|
402
395
|
];
|
|
403
396
|
if (state?.lastError) rows.push({ k: s.lastError, v: state.lastError });
|
|
@@ -467,63 +460,17 @@ function mountAccount(ctx) {
|
|
|
467
460
|
});
|
|
468
461
|
}
|
|
469
462
|
|
|
470
|
-
function mountPriority(ctx) {
|
|
471
|
-
const s = strings(ctx);
|
|
472
|
-
const { ui } = ctx;
|
|
473
|
-
|
|
474
|
-
const sheet = ui.sheet({ title: s.priorityTitle, desc: s.priorityDesc });
|
|
475
|
-
ctx.root.append(sheet.el);
|
|
476
|
-
|
|
477
|
-
const statusLine = ui.msgline();
|
|
478
|
-
|
|
479
|
-
/** 用服务端状态刷新界面(不触发 onChange)。 */
|
|
480
|
-
function apply(r) {
|
|
481
|
-
if (!r) return;
|
|
482
|
-
box.setChecked(r.prefer === true);
|
|
483
|
-
statusLine.textContent = r.running ? s.procRunning : s.procStopped;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const box = ui.checkbox(s.priorityLabel, {
|
|
487
|
-
checked: false,
|
|
488
|
-
title: s.priorityDesc,
|
|
489
|
-
onChange: async (checked) => {
|
|
490
|
-
try {
|
|
491
|
-
const r = await ctx.invoke('set', [checked]);
|
|
492
|
-
if (r?.ok) {
|
|
493
|
-
apply(r);
|
|
494
|
-
ui.toast(checked ? s.enabledOn : s.enabledOff);
|
|
495
|
-
} else {
|
|
496
|
-
// 没检测到 WorkBuddy 主进程(或其它失败):开关退回关,并把原因说清楚。
|
|
497
|
-
apply(r);
|
|
498
|
-
box.setChecked(false);
|
|
499
|
-
ui.toast(r?.error ?? s.noProcess, 'bad');
|
|
500
|
-
}
|
|
501
|
-
} catch (err) {
|
|
502
|
-
box.setChecked(false);
|
|
503
|
-
ui.toast(String(err?.message ?? err), 'bad');
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
});
|
|
507
|
-
sheet.body.append(box.el, statusLine);
|
|
508
|
-
|
|
509
|
-
async function load() {
|
|
510
|
-
apply(await ctx.invoke('state'));
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
ctx.stream({
|
|
514
|
-
message: () => load().catch(() => {}),
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
load().catch((err) => {
|
|
518
|
-
statusLine.textContent = String(err?.message ?? err);
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
|
|
522
463
|
function fmtNum(n) {
|
|
523
464
|
if (typeof n !== 'number' || !Number.isFinite(n)) return '—';
|
|
524
465
|
return String(Math.round(n * 100) / 100);
|
|
525
466
|
}
|
|
526
467
|
|
|
468
|
+
/** 有记录未回传 credit 时明确标出缺口,绝不把未知当成 0。 */
|
|
469
|
+
function fmtCredit(bucket) {
|
|
470
|
+
const known = fmtNum(bucket?.credit);
|
|
471
|
+
return (bucket?.creditUnknown ?? 0) > 0 ? `${known} + ?` : known;
|
|
472
|
+
}
|
|
473
|
+
|
|
527
474
|
function mountUsage(ctx) {
|
|
528
475
|
const s = strings(ctx);
|
|
529
476
|
const { ui } = ctx;
|
|
@@ -542,16 +489,26 @@ function mountUsage(ctx) {
|
|
|
542
489
|
const errLine = ui.msgline();
|
|
543
490
|
sheet.body.append(errLine);
|
|
544
491
|
|
|
545
|
-
// 区间概览:今天 / 近 7 天 / 近 30 天 / 全部(口径与 Coopanion
|
|
492
|
+
// 区间概览:今天 / 近 7 天 / 近 30 天 / 全部(口径与 Coopanion「用量与成本」一致:
|
|
493
|
+
// 调用数、失败数、积分、输入/输出/缓存命中/合计 token)。
|
|
546
494
|
const overview = ui.table({
|
|
547
|
-
head: [
|
|
495
|
+
head: [
|
|
496
|
+
'',
|
|
497
|
+
s.metricCalls,
|
|
498
|
+
s.metricFailed,
|
|
499
|
+
s.metricCredit,
|
|
500
|
+
s.metricInput,
|
|
501
|
+
s.metricOutput,
|
|
502
|
+
s.metricCached,
|
|
503
|
+
s.metricTotal,
|
|
504
|
+
],
|
|
548
505
|
maxHeight: '220px',
|
|
549
506
|
});
|
|
550
507
|
sheet.body.append(overview.el);
|
|
551
508
|
|
|
552
509
|
const modelSection = ui.section(s.byModel);
|
|
553
510
|
const modelTable = ui.table({
|
|
554
|
-
head: [s.colModel, s.metricCalls, s.metricCredit, s.metricInput, s.metricOutput],
|
|
511
|
+
head: [s.colModel, s.metricCalls, s.metricFailed, s.metricCredit, s.metricInput, s.metricOutput, s.metricTotal],
|
|
555
512
|
maxHeight: '260px',
|
|
556
513
|
});
|
|
557
514
|
modelSection.append(modelTable.el);
|
|
@@ -582,24 +539,46 @@ function mountUsage(ctx) {
|
|
|
582
539
|
|
|
583
540
|
function spanRow(label, bucket) {
|
|
584
541
|
const b = bucket ?? {};
|
|
585
|
-
return [
|
|
542
|
+
return [
|
|
543
|
+
label,
|
|
544
|
+
String(b.calls ?? 0),
|
|
545
|
+
String(b.failed ?? 0),
|
|
546
|
+
fmtCredit(b),
|
|
547
|
+
fmtNum(b.prompt),
|
|
548
|
+
fmtNum(b.completion),
|
|
549
|
+
fmtNum(b.cached),
|
|
550
|
+
fmtNum(b.total),
|
|
551
|
+
];
|
|
586
552
|
}
|
|
587
553
|
|
|
588
554
|
function render() {
|
|
589
555
|
const totals = state?.summary?.totals ?? {};
|
|
590
|
-
todayChip.textContent = `${s.todayCredit}: ${
|
|
556
|
+
todayChip.textContent = `${s.todayCredit}: ${fmtCredit(totals.today)} · ${s.todayTokens}: ${fmtNum(
|
|
591
557
|
(totals.today?.prompt ?? 0) + (totals.today?.completion ?? 0),
|
|
592
558
|
)}`;
|
|
593
559
|
balanceChip.textContent = `${s.balanceTitle}: ${fmtNum(state?.balance?.total)}`;
|
|
594
560
|
|
|
595
|
-
const spans = [
|
|
561
|
+
const spans = [
|
|
562
|
+
spanRow(s.spanToday, totals.today),
|
|
563
|
+
spanRow(s.spanWeek, totals.week),
|
|
564
|
+
spanRow(s.spanMonth, totals.month),
|
|
565
|
+
spanRow(s.spanAll, totals.all),
|
|
566
|
+
];
|
|
596
567
|
overview.clear();
|
|
597
568
|
for (const row of spans) overview.addRow(row);
|
|
598
569
|
|
|
599
570
|
const byModel = state?.summary?.byModel ?? [];
|
|
600
571
|
modelTable.clear(byModel.length > 0 ? undefined : s.noData);
|
|
601
572
|
for (const m of byModel) {
|
|
602
|
-
modelTable.addRow([
|
|
573
|
+
modelTable.addRow([
|
|
574
|
+
m.model,
|
|
575
|
+
String(m.calls ?? 0),
|
|
576
|
+
String(m.failed ?? 0),
|
|
577
|
+
fmtCredit(m),
|
|
578
|
+
fmtNum(m.prompt),
|
|
579
|
+
fmtNum(m.completion),
|
|
580
|
+
fmtNum(m.total),
|
|
581
|
+
]);
|
|
603
582
|
}
|
|
604
583
|
|
|
605
584
|
const packages = state?.balance?.packages ?? [];
|
|
@@ -706,7 +685,6 @@ export default {
|
|
|
706
685
|
panels: {
|
|
707
686
|
models: { mount: mountModels },
|
|
708
687
|
account: { mount: mountAccount },
|
|
709
|
-
priority: { mount: mountPriority },
|
|
710
688
|
usage: { mount: mountUsage },
|
|
711
689
|
},
|
|
712
690
|
};
|