@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/task/index.ts
CHANGED
|
@@ -53,9 +53,6 @@ import {
|
|
|
53
53
|
RepositoryBindingError,
|
|
54
54
|
resolveTaskRepositoryBinding,
|
|
55
55
|
} from "../gjc-runtime/repository-binding";
|
|
56
|
-
import { reconcileReviewSourceDelivery } from "../gjc-runtime/ultragoal-review-source";
|
|
57
|
-
import { classifyUltragoalReviewDelivery, validateUltragoalReviewDispatch } from "../gjc-runtime/ultragoal-runtime";
|
|
58
|
-
|
|
59
56
|
import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
60
57
|
import { ArtifactManager } from "../session/artifacts";
|
|
61
58
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
@@ -91,6 +88,18 @@ import {
|
|
|
91
88
|
type WorktreeBaseline,
|
|
92
89
|
} from "./worktree";
|
|
93
90
|
|
|
91
|
+
interface DuplicateIdentity {
|
|
92
|
+
role: string;
|
|
93
|
+
ownerId?: string;
|
|
94
|
+
parentSession: string | null;
|
|
95
|
+
repository: { root: string; relativeSubdir: string | null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface DuplicateDisposition {
|
|
99
|
+
action: "warned" | "superseded";
|
|
100
|
+
predecessorIds: string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
94
103
|
interface TaskResumeDescriptor {
|
|
95
104
|
toolCallId: string;
|
|
96
105
|
params: TaskParams;
|
|
@@ -99,6 +108,11 @@ interface TaskResumeDescriptor {
|
|
|
99
108
|
durableOutputAllowed?: boolean;
|
|
100
109
|
forkContextSeed?: ForkContextSeed;
|
|
101
110
|
agentSource: AgentDefinition["source"];
|
|
111
|
+
repositoryBinding: RepositoryBinding;
|
|
112
|
+
duplicateIdentity: DuplicateIdentity;
|
|
113
|
+
duplicatePolicy: "warn" | "supersede";
|
|
114
|
+
initialDisposition?: DuplicateDisposition;
|
|
115
|
+
lastAdmissionFailure?: string;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
function isTaskResumeDescriptor(value: unknown): value is TaskResumeDescriptor {
|
|
@@ -188,18 +202,12 @@ async function resolveTaskItemsWithRepositoryBindings(
|
|
|
188
202
|
for (const task of tasks) {
|
|
189
203
|
try {
|
|
190
204
|
const binding = await resolveTaskRepositoryBinding(cwd, task.repositoryBinding);
|
|
191
|
-
if (binding.relativeSubdir)
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
resolved.push({
|
|
195
|
-
...task,
|
|
196
|
-
repositoryBinding: binding,
|
|
197
|
-
});
|
|
205
|
+
if (binding.relativeSubdir) assertPathUnderRepositoryBinding(binding, ".");
|
|
206
|
+
resolved.push({ ...task, repositoryBinding: binding });
|
|
198
207
|
} catch (error) {
|
|
199
208
|
const id = task.id?.trim() ? task.id : "(missing-id)";
|
|
200
|
-
if (error instanceof RepositoryBindingError)
|
|
209
|
+
if (error instanceof RepositoryBindingError)
|
|
201
210
|
return { tasks: [], error: `Task "${id}" repository binding rejected: ${error.message}` };
|
|
202
|
-
}
|
|
203
211
|
return {
|
|
204
212
|
tasks: [],
|
|
205
213
|
error: `Task "${id}" repository binding rejected: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -209,6 +217,31 @@ async function resolveTaskItemsWithRepositoryBindings(
|
|
|
209
217
|
return { tasks: resolved };
|
|
210
218
|
}
|
|
211
219
|
|
|
220
|
+
function duplicateIdentityForTask(
|
|
221
|
+
task: TaskItem,
|
|
222
|
+
role: string,
|
|
223
|
+
ownerId: string | undefined,
|
|
224
|
+
parentSession: string | null,
|
|
225
|
+
): DuplicateIdentity {
|
|
226
|
+
const binding = task.repositoryBinding as RepositoryBinding;
|
|
227
|
+
return {
|
|
228
|
+
role: role.trim(),
|
|
229
|
+
ownerId,
|
|
230
|
+
parentSession,
|
|
231
|
+
repository: { root: binding.commonDir ?? binding.worktreeRoot, relativeSubdir: binding.relativeSubdir ?? null },
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function duplicateIdentityKey(identity: DuplicateIdentity): string {
|
|
236
|
+
return JSON.stringify([
|
|
237
|
+
identity.role,
|
|
238
|
+
identity.ownerId ?? null,
|
|
239
|
+
identity.parentSession,
|
|
240
|
+
identity.repository.root,
|
|
241
|
+
identity.repository.relativeSubdir,
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
|
|
212
245
|
function repositoryBindingFromTask(task: TaskItem): RepositoryBinding | undefined {
|
|
213
246
|
return task.repositoryBinding ? publicRepositoryBinding(task.repositoryBinding as RepositoryBinding) : undefined;
|
|
214
247
|
}
|
|
@@ -859,7 +892,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
859
892
|
}
|
|
860
893
|
|
|
861
894
|
const startedJobs: Array<{ jobId: string; taskId: string }> = [];
|
|
862
|
-
const failedSchedules:
|
|
895
|
+
const failedSchedules: Array<{
|
|
896
|
+
task: TaskItem & { id: string };
|
|
897
|
+
taskIndex: number;
|
|
898
|
+
message: string;
|
|
899
|
+
signalSkip: boolean;
|
|
900
|
+
duplicateDisposition?: DuplicateDisposition;
|
|
901
|
+
}> = [];
|
|
863
902
|
let completedJobs = 0;
|
|
864
903
|
let failedJobs = 0;
|
|
865
904
|
|
|
@@ -886,21 +925,106 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
886
925
|
};
|
|
887
926
|
|
|
888
927
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
928
|
+
const ownerId = this.session.getAgentId?.() ?? undefined;
|
|
929
|
+
const parentSession = this.session.getSessionFile();
|
|
930
|
+
const admitDuplicateLaunch = (
|
|
931
|
+
subagentId: string,
|
|
932
|
+
task: TaskItem,
|
|
933
|
+
policy: "warn" | "supersede",
|
|
934
|
+
selfSubagentId?: string,
|
|
935
|
+
): { ok: boolean; disposition?: DuplicateDisposition; error?: string; identity: DuplicateIdentity } => {
|
|
936
|
+
const identity = duplicateIdentityForTask(task, params.agent, ownerId, parentSession);
|
|
937
|
+
const key = duplicateIdentityKey(identity);
|
|
938
|
+
const predecessors = manager
|
|
939
|
+
.getSubagentRecords({ ownerId })
|
|
940
|
+
.filter(record => {
|
|
941
|
+
if (record.subagentId === selfSubagentId || record.subagentId === subagentId) return false;
|
|
942
|
+
if (record.status !== "running" && record.status !== "paused" && record.status !== "queued")
|
|
943
|
+
return false;
|
|
944
|
+
return record.duplicateIdentity === key;
|
|
945
|
+
})
|
|
946
|
+
.sort(
|
|
947
|
+
(a, b) =>
|
|
948
|
+
(b.currentJobId ? (manager.getJob(b.currentJobId)?.startTime ?? 0) : 0) -
|
|
949
|
+
(a.currentJobId ? (manager.getJob(a.currentJobId)?.startTime ?? 0) : 0) ||
|
|
950
|
+
a.subagentId.localeCompare(b.subagentId),
|
|
951
|
+
);
|
|
952
|
+
if (predecessors.length === 0) return { ok: true, identity };
|
|
953
|
+
if (policy === "warn")
|
|
954
|
+
return {
|
|
955
|
+
ok: true,
|
|
956
|
+
identity,
|
|
957
|
+
disposition: { action: "warned", predecessorIds: predecessors.map(record => record.subagentId) },
|
|
958
|
+
};
|
|
959
|
+
for (const predecessor of predecessors)
|
|
960
|
+
if (!manager.cancelSubagent(predecessor.subagentId, { ownerId }))
|
|
961
|
+
return { ok: false, identity, error: "duplicate_supersede_failed" };
|
|
962
|
+
return {
|
|
963
|
+
ok: true,
|
|
964
|
+
identity,
|
|
965
|
+
disposition: { action: "superseded", predecessorIds: predecessors.map(record => record.subagentId) },
|
|
966
|
+
};
|
|
967
|
+
};
|
|
889
968
|
let resumeRunner: ResumeRunner | undefined;
|
|
890
969
|
if (typeof manager.setResumeRunner === "function") {
|
|
891
970
|
resumeRunner = (_subagentId, message, resumeDescriptor) => {
|
|
892
971
|
const descriptor = isTaskResumeDescriptor(resumeDescriptor?.data) ? resumeDescriptor.data : undefined;
|
|
893
972
|
if (!descriptor) return undefined;
|
|
973
|
+
const admission = (() => {
|
|
974
|
+
const identity = descriptor.duplicateIdentity;
|
|
975
|
+
const key = duplicateIdentityKey(identity);
|
|
976
|
+
const predecessors = manager
|
|
977
|
+
.getSubagentRecords({ ownerId })
|
|
978
|
+
.filter(record => {
|
|
979
|
+
if (
|
|
980
|
+
record.subagentId === descriptor.task.id ||
|
|
981
|
+
!["running", "paused", "queued"].includes(record.status)
|
|
982
|
+
)
|
|
983
|
+
return false;
|
|
984
|
+
return record.duplicateIdentity === key;
|
|
985
|
+
})
|
|
986
|
+
.sort(
|
|
987
|
+
(a, b) =>
|
|
988
|
+
(b.currentJobId ? (manager.getJob(b.currentJobId)?.startTime ?? 0) : 0) -
|
|
989
|
+
(a.currentJobId ? (manager.getJob(a.currentJobId)?.startTime ?? 0) : 0) ||
|
|
990
|
+
a.subagentId.localeCompare(b.subagentId),
|
|
991
|
+
);
|
|
992
|
+
if (predecessors.length === 0) return { ok: true, identity };
|
|
993
|
+
if (descriptor.duplicatePolicy === "warn")
|
|
994
|
+
return {
|
|
995
|
+
ok: true,
|
|
996
|
+
identity,
|
|
997
|
+
disposition: { action: "warned", predecessorIds: predecessors.map(record => record.subagentId) },
|
|
998
|
+
};
|
|
999
|
+
for (const predecessor of predecessors)
|
|
1000
|
+
if (!manager.cancelSubagent(predecessor.subagentId, { ownerId }))
|
|
1001
|
+
return { ok: false, identity, error: "duplicate_supersede_failed" };
|
|
1002
|
+
return {
|
|
1003
|
+
ok: true,
|
|
1004
|
+
identity,
|
|
1005
|
+
disposition: { action: "superseded", predecessorIds: predecessors.map(record => record.subagentId) },
|
|
1006
|
+
};
|
|
1007
|
+
})();
|
|
1008
|
+
if (!admission.ok) {
|
|
1009
|
+
descriptor.lastAdmissionFailure = admission.error;
|
|
1010
|
+
return undefined;
|
|
1011
|
+
}
|
|
1012
|
+
if (!descriptor) return undefined;
|
|
894
1013
|
const forkSeeds = descriptor.forkContextSeed
|
|
895
1014
|
? new Map([[descriptor.task.id, descriptor.forkContextSeed]])
|
|
896
1015
|
: undefined;
|
|
1016
|
+
const resumedTask = {
|
|
1017
|
+
...descriptor.task,
|
|
1018
|
+
repositoryBinding: descriptor.repositoryBinding,
|
|
1019
|
+
duplicate_policy: descriptor.duplicatePolicy,
|
|
1020
|
+
};
|
|
897
1021
|
return manager.register(
|
|
898
1022
|
"task",
|
|
899
1023
|
descriptor.task.id,
|
|
900
1024
|
async ({ signal: runSignal }) => {
|
|
901
1025
|
const result = await this.#executeSync(
|
|
902
1026
|
descriptor.toolCallId,
|
|
903
|
-
{ ...descriptor.params, tasks: [
|
|
1027
|
+
{ ...descriptor.params, tasks: [resumedTask] },
|
|
904
1028
|
runSignal,
|
|
905
1029
|
undefined,
|
|
906
1030
|
[descriptor.task.id],
|
|
@@ -921,7 +1045,22 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
921
1045
|
},
|
|
922
1046
|
);
|
|
923
1047
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
924
|
-
const
|
|
1048
|
+
const rawSingleResult = result.details?.results[0];
|
|
1049
|
+
const singleResult = rawSingleResult
|
|
1050
|
+
? {
|
|
1051
|
+
...rawSingleResult,
|
|
1052
|
+
duplicateDisposition: (() => {
|
|
1053
|
+
const initial = descriptor.initialDisposition;
|
|
1054
|
+
const current = admission.disposition;
|
|
1055
|
+
if (!initial) return current;
|
|
1056
|
+
if (!current) return initial;
|
|
1057
|
+
return {
|
|
1058
|
+
action: current.action,
|
|
1059
|
+
predecessorIds: [...new Set([...initial.predecessorIds, ...current.predecessorIds])],
|
|
1060
|
+
};
|
|
1061
|
+
})(),
|
|
1062
|
+
}
|
|
1063
|
+
: rawSingleResult;
|
|
925
1064
|
return subagentRunOutcomeFromSingleResult(finalText, singleResult);
|
|
926
1065
|
},
|
|
927
1066
|
{
|
|
@@ -932,6 +1071,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
932
1071
|
id: descriptor.task.id,
|
|
933
1072
|
agent: descriptor.params.agent,
|
|
934
1073
|
agentSource: descriptor.agentSource,
|
|
1074
|
+
duplicateIdentity: duplicateIdentityKey(descriptor.duplicateIdentity),
|
|
1075
|
+
duplicateDisposition: (admission.disposition?.action ??
|
|
1076
|
+
descriptor.initialDisposition?.action) as DuplicateDisposition["action"] | undefined,
|
|
935
1077
|
description: descriptor.task.description,
|
|
936
1078
|
assignment: descriptor.task.assignment.trim(),
|
|
937
1079
|
},
|
|
@@ -968,7 +1110,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
968
1110
|
for (let i = 0; i < taskItems.length; i++) {
|
|
969
1111
|
const taskItem = taskItems[i];
|
|
970
1112
|
if (signal?.aborted) {
|
|
971
|
-
failedSchedules.push(
|
|
1113
|
+
failedSchedules.push({
|
|
1114
|
+
task: taskItem as TaskItem & { id: string },
|
|
1115
|
+
taskIndex: i,
|
|
1116
|
+
message: "cancelled before scheduling",
|
|
1117
|
+
signalSkip: true,
|
|
1118
|
+
});
|
|
972
1119
|
const progress = progressByTaskId.get(taskItem.id);
|
|
973
1120
|
if (progress) {
|
|
974
1121
|
progress.status = "aborted";
|
|
@@ -981,7 +1128,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
981
1128
|
if (signal?.aborted) {
|
|
982
1129
|
for (let skippedIndex = i; skippedIndex < taskItems.length; skippedIndex++) {
|
|
983
1130
|
const skippedTask = taskItems[skippedIndex]!;
|
|
984
|
-
failedSchedules.push(
|
|
1131
|
+
failedSchedules.push({
|
|
1132
|
+
task: skippedTask as TaskItem & { id: string },
|
|
1133
|
+
taskIndex: skippedIndex,
|
|
1134
|
+
message: "cancelled before scheduling",
|
|
1135
|
+
signalSkip: true,
|
|
1136
|
+
});
|
|
985
1137
|
const skippedProgress = progressByTaskId.get(skippedTask.id);
|
|
986
1138
|
if (skippedProgress) skippedProgress.status = "aborted";
|
|
987
1139
|
}
|
|
@@ -997,6 +1149,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
997
1149
|
const subtaskSessionFile = managedPersistence
|
|
998
1150
|
? null
|
|
999
1151
|
: path.join(batchArtifactsDir ?? externalTaskSessionsDir!, `${uniqueId}.jsonl`);
|
|
1152
|
+
const admission = admitDuplicateLaunch(uniqueId, taskItem, taskItem.duplicate_policy ?? "warn");
|
|
1153
|
+
if (!admission.ok) {
|
|
1154
|
+
failedSchedules.push({
|
|
1155
|
+
task: taskItem as TaskItem & { id: string },
|
|
1156
|
+
taskIndex: i,
|
|
1157
|
+
message: admission.error ?? "duplicate_supersede_failed",
|
|
1158
|
+
signalSkip: false,
|
|
1159
|
+
duplicateDisposition: admission.disposition,
|
|
1160
|
+
});
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1000
1163
|
const jobId = manager.register(
|
|
1001
1164
|
"task",
|
|
1002
1165
|
label,
|
|
@@ -1036,7 +1199,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1036
1199
|
},
|
|
1037
1200
|
);
|
|
1038
1201
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
1039
|
-
const
|
|
1202
|
+
const rawSingleResult = result.details?.results[0];
|
|
1203
|
+
const singleResult = rawSingleResult
|
|
1204
|
+
? {
|
|
1205
|
+
...rawSingleResult,
|
|
1206
|
+
duplicateDisposition: rawSingleResult.duplicateDisposition ?? admission.disposition,
|
|
1207
|
+
}
|
|
1208
|
+
: rawSingleResult;
|
|
1040
1209
|
if (progress) {
|
|
1041
1210
|
progress.status = singleResult?.paused
|
|
1042
1211
|
? "paused"
|
|
@@ -1119,6 +1288,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1119
1288
|
id: uniqueId,
|
|
1120
1289
|
agent: params.agent,
|
|
1121
1290
|
agentSource: fallbackAgentSource,
|
|
1291
|
+
duplicateIdentity: duplicateIdentityKey(admission.identity),
|
|
1292
|
+
duplicateDisposition: admission.disposition?.action,
|
|
1122
1293
|
description: taskItem.description,
|
|
1123
1294
|
assignment: taskItem.assignment.trim(),
|
|
1124
1295
|
},
|
|
@@ -1145,6 +1316,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1145
1316
|
durableOutputAllowed: Boolean(batchArtifactsDir),
|
|
1146
1317
|
forkContextSeed: frozenForkSeed,
|
|
1147
1318
|
agentSource: fallbackAgentSource,
|
|
1319
|
+
repositoryBinding: taskItem.repositoryBinding as RepositoryBinding,
|
|
1320
|
+
duplicateIdentity: admission.identity,
|
|
1321
|
+
duplicatePolicy: taskItem.duplicate_policy ?? "warn",
|
|
1322
|
+
initialDisposition: admission.disposition,
|
|
1148
1323
|
} satisfies TaskResumeDescriptor,
|
|
1149
1324
|
},
|
|
1150
1325
|
resumeRunner,
|
|
@@ -1155,10 +1330,13 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1155
1330
|
subagentId: uniqueId,
|
|
1156
1331
|
ownerId: this.session.getAgentId?.() ?? undefined,
|
|
1157
1332
|
currentJobId: jobId,
|
|
1333
|
+
currentJobGeneration: manager.getJob(jobId)?.generation,
|
|
1158
1334
|
historicalJobIds: [],
|
|
1159
1335
|
status: manager.getJob(jobId)?.status ?? "running",
|
|
1160
1336
|
sessionFile: null,
|
|
1161
1337
|
resumable: true,
|
|
1338
|
+
duplicateIdentity: duplicateIdentityKey(admission.identity),
|
|
1339
|
+
duplicateDisposition: admission.disposition?.action,
|
|
1162
1340
|
});
|
|
1163
1341
|
}
|
|
1164
1342
|
} catch (error) {
|
|
@@ -1168,7 +1346,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1168
1346
|
: error instanceof Error
|
|
1169
1347
|
? error.message
|
|
1170
1348
|
: String(error);
|
|
1171
|
-
failedSchedules.push(
|
|
1349
|
+
failedSchedules.push({
|
|
1350
|
+
task: taskItem as TaskItem & { id: string },
|
|
1351
|
+
taskIndex: i,
|
|
1352
|
+
message,
|
|
1353
|
+
signalSkip: false,
|
|
1354
|
+
});
|
|
1172
1355
|
const progress = progressByTaskId.get(taskItem.id);
|
|
1173
1356
|
if (progress) {
|
|
1174
1357
|
progress.status = "failed";
|
|
@@ -1181,11 +1364,36 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1181
1364
|
failedJobs += failedSchedules.length;
|
|
1182
1365
|
}
|
|
1183
1366
|
|
|
1367
|
+
const scheduleFailureReceipts = failedSchedules
|
|
1368
|
+
.slice()
|
|
1369
|
+
.sort((a, b) => a.taskIndex - b.taskIndex)
|
|
1370
|
+
.map((entry, index) =>
|
|
1371
|
+
buildTaskReceipt({
|
|
1372
|
+
index,
|
|
1373
|
+
id: entry.task.id,
|
|
1374
|
+
agent: params.agent,
|
|
1375
|
+
agentSource: fallbackAgentSource,
|
|
1376
|
+
task: renderTaskAssignment(entry.task.assignment, simpleMode),
|
|
1377
|
+
assignment: entry.task.assignment,
|
|
1378
|
+
description: entry.task.description,
|
|
1379
|
+
status: "failed",
|
|
1380
|
+
exitCode: 1,
|
|
1381
|
+
aborted: entry.signalSkip ? true : undefined,
|
|
1382
|
+
abortReason: entry.signalSkip ? "Cancelled before start" : undefined,
|
|
1383
|
+
truncated: false,
|
|
1384
|
+
durationMs: 0,
|
|
1385
|
+
tokens: 0,
|
|
1386
|
+
output: "",
|
|
1387
|
+
stderr: entry.message,
|
|
1388
|
+
error: entry.message,
|
|
1389
|
+
duplicateDisposition: entry.duplicateDisposition,
|
|
1390
|
+
} as SingleResult),
|
|
1391
|
+
);
|
|
1184
1392
|
if (startedJobs.length === 0) {
|
|
1185
|
-
const failureText = `Failed to start background task jobs: ${failedSchedules.join("; ")}`;
|
|
1393
|
+
const failureText = `Failed to start background task jobs: ${failedSchedules.map(entry => `${entry.task.id}: ${entry.message}`).join("; ")}`;
|
|
1186
1394
|
return {
|
|
1187
1395
|
content: [{ type: "text", text: failureText }],
|
|
1188
|
-
details: { projectAgentsDir: null, results:
|
|
1396
|
+
details: { projectAgentsDir: null, results: scheduleFailureReceipts, totalDurationMs: 0 },
|
|
1189
1397
|
};
|
|
1190
1398
|
}
|
|
1191
1399
|
|
|
@@ -1233,7 +1441,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1233
1441
|
],
|
|
1234
1442
|
details: {
|
|
1235
1443
|
projectAgentsDir: null,
|
|
1236
|
-
results:
|
|
1444
|
+
results: scheduleFailureReceipts,
|
|
1237
1445
|
totalDurationMs: 0,
|
|
1238
1446
|
progress: getProgressSnapshot(),
|
|
1239
1447
|
async: { state: asyncState, jobId: startedJobs[0].jobId, type: "task" },
|
|
@@ -1705,27 +1913,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1705
1913
|
if (taskRepositoryBinding.relativeSubdir) {
|
|
1706
1914
|
assertPathUnderRepositoryBinding(taskRepositoryBinding, ".");
|
|
1707
1915
|
}
|
|
1708
|
-
if (task.reviewSource) {
|
|
1709
|
-
await validateUltragoalReviewDispatch({
|
|
1710
|
-
cwd: this.session.cwd,
|
|
1711
|
-
dispatchId: task.reviewSource.dispatchId,
|
|
1712
|
-
cohortId: task.reviewSource.cohortId,
|
|
1713
|
-
taskId: task.id,
|
|
1714
|
-
lane: task.reviewSource.lane,
|
|
1715
|
-
snapshotId: task.reviewSource.snapshotId,
|
|
1716
|
-
generation: task.reviewSource.generation,
|
|
1717
|
-
repositoryBindingDigest: task.reviewSource.repositoryBindingDigest,
|
|
1718
|
-
stateRevision: task.reviewSource.stateRevision,
|
|
1719
|
-
rerunCommand: task.reviewSource.rerunCommand,
|
|
1720
|
-
taskSourceTaskId: task.reviewSource.taskId,
|
|
1721
|
-
createdAt: task.reviewSource.createdAt,
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
if (isIsolated && task.reviewSource) {
|
|
1725
|
-
throw new Error(
|
|
1726
|
-
"source-aware review tasks cannot use generic task isolation; dispatch them against the coordinator-owned source snapshot",
|
|
1727
|
-
);
|
|
1728
|
-
}
|
|
1729
1916
|
if (!isIsolated) {
|
|
1730
1917
|
await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, taskRepositoryBinding);
|
|
1731
1918
|
const result = await runSubprocess({
|
|
@@ -1780,32 +1967,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1780
1967
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
1781
1968
|
forkContextSeed,
|
|
1782
1969
|
});
|
|
1783
|
-
const reviewSourceDisposition = task.reviewSource
|
|
1784
|
-
? await reconcileReviewSourceDelivery({
|
|
1785
|
-
cwd: this.session.cwd,
|
|
1786
|
-
repositoryBinding: taskRepositoryBinding,
|
|
1787
|
-
reviewSource: task.reviewSource,
|
|
1788
|
-
})
|
|
1789
|
-
: undefined;
|
|
1790
|
-
const reviewSource =
|
|
1791
|
-
task.reviewSource && reviewSourceDisposition
|
|
1792
|
-
? {
|
|
1793
|
-
...task.reviewSource,
|
|
1794
|
-
...reviewSourceDisposition,
|
|
1795
|
-
...(await classifyUltragoalReviewDelivery({
|
|
1796
|
-
cwd: this.session.cwd,
|
|
1797
|
-
cohortId: task.reviewSource.cohortId,
|
|
1798
|
-
dispatchId: task.reviewSource.dispatchId,
|
|
1799
|
-
observedDisposition: reviewSourceDisposition.disposition,
|
|
1800
|
-
})),
|
|
1801
|
-
}
|
|
1802
|
-
: undefined;
|
|
1803
1970
|
return {
|
|
1804
1971
|
...result,
|
|
1805
1972
|
...(forkContext ? { forkContext } : {}),
|
|
1806
1973
|
forkContextAdvisory,
|
|
1807
1974
|
repositoryBinding: publicRepositoryBinding(taskRepositoryBinding),
|
|
1808
|
-
...(reviewSource ? { reviewSource } : {}),
|
|
1809
1975
|
};
|
|
1810
1976
|
}
|
|
1811
1977
|
|
package/src/task/receipt.ts
CHANGED
|
@@ -43,6 +43,7 @@ export interface TaskResultReceipt {
|
|
|
43
43
|
retryFailure?: { attempt: number; errorSummary: string };
|
|
44
44
|
setupFailure?: { summary: string };
|
|
45
45
|
errorSummary?: string;
|
|
46
|
+
duplicateDisposition?: SingleResult["duplicateDisposition"];
|
|
46
47
|
abortSummary?: string;
|
|
47
48
|
preview: string;
|
|
48
49
|
previewTruncated: boolean;
|
|
@@ -67,9 +68,6 @@ export interface TaskResultReceipt {
|
|
|
67
68
|
forkContextAdvisory?: SingleResult["forkContextAdvisory"];
|
|
68
69
|
/** Resolved repository identity for this delegated lane (#2901). */
|
|
69
70
|
repositoryBinding?: SingleResult["repositoryBinding"];
|
|
70
|
-
/** Runtime-derived review source disposition and rerun guidance (#3469). */
|
|
71
|
-
reviewSource?: SingleResult["reviewSource"];
|
|
72
|
-
|
|
73
71
|
roi?: TaskRoi;
|
|
74
72
|
}
|
|
75
73
|
|
|
@@ -296,6 +294,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
296
294
|
retryFailure: raw.retryFailure
|
|
297
295
|
? { attempt: raw.retryFailure.attempt, errorSummary: "Retry failure recorded." }
|
|
298
296
|
: undefined,
|
|
297
|
+
duplicateDisposition: raw.duplicateDisposition,
|
|
299
298
|
errorSummary:
|
|
300
299
|
raw.setupFailure?.summary ??
|
|
301
300
|
(raw.error
|
|
@@ -314,7 +313,6 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
314
313
|
forkContext: raw.forkContext,
|
|
315
314
|
forkContextAdvisory: raw.forkContextAdvisory,
|
|
316
315
|
repositoryBinding: raw.repositoryBinding,
|
|
317
|
-
reviewSource: raw.reviewSource,
|
|
318
316
|
roi: buildTaskRoi(raw),
|
|
319
317
|
};
|
|
320
318
|
}
|
package/src/task/render.ts
CHANGED
|
@@ -916,36 +916,6 @@ function renderAgentResult(result: TaskResultReceipt, isLast: boolean, expanded:
|
|
|
916
916
|
if (result.roi?.lowRoi) {
|
|
917
917
|
lines.push(`${continuePrefix}${theme.fg("warning", "low ROI: produced no material contribution")}`);
|
|
918
918
|
}
|
|
919
|
-
if (result.reviewSource?.disposition === "stale_review_delivery") {
|
|
920
|
-
lines.push(
|
|
921
|
-
`${continuePrefix}${theme.fg(
|
|
922
|
-
"warning",
|
|
923
|
-
truncateToWidth(
|
|
924
|
-
replaceTabs(
|
|
925
|
-
sanitizeText(
|
|
926
|
-
`STALE REVIEW: source advanced after dispatch; excluded from review gates. Rerun: ${result.reviewSource.rerunCommand}`,
|
|
927
|
-
),
|
|
928
|
-
),
|
|
929
|
-
|
|
930
|
-
100,
|
|
931
|
-
),
|
|
932
|
-
)}`,
|
|
933
|
-
);
|
|
934
|
-
} else if (result.reviewSource?.disposition === "invalid_provenance") {
|
|
935
|
-
lines.push(
|
|
936
|
-
`${continuePrefix}${theme.fg(
|
|
937
|
-
"error",
|
|
938
|
-
truncateToWidth(
|
|
939
|
-
replaceTabs(
|
|
940
|
-
sanitizeText(
|
|
941
|
-
`INVALID REVIEW PROVENANCE: receipt cannot satisfy review gates. Rerun: ${result.reviewSource.rerunCommand}`,
|
|
942
|
-
),
|
|
943
|
-
),
|
|
944
|
-
100,
|
|
945
|
-
),
|
|
946
|
-
)}`,
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
919
|
|
|
950
920
|
if (result.persistence?.recoveryRef) {
|
|
951
921
|
const label = result.persistence.ownerWorktreeApplied ? "Recovery patch" : "Unapplied recovery patch";
|
package/src/task/types.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
2
2
|
import type { Usage } from "@gajae-code/ai";
|
|
3
3
|
import { $env } from "@gajae-code/utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
-
import type { ReviewDeliveryDisposition, ReviewSourceLane } from "../gjc-runtime/ultragoal-review-source";
|
|
6
5
|
import { isValidTaskId, TASK_ID_DESCRIPTION } from "./id";
|
|
7
6
|
import type { TaskResultReceipt } from "./receipt";
|
|
8
7
|
import type { SpawnRoiReconciliation } from "./roi-reconciliation";
|
|
@@ -115,26 +114,9 @@ const createTaskItemSchema = (_contextEnabled: boolean) =>
|
|
|
115
114
|
.describe(
|
|
116
115
|
"authoritative repository identity; omitted items are stamped from session cwd before discovery/spawn and still fail closed on sibling drift",
|
|
117
116
|
),
|
|
118
|
-
|
|
117
|
+
duplicate_policy: z.enum(["warn", "supersede"]).optional().describe("duplicate launch policy; defaults to warn"),
|
|
119
118
|
});
|
|
120
119
|
|
|
121
|
-
const reviewSourceSchema = z
|
|
122
|
-
.object({
|
|
123
|
-
schema: z.literal("gjc.review_source_dispatch.v1"),
|
|
124
|
-
cohortId: z.string().min(1),
|
|
125
|
-
generation: z.number().int().min(1),
|
|
126
|
-
lane: z.enum(["cleaner", "architect", "qa", "critic"]),
|
|
127
|
-
snapshotId: z.string().startsWith("sha256:"),
|
|
128
|
-
repositoryBindingDigest: z.string().startsWith("sha256:"),
|
|
129
|
-
stateRevision: z.number().int().min(0),
|
|
130
|
-
dispatchId: z.string().min(1),
|
|
131
|
-
rerunCommand: z.string().min(1),
|
|
132
|
-
taskId: z.string().min(1),
|
|
133
|
-
createdAt: z.string().min(1),
|
|
134
|
-
})
|
|
135
|
-
.strict()
|
|
136
|
-
.describe("leader-issued immutable review source identity; review lanes only");
|
|
137
|
-
|
|
138
120
|
/** Single task item for parallel execution (default shape with context enabled). */
|
|
139
121
|
export const taskItemSchema = createTaskItemSchema(true);
|
|
140
122
|
export type TaskItem = z.infer<typeof taskItemSchema>;
|
|
@@ -218,6 +200,7 @@ export interface TaskParams {
|
|
|
218
200
|
spawnPlan?: SpawnPlanReceipt;
|
|
219
201
|
tasks: TaskItem[];
|
|
220
202
|
isolated?: boolean;
|
|
203
|
+
duplicate_policy?: "warn" | "supersede";
|
|
221
204
|
}
|
|
222
205
|
|
|
223
206
|
/** A code review finding reported by the reviewer agent */
|
|
@@ -452,6 +435,12 @@ export interface TaskRecoveryArtifactRef {
|
|
|
452
435
|
durability: "session";
|
|
453
436
|
}
|
|
454
437
|
|
|
438
|
+
/** Bounded duplicate-launch disposition carried into task receipts. */
|
|
439
|
+
export interface DuplicateDisposition {
|
|
440
|
+
action: "warned" | "superseded";
|
|
441
|
+
predecessorIds: string[];
|
|
442
|
+
}
|
|
443
|
+
|
|
455
444
|
export interface TaskPersistenceResult {
|
|
456
445
|
outcome: "applied" | "no_changes" | "recovery_available";
|
|
457
446
|
ownerWorktreeApplied: boolean;
|
|
@@ -488,6 +477,8 @@ export interface SingleResult {
|
|
|
488
477
|
setupFailure?: SetupFailureSummary;
|
|
489
478
|
aborted?: boolean;
|
|
490
479
|
abortReason?: string;
|
|
480
|
+
|
|
481
|
+
duplicateDisposition?: DuplicateDisposition;
|
|
491
482
|
paused?: boolean;
|
|
492
483
|
/** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
|
|
493
484
|
usage?: Usage;
|
|
@@ -543,21 +534,6 @@ export interface SingleResult {
|
|
|
543
534
|
head?: string;
|
|
544
535
|
branch?: string;
|
|
545
536
|
};
|
|
546
|
-
/** Leader-issued immutable source identity for source-aware review lanes (#3469). */
|
|
547
|
-
reviewSource?: {
|
|
548
|
-
schema: "gjc.review_source_dispatch.v1";
|
|
549
|
-
cohortId: string;
|
|
550
|
-
generation: number;
|
|
551
|
-
lane: ReviewSourceLane;
|
|
552
|
-
snapshotId: string;
|
|
553
|
-
repositoryBindingDigest: string;
|
|
554
|
-
stateRevision: number;
|
|
555
|
-
dispatchId: string;
|
|
556
|
-
rerunCommand: string;
|
|
557
|
-
disposition: ReviewDeliveryDisposition;
|
|
558
|
-
currentSnapshotId?: string;
|
|
559
|
-
deliveryId?: string;
|
|
560
|
-
};
|
|
561
537
|
}
|
|
562
538
|
|
|
563
539
|
/** True only for complete, factual five-bucket cost accounting. */
|