@kienntpixon/dieu-phoi 0.6.0 → 0.7.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/CHANGELOG.md +69 -0
- package/README.md +11 -2
- package/agents/dp-exec.md +1 -1
- package/agents/dp-helper.md +1 -0
- package/agents/dp-scout.md +1 -1
- package/agents/dp-verify.md +1 -0
- package/bin/dieu-phoi.cjs +28 -8
- package/hook.cjs +397 -8
- package/lib/aggregate.cjs +137 -14
- package/lib/args.cjs +1 -1
- package/lib/cbm.cjs +490 -0
- package/lib/choose.cjs +60 -2
- package/lib/commands.cjs +260 -19
- package/lib/knowledge.cjs +1 -1
- package/lib/profile.cjs +1 -1
- package/package.json +1 -1
package/lib/aggregate.cjs
CHANGED
|
@@ -56,13 +56,14 @@ function round6(v) {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Normalize a verdict to the two-layer model. Legacy `rework` (no `by`) = user reject/quality;
|
|
59
|
-
* missing `by` otherwise = agent layer.
|
|
59
|
+
* missing `by` otherwise = agent layer. Core 0.7.0: `by=auto` (fast-mode self-acceptance) is its own layer —
|
|
60
|
+
* never a user verdict, never an agent verdict (CONTRACT "Mode theo yêu cầu").
|
|
60
61
|
*/
|
|
61
62
|
function normalizeVerdict(ev) {
|
|
62
63
|
if (ev.outcome === 'rework' && !ev.by) {
|
|
63
64
|
return { by: 'user', outcome: 'reject', cause: 'quality', round: Number(ev.round) || null };
|
|
64
65
|
}
|
|
65
|
-
const by = ev.by === 'user' ? 'user' : 'agent';
|
|
66
|
+
const by = ev.by === 'user' ? 'user' : (ev.by === 'auto' ? 'auto' : 'agent');
|
|
66
67
|
let cause = ev.cause || null;
|
|
67
68
|
if (by === 'user' && ev.outcome === 'reject' && cause !== 'scope') cause = 'quality';
|
|
68
69
|
let outcome = ev.outcome;
|
|
@@ -111,12 +112,18 @@ function steerIdOf(ev) {
|
|
|
111
112
|
return typeof ev.steer_id === 'string' && ev.steer_id ? ev.steer_id : `${ev.agent_id}|${ev.ts}`;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/** core 0.7.0: an exec's own selective-test verdict (`verify=self`) is not evidence for the agent layer. */
|
|
116
|
+
function isSelf(v) {
|
|
117
|
+
return !!v && v.verify === 'self';
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
/**
|
|
115
|
-
* Join events. Returns agents (one per real agent), userVerdicts (task layer), overrides, steers.
|
|
121
|
+
* Join events. Returns agents (one per real agent), userVerdicts (task layer), autoVerdicts (by=auto), overrides, steers.
|
|
116
122
|
*/
|
|
117
123
|
function buildAgents(events) {
|
|
118
124
|
const map = new Map();
|
|
119
125
|
const userVerdicts = [];
|
|
126
|
+
const autoVerdicts = [];
|
|
120
127
|
const overrides = [];
|
|
121
128
|
const steers = [];
|
|
122
129
|
const get = (id) => {
|
|
@@ -132,6 +139,7 @@ function buildAgents(events) {
|
|
|
132
139
|
if (ev.kind === 'verdict') {
|
|
133
140
|
const v = Object.assign(e, normalizeVerdict(ev));
|
|
134
141
|
if (v.by === 'user') { userVerdicts.push(v); return; }
|
|
142
|
+
if (v.by === 'auto') { autoVerdicts.push(v); return; }
|
|
135
143
|
}
|
|
136
144
|
if (ev.kind === 'steer') steers.push(e);
|
|
137
145
|
if (typeof ev.agent_id !== 'string' || !ev.agent_id) return;
|
|
@@ -149,6 +157,8 @@ function buildAgents(events) {
|
|
|
149
157
|
const vAll = a.verdicts.slice().sort((x, y) => tsOf(x) - tsOf(y) || x._idx - y._idx);
|
|
150
158
|
// `aborted` (quota pause) is neither pass nor fail: it never sets the outcome nor counts as a result.
|
|
151
159
|
const v = vAll.filter((x) => x.outcome !== ABORTED);
|
|
160
|
+
const checked = v.filter((x) => !isSelf(x));
|
|
161
|
+
const selfs = v.filter(isSelf);
|
|
152
162
|
const verdict = v.length ? v[v.length - 1] : null;
|
|
153
163
|
const firstVerdictTs = v.length ? tsOf(v[0]) : Infinity;
|
|
154
164
|
const pick = (k) => (d[k] !== undefined && d[k] !== null ? d[k] : (c0[k] !== undefined && c0[k] !== null ? c0[k] : null));
|
|
@@ -173,7 +183,10 @@ function buildAgents(events) {
|
|
|
173
183
|
duration_s: a.completes.length ? sumOrNull(a.completes.map((x) => x.duration_s)) : null,
|
|
174
184
|
tool_uses: a.completes.length ? sumOrNull(a.completes.map((x) => x.tool_uses)) : null,
|
|
175
185
|
outcome: verdict ? verdict.outcome : null,
|
|
176
|
-
|
|
186
|
+
// agent layer = first verdict that is not the exec's own selective tests (verify=self, core 0.7.0)
|
|
187
|
+
first_outcome: checked.length ? checked[0].outcome : null,
|
|
188
|
+
self_first: selfs.length ? selfs[0].outcome : null,
|
|
189
|
+
self_verdicts: selfs.length,
|
|
177
190
|
// every non-aborted agent verdict in time order: a retry by SendMessage to the same agent is a new result
|
|
178
191
|
verdict_seq: v.map((x) => ({ outcome: x.outcome, ts: tsOf(x), idx: x._idx })),
|
|
179
192
|
aborted: vAll.length > 0 && vAll[vAll.length - 1].outcome === ABORTED,
|
|
@@ -187,24 +200,28 @@ function buildAgents(events) {
|
|
|
187
200
|
last_complete_ts: a.completes.length ? Math.max(...a.completes.map(tsOf)) : 0,
|
|
188
201
|
});
|
|
189
202
|
}
|
|
190
|
-
return { agents, userVerdicts, overrides, steers };
|
|
203
|
+
return { agents, userVerdicts, autoVerdicts, overrides, steers };
|
|
191
204
|
}
|
|
192
205
|
|
|
193
206
|
/** Group agents into tasks (sample unit = task_id; agents without task_id are their own task). */
|
|
194
|
-
function buildTasks(agents, userVerdicts) {
|
|
207
|
+
function buildTasks(agents, userVerdicts, autoVerdicts) {
|
|
195
208
|
const map = new Map();
|
|
196
209
|
const byAgent = new Map();
|
|
197
210
|
for (const a of agents) {
|
|
198
211
|
const key = a.task_id ? `task:${a.task_id}` : `agent:${a.agent_id}`;
|
|
199
|
-
if (!map.has(key)) map.set(key, { key, task_id: a.task_id, agents: [], user: [] });
|
|
212
|
+
if (!map.has(key)) map.set(key, { key, task_id: a.task_id, agents: [], user: [], auto: [] });
|
|
200
213
|
map.get(key).agents.push(a);
|
|
201
214
|
byAgent.set(a.agent_id, key);
|
|
202
215
|
}
|
|
216
|
+
for (const v of autoVerdicts || []) {
|
|
217
|
+
const t = v.task_id ? map.get(`task:${v.task_id}`) : null;
|
|
218
|
+
if (t) t.auto.push(v);
|
|
219
|
+
}
|
|
203
220
|
for (const v of userVerdicts || []) {
|
|
204
221
|
let key = v.task_id ? `task:${v.task_id}` : null;
|
|
205
222
|
if (!key && v.agent_id) key = byAgent.get(v.agent_id) || null; // legacy rework without task_id
|
|
206
223
|
if (!key) continue;
|
|
207
|
-
if (!map.has(key)) map.set(key, { key, task_id: v.task_id, agents: [], user: [] });
|
|
224
|
+
if (!map.has(key)) map.set(key, { key, task_id: v.task_id, agents: [], user: [], auto: [] });
|
|
208
225
|
map.get(key).user.push(v);
|
|
209
226
|
}
|
|
210
227
|
const tasks = [];
|
|
@@ -261,6 +278,10 @@ function buildTasks(agents, userVerdicts) {
|
|
|
261
278
|
if (userOutcome === 'accept') { success = true; source = 'user'; } else if (userOutcome === 'reject_quality') { success = false; source = 'user'; } else if (!userOutcome && agentFirst !== null) {
|
|
262
279
|
success = agentFirst; source = 'agent';
|
|
263
280
|
}
|
|
281
|
+
// core 0.7.0: by=auto never decides success; a user reject after the first auto accept overturns it.
|
|
282
|
+
const autoTs = t.auto.filter((v) => v.outcome === 'accept').map(tsOf);
|
|
283
|
+
const autoAccept = autoTs.length > 0;
|
|
284
|
+
const autoOverturned = autoAccept && user.some((v) => v.outcome === 'reject' && tsOf(v) > Math.min(...autoTs));
|
|
264
285
|
const lastUser = user[user.length - 1] || null;
|
|
265
286
|
const accepted = lastUser ? lastUser.outcome === 'accept' : attempts.some((a) => a.outcome === 'pass');
|
|
266
287
|
const costed = t.agents.filter((a) => a.completed || a.outcome);
|
|
@@ -283,6 +304,9 @@ function buildTasks(agents, userVerdicts) {
|
|
|
283
304
|
excluded: !!(first && first.steered) || userOutcome === 'reject_scope',
|
|
284
305
|
accepted,
|
|
285
306
|
agent_first_pass: agentFirst,
|
|
307
|
+
self_first: firstRes ? firstRes.self_first : null,
|
|
308
|
+
auto_accept: autoAccept,
|
|
309
|
+
auto_overturned: autoOverturned,
|
|
286
310
|
// agents with a verdict but no complete event (hook missed) make cost/time unknown (null)
|
|
287
311
|
total_cost: sumOrNull(costed.map((a) => a.cost_usd)),
|
|
288
312
|
total_time_s: sumOrNull(costed.map((a) => a.duration_s)),
|
|
@@ -309,11 +333,17 @@ function buildCells(tasks, opts) {
|
|
|
309
333
|
if (!f) continue;
|
|
310
334
|
const key = cellKey(t.task_type, f.model, f.effort);
|
|
311
335
|
if (!cells.has(key)) {
|
|
312
|
-
cells.set(key, {
|
|
336
|
+
cells.set(key, {
|
|
337
|
+
task_type: t.task_type, model: f.model, effort: f.effort, dispatched: 0, excluded: 0, tasks: [], agentTasks: [], selfTasks: [], verdicted: 0, auto: 0, overturned: 0,
|
|
338
|
+
});
|
|
313
339
|
}
|
|
314
340
|
const c = cells.get(key);
|
|
315
341
|
c.dispatched++;
|
|
342
|
+
if (t.auto_accept) c.auto++;
|
|
343
|
+
if (t.auto_overturned) c.overturned++;
|
|
316
344
|
if (t.excluded) { c.excluded++; continue; }
|
|
345
|
+
if (t.self_first) c.selfTasks.push(t);
|
|
346
|
+
if (t.self_first || t.agent_first_pass !== null) c.verdicted++;
|
|
317
347
|
if (t.success !== null) c.tasks.push(t);
|
|
318
348
|
if (t.agent_first_pass !== null) c.agentTasks.push(t);
|
|
319
349
|
}
|
|
@@ -331,6 +361,9 @@ function buildCells(tasks, opts) {
|
|
|
331
361
|
// score uses the time median, not the mean: waits for the user (gate 3, quota pauses) inflate the mean
|
|
332
362
|
// 3-13x (retro 2026-10-18: art-gen sonnet mean 483 vs p50 37 min) and would flip priors to slower tiers.
|
|
333
363
|
const timeP50 = percentile(timesMin, 0.5);
|
|
364
|
+
const notes = [];
|
|
365
|
+
if (n < MIN_N) notes.push('chưa đủ dữ liệu');
|
|
366
|
+
if (c.verdicted && c.selfTasks.length / c.verdicted > 0.5) notes.push('ít bằng chứng');
|
|
334
367
|
out.push({
|
|
335
368
|
task_type: c.task_type,
|
|
336
369
|
model: c.model,
|
|
@@ -358,8 +391,12 @@ function buildCells(tasks, opts) {
|
|
|
358
391
|
attempt_p50_min: percentile(attemptTimes, 0.5),
|
|
359
392
|
attempt_p75_min: percentile(attemptTimes, 0.75),
|
|
360
393
|
score: costMean === null && timeP50 === null ? null : round6((costMean || 0) + lambda * (timeP50 || 0)),
|
|
394
|
+
n_self: c.selfTasks.length,
|
|
395
|
+
self_pass: c.selfTasks.filter((t) => t.self_first === 'pass').length,
|
|
396
|
+
auto_accept: c.auto,
|
|
397
|
+
auto_overturned: c.overturned,
|
|
361
398
|
enough: n >= MIN_N,
|
|
362
|
-
note:
|
|
399
|
+
note: notes.join('; '),
|
|
363
400
|
});
|
|
364
401
|
}
|
|
365
402
|
out.sort((a, b) => a.task_type.localeCompare(b.task_type)
|
|
@@ -485,16 +522,102 @@ function orchestrationSummary(events) {
|
|
|
485
522
|
}
|
|
486
523
|
|
|
487
524
|
function analyze(events, opts) {
|
|
488
|
-
const { agents, userVerdicts, overrides, steers } = buildAgents(withoutDiscarded(events));
|
|
489
|
-
const tasks = buildTasks(agents, userVerdicts);
|
|
525
|
+
const { agents, userVerdicts, autoVerdicts, overrides, steers } = buildAgents(withoutDiscarded(events));
|
|
526
|
+
const tasks = buildTasks(agents, userVerdicts, autoVerdicts);
|
|
490
527
|
const cells = buildCells(tasks, opts);
|
|
491
528
|
return {
|
|
492
|
-
agents, tasks, cells, overrides, userVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
|
|
529
|
+
agents, tasks, cells, overrides, userVerdicts, autoVerdicts, steerEvents: steers, steers: buildSteers(tasks, steers, agents),
|
|
493
530
|
orchestration: orchestrationSummary(events),
|
|
494
531
|
};
|
|
495
532
|
}
|
|
496
533
|
|
|
497
534
|
module.exports = {
|
|
498
|
-
MIN_N, FAIL_OUTCOMES, HEAVY_STEERS, ABORTED, QUOTA_REASON_RE, HANDOFF_REASON_RE, discardedTaskIds, withoutDiscarded, CANONICAL_CELL_FIELDS, normalizeVerdict, isInternalComplete, buildAgents, buildTasks,
|
|
535
|
+
MIN_N, FAIL_OUTCOMES, HEAVY_STEERS, ABORTED, QUOTA_REASON_RE, HANDOFF_REASON_RE, discardedTaskIds, withoutDiscarded, CANONICAL_CELL_FIELDS, normalizeVerdict, isSelf, isInternalComplete, buildAgents, buildTasks,
|
|
499
536
|
buildCells, buildSteers, canonicalCells, orchestrationSummary, analyze, cellKey, mean, percentile, tsOf, steerIdOf,
|
|
500
537
|
};
|
|
538
|
+
|
|
539
|
+
// ------------------------------------------------------------------ core 0.7.0 (s2): stats split by mode
|
|
540
|
+
//
|
|
541
|
+
// Kept apart from the canonical cells (not part of the CLI <-> sink parity yet). A task's mode = `mode` of its first
|
|
542
|
+
// exec dispatch; tasks dispatched before modes existed (no `mode`) count as normal.
|
|
543
|
+
|
|
544
|
+
const MODE_KEYS = ['fast', 'normal', 'strict'];
|
|
545
|
+
const NO_CATCH_VERIFY = new Set(['self', 'none']);
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Per mode: tasks, verified_tasks (a verify agent ran), catch_rate (share of exec agent verdicts checked by something
|
|
549
|
+
* other than self/none that came back fail), self_verdicts, batch_verify_fails, user_verdict_tasks, rejects (quality),
|
|
550
|
+
* reject_rate, verify_min (minutes of verify agents), test_s (sum of complete.test_s over the task's agents),
|
|
551
|
+
* test_s_per_task, full_suite_runs. `events` = the same events given to analyze(); `tasks` = analyze().tasks.
|
|
552
|
+
*/
|
|
553
|
+
function modeSummary(events, tasks) {
|
|
554
|
+
const evs = withoutDiscarded(events).filter(Boolean);
|
|
555
|
+
const dispatchMode = new Map();
|
|
556
|
+
const tests = new Map();
|
|
557
|
+
const verdicts = new Map();
|
|
558
|
+
for (const ev of evs) {
|
|
559
|
+
if (ev.kind === 'dispatch' && typeof ev.agent_id === 'string' && !dispatchMode.has(ev.agent_id)) dispatchMode.set(ev.agent_id, ev.mode);
|
|
560
|
+
if (ev.kind === 'complete' && typeof ev.agent_id === 'string') {
|
|
561
|
+
const t = tests.get(ev.agent_id) || { s: 0, timed: 0, full: 0 };
|
|
562
|
+
const s = finiteNum(ev.test_s);
|
|
563
|
+
if (s !== null) { t.s += s; t.timed++; }
|
|
564
|
+
t.full += finiteNum(ev.full_suite_runs) || 0;
|
|
565
|
+
tests.set(ev.agent_id, t);
|
|
566
|
+
}
|
|
567
|
+
if (ev.kind === 'verdict' && ev.by !== 'user' && typeof ev.agent_id === 'string') {
|
|
568
|
+
if (!verdicts.has(ev.agent_id)) verdicts.set(ev.agent_id, []);
|
|
569
|
+
verdicts.get(ev.agent_id).push(ev);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const out = {};
|
|
573
|
+
for (const m of MODE_KEYS) {
|
|
574
|
+
out[m] = {
|
|
575
|
+
tasks: 0, verified_tasks: 0, checked_verdicts: 0, caught: 0, catch_rate: null, self_verdicts: 0, batch_verify_fails: 0,
|
|
576
|
+
user_verdict_tasks: 0, rejects: 0, reject_rate: null, verify_min: 0, test_s: 0, test_timed_tasks: 0, test_s_per_task: null, full_suite_runs: 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
for (const t of tasks || []) {
|
|
580
|
+
if (!t.first) continue;
|
|
581
|
+
const raw = dispatchMode.get(t.first.agent_id);
|
|
582
|
+
const r = out[MODE_KEYS.includes(raw) ? raw : 'normal'];
|
|
583
|
+
r.tasks++;
|
|
584
|
+
if (t.verifies.length) r.verified_tasks++;
|
|
585
|
+
for (const v of t.verifies) r.verify_min += (finiteNum(v.duration_s) || 0) / 60;
|
|
586
|
+
let ts = 0;
|
|
587
|
+
let timed = false;
|
|
588
|
+
for (const a of t.attempts.concat(t.verifies)) {
|
|
589
|
+
const x = tests.get(a.agent_id);
|
|
590
|
+
if (!x) continue;
|
|
591
|
+
if (x.timed) { ts += x.s; timed = true; }
|
|
592
|
+
r.full_suite_runs += x.full;
|
|
593
|
+
}
|
|
594
|
+
if (timed) { r.test_s += ts; r.test_timed_tasks++; }
|
|
595
|
+
for (const a of t.attempts) {
|
|
596
|
+
if (a.role !== 'exec') continue;
|
|
597
|
+
for (const v of verdicts.get(a.agent_id) || []) {
|
|
598
|
+
if (v.outcome === 'fail' && /^batch-verify(?:-|$)/.test(String(v.reason_code || ''))) r.batch_verify_fails++;
|
|
599
|
+
if (v.verify === 'self') r.self_verdicts++;
|
|
600
|
+
if (v.outcome !== 'pass' && v.outcome !== 'fail') continue;
|
|
601
|
+
if (NO_CATCH_VERIFY.has(v.verify || 'none')) continue;
|
|
602
|
+
r.checked_verdicts++;
|
|
603
|
+
if (v.outcome === 'fail') r.caught++;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (t.user_verdicts.length) {
|
|
607
|
+
r.user_verdict_tasks++;
|
|
608
|
+
if (t.user_verdicts.some((v) => v.outcome === 'reject' && v.cause !== 'scope')) r.rejects++;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
for (const m of MODE_KEYS) {
|
|
612
|
+
const r = out[m];
|
|
613
|
+
r.catch_rate = r.checked_verdicts ? round6(r.caught / r.checked_verdicts) : null;
|
|
614
|
+
r.reject_rate = r.user_verdict_tasks ? round6(r.rejects / r.user_verdict_tasks) : null;
|
|
615
|
+
r.verify_min = round6(r.verify_min);
|
|
616
|
+
r.test_s = round6(r.test_s);
|
|
617
|
+
r.test_s_per_task = r.test_timed_tasks ? round6(r.test_s / r.test_timed_tasks) : null;
|
|
618
|
+
}
|
|
619
|
+
return out;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
module.exports.MODE_KEYS = MODE_KEYS;
|
|
623
|
+
module.exports.modeSummary = modeSummary;
|
package/lib/args.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const core = require('../hook.cjs');
|
|
6
6
|
|
|
7
|
-
const BOOLEAN_FLAGS = new Set(['json', 'help', 'h', 'purge', 'version', 'v', 'dry-run', 'no-sync', 'approve', 'yes', 'force', 'follow', 'remove', 'dirty', 'no-cache', 'untagged', 'no-knowledge']);
|
|
7
|
+
const BOOLEAN_FLAGS = new Set(['json', 'help', 'h', 'purge', 'version', 'v', 'dry-run', 'no-sync', 'approve', 'yes', 'force', 'follow', 'remove', 'dirty', 'no-cache', 'untagged', 'no-knowledge', 'net', 'with-codebase-memory', 'cbm', 'allow-custom-source', 'auto']);
|
|
8
8
|
|
|
9
9
|
/** Minimal argv parser: positionals, --k v, --k=v, boolean flags. */
|
|
10
10
|
function parseArgs(argv) {
|