@bumpyclock/pi-tasque 0.1.0 → 0.2.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.
Files changed (34) hide show
  1. package/README.md +168 -140
  2. package/package.json +24 -6
  3. package/src/bridge/bridge-tool.ts +10 -13
  4. package/src/bridge/import-tsq.ts +5 -30
  5. package/src/bridge/promote-todo.ts +1 -4
  6. package/src/bridge/types.ts +11 -12
  7. package/src/durable-tasks/bulk-contract.ts +176 -0
  8. package/src/durable-tasks/cache.ts +1 -4
  9. package/src/durable-tasks/change-command-builder.ts +315 -0
  10. package/src/durable-tasks/handoff-guard.ts +329 -0
  11. package/src/durable-tasks/project.ts +71 -0
  12. package/src/durable-tasks/runner.ts +1 -4
  13. package/src/durable-tasks/status.ts +20 -5
  14. package/src/durable-tasks/task-mappers.ts +160 -0
  15. package/src/durable-tasks/task-schema.ts +193 -0
  16. package/src/durable-tasks/task-tool.ts +197 -0
  17. package/src/durable-tasks/task-validation.ts +123 -0
  18. package/src/durable-tasks/tools-bulk.ts +141 -0
  19. package/src/durable-tasks/tools-change.ts +111 -382
  20. package/src/durable-tasks/tools-claim.ts +15 -43
  21. package/src/durable-tasks/tools-handoff.ts +95 -0
  22. package/src/durable-tasks/tools-query.ts +58 -29
  23. package/src/durable-tasks/tools-spec.ts +230 -0
  24. package/src/durable-tasks/tools-tree-create.ts +221 -0
  25. package/src/guidelines/internal-tools.ts +33 -0
  26. package/src/guidelines/task.ts +5 -0
  27. package/src/guidelines/todo.ts +5 -0
  28. package/src/index.ts +2 -13
  29. package/src/session-todos/state/replay.ts +10 -13
  30. package/src/session-todos/todo-overlay.ts +1 -1
  31. package/src/session-todos/todo.ts +11 -15
  32. package/src/session-todos/tool/types.ts +7 -7
  33. package/src/shared/error-utils.ts +29 -0
  34. package/src/shared/validation.ts +25 -0
@@ -1,60 +1,62 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import {
3
3
  defineTool,
4
+ type AgentToolResult,
4
5
  type ExtensionAPI,
5
6
  type ExtensionContext,
6
7
  } from "@earendil-works/pi-coding-agent";
7
8
  import { type Static, Type } from "typebox";
8
- import { runQueuedMutation } from "./mutation-queue.js";
9
- import { runTsqJson } from "./runner.js";
9
+ import {
10
+ CHANGE_TASKS_PROMPT_GUIDELINES,
11
+ CHANGE_TASKS_PROMPT_SNIPPET,
12
+ } from "../guidelines/internal-tools.js";
13
+ import {
14
+ asRecord,
15
+ copyKnownErrorFields,
16
+ } from "../shared/error-utils.js";
10
17
  import {
11
18
  errorToolDetails,
12
19
  okToolDetails,
13
20
  textToolResult,
14
21
  } from "../shared/tool-result.js";
22
+ import {
23
+ buildMutationCommand,
24
+ TSQ_CHANGE_ACTIONS,
25
+ type TsqChangeAction,
26
+ } from "./change-command-builder.js";
27
+ import { runQueuedMutation } from "./mutation-queue.js";
28
+ import { runTsqJson } from "./runner.js";
15
29
 
16
30
  export const TSQ_CHANGE_TOOL_NAME = "tsq_change";
17
31
 
18
- const TSQ_CHANGE_ACTIONS = [
19
- "create",
20
- "note",
21
- "done",
22
- "reopen",
23
- "defer",
24
- "start",
25
- "claim_assign_only",
26
- "block",
27
- "unblock",
28
- "order",
29
- "unorder",
30
- ] as const;
31
-
32
- export type TsqChangeAction = (typeof TSQ_CHANGE_ACTIONS)[number];
32
+ export type { TsqChangeAction } from "./change-command-builder.js";
33
33
 
34
34
  export const TsqChangeParamsSchema = Type.Object(
35
35
  {
36
36
  action: StringEnum(TSQ_CHANGE_ACTIONS, {
37
- description: "Durable Tasque mutation to run",
37
+ description: "Durable task mutation to run",
38
38
  }),
39
39
  title: Type.Optional(
40
40
  Type.String({ description: "Task title (required for create)" }),
41
41
  ),
42
42
  id: Type.Optional(
43
43
  Type.String({
44
- description: "Tasque task id for lifecycle/note/claim actions",
44
+ description: "Durable task id for lifecycle/note/claim actions",
45
45
  }),
46
46
  ),
47
47
  kind: Type.Optional(
48
- Type.String({ description: "Tasque task kind (required for create)" }),
48
+ Type.String({ description: "Durable task kind (required for create)" }),
49
49
  ),
50
50
  priority: Type.Optional(
51
- Type.Integer({ description: "Tasque priority (required for create)" }),
51
+ Type.Integer({
52
+ description: "Durable task priority (required for create)",
53
+ }),
52
54
  ),
53
55
  description: Type.Optional(
54
56
  Type.String({ description: "Task description (create only)" }),
55
57
  ),
56
58
  parent: Type.Optional(
57
- Type.String({ description: "Parent Tasque task id (create only)" }),
59
+ Type.String({ description: "Parent durable task id (create only)" }),
58
60
  ),
59
61
  planned: Type.Optional(
60
62
  Type.Boolean({ description: "Mark created task planned" }),
@@ -109,367 +111,120 @@ export type TsqChangeDetails = ReturnType<
109
111
  typeof okToolDetails<TsqChangeSuccessData>
110
112
  >;
111
113
 
112
- type ValidationResult =
113
- | {
114
- readonly ok: true;
115
- readonly action: TsqChangeAction;
116
- readonly argv: string[];
117
- }
118
- | { readonly ok: false; readonly message: string };
119
-
120
114
  export function registerTsqChangeTool(pi: ExtensionAPI): void {
121
115
  pi.registerTool(
122
116
  defineTool({
123
117
  name: TSQ_CHANGE_TOOL_NAME,
124
- label: "Tasque Change",
118
+ label: "Task Change",
125
119
  description:
126
- "Run minimal durable Tasque mutations. Supports create, note, done, reopen, defer, start, assignment-only claim, and block/order edge changes. No raw tsq passthrough.",
127
- promptSnippet:
128
- "tsq_change: mutate durable Tasque tasks only through approved lifecycle/note/edge actions.",
129
- promptGuidelines: [
130
- "Use tsq_change only for explicit durable Tasque mutations; do not use it as a raw tsq passthrough.",
131
- "Use todo for current-session checklist steps; use tsq_change when durable Tasque state must change.",
132
- "Use block/unblock for hard blockers and order/unorder for sequencing where one task should happen after another.",
133
- "Use tsq_query with action deps or show to inspect durable graph state before or after edge changes.",
134
- ],
120
+ "Mutate durable tasks: lifecycle, notes, ownership, dependencies, and sequencing.",
121
+ promptSnippet: CHANGE_TASKS_PROMPT_SNIPPET,
122
+ promptGuidelines: CHANGE_TASKS_PROMPT_GUIDELINES,
135
123
  parameters: TsqChangeParamsSchema,
136
124
  executionMode: "sequential",
137
125
 
138
126
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
139
- const command = buildMutationCommand(
140
- params as Readonly<Record<string, unknown>>,
141
- );
142
- if (!command.ok) {
143
- return validationErrorResult(command.message);
144
- }
145
-
146
- try {
147
- const result = await runMutation(pi, ctx, command.argv, signal);
148
- return textToolResult(
149
- formatSuccess(command.action, params, result),
150
- okToolDetails({
151
- action: command.action,
152
- argv: command.argv,
153
- result,
154
- }),
155
- );
156
- } catch (error) {
157
- const message = getErrorMessage(error);
158
- return textToolResult(
159
- `Error: ${message}`,
160
- errorToolDetails({
161
- code: getErrorCode(error),
162
- message,
163
- details: {
164
- action: command.action,
165
- argv: command.argv,
166
- error: serializeError(error),
167
- },
168
- }),
169
- );
170
- }
127
+ return executeTsqChange(pi, params as TsqChangeParams, signal, ctx);
171
128
  },
172
129
  }),
173
130
  );
174
131
  }
175
132
 
176
- function runMutation(
133
+ export async function executeTsqChange(
177
134
  pi: ExtensionAPI,
178
- ctx: ExtensionContext,
179
- argv: readonly string[],
135
+ params: TsqChangeParams,
180
136
  signal: AbortSignal | undefined,
181
- ): Promise<unknown> {
182
- const options = signal === undefined ? {} : { signal };
183
- return runQueuedMutation(ctx.cwd, () =>
184
- runTsqJson(pi, { cwd: ctx.cwd }, argv, options),
137
+ ctx: Pick<ExtensionContext, "cwd">,
138
+ ): Promise<AgentToolResult<TsqChangeDetails>> {
139
+ const command = buildMutationCommand(
140
+ params as Readonly<Record<string, unknown>>,
185
141
  );
186
- }
187
-
188
- function buildMutationCommand(
189
- params: Readonly<Record<string, unknown>>,
190
- ): ValidationResult {
191
- const action = params.action;
192
- if (!isTsqChangeAction(action)) {
193
- return {
194
- ok: false,
195
- message: "action must be a supported tsq_change action",
196
- };
197
- }
198
-
199
- switch (action) {
200
- case "create":
201
- return buildCreateArgv(params, action);
202
- case "note":
203
- return buildNoteArgv(params, action);
204
- case "done":
205
- return buildOptionalNoteArgv(params, action, "done");
206
- case "reopen":
207
- return buildIdOnlyArgv(params, action, "reopen");
208
- case "defer":
209
- return buildOptionalNoteArgv(params, action, "defer");
210
- case "start":
211
- return buildIdOnlyArgv(params, action, "start");
212
- case "claim_assign_only":
213
- return buildClaimAssignOnlyArgv(params, action);
214
- case "block":
215
- return buildBlockArgv(params, action, "block");
216
- case "unblock":
217
- return buildBlockArgv(params, action, "unblock");
218
- case "order":
219
- return buildOrderArgv(params, action, "order");
220
- case "unorder":
221
- return buildOrderArgv(params, action, "unorder");
222
- }
223
- }
224
-
225
- function buildCreateArgv(
226
- params: Readonly<Record<string, unknown>>,
227
- action: TsqChangeAction,
228
- ): ValidationResult {
229
- const title = requireNonEmptyString(params, "title");
230
- if (!title.ok) {
231
- return title;
232
- }
233
- const kind = requireNonEmptyString(params, "kind");
234
- if (!kind.ok) {
235
- return kind;
236
- }
237
- const priority = requireInteger(params, "priority");
238
- if (!priority.ok) {
239
- return priority;
240
- }
241
- const planned = getOptionalBoolean(params, "planned");
242
- if (!planned.ok) {
243
- return planned;
244
- }
245
- const needsPlan = getOptionalBoolean(params, "needsPlan");
246
- if (!needsPlan.ok) {
247
- return needsPlan;
248
- }
249
- if (planned.value === true && needsPlan.value === true) {
250
- return {
251
- ok: false,
252
- message: "planned and needsPlan cannot both be true",
253
- };
254
- }
255
-
256
- const argv = ["create", `--kind=${kind.value}`, "-p", String(priority.value)];
257
- const description = appendOptionalStringFlag(
258
- argv,
259
- params,
260
- "description",
261
- "--description",
262
- );
263
- if (description !== undefined) {
264
- return description;
265
- }
266
- const parent = appendOptionalStringFlag(argv, params, "parent", "--parent");
267
- if (parent !== undefined) {
268
- return parent;
269
- }
270
- if (planned.value === true) {
271
- argv.push("--planned");
272
- } else if (needsPlan.value === true) {
273
- argv.push("--needs-plan");
274
- }
275
- argv.push("--", title.value);
276
-
277
- return { ok: true, action, argv };
278
- }
279
-
280
- function buildNoteArgv(
281
- params: Readonly<Record<string, unknown>>,
282
- action: TsqChangeAction,
283
- ): ValidationResult {
284
- const id = requireNonEmptyString(params, "id");
285
- if (!id.ok) {
286
- return id;
287
- }
288
- const note = requireNonEmptyString(params, "note");
289
- if (!note.ok) {
290
- return note;
291
- }
292
- return { ok: true, action, argv: ["note", id.value, "--", note.value] };
293
- }
294
-
295
- function buildOptionalNoteArgv(
296
- params: Readonly<Record<string, unknown>>,
297
- action: TsqChangeAction,
298
- command: "done" | "defer",
299
- ): ValidationResult {
300
- const id = requireNonEmptyString(params, "id");
301
- if (!id.ok) {
302
- return id;
303
- }
304
- const argv = [command, id.value];
305
- const note = getOptionalNonEmptyString(params, "note");
306
- if (!note.ok) {
307
- return note;
308
- }
309
- if (note.value !== undefined) {
310
- argv.push(`--note=${note.value}`);
311
- }
312
- return { ok: true, action, argv };
313
- }
314
-
315
- function buildIdOnlyArgv(
316
- params: Readonly<Record<string, unknown>>,
317
- action: TsqChangeAction,
318
- command: "reopen" | "start",
319
- ): ValidationResult {
320
- const id = requireNonEmptyString(params, "id");
321
- if (!id.ok) {
322
- return id;
323
- }
324
- return { ok: true, action, argv: [command, id.value] };
325
- }
326
-
327
- function buildClaimAssignOnlyArgv(
328
- params: Readonly<Record<string, unknown>>,
329
- action: TsqChangeAction,
330
- ): ValidationResult {
331
- const id = requireNonEmptyString(params, "id");
332
- if (!id.ok) {
333
- return id;
334
- }
335
- const assignee = requireNonEmptyString(params, "assignee");
336
- if (!assignee.ok) {
337
- return assignee;
338
- }
339
- return {
340
- ok: true,
341
- action,
342
- argv: ["claim", id.value, `--assignee=${assignee.value}`],
343
- };
344
- }
345
-
346
- function buildBlockArgv(
347
- params: Readonly<Record<string, unknown>>,
348
- action: TsqChangeAction,
349
- command: "block" | "unblock",
350
- ): ValidationResult {
351
- const child = requireNonEmptyString(params, "child");
352
- if (!child.ok) {
353
- return child;
354
- }
355
- const blocker = requireNonEmptyString(params, "blocker");
356
- if (!blocker.ok) {
357
- return blocker;
358
- }
359
- if (child.value === blocker.value) {
360
- return { ok: false, message: "child and blocker cannot be the same task" };
361
- }
362
- return {
363
- ok: true,
364
- action,
365
- argv: [command, child.value, "by", blocker.value],
366
- };
367
- }
368
-
369
- function buildOrderArgv(
370
- params: Readonly<Record<string, unknown>>,
371
- action: TsqChangeAction,
372
- command: "order" | "unorder",
373
- ): ValidationResult {
374
- const later = requireNonEmptyString(params, "later");
375
- if (!later.ok) {
376
- return later;
377
- }
378
- const earlier = requireNonEmptyString(params, "earlier");
379
- if (!earlier.ok) {
380
- return earlier;
381
- }
382
- if (later.value === earlier.value) {
383
- return { ok: false, message: "later and earlier cannot be the same task" };
384
- }
385
- return {
386
- ok: true,
387
- action,
388
- argv: [command, later.value, "after", earlier.value],
389
- };
390
- }
391
-
392
- function appendOptionalStringFlag(
393
- argv: string[],
394
- params: Readonly<Record<string, unknown>>,
395
- field: "description" | "parent",
396
- flag: string,
397
- ): ValidationResult | undefined {
398
- const value = getOptionalNonEmptyString(params, field);
399
- if (!value.ok) {
400
- return value;
401
- }
402
- if (value.value !== undefined) {
403
- argv.push(`${flag}=${value.value}`);
142
+ if (!command.ok) {
143
+ return validationErrorResult(command.message);
144
+ }
145
+
146
+ try {
147
+ const result = await runMutation(pi, ctx, command.argv, signal);
148
+ return textToolResult(
149
+ formatSuccess(command.action, params, result),
150
+ okToolDetails({
151
+ action: command.action,
152
+ argv: command.argv,
153
+ result,
154
+ }),
155
+ );
156
+ } catch (error) {
157
+ const message = getErrorMessage(error);
158
+ return textToolResult(
159
+ `Error: ${message}`,
160
+ errorToolDetails({
161
+ code: getErrorCode(error),
162
+ message,
163
+ details: {
164
+ action: command.action,
165
+ argv: command.argv,
166
+ error: serializeError(error),
167
+ },
168
+ }),
169
+ );
404
170
  }
405
- return undefined;
406
171
  }
407
172
 
408
- function requireNonEmptyString(
409
- params: Readonly<Record<string, unknown>>,
410
- field: string,
411
- ):
412
- | { readonly ok: true; readonly value: string }
413
- | { readonly ok: false; readonly message: string } {
414
- const value = params[field];
415
- if (typeof value !== "string" || value.trim().length === 0) {
416
- return { ok: false, message: `${field} is required` };
417
- }
418
- return { ok: true, value };
419
- }
173
+ // --- mark_planned helper (tsq-5.2) ---
420
174
 
421
- function getOptionalNonEmptyString(
422
- params: Readonly<Record<string, unknown>>,
423
- field: string,
424
- ):
425
- | { readonly ok: true; readonly value: string | undefined }
426
- | { readonly ok: false; readonly message: string } {
427
- const value = params[field];
428
- if (value === undefined) {
429
- return { ok: true, value: undefined };
430
- }
431
- if (typeof value !== "string") {
432
- return { ok: false, message: `${field} must be a string` };
433
- }
434
- if (value.trim().length === 0) {
435
- return { ok: true, value: undefined };
436
- }
437
- return { ok: true, value };
175
+ export interface TsqMarkPlannedSuccessData {
176
+ readonly argv: readonly string[];
177
+ readonly result: unknown;
438
178
  }
439
179
 
440
- function requireInteger(
441
- params: Readonly<Record<string, unknown>>,
442
- field: string,
443
- ):
444
- | { readonly ok: true; readonly value: number }
445
- | { readonly ok: false; readonly message: string } {
446
- const value = params[field];
447
- if (typeof value !== "number" || !Number.isInteger(value)) {
448
- return { ok: false, message: `${field} is required` };
449
- }
450
- return { ok: true, value };
451
- }
180
+ export type TsqMarkPlannedDetails = ReturnType<
181
+ typeof okToolDetails<TsqMarkPlannedSuccessData>
182
+ >;
452
183
 
453
- function getOptionalBoolean(
454
- params: Readonly<Record<string, unknown>>,
455
- field: string,
456
- ):
457
- | { readonly ok: true; readonly value: boolean | undefined }
458
- | { readonly ok: false; readonly message: string } {
459
- const value = params[field];
460
- if (value === undefined) {
461
- return { ok: true, value: undefined };
462
- }
463
- if (typeof value !== "boolean") {
464
- return { ok: false, message: `${field} must be a boolean` };
184
+ export async function executeTsqMarkPlanned(
185
+ pi: ExtensionAPI,
186
+ taskId: string,
187
+ signal: AbortSignal | undefined,
188
+ ctx: Pick<ExtensionContext, "cwd">,
189
+ ): Promise<AgentToolResult<TsqMarkPlannedDetails>> {
190
+ const trimmed = taskId.trim();
191
+ if (trimmed.length === 0) {
192
+ return validationErrorResult("task id is required");
193
+ }
194
+
195
+ const argv = ["planned", trimmed];
196
+
197
+ try {
198
+ const result = await runMutation(pi, ctx, argv, signal);
199
+ return textToolResult(
200
+ `Marked ${trimmed} as planned`,
201
+ okToolDetails({ argv, result }),
202
+ );
203
+ } catch (error) {
204
+ const message = getErrorMessage(error);
205
+ return textToolResult(
206
+ `Error: ${message}`,
207
+ errorToolDetails({
208
+ code: getErrorCode(error),
209
+ message,
210
+ details: {
211
+ argv,
212
+ error: serializeError(error),
213
+ },
214
+ }),
215
+ );
465
216
  }
466
- return { ok: true, value };
467
217
  }
468
218
 
469
- function isTsqChangeAction(value: unknown): value is TsqChangeAction {
470
- return (
471
- typeof value === "string" &&
472
- (TSQ_CHANGE_ACTIONS as readonly string[]).includes(value)
219
+ function runMutation(
220
+ pi: ExtensionAPI,
221
+ ctx: Pick<ExtensionContext, "cwd">,
222
+ argv: readonly string[],
223
+ signal: AbortSignal | undefined,
224
+ ): Promise<unknown> {
225
+ const options = signal === undefined ? {} : { signal };
226
+ return runQueuedMutation(ctx.cwd, () =>
227
+ runTsqJson(pi, { cwd: ctx.cwd }, argv, options),
473
228
  );
474
229
  }
475
230
 
@@ -546,13 +301,6 @@ function extractTaskLike(result: unknown): {
546
301
  };
547
302
  }
548
303
 
549
- function asRecord(value: unknown): Record<string, unknown> | undefined {
550
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
551
- return undefined;
552
- }
553
- return value as Record<string, unknown>;
554
- }
555
-
556
304
  function getErrorMessage(error: unknown): string {
557
305
  if (error instanceof Error) {
558
306
  return error.message;
@@ -579,22 +327,3 @@ function serializeError(error: unknown): Record<string, unknown> {
579
327
  }
580
328
  return { value: String(error) };
581
329
  }
582
-
583
- function copyKnownErrorFields(error: Error): Record<string, unknown> {
584
- const record = error as unknown as Record<string, unknown>;
585
- const output: Record<string, unknown> = {};
586
- for (const key of [
587
- "code",
588
- "command",
589
- "details",
590
- "stderr",
591
- "stdout",
592
- "killed",
593
- "args",
594
- ] as const) {
595
- if (record[key] !== undefined) {
596
- output[key] = record[key];
597
- }
598
- }
599
- return output;
600
- }
@@ -5,12 +5,21 @@ import {
5
5
  type ExtensionContext,
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import { type Static, Type } from "typebox";
8
+ import {
9
+ CLAIM_TASK_PROMPT_GUIDELINES,
10
+ CLAIM_TASK_PROMPT_SNIPPET,
11
+ } from "../guidelines/internal-tools.js";
8
12
  import {
9
13
  applyTaskMutation,
10
14
  type Op,
11
15
  } from "../session-todos/state/state-reducer.js";
12
16
  import { commitState, getState } from "../session-todos/state/store.js";
13
17
  import type { Task } from "../session-todos/tool/types.js";
18
+ import {
19
+ asRecord,
20
+ copyKnownErrorFields,
21
+ isRecord,
22
+ } from "../shared/error-utils.js";
14
23
  import {
15
24
  errorToolDetails,
16
25
  okToolDetails,
@@ -24,7 +33,7 @@ export const TSQ_CLAIM_TOOL_NAME = "tsq_claim";
24
33
 
25
34
  export const TsqClaimParamsSchema = Type.Object(
26
35
  {
27
- id: Type.String({ description: "Named Tasque task id to claim." }),
36
+ id: Type.String({ description: "Named durable task id to claim." }),
28
37
  assignee: Type.Optional(
29
38
  Type.String({
30
39
  description: "Agent or role claiming the task. Defaults to pi.",
@@ -38,8 +47,7 @@ export const TsqClaimParamsSchema = Type.Object(
38
47
  ),
39
48
  requireSpec: Type.Optional(
40
49
  Type.Boolean({
41
- description:
42
- "Require an attached Tasque spec before the claim succeeds.",
50
+ description: "Require an attached task spec before the claim succeeds.",
43
51
  }),
44
52
  ),
45
53
  createTodo: Type.Optional(
@@ -95,16 +103,10 @@ export function registerTsqClaimTool(pi: ExtensionAPI): void {
95
103
  pi.registerTool(
96
104
  defineTool({
97
105
  name: TSQ_CLAIM_TOOL_NAME,
98
- label: "Tasque Claim",
99
- description:
100
- "Claim a named durable Tasque task. Requires an explicit id; does not auto-select next ready work.",
101
- promptSnippet:
102
- "Use tsq_claim for named durable Tasque ownership. Provide id; assignee defaults to pi; start defaults to true.",
103
- promptGuidelines: [
104
- "Pass your own role/name as assignee when available, e.g. developer, worker, oracle.",
105
- "Use createTodo only when you want one session todo linked to the claimed durable task.",
106
- "Completing a linked session todo does not mark the Tasque task done; durable completion must be explicit.",
107
- ],
106
+ label: "Task Claim",
107
+ description: "Claim named durable task ownership.",
108
+ promptSnippet: CLAIM_TASK_PROMPT_SNIPPET,
109
+ promptGuidelines: CLAIM_TASK_PROMPT_GUIDELINES,
108
110
  parameters: TsqClaimParamsSchema,
109
111
  executionMode: "sequential",
110
112
 
@@ -394,33 +396,3 @@ function serializeError(error: unknown): Record<string, unknown> {
394
396
  }
395
397
  return { value: String(error) };
396
398
  }
397
-
398
- function copyKnownErrorFields(error: Error): Record<string, unknown> {
399
- const record = error as unknown as Record<string, unknown>;
400
- const output: Record<string, unknown> = {};
401
- for (const key of [
402
- "code",
403
- "command",
404
- "details",
405
- "stderr",
406
- "stdout",
407
- "killed",
408
- "args",
409
- ] as const) {
410
- if (record[key] !== undefined) {
411
- output[key] = record[key];
412
- }
413
- }
414
- return output;
415
- }
416
-
417
- function asRecord(value: unknown): Record<string, unknown> | undefined {
418
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
419
- return undefined;
420
- }
421
- return value as Record<string, unknown>;
422
- }
423
-
424
- function isRecord(value: unknown): value is Record<string, unknown> {
425
- return asRecord(value) !== undefined;
426
- }