@dtlabs/ai-toolkit 0.24.2 → 0.24.4
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 +16 -0
- package/lib/workflow-control.js +19 -2
- package/package.json +1 -1
- package/src/claude/workflows/pm-phase2.js +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
## 0.1.4 (2026-07-21)
|
|
2
|
+
## [0.24.4](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.3...v0.24.4) (2026-10-27)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* **pm-phase2:** regenerate a task whose Estimate is stale, not just missing verification commands ([a772767](https://github.com/dtlab-tech/ai-toolkit/commit/a772767a9d960bfe468829651e7ca2cbc7c6a17e))
|
|
8
|
+
* regenerate a task whose Estimate is stale, not just missing verification commands ([e9d794b](https://github.com/dtlab-tech/ai-toolkit/commit/e9d794b81575c49e95d0291174964058fafd009b))
|
|
9
|
+
|
|
10
|
+
## [0.24.3](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.2...v0.24.3) (2026-10-27)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* don't fail a worker call over a read-only permission denial ([1b4c521](https://github.com/dtlab-tech/ai-toolkit/commit/1b4c5217b42e9c9dcb74c9bbe60f209522644f9c))
|
|
16
|
+
* **workflow-control:** don't fail a worker call over a read-only permission denial ([fb283c6](https://github.com/dtlab-tech/ai-toolkit/commit/fb283c6a0f44f56d0ca152bcf84bf81c1ca10125))
|
|
17
|
+
|
|
2
18
|
## [0.24.2](https://github.com/dtlab-tech/ai-toolkit/compare/v0.24.1...v0.24.2) (2026-10-26)
|
|
3
19
|
|
|
4
20
|
|
package/lib/workflow-control.js
CHANGED
|
@@ -230,10 +230,27 @@ function createControl(options) {
|
|
|
230
230
|
tokenLedger.push({ agent: identity.nativeName, model: definition.model,
|
|
231
231
|
phase_delta_tokens: measuredTokens, usage: result.result?.usage || null, evidence_path: evidence.file });
|
|
232
232
|
evidence.save({ response: result, attempts });
|
|
233
|
+
// A denial on a read-only tool (Read/Glob/Grep) means the agent couldn't fetch some EXTRA
|
|
234
|
+
// context — a real, recurring pattern where a path the model constructs itself (e.g. a
|
|
235
|
+
// single leading backslash with no drive letter) resolves outside the project directory
|
|
236
|
+
// and is correctly denied. Confirmed via production evidence twice, on two different
|
|
237
|
+
// paths: the agent recovers and still produces a complete, structurally valid result, which
|
|
238
|
+
// this unconditional check threw away anyway, wasting the entire call and forcing a resume.
|
|
239
|
+
// Any OTHER tool (Write/Edit/Bash/...) stays an immediate, unconditional failure — a denial
|
|
240
|
+
// there is a near-miss on a real side effect, not a missed read, and must never be silently
|
|
241
|
+
// tolerated regardless of what the rest of the response looks like.
|
|
242
|
+
const READ_ONLY_DENIAL_TOOLS = new Set(['Read', 'Glob', 'Grep'])
|
|
233
243
|
const denials = result.result?.permission_denials;
|
|
234
244
|
if (Array.isArray(denials) && denials.length) {
|
|
235
|
-
const
|
|
236
|
-
|
|
245
|
+
const nonReadOnly = denials.filter(d => !READ_ONLY_DENIAL_TOOLS.has(d.tool_name));
|
|
246
|
+
if (nonReadOnly.length) {
|
|
247
|
+
const tools = [...new Set(nonReadOnly.map(d => d.tool_name || 'unknown'))].join(', ');
|
|
248
|
+
throw new Error('Worker permission denied: ' + id + '; tools: ' + tools);
|
|
249
|
+
}
|
|
250
|
+
// Read-only denial(s) only: fall through to the normal result-validity checks below —
|
|
251
|
+
// an agent that actually proceeded with missing context will still fail there (a bad
|
|
252
|
+
// exitCode/is_error, or the caller's own downstream structural validation), the same way
|
|
253
|
+
// it would if no denial had ever happened.
|
|
237
254
|
}
|
|
238
255
|
if (result.exitCode !== 0 || result.parseError || result.timedOut ||
|
|
239
256
|
!result.result || result.result.is_error !== false) {
|
package/package.json
CHANGED
|
@@ -440,6 +440,21 @@ function hasVerificationCommands(content) {
|
|
|
440
440
|
return FENCE_RE_LOCAL.test(verifPart)
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
// Same resume-validity role as hasVerificationCommands, for the OTHER field this agent's own
|
|
444
|
+
// prompt once asked it to merely restate (removed from "already given" wording in a prior fix —
|
|
445
|
+
// see gaia-generate-work-breakdown-task.md's own Estimate section) but which a task file
|
|
446
|
+
// generated BEFORE that fix can still carry as a literal "—" or explanatory prose instead of a
|
|
447
|
+
// number: wbs-assembler.js's own _numberOrNull throws on exactly that shape at assembly time,
|
|
448
|
+
// well after the dispatch loop below would have skipped regenerating it. A task file whose
|
|
449
|
+
// Estimate fields are not both plain integers is treated the same as one with zero verification
|
|
450
|
+
// commands — regenerated, not skipped forever — so a stale pre-fix file self-heals on the very
|
|
451
|
+
// next resume instead of requiring a hand-fix or a one-off repair script.
|
|
452
|
+
function hasValidEstimate(content) {
|
|
453
|
+
const minutes = content.match(/^- \*\*Estimate — agent minutes:\*\*\s*(.*)$/m)
|
|
454
|
+
const tokens = content.match(/^- \*\*Estimate — tokens:\*\*\s*(.*)$/m)
|
|
455
|
+
return !!minutes && !!tokens && /^\d+$/.test(minutes[1].trim()) && /^\d+$/.test(tokens[1].trim())
|
|
456
|
+
}
|
|
457
|
+
|
|
443
458
|
// Compact prompt for the per-task detail-generation worker: this task's own skeleton entry,
|
|
444
459
|
// a sibling task list (id + title + outcome only, for duplicate-implementation awareness), and
|
|
445
460
|
// the Tech-Spec excerpt (FTR-026: already pre-sliced to this task's own relevant component(s) by
|
|
@@ -637,7 +652,10 @@ return c.run(meta.name, featureDir, prefix, async () => {
|
|
|
637
652
|
for (const taskId of phase.taskIds) {
|
|
638
653
|
const task = index.tasks[taskId]
|
|
639
654
|
const detailPath = `${wbsDir}/${task.detailFile}`
|
|
640
|
-
if (task.status === 'generated' && c.exists(detailPath)
|
|
655
|
+
if (task.status === 'generated' && c.exists(detailPath)) {
|
|
656
|
+
const existing = c.read(detailPath)
|
|
657
|
+
if (hasVerificationCommands(existing) && hasValidEstimate(existing)) continue // resume: skip, no dispatch
|
|
658
|
+
}
|
|
641
659
|
const techSpecExcerpt = hasSpec
|
|
642
660
|
? buildTaskTechSpecExcerpt(components, task, specDir, c.read(specIndexPath))
|
|
643
661
|
: c.read(file('Tech-Spec.md')) // legacy fallback: unchanged, full text
|