@gajae-code/coding-agent 0.12.6 → 0.12.7
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 +3 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +1 -3
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -4
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-storage.d.ts +2 -5
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +4 -5
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +8 -8
- package/src/async/job-manager.ts +430 -46
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/bus/chat-daemon-control.ts +3 -5
- package/src/sdk/bus/index.ts +8 -324
- package/src/sdk/bus/telegram-daemon-contract.ts +2 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/session/agent-session.ts +15 -178
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-storage.ts +5 -104
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +21 -12
- package/src/session/streaming-output.ts +40 -198
- package/src/task/index.ts +230 -64
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
package/src/tools/search.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
25
25
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
26
26
|
import { formatMatchLine } from "./match-line-format";
|
|
27
|
-
import {
|
|
27
|
+
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
28
28
|
import { resolveReadPath, resolveToolSearchScope } from "./path-utils";
|
|
29
29
|
import {
|
|
30
30
|
createCachedComponent,
|
|
@@ -687,9 +687,8 @@ export const searchToolRenderer = {
|
|
|
687
687
|
const matchCount = details?.matchCount ?? 0;
|
|
688
688
|
const fileCount = details?.fileCount ?? 0;
|
|
689
689
|
const truncation = details?.meta?.truncation;
|
|
690
|
-
const directTruncation = details?.truncation;
|
|
691
690
|
const limits = details?.meta?.limits;
|
|
692
|
-
const truncated = Boolean(details?.truncated || truncation ||
|
|
691
|
+
const truncated = Boolean(details?.truncated || truncation || limits?.columnTruncated);
|
|
693
692
|
|
|
694
693
|
const missingPathsList = details?.missingPaths ?? [];
|
|
695
694
|
const missingNote =
|
|
@@ -725,13 +724,9 @@ export const searchToolRenderer = {
|
|
|
725
724
|
const truncationReasons: string[] = [];
|
|
726
725
|
if (renderedFileLimit) truncationReasons.push(`first ${renderedFileLimit} files (skip to paginate)`);
|
|
727
726
|
if (renderedPerFileLimit) truncationReasons.push(`first ${renderedPerFileLimit} matches per file`);
|
|
728
|
-
|
|
729
|
-
if (displayedTruncation) {
|
|
730
|
-
truncationReasons.push(displayedTruncation.truncatedBy === "lines" ? "line limit" : "size limit");
|
|
731
|
-
}
|
|
727
|
+
if (truncation) truncationReasons.push(truncation.truncatedBy === "lines" ? "line limit" : "size limit");
|
|
732
728
|
if (limits?.columnTruncated) truncationReasons.push(`line length ${limits.columnTruncated.maxColumn}`);
|
|
733
|
-
|
|
734
|
-
if (artifactEvidence) truncationReasons.push(artifactEvidence);
|
|
729
|
+
if (truncation?.artifactId) truncationReasons.push(formatFullOutputReference(truncation.artifactId));
|
|
735
730
|
|
|
736
731
|
const extraLines: string[] = [];
|
|
737
732
|
if (truncationReasons.length > 0) {
|
package/src/tools/subagent.ts
CHANGED
|
@@ -32,6 +32,15 @@ const subagentSchema = z.object({
|
|
|
32
32
|
id: z.string().optional().describe("single subagent id or backing job id for resume/steer"),
|
|
33
33
|
message: z.string().optional().describe("message to deliver when resuming or steering a subagent"),
|
|
34
34
|
pause: z.boolean().optional().describe("pause after steering a currently running subagent"),
|
|
35
|
+
condition: z
|
|
36
|
+
.enum(["all_terminal", "any_terminal"])
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("terminal wait condition; defaults to all_terminal"),
|
|
39
|
+
heartbeat_ms: z
|
|
40
|
+
.number()
|
|
41
|
+
.refine(value => value === 0 || (Number.isInteger(value) && value >= 100 && value <= 5000))
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("heartbeat interval; 0 disables"),
|
|
35
44
|
timeout_ms: z.number().min(0).max(MAX_AWAIT_TIMEOUT_MS).optional().describe("await timeout in milliseconds"),
|
|
36
45
|
limit: z.number().min(1).max(MAX_LIST_LIMIT).optional().describe("maximum subagents to return"),
|
|
37
46
|
verbosity: z
|
|
@@ -97,6 +106,11 @@ export interface SubagentToolDetails {
|
|
|
97
106
|
subagents: SubagentSnapshot[];
|
|
98
107
|
/** Await outcome for a live await receipt; omitted when no wait was started. */
|
|
99
108
|
awaitOutcome?: SubagentAwaitOutcome;
|
|
109
|
+
waitOutcome?: "completed" | "timed_out_wait" | "interrupted";
|
|
110
|
+
condition?: "all_terminal" | "any_terminal";
|
|
111
|
+
heartbeatMs?: number;
|
|
112
|
+
terminalIds?: string[];
|
|
113
|
+
acknowledgedTerminalIds?: string[];
|
|
100
114
|
/** True only when the parent await was interrupted; the child was not cancelled. */
|
|
101
115
|
interrupted?: true;
|
|
102
116
|
}
|
|
@@ -344,35 +358,53 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
344
358
|
signal: AbortSignal | undefined,
|
|
345
359
|
onUpdate: AgentToolUpdateCallback<SubagentToolDetails> | undefined,
|
|
346
360
|
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
347
|
-
const
|
|
348
|
-
|
|
361
|
+
const ids = params.ids
|
|
362
|
+
?.map(id => id.trim())
|
|
363
|
+
.filter(Boolean)
|
|
364
|
+
.filter((id, index, all) => all.indexOf(id) === index);
|
|
365
|
+
const records = ids?.length
|
|
366
|
+
? this.#visibleRecordsByIds(manager, ids, ownerFilter)
|
|
349
367
|
: this.#runningRecords(manager, ownerFilter);
|
|
350
|
-
const notFoundIds =
|
|
351
|
-
if (records.length === 0)
|
|
352
|
-
|
|
353
|
-
|
|
368
|
+
const notFoundIds = (ids ?? []).filter(id => !this.#findVisibleRecord(manager, id, ownerFilter));
|
|
369
|
+
if (records.length === 0)
|
|
370
|
+
return this.#buildSnapshotResult(
|
|
371
|
+
notFoundIds.map(id =>
|
|
372
|
+
this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."),
|
|
373
|
+
),
|
|
374
|
+
"Subagent await",
|
|
354
375
|
);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
376
|
+
const targets = records
|
|
377
|
+
.map(record => manager.resolveSubagentWaitTarget(record.subagentId, ownerFilter))
|
|
378
|
+
.filter((target): target is NonNullable<typeof target> => target !== undefined);
|
|
379
|
+
if (targets.length === 0) return this.#buildSnapshotResult([], "Subagent await");
|
|
380
|
+
const condition = params.condition ?? "all_terminal";
|
|
381
|
+
const handle = manager.subscribeTerminalWait(targets, condition);
|
|
382
|
+
const timeoutMs = Math.min(
|
|
383
|
+
MAX_AWAIT_TIMEOUT_MS,
|
|
384
|
+
Math.max(0, Math.floor(params.timeout_ms ?? DEFAULT_AWAIT_TIMEOUT_MS)),
|
|
385
|
+
);
|
|
386
|
+
const targetsAlreadyTerminal = targets.every(
|
|
387
|
+
target =>
|
|
388
|
+
target.initialStatus === "completed" ||
|
|
389
|
+
target.initialStatus === "failed" ||
|
|
390
|
+
target.initialStatus === "cancelled",
|
|
391
|
+
);
|
|
392
|
+
if (targetsAlreadyTerminal) {
|
|
393
|
+
const waitResult = await handle.result;
|
|
394
|
+
handle.acknowledge(waitResult.terminalJobIds);
|
|
395
|
+
handle.close();
|
|
396
|
+
const terminalJobIds = waitResult.terminalJobIds;
|
|
363
397
|
return await this.#buildRecordResult(manager, records, {
|
|
364
398
|
title: "Subagent await",
|
|
365
|
-
notFoundIds,
|
|
366
399
|
verbosity: params.verbosity ?? "receipt",
|
|
400
|
+
waitOutcome: "completed",
|
|
401
|
+
condition,
|
|
402
|
+
terminalIds: terminalJobIds,
|
|
367
403
|
});
|
|
368
404
|
}
|
|
369
|
-
|
|
370
|
-
const timeoutMs = Math.min(
|
|
371
|
-
MAX_AWAIT_TIMEOUT_MS,
|
|
372
|
-
Math.max(0, Math.floor(params.timeout_ms ?? DEFAULT_AWAIT_TIMEOUT_MS)),
|
|
373
|
-
);
|
|
374
|
-
const watchedJobIds = runningJobs.map(job => job.id);
|
|
405
|
+
const watchedJobIds = targets.map(target => target.jobId).filter((jobId): jobId is string => jobId !== null);
|
|
375
406
|
manager.watchJobs(watchedJobIds);
|
|
407
|
+
const heartbeatMs = params.heartbeat_ms === undefined ? 500 : params.heartbeat_ms;
|
|
376
408
|
let lastEmittedSignature: string | undefined;
|
|
377
409
|
const emitIfChanged = (force: boolean): void => {
|
|
378
410
|
if (!onUpdate) return;
|
|
@@ -382,57 +414,50 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
382
414
|
lastEmittedSignature = signature;
|
|
383
415
|
onUpdate(result);
|
|
384
416
|
};
|
|
385
|
-
const progressTimer =
|
|
386
|
-
|
|
387
|
-
// gated on a value-based rendered-state signature so unchanged progress no
|
|
388
|
-
// longer rebuilds the renderer component or mutates transcript lines above
|
|
389
|
-
// the viewport (the source of the await-panel repaint storms).
|
|
417
|
+
const progressTimer =
|
|
418
|
+
onUpdate && heartbeatMs > 0 ? setInterval(() => emitIfChanged(false), heartbeatMs) : undefined;
|
|
390
419
|
emitIfChanged(true);
|
|
391
|
-
|
|
392
|
-
let awaitOutcome: SubagentAwaitOutcome;
|
|
393
|
-
const { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers<SubagentAwaitOutcome>();
|
|
394
|
-
const timeoutTimer = setTimeout(() => resolveTimeout("timed_out"), timeoutMs);
|
|
395
|
-
const completionPromise = Promise.all(runningJobs.map(job => job.promise)).then(() => "completed" as const);
|
|
420
|
+
let waitOutcome: "completed" | "timed_out_wait" | "interrupted";
|
|
396
421
|
let onAbort: (() => void) | undefined;
|
|
397
422
|
try {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
423
|
+
const abortPromise = new Promise<"interrupted">(resolve => {
|
|
424
|
+
onAbort = () => resolve("interrupted");
|
|
425
|
+
if (signal?.aborted) onAbort();
|
|
426
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
427
|
+
});
|
|
428
|
+
waitOutcome =
|
|
429
|
+
targetsAlreadyTerminal && !signal?.aborted
|
|
430
|
+
? "completed"
|
|
431
|
+
: await Promise.race([
|
|
432
|
+
handle.result.then(() => "completed" as const),
|
|
433
|
+
Bun.sleep(timeoutMs).then(() => "timed_out_wait" as const),
|
|
434
|
+
abortPromise,
|
|
435
|
+
]);
|
|
407
436
|
} finally {
|
|
408
|
-
clearTimeout(timeoutTimer);
|
|
409
437
|
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
410
438
|
if (progressTimer) clearInterval(progressTimer);
|
|
411
439
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const status = manager.getJob(jobId)?.status;
|
|
415
|
-
return status === "completed" || status === "failed" || status === "cancelled";
|
|
416
|
-
});
|
|
417
|
-
manager.acknowledgeDeliveries(terminalWatchedJobIds);
|
|
418
|
-
}
|
|
440
|
+
const waitResult = waitOutcome === "completed" ? await handle.result : undefined;
|
|
441
|
+
if (waitOutcome === "completed") handle.acknowledge(waitResult?.terminalJobIds);
|
|
419
442
|
manager.unwatchJobs(watchedJobIds);
|
|
420
|
-
|
|
443
|
+
handle.close();
|
|
444
|
+
const awaitOutcome: SubagentAwaitOutcome =
|
|
445
|
+
waitOutcome === "completed" ? "completed" : waitOutcome === "timed_out_wait" ? "timed_out" : "interrupted";
|
|
421
446
|
const finalRecords = this.#visibleRecordsByIds(
|
|
422
447
|
manager,
|
|
423
448
|
records.map(record => record.subagentId),
|
|
424
449
|
ownerFilter,
|
|
425
450
|
);
|
|
426
|
-
const finalNotFoundIds = [...new Set([...notFoundIds, ...records.map(record => record.subagentId)])].filter(
|
|
427
|
-
id => !finalRecords.some(record => record.subagentId === id),
|
|
428
|
-
);
|
|
429
451
|
return await this.#buildRecordResult(manager, finalRecords, {
|
|
430
|
-
title:
|
|
431
|
-
notFoundIds
|
|
432
|
-
timedOut:
|
|
452
|
+
title: waitOutcome === "interrupted" ? "Subagent await interrupted" : "Subagent await",
|
|
453
|
+
notFoundIds,
|
|
454
|
+
timedOut: waitOutcome === "timed_out_wait",
|
|
433
455
|
verbosity: params.verbosity ?? "receipt",
|
|
434
456
|
attachLiveProgress: true,
|
|
435
457
|
awaitOutcome,
|
|
458
|
+
waitOutcome,
|
|
459
|
+
condition,
|
|
460
|
+
terminalIds: waitResult?.terminalJobIds,
|
|
436
461
|
});
|
|
437
462
|
}
|
|
438
463
|
|
|
@@ -490,7 +515,8 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
490
515
|
if (direct && isSubagentJob(direct) && (!ownerId || direct.ownerId === ownerId)) return direct;
|
|
491
516
|
return manager
|
|
492
517
|
.getAllJobs(ownerId ? { ownerId } : undefined)
|
|
493
|
-
.
|
|
518
|
+
.filter(job => isSubagentJob(job) && job.metadata?.subagent?.id === id)
|
|
519
|
+
.sort((a, b) => b.startTime - a.startTime)[0];
|
|
494
520
|
}
|
|
495
521
|
|
|
496
522
|
#visibleRecordsByIds(
|
|
@@ -551,6 +577,9 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
551
577
|
verbosity?: SubagentParams["verbosity"];
|
|
552
578
|
attachLiveProgress?: boolean;
|
|
553
579
|
awaitOutcome?: SubagentAwaitOutcome;
|
|
580
|
+
waitOutcome?: "completed" | "timed_out_wait" | "interrupted";
|
|
581
|
+
condition?: "all_terminal" | "any_terminal";
|
|
582
|
+
terminalIds?: string[];
|
|
554
583
|
},
|
|
555
584
|
): Promise<AgentToolResult<SubagentToolDetails>> {
|
|
556
585
|
const verifiedOutputIds = await this.#verifiedOutputIds(records);
|
|
@@ -565,33 +594,28 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
565
594
|
for (const id of options.notFoundIds ?? []) {
|
|
566
595
|
snapshots.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
|
|
567
596
|
}
|
|
568
|
-
if (options.awaitOutcome !== "interrupted") {
|
|
569
|
-
manager.acknowledgeDeliveries(
|
|
570
|
-
snapshots
|
|
571
|
-
.filter(
|
|
572
|
-
s =>
|
|
573
|
-
s.status !== "running" &&
|
|
574
|
-
s.status !== "paused" &&
|
|
575
|
-
s.status !== "queued" &&
|
|
576
|
-
s.status !== "not_found",
|
|
577
|
-
)
|
|
578
|
-
.map(s => s.jobId),
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
597
|
if (options.awaitOutcome === "interrupted") {
|
|
582
598
|
for (const snapshot of snapshots) {
|
|
583
|
-
if (snapshot.status === "running" || snapshot.status === "paused" || snapshot.status === "queued")
|
|
599
|
+
if (snapshot.status === "running" || snapshot.status === "paused" || snapshot.status === "queued")
|
|
584
600
|
snapshot.guidance = AWAIT_INTERRUPTED_GUIDANCE;
|
|
585
|
-
}
|
|
586
601
|
}
|
|
587
602
|
}
|
|
588
|
-
|
|
603
|
+
const details: SubagentToolDetails = {
|
|
604
|
+
subagents: snapshots,
|
|
605
|
+
...(options.awaitOutcome !== undefined ? { awaitOutcome: options.awaitOutcome } : {}),
|
|
606
|
+
...(options.waitOutcome !== undefined ? { waitOutcome: options.waitOutcome } : {}),
|
|
607
|
+
...(options.condition !== undefined ? { condition: options.condition } : {}),
|
|
608
|
+
...(options.terminalIds !== undefined ? { terminalIds: options.terminalIds } : {}),
|
|
609
|
+
};
|
|
610
|
+
if (options.awaitOutcome === "interrupted") details.interrupted = true;
|
|
611
|
+
return this.#buildSnapshotResult(snapshots, options.title, options.awaitOutcome, details);
|
|
589
612
|
}
|
|
590
613
|
|
|
591
614
|
#buildSnapshotResult(
|
|
592
615
|
snapshots: SubagentSnapshot[],
|
|
593
616
|
title: string,
|
|
594
617
|
awaitOutcome?: SubagentAwaitOutcome,
|
|
618
|
+
extraDetails?: SubagentToolDetails,
|
|
595
619
|
): AgentToolResult<SubagentToolDetails> {
|
|
596
620
|
const lines = [`## ${title} (${snapshots.length})`, ""];
|
|
597
621
|
for (const snapshot of snapshots) {
|
|
@@ -623,7 +647,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
623
647
|
}
|
|
624
648
|
return {
|
|
625
649
|
content: [{ type: "text", text: lines.join("\n").trimEnd() }],
|
|
626
|
-
details: {
|
|
650
|
+
details: extraDetails ?? {
|
|
627
651
|
subagents: snapshots,
|
|
628
652
|
...(awaitOutcome ? { awaitOutcome } : {}),
|
|
629
653
|
...(awaitOutcome === "interrupted" ? { interrupted: true } : {}),
|
|
@@ -651,8 +675,6 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
651
675
|
): Pick<SubagentSnapshot, "progress" | "liveProgressAvailable"> {
|
|
652
676
|
if (!attachLiveProgress) return {};
|
|
653
677
|
const liveProgressAvailable = manager.hasLiveSubagent(record.subagentId);
|
|
654
|
-
// Only surface progress when a live producer exists; stale/retained progress
|
|
655
|
-
// for a record with no live producer must degrade to a static snapshot (AC5).
|
|
656
678
|
if (!liveProgressAvailable) return { liveProgressAvailable: false };
|
|
657
679
|
const progress = manager.getSubagentProgress(record.subagentId);
|
|
658
680
|
return {
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
2
|
+
import type { RawArgumentValidationResult } from "@gajae-code/ai/types";
|
|
2
3
|
import type { Component } from "@gajae-code/tui";
|
|
3
4
|
import { Text } from "@gajae-code/tui";
|
|
4
5
|
import { prompt } from "@gajae-code/utils";
|
|
@@ -62,6 +63,39 @@ const TodoOpEntry = z.object({
|
|
|
62
63
|
text: z.string().optional().describe("note text"),
|
|
63
64
|
});
|
|
64
65
|
|
|
66
|
+
const TODO_WRITE_KEYS = new Set(["ops"]);
|
|
67
|
+
const TODO_OP_KEYS = new Set(["op", "list", "task", "phase", "items", "text"]);
|
|
68
|
+
const TODO_INIT_ENTRY_KEYS = new Set(["phase", "items"]);
|
|
69
|
+
|
|
70
|
+
function hasUnknownKeys(value: object, allowed: Set<string>): boolean {
|
|
71
|
+
return Object.keys(value).some(key => !allowed.has(key));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function validateRawTodoArguments(arguments_: Record<string, unknown>): RawArgumentValidationResult {
|
|
75
|
+
if (hasUnknownKeys(arguments_, TODO_WRITE_KEYS)) return { outcome: "reject" };
|
|
76
|
+
if (!Array.isArray(arguments_.ops)) return { outcome: "passthrough" };
|
|
77
|
+
for (const entry of arguments_.ops) {
|
|
78
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
79
|
+
if (hasUnknownKeys(entry, TODO_OP_KEYS)) return { outcome: "reject" };
|
|
80
|
+
const record = entry as Record<string, unknown>;
|
|
81
|
+
if ((record.op === "done" || record.op === "drop") && !record.task && !record.phase) {
|
|
82
|
+
return { outcome: "reject" };
|
|
83
|
+
}
|
|
84
|
+
const list = record.list;
|
|
85
|
+
if (!Array.isArray(list)) continue;
|
|
86
|
+
for (const item of list) {
|
|
87
|
+
if (
|
|
88
|
+
typeof item === "object" &&
|
|
89
|
+
item !== null &&
|
|
90
|
+
!Array.isArray(item) &&
|
|
91
|
+
hasUnknownKeys(item, TODO_INIT_ENTRY_KEYS)
|
|
92
|
+
)
|
|
93
|
+
return { outcome: "reject" };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return { outcome: "passthrough" };
|
|
97
|
+
}
|
|
98
|
+
|
|
65
99
|
const todoWriteSchema = z
|
|
66
100
|
.object({
|
|
67
101
|
ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"),
|
|
@@ -497,6 +531,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
497
531
|
readonly summary = "Write a structured todo list to track progress within a session";
|
|
498
532
|
readonly description: string;
|
|
499
533
|
readonly parameters = todoWriteSchema;
|
|
534
|
+
readonly rawArgumentValidation = validateRawTodoArguments;
|
|
500
535
|
readonly concurrency = "exclusive";
|
|
501
536
|
readonly strict = true;
|
|
502
537
|
readonly loadMode = "discoverable";
|
|
@@ -512,6 +547,18 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
512
547
|
_context?: AgentToolContext,
|
|
513
548
|
): Promise<AgentToolResult<TodoWriteToolDetails>> {
|
|
514
549
|
const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []);
|
|
550
|
+
const missingTarget = params.ops.find(
|
|
551
|
+
entry => (entry.op === "done" || entry.op === "drop") && !entry.task && !entry.phase,
|
|
552
|
+
);
|
|
553
|
+
if (missingTarget) {
|
|
554
|
+
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
555
|
+
const errors = [`Missing task or phase for ${missingTarget.op} operation`];
|
|
556
|
+
return {
|
|
557
|
+
content: [{ type: "text", text: formatSummary(previousPhases, errors) }],
|
|
558
|
+
details: { phases: previousPhases, storage },
|
|
559
|
+
isError: true,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
515
562
|
const { phases: updated, errors } = applyParams(previousPhases, params);
|
|
516
563
|
this.session.setTodoPhases?.(updated);
|
|
517
564
|
const storage = this.session.getSessionFile() ? "session" : "memory";
|
package/src/tools/tool-result.ts
CHANGED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { RepositoryBinding } from "./repository-binding";
|
|
2
|
-
export declare const REVIEW_SOURCE_COHORT_SCHEMA: "gjc.review_source_cohort.v1";
|
|
3
|
-
export type ReviewSourceWorkflow = "ultragoal" | "ralplan";
|
|
4
|
-
export type ReviewSourceLane = "cleaner" | "architect" | "qa" | "critic";
|
|
5
|
-
export type ReviewDeliveryDisposition = "current" | "stale_review_delivery" | "invalid_provenance";
|
|
6
|
-
export interface ReviewSourceDispatch {
|
|
7
|
-
schema: "gjc.review_source_dispatch.v1";
|
|
8
|
-
cohortId: string;
|
|
9
|
-
generation: number;
|
|
10
|
-
dispatchId: string;
|
|
11
|
-
taskId: string;
|
|
12
|
-
lane: ReviewSourceLane;
|
|
13
|
-
snapshotId: string;
|
|
14
|
-
repositoryBindingDigest: string;
|
|
15
|
-
stateRevision: number;
|
|
16
|
-
rerunCommand: string;
|
|
17
|
-
createdAt: string;
|
|
18
|
-
}
|
|
19
|
-
export interface ReviewSourceDelivery {
|
|
20
|
-
deliveryId: string;
|
|
21
|
-
taskId: string;
|
|
22
|
-
lane: ReviewSourceLane;
|
|
23
|
-
snapshotId: string;
|
|
24
|
-
disposition: ReviewDeliveryDisposition;
|
|
25
|
-
receivedAt: string;
|
|
26
|
-
rerunCommand?: string;
|
|
27
|
-
cohortId: string;
|
|
28
|
-
dispatchId: string;
|
|
29
|
-
}
|
|
30
|
-
export interface ReviewSourceCohort {
|
|
31
|
-
schema: typeof REVIEW_SOURCE_COHORT_SCHEMA;
|
|
32
|
-
cohortId: string;
|
|
33
|
-
workflow: ReviewSourceWorkflow;
|
|
34
|
-
generation: number;
|
|
35
|
-
snapshotId: string;
|
|
36
|
-
repositoryBindingDigest: string;
|
|
37
|
-
stateRevision: number;
|
|
38
|
-
createdAt: string;
|
|
39
|
-
status: "active" | "superseded";
|
|
40
|
-
supersededBy?: string;
|
|
41
|
-
dispatches: ReviewSourceDispatch[];
|
|
42
|
-
deliveries: ReviewSourceDelivery[];
|
|
43
|
-
}
|
|
44
|
-
interface CapturedReviewSource {
|
|
45
|
-
snapshotId: string;
|
|
46
|
-
repositoryBindingDigest: string;
|
|
47
|
-
}
|
|
48
|
-
export declare function captureReviewSourceSnapshot(cwd: string, repositoryBinding: RepositoryBinding): Promise<CapturedReviewSource>;
|
|
49
|
-
export declare function createReviewSourceCohort(input: {
|
|
50
|
-
workflow: ReviewSourceWorkflow;
|
|
51
|
-
generation: number;
|
|
52
|
-
snapshotId: string;
|
|
53
|
-
repositoryBindingDigest: string;
|
|
54
|
-
stateRevision: number;
|
|
55
|
-
now?: string;
|
|
56
|
-
}): ReviewSourceCohort;
|
|
57
|
-
export declare function createReviewSourceDispatch(input: {
|
|
58
|
-
cohort: ReviewSourceCohort;
|
|
59
|
-
taskId: string;
|
|
60
|
-
lane: ReviewSourceLane;
|
|
61
|
-
rerunCommand: string;
|
|
62
|
-
now?: string;
|
|
63
|
-
}): ReviewSourceDispatch;
|
|
64
|
-
export declare function normalizeReviewSourceCohorts(value: unknown): ReviewSourceCohort[];
|
|
65
|
-
export declare function reconcileReviewSourceDelivery(input: {
|
|
66
|
-
cwd: string;
|
|
67
|
-
repositoryBinding: RepositoryBinding;
|
|
68
|
-
reviewSource: {
|
|
69
|
-
schema: "gjc.review_source_dispatch.v1";
|
|
70
|
-
cohortId: string;
|
|
71
|
-
generation: number;
|
|
72
|
-
lane: ReviewSourceLane;
|
|
73
|
-
snapshotId: string;
|
|
74
|
-
repositoryBindingDigest: string;
|
|
75
|
-
stateRevision: number;
|
|
76
|
-
dispatchId: string;
|
|
77
|
-
rerunCommand: string;
|
|
78
|
-
};
|
|
79
|
-
}): Promise<{
|
|
80
|
-
disposition: ReviewDeliveryDisposition;
|
|
81
|
-
currentSnapshotId?: string;
|
|
82
|
-
}>;
|
|
83
|
-
export {};
|