@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 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
 
@@ -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 tools = [...new Set(denials.map(d => d.tool_name || 'unknown'))].join(', ');
236
- throw new Error('Worker permission denied: ' + id + '; tools: ' + tools);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtlabs/ai-toolkit",
3
- "version": "0.24.2",
3
+ "version": "0.24.4",
4
4
  "description": "AI Toolkit — reusable agents and procedures for developers to build AI-powered applications.",
5
5
  "files": [
6
6
  "docs/",
@@ -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) && hasVerificationCommands(c.read(detailPath))) continue // resume: skip, no dispatch
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