taskchef 5.1.0 → 5.1.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/.codex-plugin/plugin.json +1 -1
- package/SPEC.md +13 -9
- package/package.json +1 -1
- package/skills/taskchef-delegate/SKILL.md +8 -4
- package/src/delegation.js +37 -32
package/SPEC.md
CHANGED
|
@@ -196,10 +196,12 @@ For each assignment, `$taskchef-delegate`:
|
|
|
196
196
|
marked entry with `threadId: null`, then prefers one native client-ID wait or
|
|
197
197
|
resolution call with a 30-second timeout when Codex exposes one
|
|
198
198
|
7. when no native operation is available, takes at most two recent-thread
|
|
199
|
-
snapshots
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
snapshots on a nominal 10- and 30-second schedule after the provisional
|
|
200
|
+
result; a late first checkpoint runs immediately, and the second starts at
|
|
201
|
+
least 20 seconds after the first actually started. It filters candidates by
|
|
202
|
+
available host/project/time/worktree metadata, uses title only as an advisory
|
|
203
|
+
ordering hint, and accepts only one thread whose structured delegated input
|
|
204
|
+
starts with the exact marker
|
|
203
205
|
8. atomically fills the nullable thread ID after an exact match
|
|
204
206
|
9. returns after recording or after reporting that bounded resolution was
|
|
205
207
|
unresolved, without waiting for executor work completion.
|
|
@@ -209,11 +211,13 @@ Creation-time filtering allows five seconds of clock skew. Candidate reads run
|
|
|
209
211
|
concurrently where the host permits and inspect only structured
|
|
210
212
|
`codexDelegation.input`, never untrusted title, summary, preview, or plain-text
|
|
211
213
|
marker echoes. A native resolver result is verified against the same structured
|
|
212
|
-
marker before persistence. Zero exact matches
|
|
213
|
-
|
|
214
|
-
task-resolution errors leave the already-recorded nullable entry intact.
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
marker before persistence. Zero exact matches remain unresolved; multiple exact
|
|
215
|
+
matches are ambiguous. Snapshot, candidate-read, native-resolution, or
|
|
216
|
+
task-resolution errors leave the already-recorded nullable entry intact. The
|
|
217
|
+
fallback is bounded by two snapshots rather than an absolute wall-clock cutoff:
|
|
218
|
+
mandatory recording or tool latency can shift both attempts later, but cannot
|
|
219
|
+
erase an attempt or reduce the minimum 20-second interval between their start
|
|
220
|
+
times.
|
|
217
221
|
A `clientThreadId`, `pendingWorktreeId`, or ID in the documented provisional
|
|
218
222
|
`local:` namespace remains diagnostic context and is rejected from every path
|
|
219
223
|
that could persist the canonical `threadId` field.
|
package/package.json
CHANGED
|
@@ -82,10 +82,14 @@ for all deterministic workspace and task-record operations.
|
|
|
82
82
|
ID, call it exactly once with a timeout of at most 30 seconds. Do not invent
|
|
83
83
|
an operation or pass the provisional ID to tools that require `threadId`.
|
|
84
84
|
- When no native operation is available, take at most two `list_threads`
|
|
85
|
-
snapshots with limit 50
|
|
86
|
-
result.
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
snapshots with limit 50. The nominal schedule is 10 and 30 seconds after
|
|
86
|
+
the provisional result. Treat the first checkpoint as due, not expired: if
|
|
87
|
+
nullable recording or other required work finishes after 10 seconds, take
|
|
88
|
+
the first snapshot immediately. Start the second snapshot no sooner than
|
|
89
|
+
20 seconds after the first snapshot actually started. Useful candidate
|
|
90
|
+
work counts toward that interval; when it finishes sooner, wait only the
|
|
91
|
+
remainder. Never suppress either snapshot merely because an earlier step
|
|
92
|
+
ran late, and never take a third snapshot.
|
|
89
93
|
- Filter Codex candidates by the expected host, project, creation time
|
|
90
94
|
(allow five seconds of clock skew), and worktree environment whenever
|
|
91
95
|
those fields are present. Use the title only to prioritize reads; Codex
|
package/src/delegation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
export const THREAD_RESOLUTION_CHECKPOINTS_MS = Object.freeze([10_000,
|
|
3
|
+
export const THREAD_RESOLUTION_CHECKPOINTS_MS = Object.freeze([10_000, 30_000]);
|
|
4
4
|
export const THREAD_RESOLUTION_TIMEOUT_MS = 30_000;
|
|
5
5
|
export const THREAD_RESOLUTION_RECENT_LIMIT = 50;
|
|
6
6
|
export const THREAD_RESOLUTION_CLOCK_SKEW_MS = 5_000;
|
|
@@ -179,6 +179,9 @@ function validateResolutionSchedule(checkpointsMs, timeoutMs) {
|
|
|
179
179
|
if (!Array.isArray(checkpointsMs) || checkpointsMs.length === 0) {
|
|
180
180
|
throw new Error("checkpointsMs must contain at least one checkpoint");
|
|
181
181
|
}
|
|
182
|
+
if (checkpointsMs.length > 2) {
|
|
183
|
+
throw new Error("checkpointsMs must contain at most two checkpoints");
|
|
184
|
+
}
|
|
182
185
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
|
|
183
186
|
throw new Error("timeoutMs must be positive");
|
|
184
187
|
}
|
|
@@ -189,6 +192,9 @@ function validateResolutionSchedule(checkpointsMs, timeoutMs) {
|
|
|
189
192
|
}
|
|
190
193
|
previous = checkpoint;
|
|
191
194
|
}
|
|
195
|
+
if (checkpointsMs.length === 2 && checkpointsMs[1] - checkpointsMs[0] < 20_000) {
|
|
196
|
+
throw new Error("checkpointsMs must keep two checkpoints at least 20000ms apart");
|
|
197
|
+
}
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
async function inspectCandidates(candidates, {
|
|
@@ -365,7 +371,11 @@ export async function createAndRecordDelegation({
|
|
|
365
371
|
return false;
|
|
366
372
|
};
|
|
367
373
|
|
|
368
|
-
const acceptResolvedThread = async (thread, resolution, attempt, {
|
|
374
|
+
const acceptResolvedThread = async (thread, resolution, attempt, {
|
|
375
|
+
verified = false,
|
|
376
|
+
enforceDeadline = true,
|
|
377
|
+
} = {}) => {
|
|
378
|
+
const canContinue = enforceDeadline ? hasResolutionTime : () => true;
|
|
369
379
|
const threadId = toolIdentifier(thread.id);
|
|
370
380
|
if (
|
|
371
381
|
threadId === null
|
|
@@ -381,18 +391,18 @@ export async function createAndRecordDelegation({
|
|
|
381
391
|
return null;
|
|
382
392
|
}
|
|
383
393
|
const durableThread = { ...thread, id: threadId };
|
|
384
|
-
if (!
|
|
394
|
+
if (!canContinue()) return null;
|
|
385
395
|
if (!verified) {
|
|
386
396
|
const inspected = await inspectCandidates([durableThread], {
|
|
387
397
|
readThread,
|
|
388
398
|
taskId: prepared.id,
|
|
389
399
|
attempt,
|
|
390
|
-
canVerify:
|
|
400
|
+
canVerify: canContinue,
|
|
391
401
|
});
|
|
392
402
|
discoveryErrors.push(...inspected.errors);
|
|
393
403
|
if (inspected.exactMatches.length !== 1 || inspected.errors.length > 0) return null;
|
|
394
404
|
}
|
|
395
|
-
if (!
|
|
405
|
+
if (!canContinue()) return null;
|
|
396
406
|
try {
|
|
397
407
|
await resolveRecordedTask({ id: prepared.id, threadId });
|
|
398
408
|
} catch (error) {
|
|
@@ -470,12 +480,14 @@ export async function createAndRecordDelegation({
|
|
|
470
480
|
let exactMatches = [];
|
|
471
481
|
let ambiguousMatches = [];
|
|
472
482
|
let attemptsMade = 0;
|
|
483
|
+
let previousAttemptStartedAt = resolutionStartedAt;
|
|
473
484
|
for (let index = 0; index < checkpointsMs.length; index += 1) {
|
|
474
485
|
const attempt = index + 1;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const
|
|
486
|
+
const intervalMs = index === 0
|
|
487
|
+
? checkpointsMs[0]
|
|
488
|
+
: checkpointsMs[index] - checkpointsMs[index - 1];
|
|
489
|
+
const dueAt = previousAttemptStartedAt + intervalMs;
|
|
490
|
+
const remainingDelayMs = dueAt - now();
|
|
479
491
|
if (remainingDelayMs > 0) {
|
|
480
492
|
try {
|
|
481
493
|
await waitImpl(remainingDelayMs);
|
|
@@ -488,43 +500,36 @@ export async function createAndRecordDelegation({
|
|
|
488
500
|
break;
|
|
489
501
|
}
|
|
490
502
|
}
|
|
491
|
-
|
|
503
|
+
previousAttemptStartedAt = now();
|
|
492
504
|
attemptsMade = attempt;
|
|
493
505
|
let candidates = [];
|
|
494
506
|
let attemptFailed = false;
|
|
495
507
|
try {
|
|
496
508
|
const snapshot = await listThreads({ limit: recentLimit });
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
});
|
|
506
|
-
}
|
|
509
|
+
candidates = filterThreadCandidates(snapshot, {
|
|
510
|
+
excludedThreadIds: provisionalIds,
|
|
511
|
+
hostId: expected.hostId ?? null,
|
|
512
|
+
projectId: expected.projectId ?? target.projectId ?? null,
|
|
513
|
+
title,
|
|
514
|
+
createdAfter,
|
|
515
|
+
environmentType: expected.environmentType ?? target.environment?.type ?? null,
|
|
516
|
+
});
|
|
507
517
|
} catch (error) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
});
|
|
515
|
-
}
|
|
518
|
+
attemptFailed = true;
|
|
519
|
+
discoveryErrors.push({
|
|
520
|
+
attempt,
|
|
521
|
+
operation: "listThreads",
|
|
522
|
+
message: error instanceof Error ? error.message : String(error),
|
|
523
|
+
});
|
|
516
524
|
}
|
|
517
|
-
if (!hasResolutionTime()) break;
|
|
518
525
|
const inspected = await inspectCandidates(candidates, {
|
|
519
526
|
readThread,
|
|
520
527
|
taskId: prepared.id,
|
|
521
528
|
attempt,
|
|
522
|
-
canVerify: hasResolutionTime,
|
|
523
529
|
});
|
|
524
530
|
exactMatches = inspected.exactMatches;
|
|
525
531
|
discoveryErrors.push(...inspected.errors);
|
|
526
532
|
if (inspected.errors.length > 0) attemptFailed = true;
|
|
527
|
-
if (!hasResolutionTime()) break;
|
|
528
533
|
if (exactMatches.length > 1) ambiguousMatches = exactMatches;
|
|
529
534
|
if (
|
|
530
535
|
exactMatches.length === 1
|
|
@@ -536,7 +541,7 @@ export async function createAndRecordDelegation({
|
|
|
536
541
|
exactMatches[0],
|
|
537
542
|
"discovered",
|
|
538
543
|
attempt,
|
|
539
|
-
{ verified: true },
|
|
544
|
+
{ verified: true, enforceDeadline: false },
|
|
540
545
|
);
|
|
541
546
|
if (resolved !== null) return resolved;
|
|
542
547
|
}
|