dsh-codex-approval 0.2.2 → 0.3.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/LICENSE +21 -21
- package/README.md +172 -150
- package/cordis.patch.yml +4 -4
- package/enrich.js +68 -68
- package/i18n.js +140 -57
- package/index.js +479 -389
- package/judge.js +164 -164
- package/modes.js +62 -62
- package/package.json +42 -42
- package/rules.js +90 -90
package/i18n.js
CHANGED
|
@@ -1,57 +1,140 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-codex-approval — i18n.js
|
|
3
|
-
*
|
|
4
|
-
* zh/en copy for the /approval-mode command. The host reads the user's
|
|
5
|
-
* locale preference from the dsh settings service (`locale.preference`,
|
|
6
|
-
* owned by dsh-client-locale, persisted in settings.yaml); without a value
|
|
7
|
-
* (or without settings at all) the copy falls back to English.
|
|
8
|
-
*
|
|
9
|
-
* Pure functions only: pick the locale, render command texts.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const LOCALES = ["zh", "en"];
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The full message table. Keys are identical across locales so a missing
|
|
16
|
-
* translation fails loudly in tests rather than silently at runtime.
|
|
17
|
-
*/
|
|
18
|
-
export const T = {
|
|
19
|
-
zh: {
|
|
20
|
-
showWithOverride: (effective, override, configDefault) =>
|
|
21
|
-
`当前模式:${effective}(会话覆盖:${override},配置默认:${configDefault})`,
|
|
22
|
-
showNoOverride: (effective, configDefault) =>
|
|
23
|
-
`当前模式:${effective}(配置默认:${configDefault},无会话覆盖)`,
|
|
24
|
-
cleared: (configDefault) => `已清除会话覆盖 → 回落 ${configDefault}(配置默认)`,
|
|
25
|
-
clearedMemoryOnly: (configDefault) => `已清除会话覆盖 → 回落 ${configDefault}(配置默认;settings 不可用,仅内存)`,
|
|
26
|
-
switched: (mode) => `已切换 → ${mode}(本会话)`,
|
|
27
|
-
switchedMemoryOnly: (mode) => `已切换 → ${mode}(本会话;settings 不可用,重启后丢失)`,
|
|
28
|
-
unknown: (input) => `未知模式 "${input}" — 用 manual | ai | ai-auto(或 1/2/3),或用 default 清除覆盖`
|
|
29
|
-
},
|
|
30
|
-
en: {
|
|
31
|
-
showWithOverride: (effective, override, configDefault) =>
|
|
32
|
-
`mode: ${effective} (session override: ${override}, config default: ${configDefault})`,
|
|
33
|
-
showNoOverride: (effective, configDefault) =>
|
|
34
|
-
`mode: ${effective} (config default: ${configDefault}, no session override)`,
|
|
35
|
-
cleared: (configDefault) => `override cleared → ${configDefault} (config default)`,
|
|
36
|
-
clearedMemoryOnly: (configDefault) => `override cleared → ${configDefault} (config default; memory-only: settings unavailable)`,
|
|
37
|
-
switched: (mode) => `switched → ${mode} (this session)`,
|
|
38
|
-
switchedMemoryOnly: (mode) => `switched → ${mode} (this session; memory-only: settings unavailable, lost on restart)`,
|
|
39
|
-
unknown: (input) => `unknown mode "${input}" — use manual | ai | ai-auto (or 1/2/3), or "default" to clear the override`
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Pick the command copy locale from a raw preference value.
|
|
45
|
-
* @param pref - settings `locale.preference` (e.g. "zh", "en", or undefined)
|
|
46
|
-
* @returns "zh" | "en" — valid values pass through; anything else → "en"
|
|
47
|
-
*/
|
|
48
|
-
export function pickLocale(pref) {
|
|
49
|
-
return pref === "zh" ? "zh" : "en";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** The command description for the given locale (registered once at boot). */
|
|
53
|
-
export function commandDescription(locale) {
|
|
54
|
-
return locale === "zh"
|
|
55
|
-
? "显示或切换审批模式(manual | ai | ai-auto,或 1/2/3)"
|
|
56
|
-
: "Show or switch the approval mode (manual | ai | ai-auto, or 1/2/3)";
|
|
57
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-codex-approval — i18n.js
|
|
3
|
+
*
|
|
4
|
+
* zh/en copy for the /approval-mode command. The host reads the user's
|
|
5
|
+
* locale preference from the dsh settings service (`locale.preference`,
|
|
6
|
+
* owned by dsh-client-locale, persisted in settings.yaml); without a value
|
|
7
|
+
* (or without settings at all) the copy falls back to English.
|
|
8
|
+
*
|
|
9
|
+
* Pure functions only: pick the locale, render command texts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const LOCALES = ["zh", "en"];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The full message table. Keys are identical across locales so a missing
|
|
16
|
+
* translation fails loudly in tests rather than silently at runtime.
|
|
17
|
+
*/
|
|
18
|
+
export const T = {
|
|
19
|
+
zh: {
|
|
20
|
+
showWithOverride: (effective, override, configDefault) =>
|
|
21
|
+
`当前模式:${effective}(会话覆盖:${override},配置默认:${configDefault})`,
|
|
22
|
+
showNoOverride: (effective, configDefault) =>
|
|
23
|
+
`当前模式:${effective}(配置默认:${configDefault},无会话覆盖)`,
|
|
24
|
+
cleared: (configDefault) => `已清除会话覆盖 → 回落 ${configDefault}(配置默认)`,
|
|
25
|
+
clearedMemoryOnly: (configDefault) => `已清除会话覆盖 → 回落 ${configDefault}(配置默认;settings 不可用,仅内存)`,
|
|
26
|
+
switched: (mode) => `已切换 → ${mode}(本会话)`,
|
|
27
|
+
switchedMemoryOnly: (mode) => `已切换 → ${mode}(本会话;settings 不可用,重启后丢失)`,
|
|
28
|
+
unknown: (input) => `未知模式 "${input}" — 用 manual | ai | ai-auto(或 1/2/3),或用 default 清除覆盖`
|
|
29
|
+
},
|
|
30
|
+
en: {
|
|
31
|
+
showWithOverride: (effective, override, configDefault) =>
|
|
32
|
+
`mode: ${effective} (session override: ${override}, config default: ${configDefault})`,
|
|
33
|
+
showNoOverride: (effective, configDefault) =>
|
|
34
|
+
`mode: ${effective} (config default: ${configDefault}, no session override)`,
|
|
35
|
+
cleared: (configDefault) => `override cleared → ${configDefault} (config default)`,
|
|
36
|
+
clearedMemoryOnly: (configDefault) => `override cleared → ${configDefault} (config default; memory-only: settings unavailable)`,
|
|
37
|
+
switched: (mode) => `switched → ${mode} (this session)`,
|
|
38
|
+
switchedMemoryOnly: (mode) => `switched → ${mode} (this session; memory-only: settings unavailable, lost on restart)`,
|
|
39
|
+
unknown: (input) => `unknown mode "${input}" — use manual | ai | ai-auto (or 1/2/3), or "default" to clear the override`
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pick the command copy locale from a raw preference value.
|
|
45
|
+
* @param pref - settings `locale.preference` (e.g. "zh", "en", or undefined)
|
|
46
|
+
* @returns "zh" | "en" — valid values pass through; anything else → "en"
|
|
47
|
+
*/
|
|
48
|
+
export function pickLocale(pref) {
|
|
49
|
+
return pref === "zh" ? "zh" : "en";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The command description for the given locale (registered once at boot). */
|
|
53
|
+
export function commandDescription(locale) {
|
|
54
|
+
return locale === "zh"
|
|
55
|
+
? "显示或切换审批模式(manual | ai | ai-auto,或 1/2/3)"
|
|
56
|
+
: "Show or switch the approval mode (manual | ai | ai-auto, or 1/2/3)";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Human-readable denial-source labels, keyed per locale. Every source the
|
|
61
|
+
* plugin can stage in a denial record must have a label in both locales
|
|
62
|
+
* (rule / ai / ai-error / fallback) so renderDenialNotice never leaks a raw
|
|
63
|
+
* internal kind to the model.
|
|
64
|
+
*/
|
|
65
|
+
const SOURCE_LABELS = {
|
|
66
|
+
zh: {
|
|
67
|
+
rule: "确定性规则",
|
|
68
|
+
ai: "AI 评审",
|
|
69
|
+
"ai-error": "AI 评审故障兜底(failOpen)",
|
|
70
|
+
fallback: "兜底策略"
|
|
71
|
+
},
|
|
72
|
+
en: {
|
|
73
|
+
rule: "deterministic rule",
|
|
74
|
+
ai: "AI judge",
|
|
75
|
+
"ai-error": "AI judge failure fallback (failOpen)",
|
|
76
|
+
fallback: "fallback policy"
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The denial-feedback copy table. Renders one staged denial into the exact
|
|
82
|
+
* corrective message the `agent/pre-step` injector appends to the next model
|
|
83
|
+
* request. Keys are identical across locales so a missing translation fails
|
|
84
|
+
* loudly in tests rather than silently at runtime.
|
|
85
|
+
*/
|
|
86
|
+
const NOTICE = {
|
|
87
|
+
zh: {
|
|
88
|
+
deniedByReviewer: (cmd) => `[auto-review] 上一个操作 ${cmd} 被自动审批评审拒绝——这不是用户的拒绝。`,
|
|
89
|
+
unknownCommand: "(未知命令)",
|
|
90
|
+
sourceLine: (src, risk) => `来源:${src}${risk !== void 0 ? `;风险:${risk}` : ""}`,
|
|
91
|
+
viaAsk: "(全自动模式下 \"ask\" 被解析为 \"deny\",未经过人类确认)",
|
|
92
|
+
rationale: (text) => `评审理由:${text}`,
|
|
93
|
+
rationaleMissing: "未提供评审理由。",
|
|
94
|
+
directive: "不要通过变通手段或间接执行绕开该操作;请改用实质更安全的替代方案,或停下来询问用户。"
|
|
95
|
+
},
|
|
96
|
+
en: {
|
|
97
|
+
deniedByReviewer: (cmd) => `[auto-review] The previous action ${cmd} was denied by the automatic approval reviewer — this was NOT a user rejection.`,
|
|
98
|
+
unknownCommand: "(unknown command)",
|
|
99
|
+
sourceLine: (src, risk) => `Source: ${src}${risk !== void 0 ? `; risk: ${risk}` : ""}`,
|
|
100
|
+
viaAsk: " (denied by the auto mode default: \"ask\" resolved to \"deny\" without a human)",
|
|
101
|
+
rationale: (text) => `Review rationale: ${text}`,
|
|
102
|
+
rationaleMissing: "No rationale was provided.",
|
|
103
|
+
directive: "Do not pursue this action via workaround or indirect execution. Continue with a materially safer alternative, or stop and ask the user."
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Render one staged denial record into a corrective paragraph.
|
|
109
|
+
* @param record - { command, source, match?, risk?, aiReason?, viaAsk? }
|
|
110
|
+
* @param t - the locale's NOTICE table
|
|
111
|
+
* @returns the paragraph text (no trailing newline).
|
|
112
|
+
*/
|
|
113
|
+
function renderNoticeOne(record, t, locale) {
|
|
114
|
+
const command = record.command === undefined || record.command === ""
|
|
115
|
+
? t.unknownCommand
|
|
116
|
+
: `\`${record.command}\``;
|
|
117
|
+
const src = (SOURCE_LABELS[locale] ?? {})[record.source] ?? record.source;
|
|
118
|
+
const lines = [
|
|
119
|
+
t.deniedByReviewer(command),
|
|
120
|
+
t.sourceLine(src, record.risk) + (record.viaAsk === true ? t.viaAsk : ""),
|
|
121
|
+
record.aiReason !== undefined
|
|
122
|
+
? t.rationale(record.aiReason)
|
|
123
|
+
: t.rationaleMissing
|
|
124
|
+
];
|
|
125
|
+
return lines.join("\n");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Render staged denials into the single corrective message injected before
|
|
130
|
+
* the next model step. Multiple denials render in order, separated by a blank
|
|
131
|
+
* line, each prefixed; the closing directive is emitted once at the end.
|
|
132
|
+
* @param queue - staged denial records (1..denyFeedbackMax).
|
|
133
|
+
* @param locale - "zh" | "en".
|
|
134
|
+
* @returns the full message text.
|
|
135
|
+
*/
|
|
136
|
+
export function renderDenialNotice(queue, locale) {
|
|
137
|
+
const t = NOTICE[locale] ?? NOTICE.en;
|
|
138
|
+
const body = queue.map((record) => renderNoticeOne(record, t, locale)).join("\n\n");
|
|
139
|
+
return `${body}\n${t.directive}`;
|
|
140
|
+
}
|