dowafu 0.5.0 → 0.5.1
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/daemon.js +8 -4
- package/dist/db.js +11 -0
- package/dist/job.js +19 -2
- package/dist/mcp/protocol.js +32 -3
- package/dist/messages.js +34 -30
- package/dist/output.js +5 -5
- package/dist/prompt.js +7 -5
- package/package.json +1 -1
package/dist/daemon.js
CHANGED
|
@@ -79,9 +79,13 @@ export function workerFailureMessage(lang, code, signal, stderr) {
|
|
|
79
79
|
export function runCliJob(job, dbPath, lang = "en", deps = DEFAULT_RUN_CLI_JOB_DEPS) {
|
|
80
80
|
const sourceMode = fileURLToPath(import.meta.url).endsWith(".ts");
|
|
81
81
|
const cliPath = fileURLToPath(new URL(sourceMode ? "./cli.ts" : "./cli.js", import.meta.url));
|
|
82
|
+
// v5 jobs carry an explicit language; rows queued by an older client keep NULL and retain
|
|
83
|
+
// the daemon-startup fallback. Pass the resolved value to the child so its prompt and
|
|
84
|
+
// stdout use the same language as the daemon's own failure message.
|
|
85
|
+
const jobLang = job.lang ?? lang;
|
|
82
86
|
const args = sourceMode
|
|
83
|
-
? ["--import", "tsx", cliPath, job.ticket, "--yes", "--job-id", job.id, "--db", dbPath]
|
|
84
|
-
: [cliPath, job.ticket, "--yes", "--job-id", job.id, "--db", dbPath];
|
|
87
|
+
? ["--import", "tsx", cliPath, job.ticket, "--yes", "--job-id", job.id, "--db", dbPath, "--lang", jobLang]
|
|
88
|
+
: [cliPath, job.ticket, "--yes", "--job-id", job.id, "--db", dbPath, "--lang", jobLang];
|
|
85
89
|
return new Promise((resolve) => {
|
|
86
90
|
const child = deps.spawn(process.execPath, args, { stdio: ["inherit", "inherit", "pipe"], env: process.env });
|
|
87
91
|
let stderrTail = Buffer.alloc(0);
|
|
@@ -90,12 +94,12 @@ export function runCliJob(job, dbPath, lang = "en", deps = DEFAULT_RUN_CLI_JOB_D
|
|
|
90
94
|
deps.stderr.write(chunk);
|
|
91
95
|
stderrTail = Buffer.concat([stderrTail, chunk]).subarray(-WORKER_STDERR_TAIL_BYTES);
|
|
92
96
|
});
|
|
93
|
-
child.once("error", (err) => resolve({ error: m(
|
|
97
|
+
child.once("error", (err) => resolve({ error: m(jobLang, "daemonWorkerSpawnFailed", maskString(String(err))) }));
|
|
94
98
|
child.once("exit", (code, signal) => {
|
|
95
99
|
if (code === 0)
|
|
96
100
|
resolve({});
|
|
97
101
|
else
|
|
98
|
-
resolve({ error: workerFailureMessage(
|
|
102
|
+
resolve({ error: workerFailureMessage(jobLang, code, signal, stderrTail.toString("utf8")) });
|
|
99
103
|
});
|
|
100
104
|
});
|
|
101
105
|
}
|
package/dist/db.js
CHANGED
|
@@ -180,6 +180,17 @@ const MIGRATIONS = [
|
|
|
180
180
|
db.exec("ALTER TABLE provider_keys ADD COLUMN last_test_succeeded INTEGER");
|
|
181
181
|
},
|
|
182
182
|
},
|
|
183
|
+
// queue-lang-rerun §A: old queued rows deliberately remain NULL so they keep the daemon's
|
|
184
|
+
// startup-language behavior. Inspect columns even when a downgraded fixture already has
|
|
185
|
+
// user_version 5; SQLite has no ADD COLUMN IF NOT EXISTS form for this migration.
|
|
186
|
+
{
|
|
187
|
+
version: 5,
|
|
188
|
+
up: (db) => {
|
|
189
|
+
const columns = new Set(db.prepare("PRAGMA table_info(jobs)").all().map((column) => column.name));
|
|
190
|
+
if (!columns.has("lang"))
|
|
191
|
+
db.exec("ALTER TABLE jobs ADD COLUMN lang TEXT");
|
|
192
|
+
},
|
|
193
|
+
},
|
|
183
194
|
];
|
|
184
195
|
const LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
185
196
|
function migrate(db, lang) {
|
package/dist/job.js
CHANGED
|
@@ -21,13 +21,30 @@ export function findJobsByIdPrefix(db, prefix) {
|
|
|
21
21
|
export function listPendingApprovalJobs(db) {
|
|
22
22
|
return db.prepare("SELECT * FROM jobs WHERE status = 'pending_approval' ORDER BY created_at, id").all();
|
|
23
23
|
}
|
|
24
|
-
export function submitJob(db, ticket, id = randomUUID()) {
|
|
24
|
+
export function submitJob(db, ticket, id = randomUUID(), lang = null) {
|
|
25
25
|
if (!db.prepare("SELECT 1 FROM tickets WHERE name = ?").get(ticket))
|
|
26
26
|
throw new Error(`ticket does not exist: ${ticket}`);
|
|
27
27
|
const createdAt = now();
|
|
28
|
-
db.prepare("INSERT INTO jobs (id, ticket, status, created_at) VALUES (?, ?, 'pending_approval', ?)").run(id, ticket, createdAt);
|
|
28
|
+
db.prepare("INSERT INTO jobs (id, ticket, lang, status, created_at) VALUES (?, ?, ?, 'pending_approval', ?)").run(id, ticket, lang, createdAt);
|
|
29
29
|
return getJob(db, id);
|
|
30
30
|
}
|
|
31
|
+
// A dispatch can leave one __summary__ row plus one row per spoke. Count distinct result
|
|
32
|
+
// keys rather than rows so a single run remains one reminder even when its summary is absent
|
|
33
|
+
// or it has many spokes. The UNION covers both historical CLI keys (ticket name) and queued
|
|
34
|
+
// job keys (jobs.id for the same ticket) without double-counting a pathological overlap.
|
|
35
|
+
export function countTicketResultRuns(db, ticket) {
|
|
36
|
+
const row = db
|
|
37
|
+
.prepare(`SELECT COUNT(*) AS count FROM (
|
|
38
|
+
SELECT job_id FROM results WHERE job_id = ? GROUP BY job_id
|
|
39
|
+
UNION
|
|
40
|
+
SELECT r.job_id
|
|
41
|
+
FROM jobs j JOIN results r ON r.job_id = j.id
|
|
42
|
+
WHERE j.ticket = ?
|
|
43
|
+
GROUP BY r.job_id
|
|
44
|
+
)`)
|
|
45
|
+
.get(ticket, ticket);
|
|
46
|
+
return Number(row.count);
|
|
47
|
+
}
|
|
31
48
|
export function approveJob(db, id) {
|
|
32
49
|
return one(db, `UPDATE jobs
|
|
33
50
|
SET status = 'approved', approved_at = ?
|
package/dist/mcp/protocol.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { defaultDbPath, openDb } from "../db.js";
|
|
3
|
-
import { approveJob, findJobsByIdPrefix, getJob, submitJob } from "../job.js";
|
|
3
|
+
import { approveJob, countTicketResultRuns, findJobsByIdPrefix, getJob, submitJob } from "../job.js";
|
|
4
4
|
import { DAEMON_STALE_MS, daemonAlive, daemonStatePath, daemonWarning } from "../liveness.js";
|
|
5
5
|
import { readProgress } from "../progress.js";
|
|
6
6
|
import { m } from "../messages.js";
|
|
@@ -20,7 +20,10 @@ export const TOOLS = [
|
|
|
20
20
|
description: "Queue a DB ticket by name. It remains pending human terminal approval and is not executed by this tool.",
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: "object",
|
|
23
|
-
properties: {
|
|
23
|
+
properties: {
|
|
24
|
+
ticket: { type: "string", description: "DB ticket name, not a filesystem path" },
|
|
25
|
+
lang: { type: "string", enum: ["en", "zh-tw", "zh"], description: "Language for this queued job's CLI output and spoke prompts" },
|
|
26
|
+
},
|
|
24
27
|
required: ["ticket"],
|
|
25
28
|
},
|
|
26
29
|
},
|
|
@@ -70,6 +73,17 @@ function stringParam(params, name) {
|
|
|
70
73
|
const value = record(params)?.[name];
|
|
71
74
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
72
75
|
}
|
|
76
|
+
// Keep this acceptance and normalization identical to the CLI's --lang contract without
|
|
77
|
+
// importing cli-args.ts: cli-args imports the MCP HTTP transport, so sharing it here would
|
|
78
|
+
// create a protocol -> CLI args -> HTTP -> protocol runtime cycle.
|
|
79
|
+
function parseSubmitLang(value) {
|
|
80
|
+
const normalized = value.trim().toLowerCase();
|
|
81
|
+
if (normalized === "en")
|
|
82
|
+
return "en";
|
|
83
|
+
if (normalized === "zh-tw" || normalized === "zh")
|
|
84
|
+
return "zh";
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
73
87
|
function keyStatusText(lang, row) {
|
|
74
88
|
if (row.maskedTail === null)
|
|
75
89
|
return m(lang, "keyStatusMissing");
|
|
@@ -122,6 +136,9 @@ export function createProtocol(options) {
|
|
|
122
136
|
for (const { name: ticket } of names) {
|
|
123
137
|
const doc = getTicketDoc(options.db, ticket, options.lang);
|
|
124
138
|
lines.push(m(options.lang, "mcpTicketsRow", ticket, doc.spokes.length));
|
|
139
|
+
const priorRuns = countTicketResultRuns(options.db, ticket);
|
|
140
|
+
if (priorRuns > 0)
|
|
141
|
+
lines.push(` ${m(options.lang, "mcpPriorResultsReminder", priorRuns)}`);
|
|
125
142
|
for (const spoke of doc.spokes) {
|
|
126
143
|
lines.push(m(options.lang, "mcpTicketsSpoke", spoke.agent, spoke.provider, spoke.model, spoke.allow.length));
|
|
127
144
|
}
|
|
@@ -139,11 +156,23 @@ export function createProtocol(options) {
|
|
|
139
156
|
const ticket = stringParam(args, "ticket");
|
|
140
157
|
if (!ticket)
|
|
141
158
|
return text("dispatch_submit requires a non-empty ticket name", true);
|
|
159
|
+
const suppliedLang = record(args)?.lang;
|
|
160
|
+
let jobLang = null;
|
|
161
|
+
if (suppliedLang !== undefined) {
|
|
162
|
+
if (typeof suppliedLang !== "string")
|
|
163
|
+
return text(m(options.lang, "mcpSubmitInvalidLang", "(not a string)", m(options.lang, "availableLangValues")), true);
|
|
164
|
+
jobLang = parseSubmitLang(suppliedLang);
|
|
165
|
+
if (jobLang === null)
|
|
166
|
+
return text(m(options.lang, "mcpSubmitInvalidLang", suppliedLang, m(options.lang, "availableLangValues")), true);
|
|
167
|
+
}
|
|
142
168
|
try {
|
|
143
|
-
const
|
|
169
|
+
const priorRuns = countTicketResultRuns(options.db, ticket);
|
|
170
|
+
const job = submitJob(options.db, ticket, undefined, jobLang);
|
|
144
171
|
const cliPath = path.resolve(process.argv[1] ?? "cli.js");
|
|
145
172
|
const approveCommand = `node ${JSON.stringify(cliPath)} approve ${job.id.slice(0, 8)} --db ${JSON.stringify(options.dbPath)}`;
|
|
146
173
|
const lines = [m(options.lang, "mcpSubmitQueued", job.id), m(options.lang, "mcpApproveCommand", approveCommand), formatTicket(options.db, ticket, options.lang)];
|
|
174
|
+
if (priorRuns > 0)
|
|
175
|
+
lines.push(m(options.lang, "mcpPriorResultsReminder", priorRuns));
|
|
147
176
|
const offline = warning();
|
|
148
177
|
if (offline)
|
|
149
178
|
lines.push(offline);
|
package/dist/messages.js
CHANGED
|
@@ -27,6 +27,17 @@ const MESSAGES = {
|
|
|
27
27
|
dryRunNotice: () => "--dry-run:僅解析/驗證/估算/印報表,未呼叫任何 API。",
|
|
28
28
|
helpText: (cmd) => `用法:${cmd} <ticket-id> [options]
|
|
29
29
|
|
|
30
|
+
子指令(各自有自己的 --help/用法訊息):
|
|
31
|
+
${cmd} ticket ... 建立、匯入、檢視工單
|
|
32
|
+
${cmd} result <id> 印出一次派工的結果(各 spoke 原文與稽核表)
|
|
33
|
+
${cmd} key 設定 provider API key(互動選單)
|
|
34
|
+
${cmd} providers ... 型號白名單與啟用狀態
|
|
35
|
+
${cmd} token ... 入站 HTTP 憑證的發/列/撤銷
|
|
36
|
+
${cmd} approve [jobId] 核准佇列中等待的派工
|
|
37
|
+
${cmd} serve 啟動常駐 daemon(含 MCP 的 HTTP binding)
|
|
38
|
+
${cmd} serve --stop [--yes] 停止常駐 daemon;有執行中的 job 時會先確認
|
|
39
|
+
${cmd} mcp 以 stdio 提供 MCP server
|
|
40
|
+
|
|
30
41
|
--lang <en|zh-tw> CLI 輸出與 spoke prompt 的語言,預設 en
|
|
31
42
|
--repo-root <dir> 白名單邊界與 .claude/agents 的根,預設 cwd
|
|
32
43
|
--db <path> SQLite 工單與產物資料庫,預設 DISPATCH_HOME/dowafu.db
|
|
@@ -47,18 +58,7 @@ const MESSAGES = {
|
|
|
47
58
|
--yes 略過派工確認。非互動環境(stdin 不是 TTY)沒帶就中止
|
|
48
59
|
--doctor 印出設定自檢(不呼叫 API、不花錢)後結束(exit 0)
|
|
49
60
|
--help, -h 印本說明後結束(exit 0)
|
|
50
|
-
--version, -V 印版本號後結束(exit 0
|
|
51
|
-
|
|
52
|
-
子指令(各自有自己的 --help/用法訊息):
|
|
53
|
-
${cmd} ticket ... 建立、匯入、檢視工單
|
|
54
|
-
${cmd} result <id> 印出一次派工的結果(各 spoke 原文與稽核表)
|
|
55
|
-
${cmd} key 設定 provider API key(互動選單)
|
|
56
|
-
${cmd} providers ... 型號白名單與啟用狀態
|
|
57
|
-
${cmd} token ... 入站 HTTP 憑證的發/列/撤銷
|
|
58
|
-
${cmd} approve [jobId] 核准佇列中等待的派工
|
|
59
|
-
${cmd} serve 啟動常駐 daemon(含 MCP 的 HTTP binding)
|
|
60
|
-
${cmd} serve --stop [--yes] 停止常駐 daemon;有執行中的 job 時會先確認
|
|
61
|
-
${cmd} mcp 以 stdio 提供 MCP server`,
|
|
61
|
+
--version, -V 印版本號後結束(exit 0)`,
|
|
62
62
|
availableLangValues: () => "en、zh-tw、zh",
|
|
63
63
|
availableValuesSuffix: (values) => `(可用值:${values})`,
|
|
64
64
|
numberFlagInvalid: (name, value) => `--${name} 需要數字,收到:${value}`,
|
|
@@ -136,6 +136,8 @@ const MESSAGES = {
|
|
|
136
136
|
mcpEnabledModelsTitle: () => "已啟用型號白名單:",
|
|
137
137
|
mcpEnabledModelRow: (provider, model) => ` ${provider}/${model}`,
|
|
138
138
|
mcpSubmitQueued: (jobId) => `已排入佇列,job id = ${jobId},狀態 pending_approval。尚未執行,須由人在終端機核准。`,
|
|
139
|
+
mcpSubmitInvalidLang: (value, available) => `dispatch_submit 的 lang 無效:${value}(可用值:${available})。未建立 job。`,
|
|
140
|
+
mcpPriorResultsReminder: (runs) => `提醒:這張工單已有 ${runs} 次結果;這不是錯誤,仍可照常排入佇列。`,
|
|
139
141
|
mcpApproveCommand: (command) => `請在終端機執行:${command}`,
|
|
140
142
|
mcpJobStarted: (at) => `開始於 ${at}`,
|
|
141
143
|
mcpJobFinished: (at) => `結束於 ${at}`,
|
|
@@ -273,8 +275,8 @@ const MESSAGES = {
|
|
|
273
275
|
observationCountCell: (display) => `觀察:${display}`,
|
|
274
276
|
cannotCountObservations: () => "無法計數",
|
|
275
277
|
cannotVerifySectionCell: (passFail) => `無法驗證欄:${passFail}`,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
templatePlaceholdersPassCell: () => "佔位符:pass",
|
|
279
|
+
templatePlaceholdersFailCell: (count) => `佔位符:fail(內容可用,但含 ${count} 處佔位符標記,交付前請清理)`,
|
|
278
280
|
auditUnavailable: () => "(無法稽核)",
|
|
279
281
|
summaryHeader: (ticketId) => `# dispatch summary — ${ticketId}
|
|
280
282
|
|
|
@@ -345,7 +347,7 @@ const MESSAGES = {
|
|
|
345
347
|
doctorModelListLoadFailedValue: (reason) => `無法載入:${reason}`,
|
|
346
348
|
doctorLensLine: (value) => ` lens 定義 ${value}`,
|
|
347
349
|
doctorLensFoundValue: (dirPath, total, closingCount, closingNames, noClosingSuffix) => `${dirPath} 找到 ${total} 個定義檔,其中 ${closingCount} 個具備固定收尾句:\n${" ".repeat(14)}${closingNames}${noClosingSuffix}`,
|
|
348
|
-
doctorLensNoClosingSuffix: (count, names) => `\n${" ".repeat(14)}
|
|
350
|
+
doctorLensNoClosingSuffix: (count, names) => `\n${" ".repeat(14)}(另有 ${count} 個不是 lens:${names})`,
|
|
349
351
|
doctorLensDirMissingValue: (dirPath) => `目錄不存在:${dirPath}`,
|
|
350
352
|
doctorFooter: () => "本指令不呼叫任何 API,不會花錢。缺的項目怎麼補,見 README 的〈API keys〉一節。",
|
|
351
353
|
},
|
|
@@ -355,6 +357,17 @@ const MESSAGES = {
|
|
|
355
357
|
dryRunNotice: () => "--dry-run: parsed, validated, estimated, and reported only; no API calls were made.",
|
|
356
358
|
helpText: (cmd) => `Usage: ${cmd} <ticket-id> [options]
|
|
357
359
|
|
|
360
|
+
Subcommands (each has its own usage message):
|
|
361
|
+
${cmd} ticket ... Create, import and inspect tickets
|
|
362
|
+
${cmd} result <id> Print the results of one dispatch (spoke output and audit table)
|
|
363
|
+
${cmd} key Set provider API keys (interactive menu)
|
|
364
|
+
${cmd} providers ... Model whitelist and enabled state
|
|
365
|
+
${cmd} token ... Issue, list and revoke inbound HTTP tokens
|
|
366
|
+
${cmd} approve [jobId] Approve a dispatch waiting in the queue
|
|
367
|
+
${cmd} serve Run the daemon (includes the MCP HTTP binding)
|
|
368
|
+
${cmd} serve --stop [--yes] Stop the daemon; asks first if jobs are running
|
|
369
|
+
${cmd} mcp Serve MCP over stdio
|
|
370
|
+
|
|
358
371
|
--lang <en|zh-tw> Language for CLI output and spoke prompts, default en
|
|
359
372
|
--repo-root <dir> Root for the allowlist boundary and .claude/agents, default cwd
|
|
360
373
|
--db <path> SQLite ticket/result database, default DISPATCH_HOME/dowafu.db
|
|
@@ -375,18 +388,7 @@ const MESSAGES = {
|
|
|
375
388
|
--yes Skip the dispatch confirmation. Aborts in non-interactive environments (stdin not a TTY) unless given
|
|
376
389
|
--doctor Print the configuration self-check (no API call, no cost) and exit (exit 0)
|
|
377
390
|
--help, -h Print this help and exit (exit 0)
|
|
378
|
-
--version, -V Print the version and exit (exit 0)
|
|
379
|
-
|
|
380
|
-
Subcommands (each has its own usage message):
|
|
381
|
-
${cmd} ticket ... Create, import and inspect tickets
|
|
382
|
-
${cmd} result <id> Print the results of one dispatch (spoke output and audit table)
|
|
383
|
-
${cmd} key Set provider API keys (interactive menu)
|
|
384
|
-
${cmd} providers ... Model whitelist and enabled state
|
|
385
|
-
${cmd} token ... Issue, list and revoke inbound HTTP tokens
|
|
386
|
-
${cmd} approve [jobId] Approve a dispatch waiting in the queue
|
|
387
|
-
${cmd} serve Run the daemon (includes the MCP HTTP binding)
|
|
388
|
-
${cmd} serve --stop [--yes] Stop the daemon; asks first if jobs are running
|
|
389
|
-
${cmd} mcp Serve MCP over stdio`,
|
|
391
|
+
--version, -V Print the version and exit (exit 0)`,
|
|
390
392
|
availableLangValues: () => "en, zh-tw, zh",
|
|
391
393
|
availableValuesSuffix: (values) => ` (available: ${values})`,
|
|
392
394
|
numberFlagInvalid: (name, value) => `--${name} requires a number, got: ${value}`,
|
|
@@ -462,6 +464,8 @@ Subcommands (each has its own usage message):
|
|
|
462
464
|
mcpEnabledModelsTitle: () => "Enabled model allowlist:",
|
|
463
465
|
mcpEnabledModelRow: (provider, model) => ` ${provider}/${model}`,
|
|
464
466
|
mcpSubmitQueued: (jobId) => `Job queued: ${jobId}; status pending_approval. It has not started and needs terminal approval by a human.`,
|
|
467
|
+
mcpSubmitInvalidLang: (value, available) => `dispatch_submit lang is invalid: ${value} (available values: ${available}). No job was created.`,
|
|
468
|
+
mcpPriorResultsReminder: (runs) => `Reminder: this ticket already has ${runs} result run(s). This is not an error; it can still be queued.`,
|
|
465
469
|
mcpApproveCommand: (command) => `Run this in a terminal: ${command}`,
|
|
466
470
|
mcpJobStarted: (at) => `Started at ${at}`,
|
|
467
471
|
mcpJobFinished: (at) => `Finished at ${at}`,
|
|
@@ -606,8 +610,8 @@ Subcommands (each has its own usage message):
|
|
|
606
610
|
observationCountCell: (display) => `Observations:${display}`,
|
|
607
611
|
cannotCountObservations: () => "uncountable",
|
|
608
612
|
cannotVerifySectionCell: (passFail) => `Cannot-verify section:${passFail}`,
|
|
609
|
-
|
|
610
|
-
|
|
613
|
+
templatePlaceholdersPassCell: () => "Template placeholders:pass",
|
|
614
|
+
templatePlaceholdersFailCell: (count) => `Template placeholders:fail (content is usable, but contains ${count} placeholder marker(s); clean them before delivery)`,
|
|
611
615
|
auditUnavailable: () => "(audit unavailable)",
|
|
612
616
|
summaryHeader: (ticketId) => `# dispatch summary — ${ticketId}
|
|
613
617
|
|
|
@@ -674,7 +678,7 @@ Subcommands (each has its own usage message):
|
|
|
674
678
|
doctorModelListLoadFailedValue: (reason) => `failed to load: ${reason}`,
|
|
675
679
|
doctorLensLine: (value) => ` Lens defs ${value}`,
|
|
676
680
|
doctorLensFoundValue: (dirPath, total, closingCount, closingNames, noClosingSuffix) => `${dirPath} holds ${total} definition file(s), ${closingCount} with a fixed closing line:\n${" ".repeat(15)}${closingNames}${noClosingSuffix}`,
|
|
677
|
-
doctorLensNoClosingSuffix: (count, names) => `\n${" ".repeat(15)}(${count}
|
|
681
|
+
doctorLensNoClosingSuffix: (count, names) => `\n${" ".repeat(15)}(${count} other agent file(s), not lenses: ${names})`,
|
|
678
682
|
doctorLensDirMissingValue: (dirPath) => `directory not found: ${dirPath}`,
|
|
679
683
|
doctorFooter: () => 'This command calls no API and costs nothing. To fill in what is missing, see "API keys" in the README.',
|
|
680
684
|
},
|
package/dist/output.js
CHANGED
|
@@ -23,12 +23,12 @@ function formatFinishReasonCell(r, lang) {
|
|
|
23
23
|
return null;
|
|
24
24
|
return m(lang, "finishReasonWarningCell", r.finishReason ?? "unknown");
|
|
25
25
|
}
|
|
26
|
-
// 工單
|
|
27
|
-
//
|
|
26
|
+
// 工單 report-format §三 B:污染標記只改稽核欄的 pass/fail 語義,絕不改 SpokeRunResult
|
|
27
|
+
// (其 status 仍由執行器/job 狀態機決定)。失敗格給可操作的總命中數,細節仍在 JSON audit。
|
|
28
28
|
function formatPlaceholdersCell(hits, lang) {
|
|
29
29
|
if (hits.length === 0)
|
|
30
|
-
return m(lang, "
|
|
31
|
-
return
|
|
30
|
+
return m(lang, "templatePlaceholdersPassCell");
|
|
31
|
+
return m(lang, "templatePlaceholdersFailCell", hits.reduce((total, hit) => total + hit.count, 0));
|
|
32
32
|
}
|
|
33
33
|
export function buildSummaryMarkdown(ticketId, results, audits, toolCallAudits, lang) {
|
|
34
34
|
const rows = results.map((r) => {
|
|
@@ -54,7 +54,7 @@ export function buildSummaryMarkdown(ticketId, results, audits, toolCallAudits,
|
|
|
54
54
|
if (a) {
|
|
55
55
|
cells.push(m(lang, "closingLineCell", a.finalLinePass ? "pass" : "fail"),
|
|
56
56
|
// v1.9 §15:null(數不出來)與 0(明確為零)須可區分,不得混印
|
|
57
|
-
m(lang, "observationCountCell", a.observationCount !== null ? String(a.observationCount) : m(lang, "cannotCountObservations")), m(lang, "cannotVerifySectionCell", a.cannotVerifySectionPresent ? "pass" : "fail"),
|
|
57
|
+
m(lang, "observationCountCell", a.observationCount !== null ? String(a.observationCount) : m(lang, "cannotCountObservations")), m(lang, "cannotVerifySectionCell", a.cannotVerifySectionPresent ? "pass" : "fail"), formatPlaceholdersCell(a.templatePlaceholdersFound, lang));
|
|
58
58
|
}
|
|
59
59
|
const auditCell = cells.length > 0 ? cells.join(" / ") : m(lang, "auditUnavailable");
|
|
60
60
|
// plan_fixes_v1.0.md §4:無價目資料須與「估出來是 $0」區分,不能印成空白或 0——
|
package/dist/prompt.js
CHANGED
|
@@ -8,9 +8,10 @@ import path from "node:path";
|
|
|
8
8
|
// 結束,因為稽核靠那句話判斷回報有沒有寫完。翻譯時刻意保留祈使句與「原文是主要依據、
|
|
9
9
|
// 行號是輔助」這兩處措辭:實測顯示 spoke 對句型敏感,描述句會被當成建議略過。
|
|
10
10
|
const REPORT_TEMPLATE_EN = `# Observations
|
|
11
|
-
1.
|
|
11
|
+
1. Example: a retry after a timeout can repeat a write operation.
|
|
12
12
|
Evidence: <file:line, or explicit reasoning>
|
|
13
13
|
Quote: <when citing a file, copy that one line verbatim; write "reasoning" when the evidence is reasoning>
|
|
14
|
+
—— follow this shape for every observation; replace every angle-bracket marker with actual content and do not leave any behind.
|
|
14
15
|
—— when citing a file, the path must match the string in "Allowed reads" character for character; do not abbreviate it or write the filename alone.
|
|
15
16
|
—— **the quote is the primary evidence, the line number is secondary**: the hub locates the
|
|
16
17
|
real position by matching the quote, so quote verbatim. Copy only the line you are sure
|
|
@@ -21,9 +22,10 @@ const REPORT_TEMPLATE_EN = `# Observations
|
|
|
21
22
|
|
|
22
23
|
These are observations and questions. Whether to adopt them is for the hub and the user to decide.`;
|
|
23
24
|
const REPORT_TEMPLATE = `# 觀察
|
|
24
|
-
1.
|
|
25
|
+
1. 範例:逾時後的重試作業可能重複執行一次寫入。
|
|
25
26
|
依據:<檔案:行號 或 明確推理>
|
|
26
27
|
原文:<引用檔案時,逐字複製該處的一行原文;依據為推理時寫「推理」>
|
|
28
|
+
——每條觀察均依此形狀撰寫;所有角括號標記都要換成實際內容,不要保留。
|
|
27
29
|
——引用檔案時,路徑須與「允許讀取」清單中的字串逐字相同,不得縮寫或只寫檔名。
|
|
28
30
|
——**原文是主要依據,行號是輔助**:hub 會用原文比對出實際位置,所以原文必須逐字,
|
|
29
31
|
寧可只複製確定的那一行,也不要憑印象重寫。
|
|
@@ -32,9 +34,9 @@ const REPORT_TEMPLATE = `# 觀察
|
|
|
32
34
|
- <需要但讀不到的檔案,或清單不足之處>;沒有則寫「無」
|
|
33
35
|
|
|
34
36
|
以上為觀察與問題,採用與否由 hub 與使用者裁決。`;
|
|
35
|
-
// 工單
|
|
36
|
-
//
|
|
37
|
-
//
|
|
37
|
+
// 工單 report-format §二:這是「已知污染標記」清單,不再與模板雙向同源。模板現有的尖括號
|
|
38
|
+
// 標記仍須收錄(prompt.test.ts 防漏),而已移除的 <觀察>/<observation> 也必須保留:真實
|
|
39
|
+
// spoke 曾把它們原樣帶進交付物。清單從這裡匯出,audit.ts 不得重打一份字串。
|
|
38
40
|
export const REPORT_PLACEHOLDERS = [
|
|
39
41
|
"<觀察>",
|
|
40
42
|
"<檔案:行號 或 明確推理>",
|
package/package.json
CHANGED