cortico-world-simple-trpg-check 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/dist/webui.js +20 -16
- package/package.json +1 -1
- package/src/webui/client.ts +18 -14
- package/src/world.ts +7 -6
- package/src/secret-file.ts +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.3 - 2026-11-13
|
|
4
|
+
|
|
5
|
+
- Save a Jev API key for the selected service directly in the configuration panel. Existing keys and legacy key lookup remain supported.
|
|
6
|
+
|
|
3
7
|
## 0.4.2 - 2026-10-21
|
|
4
8
|
|
|
5
9
|
- Shorten the environment prompt while retaining narrative and reward bookkeeping rules.
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ corepack pnpm add --ignore-workspace cortico-world-simple-trpg-check
|
|
|
33
33
|
|
|
34
34
|
默认 JEV 模型使用现有技能评分校准。Luna 与其他模型显示“未校准”,按档位代表值取期望;财富与信用评级仍使用专用标尺。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
在配置页选择决策服务,输入该服务的 API Key 并点击“保存密钥”。同一服务的模型共用密钥。填写并保存后,下一次模型请求读取密钥。
|
|
37
37
|
|
|
38
38
|
| 决策服务 | 密钥变量 |
|
|
39
39
|
| --- | --- |
|
package/dist/webui.js
CHANGED
|
@@ -183,23 +183,27 @@ async function mountConfig(ctx) {
|
|
|
183
183
|
}
|
|
184
184
|
if (key === "jevSource") {
|
|
185
185
|
const sourceName = config.jevSource === "openrouter" ? "OpenRouter" : config.jevSource === "custom" ? "\u81EA\u5B9A\u4E49\u670D\u52A1" : "TypeSafe";
|
|
186
|
-
const keyStatus = ui.
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
186
|
+
const keyStatus = ui.pill(state.keySet ? "\u5BC6\u94A5\u5DF2\u914D\u7F6E" : "\u5BC6\u94A5\u672A\u914D\u7F6E", state.keySet ? "on" : "off");
|
|
187
|
+
const input = ui.input({ type: "password", cls: "mono", onCommit: () => saveKey.click() });
|
|
188
|
+
const saveKey = ui.button("\u4FDD\u5B58\u5BC6\u94A5", { size: "sm", variant: "primary", onClick: () => {
|
|
189
|
+
if (!input.value.trim()) return;
|
|
190
|
+
saveKey.disabled = true;
|
|
191
|
+
void ctx.invoke("saveKey", [config.jevSource, input.value]).then((next) => {
|
|
192
|
+
state = next;
|
|
193
|
+
status = "\u5DF2\u4FDD\u5B58";
|
|
194
|
+
failed = false;
|
|
195
|
+
render();
|
|
196
|
+
void ctx.refresh().catch(() => void 0);
|
|
197
|
+
}).catch((error) => {
|
|
198
|
+
result.textContent = error instanceof Error ? error.message : "\u4FDD\u5B58\u5931\u8D25";
|
|
199
|
+
result.classList.add("bad");
|
|
200
|
+
}).finally(() => {
|
|
201
|
+
saveKey.disabled = false;
|
|
202
|
+
});
|
|
203
|
+
} });
|
|
200
204
|
const keyRow = ui.rowbar();
|
|
201
|
-
keyRow.append(
|
|
202
|
-
fields.
|
|
205
|
+
keyRow.append(input, saveKey);
|
|
206
|
+
fields.append(field, keyStatus, ui.field(`${sourceName} API Key`, keyRow));
|
|
203
207
|
} else fields.appendChild(field);
|
|
204
208
|
if (key === "jevModel" && !hasCalibratedReadout(decisionModel(config))) {
|
|
205
209
|
fields.appendChild(ui.msgline("\u672A\u6821\u51C6\uFF1A\u6280\u80FD\u6C34\u5E73\u6309\u6863\u4F4D\u4EE3\u8868\u503C\u53D6\u671F\u671B\u3002"));
|
package/package.json
CHANGED
package/src/webui/client.ts
CHANGED
|
@@ -98,21 +98,25 @@ async function mountConfig(ctx: WebUiPanelContext): Promise<void> {
|
|
|
98
98
|
}
|
|
99
99
|
if (key === 'jevSource') {
|
|
100
100
|
const sourceName = config.jevSource === 'openrouter' ? 'OpenRouter' : config.jevSource === 'custom' ? '自定义服务' : 'TypeSafe';
|
|
101
|
-
const keyStatus = ui.
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
const keyStatus = ui.pill(state.keySet ? '密钥已配置' : '密钥未配置', state.keySet ? 'on' : 'off');
|
|
102
|
+
const input = ui.input({ type: 'password', cls: 'mono', onCommit: () => saveKey.click() });
|
|
103
|
+
const saveKey = ui.button('保存密钥', { size: 'sm', variant: 'primary', onClick: () => {
|
|
104
|
+
if (!input.value.trim()) return;
|
|
105
|
+
saveKey.disabled = true;
|
|
106
|
+
void ctx.invoke<ConfigState>('saveKey', [config.jevSource, input.value]).then(next => {
|
|
107
|
+
state = next;
|
|
108
|
+
status = '已保存';
|
|
109
|
+
failed = false;
|
|
110
|
+
render();
|
|
111
|
+
void ctx.refresh().catch(() => undefined);
|
|
112
|
+
}).catch(error => {
|
|
113
|
+
result.textContent = error instanceof Error ? error.message : '保存失败';
|
|
114
|
+
result.classList.add('bad');
|
|
115
|
+
}).finally(() => { saveKey.disabled = false; });
|
|
116
|
+
} });
|
|
113
117
|
const keyRow = ui.rowbar();
|
|
114
|
-
keyRow.append(
|
|
115
|
-
fields.
|
|
118
|
+
keyRow.append(input, saveKey);
|
|
119
|
+
fields.append(field, keyStatus, ui.field(`${sourceName} API Key`, keyRow));
|
|
116
120
|
} else fields.appendChild(field);
|
|
117
121
|
if (key === 'jevModel' && !hasCalibratedReadout(decisionModel(config))) {
|
|
118
122
|
fields.appendChild(ui.msgline('未校准:技能水平按档位代表值取期望。'));
|
package/src/world.ts
CHANGED
|
@@ -35,7 +35,6 @@ import { formatContest, formatReceipt, type ContestSide, type SkillCheck } from
|
|
|
35
35
|
import { SystemOneSkillScorer, type SkillScorer } from './model.ts';
|
|
36
36
|
import { budgetViolation, checksLabel, type CheckRequest, type CheckSpec } from './request.ts';
|
|
37
37
|
import { discoverCondaPythonOptions } from './conda-environments.ts';
|
|
38
|
-
import { ensureSecretPlaceholder, openSecretFile } from './secret-file.ts';
|
|
39
38
|
|
|
40
39
|
const ENV_PROMPT_FILE = fileURLToPath(new URL('./ENV_PROMPT.md', import.meta.url));
|
|
41
40
|
|
|
@@ -175,7 +174,6 @@ export class SimpleTrpgCheckWorld implements World {
|
|
|
175
174
|
private readonly ctx: WorldContext<SimpleTrpgCheckConfigSection>,
|
|
176
175
|
scorer?: SkillScorer,
|
|
177
176
|
private readonly roll: () => number = () => randomInt(1, 101),
|
|
178
|
-
private readonly openFile: (path: string) => void | Promise<void> = openSecretFile,
|
|
179
177
|
) {
|
|
180
178
|
this.scorer = scorer ?? new SystemOneSkillScorer(ctx);
|
|
181
179
|
}
|
|
@@ -255,16 +253,19 @@ export class SimpleTrpgCheckWorld implements World {
|
|
|
255
253
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
256
254
|
}
|
|
257
255
|
}
|
|
258
|
-
if (method === '
|
|
256
|
+
if (method === 'saveKey') {
|
|
259
257
|
const source = args[0];
|
|
260
258
|
if (source !== this.ctx.cfg.jevSource || this.ctx.cfg.backend !== 'jev') {
|
|
261
259
|
throw new Error('决策服务已改变,请重试');
|
|
262
260
|
}
|
|
263
261
|
const secretName = this.ctx.cfg.jevSource === 'openrouter' ? SIMPLE_TRPG_CHECK_OPENROUTER_SECRET
|
|
264
262
|
: this.ctx.cfg.jevSource === 'custom' ? SIMPLE_TRPG_CHECK_CUSTOM_SECRET : SIMPLE_TRPG_CHECK_TYPESAFE_SECRET;
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
const value = args[1];
|
|
264
|
+
if (typeof value !== 'string') throw new Error('密钥须为文本');
|
|
265
|
+
const key = value.trim();
|
|
266
|
+
if (/\s/.test(key)) throw new Error('密钥不能包含空白字符');
|
|
267
|
+
if (key) this.ctx.storeSecret(secretName, key);
|
|
268
|
+
return { config: { ...this.ctx.cfg }, keySet: this.hasJevKey() };
|
|
268
269
|
}
|
|
269
270
|
throw new Error('未知操作');
|
|
270
271
|
},
|
package/src/secret-file.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
export function ensureSecretPlaceholder(botDir: string, name: string): string {
|
|
6
|
-
const file = join(botDir, '.env');
|
|
7
|
-
const contents = existsSync(file) ? readFileSync(file, 'utf8') : '';
|
|
8
|
-
if (!new RegExp(`^${name}=`, 'm').test(contents)) {
|
|
9
|
-
const line = `${contents && !contents.endsWith('\n') ? '\n' : ''}${name}=\n`;
|
|
10
|
-
if (existsSync(file)) appendFileSync(file, line);
|
|
11
|
-
else writeFileSync(file, line, { mode: 0o600 });
|
|
12
|
-
}
|
|
13
|
-
return file;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function openSecretFile(file: string): Promise<void> {
|
|
17
|
-
const command = process.platform === 'win32' ? 'notepad.exe' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const child = spawn(command, [file], { detached: true, stdio: 'ignore' });
|
|
20
|
-
child.once('error', reject);
|
|
21
|
-
child.once('spawn', () => { child.unref(); resolve(); });
|
|
22
|
-
});
|
|
23
|
-
}
|