@kienntpixon/dieu-phoi 0.2.0 → 0.2.2
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 +14 -0
- package/README.md +4 -4
- package/bin/dieu-phoi.cjs +3 -0
- package/hook.cjs +50 -6
- package/lib/choose.cjs +7 -8
- package/lib/commands.cjs +37 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog — @kienntpixon/dieu-phoi
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
- **Trần model hạ xuống `opus/medium`** (trước là `opus/high`). Thang tier bỏ `opus/high`: `haiku/low … sonnet/high < opus/medium`. Mọi (model, effort) cao hơn (opus `high`/`xhigh`/`max`, Fable) bị kẹp về `opus/medium` khi đọc log và profile; `--override opus/high` / `--approved opus/high` bị từ chối (không nằm trên thang, lỗi nêu trần `opus/medium`).
|
|
6
|
+
- Prior mặc định `plan-design` → `opus/medium`. Sàn score ≥ 9 → `tối thiểu opus/medium`.
|
|
7
|
+
- Leo thang quá trần → `ask_user` với ghi chú "hỏi người dùng: làm rõ yêu cầu hoặc đổi kế hoạch" (không còn gợi ý chạy `opus/high`). Bỏ ước tính thô `opus/high` khỏi `ROUGH`.
|
|
8
|
+
|
|
9
|
+
## 0.2.1
|
|
10
|
+
|
|
11
|
+
- **Định dạng `task_id` được kiểm:** đúng 8 ký tự `[a-z0-9]`. Một phiên thật đã tự bịa `almanac-cat-1791568325` làm root id mà không gì chặn. `plan-table --approve` (cả `--root` lẫn `task_id` từng bước), `choose --task`, `verdict --task`, `steer --task` giờ từ chối id sai: exit ≠ 0, lỗi tiếng Việt nêu id sai và gợi ý một id mới hợp lệ (`task_id "…" không hợp lệ: cần đúng 8 ký tự a-z0-9 (vd. k3f9x2ab)`), không ghi gì. `plan-table` không `--approve` chỉ cảnh báo trên stderr.
|
|
12
|
+
- Lệnh mới `dieu-phoi new-id [--count n] [--json]`: in task_id mới hợp lệ, mỗi dòng một id (sinh bằng `crypto`), để điều phối viên không bao giờ tự bịa id.
|
|
13
|
+
- Core (`hook.cjs`) export `isValidTaskId`, `newTaskId`, `taskIdError` — một chỗ duy nhất cho cả CLI và hook.
|
|
14
|
+
- Hook không từ chối hay bỏ sự kiện: vẫn nhận dạng cũ `[A-Za-z0-9_-]{1,32}` để log/phiên cũ còn đọc được, nhưng `dispatch` có id sai định dạng mang thêm `task_id_invalid: true` (trường bổ sung, `schema` vẫn 1). `doctor` có dòng `task-ids`: CẢNH BÁO khi có dispatch như vậy trong 7 ngày gần đây.
|
|
15
|
+
- Hook `SessionStart(compact)` thêm trường `systemMessage`: một dòng tiếng Việt cho người dùng, vd. `dieu-phoi: đã nạp lại trạng thái điều phối sau khi nén (6 task, 2 agent đang chạy, 2 bước chưa giao)` — cùng số đếm với dòng tiêu đề trạng thái. Phiên không có dispatch của dieu-phoi vẫn không in gì.
|
|
16
|
+
|
|
3
17
|
## 0.2.0
|
|
4
18
|
|
|
5
19
|
- **Nâng cấp:** **dán lại `sheet/Code.gs` và deploy bản mới TRƯỚC khi nâng CLI** — Code.gs cũ bỏ qua (làm rơi) các trường mới `session_id, orch_ctx_tokens, orch_cum_usd, orch_ctx_baseline, orch_compactions` của bản ghi được đẩy lên trong lúc chưa cập nhật; sau đó `npm i -g @kienntpixon/dieu-phoi@0.2.0` rồi `dieu-phoi setup`.
|
package/README.md
CHANGED
|
@@ -43,20 +43,21 @@ Khởi động lại Claude Code sau khi cài. Kiểm tra bằng `dieu-phoi doct
|
|
|
43
43
|
| `choose --type T --score N [--urgency U] [--reversible y\|n] [--verifiable y\|n] [--role R] [--task ID] [--override M/E \| --approved M/E] [--skill-dir P] [--seed S] [--json]` | đề xuất model + effort kèm lý do, `action`, `role`, ước tính chi phí/thời gian. `--override` cần `--task`. Kèm `orch_ctx_tokens`, `orch_ctx_growth`, `orch_ctx_over` (như `ctx`); vượt trần mềm → thêm một dòng cảnh báo |
|
|
44
44
|
| `ctx [--session <id>] [--json]` | context phiên điều phối: `ctx_tokens` (request cuối), `ctx_baseline` (request đầu: system prompt + tools), `ctx_growth`, chi phí cộng dồn `cum_usd`, số lần đã nén `compactions`; `over` = `ctx_growth` > `orch_ctx_soft_cap` (profile, mặc định 60000 token tăng, chỉ được hạ). Session mặc định = `$CLAUDE_CODE_SESSION_ID`. JSON: `session_id, ctx_tokens, ctx_baseline, ctx_growth, cum_usd, compactions, soft_cap, over, advice`. Vượt trần → khuyên gõ `/compact` (trạng thái điều phối được nạp lại tự động). Đọc tăng dần qua cache `data/orch/`. Không đo được → exit 0, các số `null`, `over: false` |
|
|
45
45
|
| `state [--session <id>] [--json]` | trạng thái điều phối của phiên dựng lại chỉ từ log: mỗi task — cổng (1/2/3/xong), vòng, tier, các lượt (agent, role, try, model/effort, running/done/pass/fail/timeout), verdict người dùng, steer cuối, thư mục chạy; nhóm theo kế hoạch đã duyệt (tier đã duyệt, bước "chưa giao", thư mục chạy theo root). Hook `SessionStart(compact)` tự nạp bản này vào ngữ cảnh sau `/compact` hoặc auto-compact. Bản chữ ≤ 8.000 ký tự |
|
|
46
|
-
| `plan-table --file <steps.json> [--json] [--approve --root <root_task_id>]` | bảng phân công tiếng Việt (Bước \| Model/effort \| Rubric: điểm · kiểm chứng · đảo ngược \| Lý do \| Ước tính) + tổng chi phí + thời gian đường găng. Bước lỗi (override sai) hiện \"từ chối: …\", các bước khác vẫn hiện. Sau khi người dùng duyệt: `--approve --root R` ghi kế hoạch đã duyệt vào `data/plans/R.json` (cục bộ; `approval` = số lần duyệt, duyệt lại = +1, lần cũ vào `history`) và in thêm "Đã ghi kế hoạch đã duyệt (root R, lần duyệt n)". Từ chối (không ghi gì) khi bước thiếu `task_id`, `task_id` lặp, hoặc trùng `--root` |
|
|
46
|
+
| `plan-table --file <steps.json> [--json] [--approve --root <root_task_id>]` | bảng phân công tiếng Việt (Bước \| Model/effort \| Rubric: điểm · kiểm chứng · đảo ngược \| Lý do \| Ước tính) + tổng chi phí + thời gian đường găng. Bước lỗi (override sai) hiện \"từ chối: …\", các bước khác vẫn hiện. Sau khi người dùng duyệt: `--approve --root R` ghi kế hoạch đã duyệt vào `data/plans/R.json` (cục bộ; `approval` = số lần duyệt, duyệt lại = +1, lần cũ vào `history`) và in thêm "Đã ghi kế hoạch đã duyệt (root R, lần duyệt n)". Từ chối (không ghi gì) khi bước thiếu `task_id`, `task_id` sai định dạng, `task_id` lặp, hoặc trùng `--root`; `--root` phải đúng 8 ký tự `a-z0-9` |
|
|
47
|
+
| `new-id [--count n] [--json]` | in task_id mới hợp lệ (đúng 8 ký tự `a-z0-9`), mỗi dòng một id (mặc định 1, tối đa 1000; `--json`: mảng). Lấy mọi `task_id` / `root_task_id` từ đây — không tự bịa. `choose/verdict/steer --task` và `plan-table --approve` từ chối id sai định dạng (kèm gợi ý id mới); hook vẫn ghi id cũ nhưng gắn `task_id_invalid: true`, `doctor` cảnh báo |
|
|
47
48
|
| `sync [--json]` | đẩy bản ghi chưa đồng bộ lên sink, lô ≤ 200, có thử lại. Bản ghi sink từ chối → `data/sync-rejected.jsonl`, `doctor` báo số lượng |
|
|
48
49
|
| `retro [--json]` | tóm tắt cho vòng retro: số liệu từng ô, đếm `reason_code`, danh sách loại task, tóm tắt điều phối (`orchestration`) |
|
|
49
50
|
| `uninstall [--purge]` | gỡ hook và đúng các quyền thư mục `runs` đã thêm khỏi settings (có sao lưu); `--purge` xoá cả `hook.cjs`. Dữ liệu giữ lại |
|
|
50
51
|
|
|
51
52
|
### `choose`
|
|
52
53
|
|
|
53
|
-
1. **Luật an toàn** (cố định trong core): trần `opus/high
|
|
54
|
+
1. **Luật an toàn** (cố định trong core): trần `opus/medium` (opus/high, xhigh, max, Fable bị kẹp về opus/medium; `--override opus/high` bị từ chối). Sàn: score ≥ 9 → opus/medium; `--verifiable n` → tối thiểu opus/medium; `--reversible n` → tối thiểu opus/medium (role exec). Score ≥ 9, `--verifiable n`, `--reversible n` hoặc `urgent` → không thăm dò xuống tier thấp. Sàn được ghi rõ trong `reason`.
|
|
54
55
|
2. **Leo thang** với `--task <task_id>`: fail 1 lần → cùng tier (làm lại kèm lỗi); fail 2 lần cùng tier → lên một tier; tier kế vượt opus/medium (role exec) → **không tự lập lại kế hoạch**, trả `action: "ask_user"` kèm `fails` và `tried` để hỏi người dùng. Fail = agent `fail|timeout` hoặc người dùng `reject --cause quality`. Lượt bị steer `plan`/`redirect` không tính.
|
|
55
56
|
3. **Prior** từ `<skill-dir>/profile/profile.json` (không thấy thì dùng bản sao dựng sẵn của `skill/profile/profile.json` v1.0.0). `profile_source` trong kết quả cho biết đang dùng file nào (`builtin` hoặc đường dẫn).
|
|
56
57
|
4. **Thompson sampling** khi ô của prior có ≥ 5 task đã xong + có verdict: rút p ~ Beta(1+pass, 1+fail) cho từng tier đủ dữ liệu, điểm = E[cost]/p + λ·E[time_phút]/p (urgent dùng p75 thời gian), chọn điểm thấp nhất. Tier có p75 vượt `time_cap_min` của loại task bị loại.
|
|
57
58
|
5. **Thăm dò** một tier thấp hơn với xác suất `explore_rate` (mặc định 0.1) khi luật an toàn cho phép.
|
|
58
59
|
|
|
59
|
-
Thang tier: `haiku/low < haiku/medium < haiku/high < sonnet/medium < sonnet/high < opus/medium
|
|
60
|
+
Thang tier: `haiku/low < haiku/medium < haiku/high < sonnet/medium < sonnet/high < opus/medium` (trần).
|
|
60
61
|
|
|
61
62
|
Kết quả `--json`: `{"action":"dispatch","model":"sonnet","model_id":"claude-sonnet-5-5","effort":"high","reason":"…","explored":false,"tier":"sonnet/high","budget_s":900,"est_cost_usd":0.2,"est_time_min":6,"est_source":"rough","profile_source":"builtin"}`. `model` là tên ngắn dùng thẳng cho tham số `model` của Agent tool; `budget_s` = 2 × trung vị thời gian của ô (chưa đủ dữ liệu thì lấy `time_cap_min`). `--seed` cho kết quả lặp lại được.
|
|
62
63
|
|
|
@@ -76,7 +77,6 @@ Kết quả `--json`: `{"action":"dispatch","model":"sonnet","model_id":"claude-
|
|
|
76
77
|
| sonnet/medium | 55k | 6k | 4 | 0.17 |
|
|
77
78
|
| sonnet/high | 60k | 8k | 6 | 0.20 |
|
|
78
79
|
| opus/medium | 60k | 8k | 8 | 0.40 |
|
|
79
|
-
| opus/high | 80k | 12k | 12 | 0.56 |
|
|
80
80
|
|
|
81
81
|
### `plan-table`
|
|
82
82
|
|
package/bin/dieu-phoi.cjs
CHANGED
|
@@ -30,6 +30,8 @@ Cách dùng: dieu-phoi <lệnh> [tuỳ chọn]
|
|
|
30
30
|
trạng thái điều phối của phiên dựng lại từ log (cũng được nạp tự động sau /compact)
|
|
31
31
|
plan-table --file <steps.json> [--json] [--approve --root <root_task_id>]
|
|
32
32
|
bảng phân công: Bước | Model/effort | Lý do | Ước tính + tổng + đường găng
|
|
33
|
+
new-id [--count n] [--json]
|
|
34
|
+
in task_id mới hợp lệ (8 ký tự a-z0-9), mỗi dòng một id — không tự bịa id
|
|
33
35
|
sync đẩy bản ghi chưa đồng bộ lên sink
|
|
34
36
|
retro [--json] tóm tắt cho vòng retro (không chứa nội dung)
|
|
35
37
|
uninstall [--purge] gỡ hook khỏi settings.json (--purge xoá cả hook.cjs; dữ liệu giữ lại)
|
|
@@ -64,6 +66,7 @@ async function main(argv) {
|
|
|
64
66
|
verdict: c.cmdVerdict,
|
|
65
67
|
steer: c.cmdSteer,
|
|
66
68
|
'plan-table': c.cmdPlanTable,
|
|
69
|
+
'new-id': c.cmdNewId,
|
|
67
70
|
stats: c.cmdStats,
|
|
68
71
|
choose: c.cmdChoose,
|
|
69
72
|
ctx: c.cmdCtx,
|
package/hook.cjs
CHANGED
|
@@ -19,7 +19,7 @@ const path = require('path');
|
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const crypto = require('crypto');
|
|
21
21
|
|
|
22
|
-
const CORE_VERSION = '0.2.
|
|
22
|
+
const CORE_VERSION = '0.2.2';
|
|
23
23
|
const SCHEMA = 1;
|
|
24
24
|
const TASK_TYPES = [
|
|
25
25
|
'search', 'extract', 'mechanical-edit', 'feature-impl', 'debug', 'refactor',
|
|
@@ -226,6 +226,34 @@ function parseTag(text) {
|
|
|
226
226
|
return tag;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// ------------------------------------------------------------------ task_id
|
|
230
|
+
|
|
231
|
+
// CONTRACT: task_id = exactly 8 chars [a-z0-9], obtained from `dieu-phoi new-id` (never invented).
|
|
232
|
+
// The tag parser still accepts the legacy 1–32 [A-Za-z0-9_-] form so old logs/sessions parse; such
|
|
233
|
+
// dispatches are flagged `task_id_invalid: true` instead of being dropped.
|
|
234
|
+
const TASK_ID_RE = /^[a-z0-9]{8}$/;
|
|
235
|
+
const TASK_ID_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
236
|
+
|
|
237
|
+
function isValidTaskId(s) {
|
|
238
|
+
return typeof s === 'string' && TASK_ID_RE.test(s);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Fresh random task_id: 8 chars [a-z0-9], unbiased (rejection sampling on crypto bytes). */
|
|
242
|
+
function newTaskId() {
|
|
243
|
+
let out = '';
|
|
244
|
+
while (out.length < 8) {
|
|
245
|
+
for (const b of crypto.randomBytes(16)) {
|
|
246
|
+
if (b < 252 && out.length < 8) out += TASK_ID_ALPHABET[b % 36];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Vietnamese error for a bad id, with a freshly generated valid suggestion. */
|
|
253
|
+
function taskIdError(id) {
|
|
254
|
+
return `task_id "${id}" không hợp lệ: cần đúng 8 ký tự a-z0-9 (vd. ${newTaskId()}) — lấy id bằng \`dieu-phoi new-id\``;
|
|
255
|
+
}
|
|
256
|
+
|
|
229
257
|
// ------------------------------------------------------------------ models / pricing
|
|
230
258
|
|
|
231
259
|
function normalizeModel(m) {
|
|
@@ -1056,11 +1084,23 @@ function stateBody(state, kept) {
|
|
|
1056
1084
|
* Vietnamese text of a session state, at most `cap` chars. When too long, done tasks go first
|
|
1057
1085
|
* (oldest first), then the oldest open ones; the count of omitted tasks is stated.
|
|
1058
1086
|
*/
|
|
1059
|
-
|
|
1060
|
-
|
|
1087
|
+
/** Counts shared by the state header and the SessionStart systemMessage. */
|
|
1088
|
+
function stateCounts(state) {
|
|
1061
1089
|
const open = state.tasks.filter((t) => t.gate !== 'done' && !t.pending);
|
|
1062
1090
|
const pending = state.tasks.filter((t) => t.pending).length;
|
|
1063
1091
|
const running = state.tasks.reduce((n, t) => n + t.attempts.filter((a) => a.status === 'running').length, 0);
|
|
1092
|
+
return { tasks: state.tasks.length, open, pending, running };
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** One short Vietnamese line shown to the user after /compact re-injected the state. */
|
|
1096
|
+
function compactSystemMessage(state) {
|
|
1097
|
+
const c = stateCounts(state);
|
|
1098
|
+
return `dieu-phoi: đã nạp lại trạng thái điều phối sau khi nén (${c.tasks} task, ${c.running} agent đang chạy, ${c.pending} bước chưa giao)`;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function formatSessionState(state, cap) {
|
|
1102
|
+
const limit = cap || STATE_TEXT_CAP;
|
|
1103
|
+
const { open, pending, running } = stateCounts(state);
|
|
1064
1104
|
const header = `Phiên ${state.session_id}: ${state.tasks.length} task (${open.length} chưa xong, ${pending} bước chưa giao, ${running} agent đang chạy). `
|
|
1065
1105
|
+ `Thư mục chạy: ${state.runs_dir} (chỉ trỏ tới, không tự đọc).`;
|
|
1066
1106
|
if (!state.tasks.length) return `${header}\nChưa có task nào được giao trong phiên này.`;
|
|
@@ -1093,7 +1133,10 @@ function handleSessionStart(p, ctx) {
|
|
|
1093
1133
|
const state = buildSessionState(events, sid, { plansDir: ctx.paths.plans });
|
|
1094
1134
|
const text = `${COMPACT_PREFIX}\n${formatSessionState(state, STATE_TEXT_CAP)}`;
|
|
1095
1135
|
return {
|
|
1096
|
-
stdout: JSON.stringify({
|
|
1136
|
+
stdout: JSON.stringify({
|
|
1137
|
+
systemMessage: compactSystemMessage(state),
|
|
1138
|
+
hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: text },
|
|
1139
|
+
}),
|
|
1097
1140
|
};
|
|
1098
1141
|
}
|
|
1099
1142
|
|
|
@@ -1133,6 +1176,7 @@ function buildDispatch(p, ctx) {
|
|
|
1133
1176
|
agent_id: agentId,
|
|
1134
1177
|
session: p.session_id ? sha8(p.session_id) : null,
|
|
1135
1178
|
task_id: tag.task_id,
|
|
1179
|
+
...(tag.task_id && !isValidTaskId(tag.task_id) ? { task_id_invalid: true } : {}),
|
|
1136
1180
|
task_type: tag.task_type,
|
|
1137
1181
|
score: tag.score,
|
|
1138
1182
|
urgency: tag.urgency,
|
|
@@ -1444,10 +1488,10 @@ async function runSync(ctx, opts) {
|
|
|
1444
1488
|
// ------------------------------------------------------------------ exports / main
|
|
1445
1489
|
|
|
1446
1490
|
module.exports = {
|
|
1447
|
-
runsDir, loadPlans, buildSessionState, formatSessionState, handleSessionStart, dispatchInSession, STATE_TEXT_CAP, COMPACT_PREFIX,
|
|
1491
|
+
runsDir, loadPlans, buildSessionState, formatSessionState, stateCounts, compactSystemMessage, handleSessionStart, dispatchInSession, STATE_TEXT_CAP, COMPACT_PREFIX,
|
|
1448
1492
|
CORE_VERSION, SCHEMA, TASK_TYPES, URGENCIES, ROLES, MODEL_ALIASES, UNTAGGED,
|
|
1449
1493
|
sha8, nowIso, readJsonFile, writeFileAtomic, defaultClaudeDir, resolveHome, pathsFor, loadConfig, makeCtx,
|
|
1450
|
-
logError, computeUserHash, userHash, commonFields, parseTag, normalizeModel, loadPricing,
|
|
1494
|
+
logError, computeUserHash, userHash, commonFields, parseTag, TASK_ID_RE, isValidTaskId, newTaskId, taskIdError, normalizeModel, loadPricing,
|
|
1451
1495
|
priceFor, usageBuckets, requestCost, parseJsonl, readTranscript, lastUuidOf,
|
|
1452
1496
|
tagFromTranscript, summarizeTranscript, orchestratorUsage, orchCacheFile, findSessionTranscript, SESSION_ID_RE, HOOK_COLD_MAX_BYTES, appendEvent, eventLinesFrom, readEventLines, readEvents,
|
|
1453
1497
|
loadSyncOffset, saveSyncOffset, recordRejected, unsyncedInfo, loadSegments, saveSegments, segmentFile,
|
package/lib/choose.cjs
CHANGED
|
@@ -6,7 +6,8 @@ const crypto = require('crypto');
|
|
|
6
6
|
const core = require('../hook.cjs');
|
|
7
7
|
const agg = require('./aggregate.cjs');
|
|
8
8
|
|
|
9
|
-
// Tier ladder, cheapest first. Ceiling = opus/
|
|
9
|
+
// Tier ladder, cheapest first. Ceiling = opus/medium (core safety rule): anything above
|
|
10
|
+
// (opus/high, xhigh, max, Fable) clamps to opus/medium in tierIndex().
|
|
10
11
|
const LADDER = [
|
|
11
12
|
{ model: 'haiku', effort: 'low' },
|
|
12
13
|
{ model: 'haiku', effort: 'medium' },
|
|
@@ -14,7 +15,6 @@ const LADDER = [
|
|
|
14
15
|
{ model: 'sonnet', effort: 'medium' },
|
|
15
16
|
{ model: 'sonnet', effort: 'high' },
|
|
16
17
|
{ model: 'opus', effort: 'medium' },
|
|
17
|
-
{ model: 'opus', effort: 'high' },
|
|
18
18
|
];
|
|
19
19
|
const TOP = LADDER.length - 1;
|
|
20
20
|
const EFFORT_RANK = { low: 0, medium: 1, high: 2, xhigh: 3, max: 4 };
|
|
@@ -31,7 +31,7 @@ const DEFAULT_PRIORS = {
|
|
|
31
31
|
review: 'opus/medium',
|
|
32
32
|
'research-synth': 'opus/medium',
|
|
33
33
|
'content-write': 'sonnet/high',
|
|
34
|
-
'plan-design': 'opus/
|
|
34
|
+
'plan-design': 'opus/medium',
|
|
35
35
|
'data-analysis': 'sonnet/high',
|
|
36
36
|
};
|
|
37
37
|
const DEFAULT_TIME_CAP_MIN = {
|
|
@@ -186,7 +186,7 @@ const OPUS_MEDIUM = 5;
|
|
|
186
186
|
* Escalation within one task_id (CONTRACT "Luật an toàn"):
|
|
187
187
|
* - one fail at a tier -> same tier again (retry with the error);
|
|
188
188
|
* - two fails at the same tier -> next tier up;
|
|
189
|
-
* - if that next tier would exceed opus/medium
|
|
189
|
+
* - if that next tier would exceed the opus/medium ceiling -> ask the user (no auto re-plan).
|
|
190
190
|
* Fail = agent fail|timeout or user reject cause=quality (eff_outcome). Attempts steered
|
|
191
191
|
* plan/redirect by the user are ignored.
|
|
192
192
|
*/
|
|
@@ -213,7 +213,7 @@ function escalation(task, role) {
|
|
|
213
213
|
if (((role || 'exec') === 'exec' && next > OPUS_MEDIUM) || next > TOP) {
|
|
214
214
|
return Object.assign(base, {
|
|
215
215
|
floor: Math.min(TOP, next), ask: true,
|
|
216
|
-
note: `task đã fail ${totalFails} lần (đã thử ${tried.join(', ')}); tier kế vượt opus/medium — hỏi người dùng: làm rõ yêu cầu
|
|
216
|
+
note: `task đã fail ${totalFails} lần (đã thử ${tried.join(', ')}); tier kế vượt trần opus/medium — hỏi người dùng: làm rõ yêu cầu hoặc đổi kế hoạch`,
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
219
|
return Object.assign(base, { floor: next, note: `task fail ${fails} lần ở ${tierName(cur)} → lên ${tierName(next)}` });
|
|
@@ -264,7 +264,6 @@ const ROUGH = {
|
|
|
264
264
|
'sonnet/medium': { input: 55000, output: 6000, minutes: 4 },
|
|
265
265
|
'sonnet/high': { input: 60000, output: 8000, minutes: 6 },
|
|
266
266
|
'opus/medium': { input: 60000, output: 8000, minutes: 8 },
|
|
267
|
-
'opus/high': { input: 80000, output: 12000, minutes: 12 },
|
|
268
267
|
};
|
|
269
268
|
|
|
270
269
|
function roughEstimate(idx, pricing) {
|
|
@@ -311,7 +310,7 @@ function choose(opts) {
|
|
|
311
310
|
if (opts.override !== undefined && opts.override !== null) {
|
|
312
311
|
overrideIdx = parseTier(String(opts.override));
|
|
313
312
|
if (overrideIdx === null) {
|
|
314
|
-
throw new Error(`--override "${opts.override}" không nằm trên thang tier (trần opus/
|
|
313
|
+
throw new Error(`--override "${opts.override}" không nằm trên thang tier (trần opus/medium, không Fable): ${ladderList}`);
|
|
315
314
|
}
|
|
316
315
|
}
|
|
317
316
|
let approvedIdx = null;
|
|
@@ -345,7 +344,7 @@ function choose(opts) {
|
|
|
345
344
|
// Safety floors (core only; a profile can never lower them).
|
|
346
345
|
let safetyFloor = 0;
|
|
347
346
|
const floorWhy = [];
|
|
348
|
-
if (score >= 9) { safetyFloor = Math.max(safetyFloor, TOP); floorWhy.push(`score ${score}≥9 → tối thiểu opus/
|
|
347
|
+
if (score >= 9) { safetyFloor = Math.max(safetyFloor, TOP); floorWhy.push(`score ${score}≥9 → tối thiểu opus/medium`); }
|
|
349
348
|
if (!verifiable) { safetyFloor = Math.max(safetyFloor, OPUS_MEDIUM); floorWhy.push('không kiểm chứng bằng máy → tối thiểu opus/medium'); }
|
|
350
349
|
if (!reversible && role === 'exec') { safetyFloor = Math.max(safetyFloor, OPUS_MEDIUM); floorWhy.push('khó hoàn tác → tối thiểu opus/medium'); }
|
|
351
350
|
|
package/lib/commands.cjs
CHANGED
|
@@ -15,6 +15,14 @@ const USER_OUTCOMES = ['accept', 'reject'];
|
|
|
15
15
|
const CAUSES = ['quality', 'scope'];
|
|
16
16
|
const STEER_KINDS = ['minor', 'plan', 'redirect'];
|
|
17
17
|
const VERIFY = ['tests', 'lint', 'review', 'judgment', 'none'];
|
|
18
|
+
const INVALID_ID_WINDOW_DAYS = 7;
|
|
19
|
+
const NEW_ID_MAX = 1000;
|
|
20
|
+
|
|
21
|
+
/** Throw the Vietnamese task_id error (with a fresh suggestion) unless `id` is a valid task_id. */
|
|
22
|
+
function requireTaskId(id) {
|
|
23
|
+
if (!core.isValidTaskId(id)) throw new Error(core.taskIdError(id));
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
18
26
|
|
|
19
27
|
function skillDirOf(opts, ctx) {
|
|
20
28
|
if (typeof opts['skill-dir'] === 'string') return path.resolve(opts['skill-dir']);
|
|
@@ -129,6 +137,11 @@ async function cmdDoctor(opts) {
|
|
|
129
137
|
add(true, 'internal-subagents', skipped && skipped.count
|
|
130
138
|
? `bỏ qua ${skipped.count} SubagentStop của subagent nội bộ Claude Code (lần cuối ${skipped.last_ts})`
|
|
131
139
|
: 'chưa bỏ qua SubagentStop nội bộ nào');
|
|
140
|
+
const since = Date.now() - INVALID_ID_WINDOW_DAYS * 86400000;
|
|
141
|
+
const badIds = events.filter((e) => e.kind === 'dispatch' && e.task_id_invalid === true && !(Date.parse(e.ts) < since));
|
|
142
|
+
add(!badIds.length, 'task-ids', badIds.length
|
|
143
|
+
? `${badIds.length} dispatch trong ${INVALID_ID_WINDOW_DAYS} ngày gần đây có task_id sai định dạng (${Array.from(new Set(badIds.map((e) => e.task_id))).slice(0, 3).map((x) => `"${x}"`).join(', ')}) — task_id phải đúng 8 ký tự a-z0-9, lấy bằng \`dieu-phoi new-id\``
|
|
144
|
+
: `mọi dispatch trong ${INVALID_ID_WINDOW_DAYS} ngày gần đây có task_id đúng định dạng`, 'CẢNH BÁO');
|
|
132
145
|
const orch = measureOrch(opts, ctx, profileOrNull(opts, ctx));
|
|
133
146
|
if (orch.ctx_tokens !== null) {
|
|
134
147
|
add(!orch.over, 'orch-ctx', `session ${orch.session_id}: ${orch.ctx_tokens} token, tăng ${orch.ctx_growth}/${orch.soft_cap}, ≈ ${fmtUsd(orch.cum_usd)} — ${orch.advice}`, 'CẢNH BÁO');
|
|
@@ -255,6 +268,7 @@ function verdictUser(opts, ctx, events) {
|
|
|
255
268
|
const taskId = opts.task;
|
|
256
269
|
const outcome = opts._[0];
|
|
257
270
|
if (typeof taskId !== 'string' || !taskId) throw new Error('--task cần task_id');
|
|
271
|
+
requireTaskId(taskId);
|
|
258
272
|
if (!USER_OUTCOMES.includes(outcome)) throw new Error(`verdict --task: outcome phải là ${USER_OUTCOMES.join('|')}`);
|
|
259
273
|
let cause = typeof opts.cause === 'string' ? opts.cause : null;
|
|
260
274
|
if (outcome === 'reject' && !CAUSES.includes(cause)) throw new Error(`reject cần --cause ${CAUSES.join('|')} (scope = yêu cầu đổi/thiếu, không phải lỗi model)`);
|
|
@@ -307,6 +321,7 @@ function cmdSteer(opts) {
|
|
|
307
321
|
const kind = opts.kind;
|
|
308
322
|
if (!ref && !opts.task) throw new Error('Cú pháp: dieu-phoi steer <agent_id> | --task <task_id> --kind minor|plan|redirect [--reason R]');
|
|
309
323
|
if (!STEER_KINDS.includes(kind)) throw new Error(`--kind phải là ${STEER_KINDS.join('|')}`);
|
|
324
|
+
if (!ref) requireTaskId(String(opts.task));
|
|
310
325
|
const ctx = ctxOf(opts);
|
|
311
326
|
const events = core.readEvents(ctx);
|
|
312
327
|
const steerId = crypto.randomBytes(4).toString('hex');
|
|
@@ -557,6 +572,7 @@ function cmdChoose(opts) {
|
|
|
557
572
|
if (opts.override !== undefined && typeof opts.task !== 'string') {
|
|
558
573
|
throw new Error('--override cần --task <task_id> (để học override theo từng task)');
|
|
559
574
|
}
|
|
575
|
+
if (opts.task !== undefined) requireTaskId(typeof opts.task === 'string' ? opts.task : '');
|
|
560
576
|
const input = chooseInput(opts, ctx, core.readEvents(ctx));
|
|
561
577
|
const res = chooser.choose(input);
|
|
562
578
|
const orch = measureOrch(opts, ctx, input.profile);
|
|
@@ -682,8 +698,6 @@ function buildPlanTable(rawSteps, base) {
|
|
|
682
698
|
};
|
|
683
699
|
}
|
|
684
700
|
|
|
685
|
-
const ROOT_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
686
|
-
|
|
687
701
|
/**
|
|
688
702
|
* Record the plan the user approved at gate 1: <data>/plans/<root>.json (local only — not an event, never
|
|
689
703
|
* synced). Each re-approval increments `approval` (number of approvals for this root) and overwrites the
|
|
@@ -704,6 +718,9 @@ function planTaskIdProblems(plan, root) {
|
|
|
704
718
|
const problems = [];
|
|
705
719
|
const missing = plan.steps.filter((r) => !r.task_id).map((r) => r.step);
|
|
706
720
|
if (missing.length) problems.push(`thiếu task_id ở bước ${missing.join(', ')}`);
|
|
721
|
+
for (const r of plan.steps) {
|
|
722
|
+
if (r.task_id && !core.isValidTaskId(r.task_id)) problems.push(`bước ${r.step}: ${core.taskIdError(r.task_id)}`);
|
|
723
|
+
}
|
|
707
724
|
const by = new Map();
|
|
708
725
|
for (const r of plan.steps) if (r.task_id) by.set(r.task_id, (by.get(r.task_id) || []).concat(r.step));
|
|
709
726
|
for (const [id, steps] of by) if (steps.length > 1) problems.push(`task_id "${id}" bị dùng lại ở các bước ${steps.join(', ')}`);
|
|
@@ -738,15 +755,29 @@ function recordApprovedPlan(ctx, root, stepsFile, plan) {
|
|
|
738
755
|
return Object.assign({ file }, rec);
|
|
739
756
|
}
|
|
740
757
|
|
|
758
|
+
// ------------------------------------------------------------------ new-id
|
|
759
|
+
|
|
760
|
+
/** `dieu-phoi new-id [--count n]`: fresh valid task_ids, one per line (the orchestrator never invents ids). */
|
|
761
|
+
function cmdNewId(opts) {
|
|
762
|
+
const raw = opts.count === undefined ? '1' : String(opts.count);
|
|
763
|
+
const n = /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
764
|
+
if (!Number.isInteger(n) || n < 1 || n > NEW_ID_MAX) throw new Error(`--count phải là số nguyên 1–${NEW_ID_MAX}`);
|
|
765
|
+
const ids = new Set();
|
|
766
|
+
while (ids.size < n) ids.add(core.newTaskId());
|
|
767
|
+
const list = Array.from(ids);
|
|
768
|
+
return { code: 0, out: opts.json ? JSON.stringify(list) : list.join('\n') };
|
|
769
|
+
}
|
|
770
|
+
|
|
741
771
|
function mdCell(s) {
|
|
742
772
|
return String(s).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
743
773
|
}
|
|
744
774
|
|
|
745
775
|
function cmdPlanTable(opts) {
|
|
746
776
|
if (typeof opts.file !== 'string') throw new Error('Cú pháp: dieu-phoi plan-table --file <steps.json> [--json] [--approve --root <root_task_id>]');
|
|
747
|
-
if (opts.approve &&
|
|
748
|
-
throw new Error(
|
|
777
|
+
if (opts.approve && typeof opts.root !== 'string') {
|
|
778
|
+
throw new Error(`--approve cần --root <root_task_id> (đúng 8 ký tự a-z0-9, vd. ${core.newTaskId()})`);
|
|
749
779
|
}
|
|
780
|
+
if (opts.approve) requireTaskId(opts.root);
|
|
750
781
|
let raw;
|
|
751
782
|
try {
|
|
752
783
|
raw = JSON.parse(fs.readFileSync(path.resolve(opts.file), 'utf8').replace(/^/, ''));
|
|
@@ -759,7 +790,7 @@ function cmdPlanTable(opts) {
|
|
|
759
790
|
const plan = buildPlanTable(raw, base);
|
|
760
791
|
const problems = planTaskIdProblems(plan, opts.approve ? String(opts.root) : null);
|
|
761
792
|
if (opts.approve && problems.length) {
|
|
762
|
-
throw new Error(`plan-table --approve: không ghi kế hoạch — ${problems.join('; ')}. Mỗi bước cần task_id riêng, khác --root.`);
|
|
793
|
+
throw new Error(`plan-table --approve: không ghi kế hoạch — ${problems.join('; ')}. Mỗi bước cần task_id riêng (8 ký tự a-z0-9, lấy bằng \`dieu-phoi new-id\`), khác --root.`);
|
|
763
794
|
}
|
|
764
795
|
if (problems.length) process.stderr.write(`dieu-phoi plan-table: cảnh báo — ${problems.join('; ')} (cần sửa trước khi --approve)\n`);
|
|
765
796
|
const rec = opts.approve ? recordApprovedPlan(ctx, String(opts.root), path.resolve(opts.file), plan) : null;
|
|
@@ -790,5 +821,5 @@ function cmdPlanTable(opts) {
|
|
|
790
821
|
|
|
791
822
|
module.exports = {
|
|
792
823
|
cmdSetup, cmdUninstall, cmdDoctor, cmdHook, cmdSync, cmdVerdict, cmdSteer, cmdStats, cmdRetro, cmdChoose, cmdCtx, cmdState, measureOrch,
|
|
793
|
-
cmdPlanTable, buildPlanTable, recordApprovedPlan, planTaskIdProblems, criticalPath, resolveAgentId, AGENT_OUTCOMES, USER_OUTCOMES, VERIFY,
|
|
824
|
+
cmdPlanTable, cmdNewId, requireTaskId, buildPlanTable, recordApprovedPlan, planTaskIdProblems, criticalPath, resolveAgentId, AGENT_OUTCOMES, USER_OUTCOMES, VERIFY,
|
|
794
825
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kienntpixon/dieu-phoi",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Hook + CLI ghi log subagent của Claude Code và chọn model/effort theo chi phí + thời gian tới lúc nghiệm thu.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kiennt <kiennt@pixon.games>",
|