taskchef 5.12.0 → 6.1.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: taskchef-delegate
3
- description: "Dispatch actionable requests through the per-user TaskChef workspace into independently openable Codex project tasks. Use automatically for actionable work received in the canonical TaskChef dispatcher workspace. From any other project, use only when the user explicitly asks to delegate or split separate work into Codex tasks; TaskChef-related subject matter alone is not delegation intent. Record before creation, resolve provisional identity by exact marker within a strict bound, and never wait for executor completion."
3
+ description: "Dispatch actionable requests through the per-user TaskChef workspace into independently openable Codex project tasks. Use automatically for actionable work received in the canonical TaskChef dispatcher workspace. From any other project, use only when the user explicitly asks to delegate or split separate work into Codex tasks; TaskChef-related subject matter alone is not delegation intent. Record before creation, require executor self-linking, and never wait for executor completion."
4
4
  ---
5
5
 
6
6
  # TaskChef Delegate
@@ -12,154 +12,76 @@ and return immediately.
12
12
 
13
13
  A task whose initial structured `codexDelegation.input` starts with an exact
14
14
  `<!-- taskchef_id=<full UUID> -->` marker already owns that delegated
15
- assignment. Execute the assignment in the current task. Do not re-dispatch it
16
- merely because it concerns TaskChef or a configured project.
15
+ assignment. Execute it in the current task. Do not re-dispatch it merely
16
+ because it concerns TaskChef or a configured project. Explicit requests to
17
+ delegate separate work remain valid.
17
18
 
18
- This does not prevent the task from using TaskChef later. Use this skill
19
- normally when the initial assignment explicitly asks to delegate separate
20
- work, or when the user later explicitly requests a new delegation.
21
-
22
- Use the bundled TaskChef `prepare_dispatch`, `record_task`, `resolve_task`, and `report_result`
23
- tools for deterministic workspace and task-record operations. Call them
24
- directly; never probe for them or fall back to shell CLI writes. If a required
25
- tool is unavailable, stop and report that the TaskChef plugin must be reloaded
26
- or reinstalled. Resolve the plugin root from this skill only when writing an
27
- explicitly requested benchmark artifact.
19
+ Use the bundled `prepare_dispatch`, `record_task`, `link_task`, and
20
+ `report_result` MCP tools directly. Never fall back to shell writes. If a
21
+ required tool is unavailable, stop and report that the TaskChef plugin must be
22
+ reloaded or installed.
28
23
 
29
24
  ## Boundaries
30
25
 
31
- - Keep implementation, tests, and reports in the TaskChef source repository.
32
- - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a dispatcher
26
+ - Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in the dispatcher
33
27
  workspace.
34
28
  - Use real Codex tasks, never collaboration or subagent tools.
35
- - Use only TaskChef's `UserPromptSubmit` hook: after identity is separately
36
- verified, it records the initial turn and provides read-only current-turn
37
- context on follow-up prompts. Never infer
38
- lifecycle state from hooks.
39
- - Never use schedules, daemons, background monitors, or indefinite polling.
40
- The only permitted thread discovery is the bounded exact-marker resolution
41
- after a provisional creation result.
42
- - Never wait for delegated work after executor creation.
43
- - Never collect transcripts or hidden reasoning.
29
+ - Never use hooks, schedules, daemons, background monitors, recent-task
30
+ searches, transcripts, hidden reasoning, or polling for identity.
31
+ - Never wait for delegated work after native creation.
32
+ - Treat executor-supplied identity as a cooperative assertion in TaskChef's
33
+ local single-user trust boundary, not transport-authenticated proof.
44
34
 
45
35
  ## Dispatch
46
36
 
47
37
  1. Split the request into the smallest independently useful outcomes. Include
48
38
  constraints, expected testing, and reporting in every instruction.
49
- 2. In parallel, list the native Codex projects once and call `prepare_dispatch`
50
- exactly once for each outcome. Every preparation produces a distinct task
51
- UUID and marker; never reuse either across executors. The preparation tool
52
- resolves the canonical workspace, loads and validates the configured routing targets,
53
- generates the lowercase full UUID task ID, and returns `preparedAt` plus the
54
- exact first-line marker. Use
55
- `$taskchef-bootstrap` if the workspace is missing or unhealthy.
56
- The tool resolves an absolute (or `~/`-prefixed) `TASKCHEF_WORKSPACE`, then
57
- `~/.agents/taskchef`; do not substitute the current project. Reject the
58
- dispatcher workspace itself as a target. Never take a pre-creation thread
59
- snapshot.
60
- 3. Classify against configured `name`, every URL in the `githubRepos` list, and
61
- `description`. Use `path` only as checkout identity. Managed `*-workspace`
62
- projects advertise their child or sub-repositories in this list.
63
- When the prompt contains a GitHub issue or pull-request URL, canonicalize
64
- its case-insensitive owner/repository identity, ignoring `http` versus
65
- `https`, an optional `www`, a trailing slash or `.git`, and the issue or PR
66
- suffix. Check that identity against every repository URL of every configured
67
- project. Route on this evidence only when exactly one configured project
68
- matches. Ask instead of guessing when no project or several projects match.
69
- 4. Resolve the selected configured path against the already-loaded native
70
- projects and require an exact match. Do not list native projects again.
71
- 5. For each outcome, use only its corresponding task ID, preparation time, and
72
- marker returned by its own preparation call.
73
- Prefix the complete executor instruction with
74
- exactly `<!-- taskchef_id=<full UUID> -->` as the first line, followed by a
75
- blank line, this executor-role paragraph, another blank line, the result
76
- paragraph below, another blank line, and the instruction body:
39
+ 2. In parallel, list native Codex projects once and call `prepare_dispatch`
40
+ exactly once per outcome. Never reuse a task ID or marker. Do not take a
41
+ pre-creation thread snapshot.
42
+ 3. Route against configured project `name`, `description`, and canonical
43
+ `githubRepos`; use `path` only as checkout identity. Require exactly one
44
+ match and an exact native-project path. Ask instead of guessing.
45
+ 4. Prefix each executor instruction with the preparation's exact marker as the
46
+ first line, a blank line, and these required paragraphs before the body:
77
47
 
78
48
  > This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.
79
49
 
80
- > Before ending, call the TaskChef report_result MCP tool with completed, needs_input, or failed and a concise summary. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.
81
-
82
- Preserve this
83
- marked instruction for recording, and note the creation time. The exact
84
- random marker is the sole correlation proof.
85
- 6. Before creating each executor, call `record_task` exactly once with `id`,
86
- `project`, `title`, the marked `instruction`, and `threadId: null`. This
87
- closes the hook race: the entry exists before the executor can submit its
88
- initial prompt.
89
- 7. Create one real Codex task using the exact configured project, a local
90
- environment on its executor host, the marked instruction, and a short title.
91
- 8. If creation returns a durable `threadId`, call `resolve_task` immediately.
92
- If creation returns only `clientThreadId`, `pendingWorktreeId`, or a `local:`
93
- ID, retain it only for the created-thread directive and run this one bounded
94
- identity-resolution sequence:
95
- - Check immediately, then at 10 and 30 seconds after creation. At each
96
- checkpoint, call the native recent-task listing once.
97
- - Restrict candidates to Codex tasks created no earlier than five seconds
98
- before `preparedAt`, on the returned host and selected project when those
99
- fields are available. Prefer the exact title only as an ordering hint.
100
- - Read each remaining bounded candidate at most once per checkpoint and
101
- accept only the single task whose structured initial
102
- `codexDelegation.input` begins with this outcome's exact marker.
103
- - Call `resolve_task` only for that one verified durable child ID. Never use
104
- `session_id`, a source/parent ID, title similarity, or a provisional ID as
105
- identity proof.
106
- - Stop at 30 seconds. Zero matches remain unresolved; multiple exact matches
107
- are an ambiguity and also remain unresolved. Never guess or continue
108
- polling.
109
-
110
- The initial hook may wait for this canonical record transition so it can
111
- inject the verified child ID and current turn ID. Codex documents that
112
- subagent hooks receive the parent `session_id`, so the hook must never write
113
- that field into `threadId`.
114
-
115
- 9. If executor creation fails, call `report_result` for the already-recorded
116
- task with `failed`, null thread/turn IDs, and a concise creation error.
117
- 10. Emit the appropriate created-thread directive and return after the bounded
118
- identity step.
119
- Treat a nullable record as preserved but not yet linked. Do not read an
120
- executor for progress and never wait for executor work completion.
121
-
122
- The bundled structured tools are the only dispatch-time path to the canonical
123
- workspace. They reuse the same exact-field validation, locking, atomic writes,
124
- and one-way nullable resolution as the CLI without shell parsing, stdin, or
125
- per-command filesystem escalation. The package exports repository routing,
126
- marker, and record-before-create orchestration helpers for deterministic tests.
127
- Keep the CLI for bootstrap, manual inspection, and recovery outside delegation.
128
-
129
- ## Later resolution
130
-
131
- The trusted initial hook never resolves identity from its `session_id`. It uses
132
- only the child ID already stored by immediate or bounded exact-marker
133
- resolution. For manual recovery, require one exact structured marker match,
134
- then call `resolve_task` once. Never edit `tasks.jsonl` directly. Resolution is
135
- an idempotent one-way transition from `threadId: null` to one unique durable
136
- child thread ID.
137
-
138
- ## Legacy benchmark compatibility
139
-
140
- The schema-v1 benchmark fixtures below describe the removed snapshot resolver
141
- and remain only for historical-result validation. Do not use that resolver for
142
- new delegation benchmarks. A future benchmark schema should measure record,
143
- creation, bounded exact-marker resolution, and result callback without
144
- indefinite polling. The
145
- legacy fixture starts from
146
- `<plugin-root>/assets/e2e-benchmark-example.json`. Capture one ISO start/end
147
- interval for the parallel preparation/project-list operation, followed by
148
- sequential non-overlapping creation, recording, and optional provisional
149
- resolution stages. Follow the complete schema and workflow transitions in
150
- `<plugin-root>/SPEC.md`; the example is the durable-success starting shape.
151
- Omit later stages when an earlier operation stops the workflow.
152
- Fallback snapshot observations additionally require `recentTaskCount`,
153
- `candidateCount`, `exactMatchCount`, and `resolveWriteMs`. For each snapshot,
154
- also record `resolveWriteOutcome` as `not-attempted`, `succeeded`, or `failed`;
155
- a unique match whose atomic write fails remains unresolved. After the bounded
156
- workflow ends, verify the canonical task record once and, when resolved, read
157
- the task once to verify the requested output. A failed verification remains a
158
- false validation flag even when the corresponding operation succeeded. Feed
159
- one exact JSON value to
160
- `node <plugin-root>/scripts/e2e-benchmark.js write <output-directory>` on closed,
161
- non-interactive stdin. Keep timestamped results in the TaskChef source
162
- repository's ignored `reports/e2e-benchmarks/` directory. Never include hidden
163
- reasoning or transcripts. Use the script's `clean` command before establishing
164
- a replacement baseline. Mark candidate filtering effective only when the
165
- fallback snapshot narrows reads below the complete recent-task window.
50
+ > Before any other work, read this executor's own durable Codex thread ID from the current task's CODEX_THREAD_ID environment value and call the TaskChef link_task MCP tool with that thread ID and the marked TaskChef task ID. Never use CODEX_SESSION_ID or the parent or delegator thread ID. If linking fails, CODEX_THREAD_ID is unavailable, or the tool is unavailable, report the failure visibly and retry on a later turn; do not guess an identity or continue substantive work while the task is link-pending.
51
+
52
+ > Before ending, call the TaskChef report_result MCP tool with the marked task ID, this executor's self-linked thread ID, the current turn ID from an exact native read of that same thread, completed, needs_input, or failed, and a concise summary. Never reuse a prior turn ID after a follow-up. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.
53
+
54
+ 5. Before creating each executor, call `record_task` exactly once with `id`,
55
+ `project`, `title`, the exact marked `instruction`, and `threadId: null`.
56
+ 6. Create one real Codex task using the exact configured project, an appropriate
57
+ native environment, the marked instruction, and a short title.
58
+ 7. Return immediately. Preserve a returned provisional client ID only for the
59
+ created-thread directive. Do not call `link_task` from the dispatcher even
60
+ when creation returns a durable ID; the child must self-link.
61
+ 8. If creation fails after recording, call `report_result` with `failed`, null
62
+ thread/turn IDs, and a bounded summary before returning the failure.
63
+
64
+ ## Executor contract
65
+
66
+ The executor must make `link_task(taskId, threadId)` its first TaskChef action.
67
+ It obtains its own durable ID from the current task's `CODEX_THREAD_ID`, never
68
+ from the delegation's `sourceThreadId`, `CODEX_SESSION_ID`, inherited session
69
+ metadata, title matching, or a parent task.
70
+ Identical retries are safe. A rejected link, unavailable tool, or interrupted
71
+ initial turn leaves the record visibly link-pending and retryable; the executor
72
+ must not guess or do substantive work first.
73
+
74
+ For every semantic result, the executor supplies its linked thread ID and the
75
+ current turn ID obtained by reading that exact thread. A follow-up must use the
76
+ new turn ID. Do not reuse the initial turn ID. `needs_input` is only for a real
77
+ user decision, not live approval UI.
78
+
79
+ The filesystem watcher surfaces `link_task` and `report_result` writes to the
80
+ dashboard. The linked child ID drives the exact Codex deep link.
81
+
82
+ ## Legacy recovery
83
+
84
+ `taskchef task resolve` exists only for unresolved records created before the
85
+ self-linking schema. Require an exact marker match and a unique durable child
86
+ ID. The command rejects new self-linking records. Never edit `tasks.jsonl`
87
+ directly.
@@ -47,11 +47,12 @@ all deterministic task-log operations.
47
47
  4. Treat `updatedBy: mcp`, `status: failed`, and null thread/turn IDs as a fresh
48
48
  executor-creation failure. No live read is possible or needed; report the
49
49
  stored failure summary, not unresolved. Otherwise, only a snapshot with
50
- `updatedBy: mcp` is a cached semantic result. A
51
- dispatcher- or hook-written `working` snapshot has no semantic callback and
52
- requires one live task query when selected; if the task is inactive and no
53
- callback exists, report the outcome as unknown rather than treating
54
- `working` as fresh. When identity is certain and metadata says the thread is
50
+ `updatedBy: mcp`, a result status, a non-null summary, and a non-null turn ID
51
+ is a cached semantic result. Any `working` snapshot has no semantic
52
+ callback, including a self-linked `updatedBy: mcp` snapshot, and requires
53
+ one live task query when selected; if the task is inactive and no callback
54
+ exists, report the outcome as unknown rather than treating `working` as
55
+ fresh. When identity is certain and metadata says the thread is
55
56
  inactive, trust the latest MCP result by default in a broad overview. Do not
56
57
  read every idle terminal task in an overview merely because native
57
58
  `updatedAt` is later: callbacks normally run before Codex finalizes the same
@@ -74,9 +75,12 @@ all deterministic task-log operations.
74
75
  completed, failed, unresolved, or unknown. Show the cached summary when it
75
76
  remains fresh. If a newer turn exists without a callback, describe the live
76
77
  state and label the cached result stale rather than overwriting it.
77
- 6. Never edit `tasks.jsonl` directly during reporting. The initial hook normally
78
- resolves null identity; manual recovery may call `task resolve` only after
79
- one exact structured marker match. Never persist inferred status,
78
+ 6. Never edit `tasks.jsonl` directly during reporting. The task API preserves
79
+ each record's persisted `schemaVersion`: a schema 4 null identity is executor
80
+ link-pending and must be retried by that executor, while a schema 1-3 null
81
+ identity is a legacy recovery candidate. Manual recovery may call
82
+ `task resolve` only for the latter after one exact structured marker match.
83
+ Never persist inferred status,
80
84
  transcripts, prose classifications, or hidden reasoning. Do not poll or wait.
81
85
 
82
86
  If the task history is empty, say that TaskChef has not recorded any tasks. If
package/src/cli.js CHANGED
@@ -464,12 +464,13 @@ Usage:
464
464
  taskchef project remove <name> [--json] [--workspace <path>]
465
465
  taskchef dispatch prepare [--json] [--workspace <path>]
466
466
  taskchef task record [--json] [--workspace <path>]
467
- taskchef task resolve <task-id> --thread-id <thread-id> [--json] [--workspace <path>]
467
+ taskchef task resolve <legacy-task-id> --thread-id <thread-id> [--json] [--workspace <path>]
468
468
  taskchef task show <task-id-or-8-character-prefix> [--json] [--workspace <path>]
469
469
  taskchef task list [--project <name-or-path>] [--ascending] [--full-id] [--json] [--workspace <path>]
470
470
  taskchef task summary [--json] [--workspace <path>]
471
471
 
472
472
  Task record reads one JSON value from closed, non-interactive standard input.
473
+ Task resolve is a legacy migration command and rejects self-linking task records.
473
474
  Task show accepts a full task ID or the exact 8-character ID printed by task list.
474
475
  Task show prints human-readable details by default; --json prints the complete task object.
475
476
  Project import reads a JSON
package/src/delegation.js CHANGED
@@ -1,30 +1,22 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
 
3
- // Bounded discovery parameters used when Codex returns only a provisional
4
- // client ID. Candidate content still needs exact-marker verification.
5
- export const THREAD_RESOLUTION_CHECKPOINTS_MS = Object.freeze([10_000, 30_000]);
6
- export const THREAD_RESOLUTION_TIMEOUT_MS = 30_000;
7
- export const THREAD_RESOLUTION_RECENT_LIMIT = 50;
8
- export const THREAD_RESOLUTION_CLOCK_SKEW_MS = 5_000;
9
3
  export const EXECUTOR_OWNERSHIP_PARAGRAPH = "This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.";
10
- export const EXECUTOR_RESULT_PARAGRAPH = "Before ending, call the TaskChef report_result MCP tool with completed, needs_input, or failed and a concise summary. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.";
4
+ export const EXECUTOR_LINK_PARAGRAPH = "Before any other work, read this executor's own durable Codex thread ID from the current task's CODEX_THREAD_ID environment value and call the TaskChef link_task MCP tool with that thread ID and the marked TaskChef task ID. Never use CODEX_SESSION_ID or the parent or delegator thread ID. If linking fails, CODEX_THREAD_ID is unavailable, or the tool is unavailable, report the failure visibly and retry on a later turn; do not guess an identity or continue substantive work while the task is link-pending.";
5
+ export const EXECUTOR_RESULT_PARAGRAPH = "Before ending, call the TaskChef report_result MCP tool with the marked task ID, this executor's self-linked thread ID, the current turn ID from an exact native read of that same thread, completed, needs_input, or failed, and a concise summary. Never reuse a prior turn ID after a follow-up. Use needs_input only for a semantic decision or information the user must provide; a native approval prompt is live Codex state, not a TaskChef result. Do not include secrets, transcripts, or raw command output.";
11
6
 
12
7
  const UUID_SOURCE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
13
8
  const UUID_PATTERN = new RegExp(`^${UUID_SOURCE}$`);
9
+ const CODEX_UUID_V7_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
14
10
  const TASKCHEF_MARKER_PATTERN = new RegExp(`^<!-- taskchef_id=(${UUID_SOURCE}) -->$`);
15
11
  const LEGACY_TASKCHEF_MARKER_PATTERN = new RegExp(`^# taskchef_id=(${UUID_SOURCE})$`);
16
12
 
17
13
  function requireObject(value, name) {
18
- if (!value || typeof value !== "object" || Array.isArray(value)) {
19
- throw new Error(`${name} must be an object`);
20
- }
14
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${name} must be an object`);
21
15
  return value;
22
16
  }
23
17
 
24
18
  function requireString(value, name) {
25
- if (typeof value !== "string" || value.trim().length === 0) {
26
- throw new Error(`${name} must be a non-empty string`);
27
- }
19
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${name} must be a non-empty string`);
28
20
  return value;
29
21
  }
30
22
 
@@ -44,11 +36,6 @@ function parseToolResult(value, name) {
44
36
  }
45
37
  }
46
38
 
47
- function timestampMilliseconds(value) {
48
- if (typeof value !== "number" || !Number.isFinite(value)) return null;
49
- return value < 1_000_000_000_000 ? value * 1_000 : value;
50
- }
51
-
52
39
  function toolIdentifier(value) {
53
40
  if (typeof value !== "string") return null;
54
41
  const normalized = value.trim();
@@ -64,10 +51,7 @@ function attachCreationRecovery(error, taskId, resultReporting) {
64
51
  });
65
52
  return creationError;
66
53
  } catch {
67
- const wrapped = new Error(
68
- `Executor creation failed for recorded TaskChef task ${taskId}.`,
69
- { cause: creationError },
70
- );
54
+ const wrapped = new Error(`Executor creation failed for recorded TaskChef task ${taskId}.`, { cause: creationError });
71
55
  wrapped.taskChefTaskId = taskId;
72
56
  wrapped.taskChefResultReporting = resultReporting;
73
57
  return wrapped;
@@ -82,12 +66,17 @@ export function isProvisionalThreadId(value) {
82
66
  export function normalizeDurableThreadId(value, name = "threadId") {
83
67
  const id = toolIdentifier(value);
84
68
  if (id === null) throw new Error(`${name} must be a non-empty string`);
85
- if (isProvisionalThreadId(id)) {
86
- throw new Error(`${name} must be a durable thread ID, not a provisional local ID`);
87
- }
69
+ if (isProvisionalThreadId(id)) throw new Error(`${name} must be a durable thread ID, not a provisional local ID`);
88
70
  return id;
89
71
  }
90
72
 
73
+ export function normalizeCodexThreadId(value, name = "threadId") {
74
+ const id = normalizeDurableThreadId(value, name).toLowerCase();
75
+ if (!CODEX_UUID_V7_PATTERN.test(id)) {
76
+ throw new Error(`${name} must be a canonical Codex UUIDv7`);
77
+ }
78
+ return id;
79
+ }
91
80
 
92
81
  export function taskChefMarker(taskId) {
93
82
  return `<!-- taskchef_id=${requireUuid(taskId)} -->`;
@@ -107,149 +96,35 @@ export function parseTaskChefMarker(instruction, { allowLegacyHeading = false }
107
96
 
108
97
  export function prepareDelegation(instruction, { taskId = randomUUID() } = {}) {
109
98
  requireString(instruction, "instruction");
110
- if (parseTaskChefMarker(instruction) !== null) {
111
- throw new Error("instruction already contains a TaskChef marker");
112
- }
99
+ if (parseTaskChefMarker(instruction) !== null) throw new Error("instruction already contains a TaskChef marker");
113
100
  const id = requireUuid(taskId);
114
101
  return {
115
102
  id,
116
- instruction: `${taskChefMarker(id)}\n\n${EXECUTOR_OWNERSHIP_PARAGRAPH}\n\n${EXECUTOR_RESULT_PARAGRAPH}\n\n${instruction}`,
103
+ instruction: `${taskChefMarker(id)}\n\n${EXECUTOR_OWNERSHIP_PARAGRAPH}\n\n${EXECUTOR_LINK_PARAGRAPH}\n\n${EXECUTOR_RESULT_PARAGRAPH}\n\n${instruction}`,
117
104
  };
118
105
  }
119
106
 
120
- export function listThreadEntries(result) {
121
- const parsed = parseToolResult(result, "list_threads result");
122
- const entries = [...(parsed.pinnedThreads ?? []), ...(parsed.threads ?? [])];
123
- const unique = new Map();
124
- for (const entry of entries) {
125
- const id = toolIdentifier(entry?.id);
126
- if (id !== null && !unique.has(id)) unique.set(id, { ...entry, id });
127
- }
128
- return [...unique.values()];
129
- }
130
-
131
- export function structuredDelegatedInputs(result) {
132
- const parsed = parseToolResult(result, "read_thread result");
133
- const inputs = [];
134
- for (const turn of parsed.turns ?? []) {
135
- for (const item of turn?.items ?? []) {
136
- if (item?.type !== "userMessage") continue;
137
- for (const part of item.content ?? []) {
138
- if (typeof part?.codexDelegation?.input === "string") {
139
- inputs.push(part.codexDelegation.input);
140
- }
141
- }
142
- }
143
- }
144
- return inputs;
145
- }
146
-
147
- export function hasExactTaskChefMarker(result, taskId) {
148
- const id = requireUuid(taskId);
149
- return structuredDelegatedInputs(result).some(
150
- (input) => parseTaskChefMarker(input) === id,
151
- );
152
- }
153
-
154
- export function filterThreadCandidates(result, {
155
- baselineThreadIds = new Set(),
156
- excludedThreadIds = new Set(),
157
- hostId = null,
158
- projectId = null,
159
- title = null,
160
- createdAfter = null,
161
- environmentType = null,
162
- } = {}) {
163
- const minimumCreatedAt = createdAfter === null
164
- ? null
165
- : timestampMilliseconds(createdAfter) - THREAD_RESOLUTION_CLOCK_SKEW_MS;
166
- const candidates = listThreadEntries(result).filter((thread) => {
167
- if (
168
- baselineThreadIds.has(thread.id)
169
- || excludedThreadIds.has(thread.id)
170
- || isProvisionalThreadId(thread.id)
171
- || thread.kind !== "codex"
172
- ) return false;
173
- if (hostId !== null && thread.hostId !== undefined && thread.hostId !== hostId) return false;
174
- if (projectId !== null && thread.projectId !== undefined && thread.projectId !== projectId) {
175
- return false;
176
- }
177
- const candidateEnvironmentType = thread.environment?.type
178
- ?? thread.environmentType
179
- ?? thread.backing?.environment?.type;
180
- if (
181
- environmentType !== null
182
- && candidateEnvironmentType !== undefined
183
- && candidateEnvironmentType !== environmentType
184
- ) return false;
185
- const candidateTime = timestampMilliseconds(thread.createdAt ?? thread.updatedAt);
186
- if (minimumCreatedAt !== null && candidateTime !== null && candidateTime < minimumCreatedAt) {
187
- return false;
188
- }
189
- return true;
190
- });
191
- if (title === null) return candidates;
192
- return candidates.sort((left, right) =>
193
- Number(right.title === title) - Number(left.title === title));
194
- }
195
-
196
107
  export async function createAndRecordDelegation(input) {
197
- const {
198
- project,
199
- title,
200
- instruction,
201
- target,
202
- createThread,
203
- recordTask,
204
- resolveRecordedTask = null,
205
- reportRecordedResult = null,
206
- taskId = randomUUID(),
207
- } = input ?? {};
108
+ const { project, title, instruction, target, createThread, recordTask, reportRecordedResult = null, taskId = randomUUID() } = input ?? {};
208
109
  requireString(project, "project");
209
110
  requireString(title, "title");
210
111
  requireObject(target, "target");
211
- for (const [value, name] of [
212
- [createThread, "createThread"],
213
- [recordTask, "recordTask"],
214
- ]) {
112
+ for (const [value, name] of [[createThread, "createThread"], [recordTask, "recordTask"]]) {
215
113
  if (typeof value !== "function") throw new Error(`${name} must be a function`);
216
114
  }
217
- for (const [value, name] of [
218
- [resolveRecordedTask, "resolveRecordedTask"],
219
- [reportRecordedResult, "reportRecordedResult"],
220
- ]) {
221
- if (value !== null && typeof value !== "function") {
222
- throw new Error(`${name} must be a function or null`);
223
- }
224
- }
115
+ if (reportRecordedResult !== null && typeof reportRecordedResult !== "function") throw new Error("reportRecordedResult must be a function or null");
225
116
 
226
117
  const prepared = prepareDelegation(instruction, { taskId });
227
- await recordTask({
228
- id: prepared.id,
229
- project,
230
- title,
231
- instruction: prepared.instruction,
232
- threadId: null,
233
- });
118
+ await recordTask({ id: prepared.id, project, title, instruction: prepared.instruction, threadId: null });
234
119
 
235
120
  let createResult;
236
121
  try {
237
- createResult = parseToolResult(await createThread({
238
- prompt: prepared.instruction,
239
- title,
240
- target,
241
- }), "create_thread result");
122
+ createResult = parseToolResult(await createThread({ prompt: prepared.instruction, title, target }), "create_thread result");
242
123
  } catch (error) {
243
124
  let resultReporting = "unavailable";
244
125
  if (reportRecordedResult !== null) {
245
126
  try {
246
- await reportRecordedResult({
247
- taskId: prepared.id,
248
- threadId: null,
249
- turnId: null,
250
- status: "failed",
251
- summary: "Executor creation failed before the executor started.",
252
- });
127
+ await reportRecordedResult({ taskId: prepared.id, threadId: null, turnId: null, status: "failed", summary: "Executor creation failed before the executor started." });
253
128
  resultReporting = "recorded";
254
129
  } catch {
255
130
  resultReporting = "failed";
@@ -261,58 +136,6 @@ export async function createAndRecordDelegation(input) {
261
136
  const returnedThreadId = toolIdentifier(createResult.threadId);
262
137
  const clientThreadId = toolIdentifier(createResult.clientThreadId);
263
138
  const pendingWorktreeId = toolIdentifier(createResult.pendingWorktreeId);
264
- const provisional = clientThreadId
265
- ?? pendingWorktreeId
266
- ?? (isProvisionalThreadId(returnedThreadId) ? returnedThreadId : null);
267
- const durableThreadId = returnedThreadId !== null
268
- && !isProvisionalThreadId(returnedThreadId)
269
- && returnedThreadId !== clientThreadId
270
- && returnedThreadId !== pendingWorktreeId
271
- ? returnedThreadId
272
- : null;
273
-
274
- if (durableThreadId !== null) {
275
- if (resolveRecordedTask !== null) {
276
- try {
277
- await resolveRecordedTask({ id: prepared.id, threadId: durableThreadId });
278
- return {
279
- status: "recorded",
280
- resolution: "immediate",
281
- ...prepared,
282
- threadId: durableThreadId,
283
- provisional,
284
- hostId: createResult.hostId ?? null,
285
- };
286
- } catch (error) {
287
- return {
288
- status: "recorded-unresolved",
289
- reason: "task-resolution-failed",
290
- resolutionError: error instanceof Error ? error.message : String(error),
291
- ...prepared,
292
- threadId: null,
293
- createdThreadId: durableThreadId,
294
- provisional,
295
- hostId: createResult.hostId ?? null,
296
- };
297
- }
298
- }
299
- return {
300
- status: "recorded-unresolved",
301
- reason: "task-resolution-unavailable",
302
- ...prepared,
303
- threadId: null,
304
- createdThreadId: durableThreadId,
305
- provisional,
306
- hostId: createResult.hostId ?? null,
307
- };
308
- }
309
-
310
- return {
311
- status: "recorded-unresolved",
312
- reason: "awaiting-bounded-marker-resolution",
313
- ...prepared,
314
- threadId: null,
315
- provisional,
316
- hostId: createResult.hostId ?? null,
317
- };
139
+ const provisional = clientThreadId ?? pendingWorktreeId ?? (isProvisionalThreadId(returnedThreadId) ? returnedThreadId : null);
140
+ return { status: "recorded-link-pending", resolution: "executor-self-link", ...prepared, threadId: null, provisional, hostId: createResult.hostId ?? null };
318
141
  }