@gajae-code/coding-agent 0.12.4 → 0.12.6
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 +36 -0
- package/README.md +4 -1
- package/dist/types/async/job-manager.d.ts +9 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +10 -8
- package/dist/types/extensibility/extensions/runner.d.ts +9 -7
- package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
- package/dist/types/gjc-runtime/team-store.d.ts +0 -4
- package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
- package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +18 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -2
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -3
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
- package/dist/types/sdk/host/host.d.ts +23 -2
- package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
- package/dist/types/session/agent-session.d.ts +5 -4
- package/dist/types/session/artifacts.d.ts +8 -5
- package/dist/types/session/attempt-record-store.d.ts +19 -0
- package/dist/types/session/client-bridge.d.ts +14 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -23
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +7 -5
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -1
- package/dist/types/task/receipt.d.ts +3 -0
- package/dist/types/task/types.d.ts +194 -0
- package/dist/types/task/worktree.d.ts +13 -6
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/output-meta.d.ts +37 -5
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -0
- package/dist/types/utils/git.d.ts +1 -0
- package/package.json +8 -8
- package/scripts/capture-sticky-viewport-showcase.ts +131 -5
- package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
- package/scripts/verify-sticky-viewport-showcase.ts +620 -93
- package/src/async/job-manager.ts +13 -5
- package/src/config/model-profiles.ts +8 -0
- package/src/config/settings-schema.ts +17 -3
- package/src/cursor.ts +20 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +425 -151
- package/src/extensibility/extensions/runner.ts +80 -6
- package/src/extensibility/extensions/wrapper.ts +22 -15
- package/src/extensibility/runtime-skill-discovery.ts +4 -6
- package/src/gjc-runtime/launch-tmux.ts +309 -293
- package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
- package/src/gjc-runtime/psmux-detect.ts +52 -6
- package/src/gjc-runtime/team-launch.ts +50 -2
- package/src/gjc-runtime/team-runtime.ts +716 -251
- package/src/gjc-runtime/team-store.ts +25 -7
- package/src/gjc-runtime/team-workers.ts +161 -1
- package/src/gjc-runtime/tmux-common.ts +16 -4
- package/src/gjc-runtime/tmux-gc.ts +37 -5
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
- package/src/gjc-runtime/tmux-provider-context.ts +451 -0
- package/src/gjc-runtime/tmux-sessions.ts +409 -156
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
- package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/internal-urls/agent-protocol.ts +98 -38
- package/src/internal-urls/artifact-protocol.ts +28 -17
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/local-protocol.ts +39 -18
- package/src/internal-urls/registry-helpers.ts +62 -0
- package/src/modes/acp/acp-agent.ts +51 -13
- package/src/modes/acp/acp-client-bridge.ts +154 -11
- package/src/modes/components/gjc-bundle-settings.ts +2 -0
- package/src/modes/components/plugin-settings.ts +5 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/monitor.md +4 -4
- package/src/sdk/acp/adapter.ts +200 -9
- package/src/sdk/broker/broker.ts +93 -10
- package/src/sdk/broker/lifecycle-ledger.ts +227 -11
- package/src/sdk/broker/lifecycle.ts +692 -98
- package/src/sdk/bus/chat-daemon-control.ts +33 -5
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -3
- package/src/sdk/bus/telegram-daemon.ts +283 -44
- package/src/sdk/bus/topic-registry.ts +133 -10
- package/src/sdk/host/host.ts +187 -9
- package/src/sdk/host/reverse-leases.ts +249 -42
- package/src/sdk/session.ts +8 -6
- package/src/session/agent-session.ts +930 -354
- package/src/session/artifacts.ts +211 -74
- package/src/session/attempt-record-store.ts +106 -0
- package/src/session/client-bridge.ts +15 -0
- package/src/session/internal/managed-session-scope.ts +332 -198
- package/src/session/internal/managed-session-storage.ts +162 -450
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +31 -28
- package/src/session/session-storage.ts +190 -53
- package/src/session/streaming-output.ts +198 -40
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +277 -64
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +26 -2
- package/src/task/render.ts +42 -1
- package/src/task/types.ts +52 -0
- package/src/task/worktree.ts +122 -43
- package/src/tools/bash.ts +818 -239
- package/src/tools/eval.ts +99 -4
- package/src/tools/fetch.ts +77 -15
- package/src/tools/find.ts +7 -3
- package/src/tools/monitor.ts +7 -1
- package/src/tools/output-meta.ts +706 -116
- package/src/tools/search.ts +9 -4
- package/src/tools/subagent-render.ts +5 -2
- package/src/tools/subagent.ts +8 -0
- package/src/tools/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
- package/src/utils/git.ts +2 -0
package/src/task/index.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Progress tracking via JSON events
|
|
13
13
|
* - Session artifacts for debugging
|
|
14
14
|
*/
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
16
16
|
import * as fs from "node:fs/promises";
|
|
17
17
|
import * as os from "node:os";
|
|
18
18
|
import path from "node:path";
|
|
@@ -53,6 +53,9 @@ 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
|
+
|
|
56
59
|
import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
57
60
|
import { ArtifactManager } from "../session/artifacts";
|
|
58
61
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
@@ -64,7 +67,7 @@ import { FORK_CONTEXT_TOKEN_BUDGET_BY_MODE } from "./fork-context-budget";
|
|
|
64
67
|
import { getTaskIdValidationError, validateAllocatedTaskId } from "./id";
|
|
65
68
|
import { AgentOutputManager } from "./output-manager";
|
|
66
69
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
67
|
-
import { assertNoRawTaskFields, buildTaskReceipt, buildTaskRoiSummary } from "./receipt";
|
|
70
|
+
import { assertNoRawTaskFields, buildTaskReceipt, buildTaskRoiSummary, type TaskResultReceipt } from "./receipt";
|
|
68
71
|
|
|
69
72
|
import { renderResult, renderCall as renderTaskCall } from "./render";
|
|
70
73
|
import { reconcileSpawnRoi } from "./roi-reconciliation";
|
|
@@ -82,6 +85,9 @@ import {
|
|
|
82
85
|
type IsolationHandle,
|
|
83
86
|
mergeTaskBranches,
|
|
84
87
|
parseIsolationMode,
|
|
88
|
+
serializeRecoveryPatchBundle,
|
|
89
|
+
verifyNestedPatchesApplied,
|
|
90
|
+
verifyRootPatchesApplied,
|
|
85
91
|
type WorktreeBaseline,
|
|
86
92
|
} from "./worktree";
|
|
87
93
|
|
|
@@ -104,9 +110,22 @@ function renderTaskAssignment(assignment: string, simpleMode: TaskSimpleMode): s
|
|
|
104
110
|
|
|
105
111
|
export function subagentRunOutcomeFromSingleResult(
|
|
106
112
|
finalText: string,
|
|
107
|
-
singleResult:
|
|
113
|
+
singleResult:
|
|
114
|
+
| (Pick<SingleResult, "aborted" | "exitCode" | "paused" | "setupFailure"> &
|
|
115
|
+
Partial<Pick<TaskResultReceipt, "status">>)
|
|
116
|
+
| undefined,
|
|
108
117
|
): string | SubagentRunOutcome {
|
|
109
118
|
if (singleResult?.paused) return { kind: "paused" };
|
|
119
|
+
if (!singleResult) return { kind: "failed", text: finalText };
|
|
120
|
+
if (singleResult?.status && singleResult.status !== "completed") {
|
|
121
|
+
return {
|
|
122
|
+
kind: "failed",
|
|
123
|
+
text: finalText,
|
|
124
|
+
...(singleResult.setupFailure && !singleResult.aborted
|
|
125
|
+
? { setupFailureSummary: singleResult.setupFailure.summary }
|
|
126
|
+
: {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
110
129
|
if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
|
|
111
130
|
return {
|
|
112
131
|
kind: "failed",
|
|
@@ -1042,7 +1061,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1042
1061
|
progress.setupFailure = singleResult?.setupFailure;
|
|
1043
1062
|
}
|
|
1044
1063
|
completedJobs += 1;
|
|
1045
|
-
if (singleResult
|
|
1064
|
+
if (singleResult?.status !== "completed") {
|
|
1046
1065
|
failedJobs += 1;
|
|
1047
1066
|
}
|
|
1048
1067
|
const remaining = taskItems.length - completedJobs;
|
|
@@ -1157,6 +1176,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1157
1176
|
}
|
|
1158
1177
|
}
|
|
1159
1178
|
|
|
1179
|
+
if (failedSchedules.length > 0) {
|
|
1180
|
+
completedJobs += failedSchedules.length;
|
|
1181
|
+
failedJobs += failedSchedules.length;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1160
1184
|
if (startedJobs.length === 0) {
|
|
1161
1185
|
const failureText = `Failed to start background task jobs: ${failedSchedules.join("; ")}`;
|
|
1162
1186
|
return {
|
|
@@ -1165,9 +1189,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1165
1189
|
};
|
|
1166
1190
|
}
|
|
1167
1191
|
|
|
1192
|
+
const asyncState = completedJobs === taskItems.length ? (failedJobs > 0 ? "failed" : "completed") : "running";
|
|
1168
1193
|
emitAsyncUpdate(
|
|
1169
|
-
|
|
1170
|
-
|
|
1194
|
+
asyncState,
|
|
1195
|
+
asyncState === "running"
|
|
1196
|
+
? `Launching ${startedJobs.length} background ${startedJobs.length === 1 ? "task" : "tasks"}...`
|
|
1197
|
+
: asyncState === "completed"
|
|
1198
|
+
? `Background task batch complete: ${completedJobs}/${taskItems.length} finished.`
|
|
1199
|
+
: `Background task batch complete with failures: ${failedJobs} failed.`,
|
|
1171
1200
|
);
|
|
1172
1201
|
|
|
1173
1202
|
const scheduleFailureSummary =
|
|
@@ -1196,7 +1225,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1196
1225
|
content: [
|
|
1197
1226
|
{
|
|
1198
1227
|
type: "text",
|
|
1199
|
-
text:
|
|
1228
|
+
text:
|
|
1229
|
+
asyncState === "running"
|
|
1230
|
+
? `Started ${startedJobs.length} background task job${startedJobs.length === 1 ? "" : "s"} using ${params.agent}.${scheduleFailureSummary} Results will be delivered when complete.\n${startedListing}\n${coordinationHint}`
|
|
1231
|
+
: `Background task batch ${asyncState}: ${completedJobs}/${taskItems.length} finished.${scheduleFailureSummary}\n${startedListing}`,
|
|
1200
1232
|
},
|
|
1201
1233
|
],
|
|
1202
1234
|
details: {
|
|
@@ -1204,7 +1236,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1204
1236
|
results: [],
|
|
1205
1237
|
totalDurationMs: 0,
|
|
1206
1238
|
progress: getProgressSnapshot(),
|
|
1207
|
-
async: { state:
|
|
1239
|
+
async: { state: asyncState, jobId: startedJobs[0].jobId, type: "task" },
|
|
1208
1240
|
},
|
|
1209
1241
|
};
|
|
1210
1242
|
}
|
|
@@ -1673,6 +1705,27 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1673
1705
|
if (taskRepositoryBinding.relativeSubdir) {
|
|
1674
1706
|
assertPathUnderRepositoryBinding(taskRepositoryBinding, ".");
|
|
1675
1707
|
}
|
|
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
|
+
}
|
|
1676
1729
|
if (!isIsolated) {
|
|
1677
1730
|
await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, taskRepositoryBinding);
|
|
1678
1731
|
const result = await runSubprocess({
|
|
@@ -1727,11 +1780,32 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1727
1780
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
1728
1781
|
forkContextSeed,
|
|
1729
1782
|
});
|
|
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;
|
|
1730
1803
|
return {
|
|
1731
1804
|
...result,
|
|
1732
1805
|
...(forkContext ? { forkContext } : {}),
|
|
1733
1806
|
forkContextAdvisory,
|
|
1734
1807
|
repositoryBinding: publicRepositoryBinding(taskRepositoryBinding),
|
|
1808
|
+
...(reviewSource ? { reviewSource } : {}),
|
|
1735
1809
|
};
|
|
1736
1810
|
}
|
|
1737
1811
|
|
|
@@ -1801,13 +1875,18 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1801
1875
|
parentTelemetry: this.session.getTelemetry?.(),
|
|
1802
1876
|
forkContextSeed,
|
|
1803
1877
|
});
|
|
1804
|
-
|
|
1878
|
+
let capturedResult = {
|
|
1805
1879
|
...result,
|
|
1806
1880
|
...(forkContext ? { forkContext } : {}),
|
|
1807
1881
|
forkContextAdvisory,
|
|
1808
1882
|
repositoryBinding: publicRepositoryBinding(taskRepositoryBinding),
|
|
1809
1883
|
};
|
|
1810
|
-
|
|
1884
|
+
const taskSucceeded =
|
|
1885
|
+
capturedResult.exitCode === 0 &&
|
|
1886
|
+
!capturedResult.error &&
|
|
1887
|
+
!capturedResult.aborted &&
|
|
1888
|
+
!capturedResult.paused;
|
|
1889
|
+
if (mergeMode === "branch" && taskSucceeded) {
|
|
1811
1890
|
try {
|
|
1812
1891
|
const commitMsg =
|
|
1813
1892
|
commitStyle === "ai" && this.session.modelRegistry
|
|
@@ -1828,8 +1907,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1828
1907
|
commitMsg,
|
|
1829
1908
|
);
|
|
1830
1909
|
const producedChanges = Boolean(commitResult?.branchName || commitResult?.nestedPatches.length);
|
|
1831
|
-
|
|
1832
|
-
...
|
|
1910
|
+
capturedResult = {
|
|
1911
|
+
...capturedResult,
|
|
1833
1912
|
branchName: commitResult?.branchName,
|
|
1834
1913
|
nestedPatches: commitResult?.nestedPatches,
|
|
1835
1914
|
producedChanges,
|
|
@@ -1839,34 +1918,51 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1839
1918
|
const branchName = `gjc/task/${task.id}`;
|
|
1840
1919
|
await git.branch.tryDelete(repoRoot, branchName);
|
|
1841
1920
|
const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
|
|
1842
|
-
|
|
1921
|
+
capturedResult = { ...capturedResult, error: `Merge failed: ${msg}` };
|
|
1843
1922
|
}
|
|
1844
1923
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1924
|
+
try {
|
|
1925
|
+
const delta = await captureDeltaPatch(isolationDir, taskBaseline);
|
|
1926
|
+
await initializeLocalRoot(localProtocolOptions);
|
|
1927
|
+
const artifactId = validateAllocatedTaskId(task.id);
|
|
1928
|
+
const patchUri = `local://subagents/${artifactId}-${Snowflake.next()}.patch`;
|
|
1929
|
+
const patchPath = resolveLocalUrlToPath(patchUri, localProtocolOptions);
|
|
1930
|
+
const rootPatchBytes = Buffer.from(delta.rootPatch, "utf8");
|
|
1931
|
+
const recoveryBytes = Buffer.from(serializeRecoveryPatchBundle(delta), "utf8");
|
|
1932
|
+
await fs.mkdir(path.dirname(patchPath), { recursive: true, mode: 0o700 });
|
|
1933
|
+
await Bun.write(patchPath, recoveryBytes);
|
|
1934
|
+
isolatedPatchBytes.set(patchPath, rootPatchBytes);
|
|
1935
|
+
const producedChanges = Boolean(
|
|
1936
|
+
delta.rootPatch.trim() || delta.nestedPatches.length || delta.captureErrors?.length,
|
|
1937
|
+
);
|
|
1938
|
+
const recoveryRef =
|
|
1939
|
+
recoveryBytes.byteLength > 0
|
|
1940
|
+
? {
|
|
1941
|
+
uri: patchUri,
|
|
1942
|
+
sizeBytes: recoveryBytes.byteLength,
|
|
1943
|
+
sha256: createHash("sha256").update(recoveryBytes).digest("hex"),
|
|
1944
|
+
durability: "session" as const,
|
|
1945
|
+
}
|
|
1946
|
+
: undefined;
|
|
1947
|
+
const captureError = delta.captureErrors?.length
|
|
1948
|
+
? `Patch capture incomplete: ${delta.captureErrors.join("; ")}`
|
|
1949
|
+
: undefined;
|
|
1950
|
+
const resultError = captureError
|
|
1951
|
+
? `${capturedResult.error ? `${capturedResult.error}; ` : ""}${captureError}`
|
|
1952
|
+
: capturedResult.error;
|
|
1953
|
+
return {
|
|
1954
|
+
...capturedResult,
|
|
1955
|
+
...(resultError ? { error: resultError } : {}),
|
|
1956
|
+
patchPath,
|
|
1957
|
+
nestedPatches: delta.nestedPatches,
|
|
1958
|
+
producedChanges,
|
|
1959
|
+
recoveryRef,
|
|
1960
|
+
};
|
|
1961
|
+
} catch (patchErr) {
|
|
1962
|
+
const msg = patchErr instanceof Error ? patchErr.message : String(patchErr);
|
|
1963
|
+
const existingError = capturedResult.error ? `${capturedResult.error}; ` : "";
|
|
1964
|
+
return { ...capturedResult, error: `${existingError}Patch capture failed: ${msg}` };
|
|
1868
1965
|
}
|
|
1869
|
-
return resultWithForkContext;
|
|
1870
1966
|
} catch (err) {
|
|
1871
1967
|
const message = err instanceof Error ? err.message : String(err);
|
|
1872
1968
|
const assignment = task.assignment.trim();
|
|
@@ -1966,13 +2062,23 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1966
2062
|
let mergeSummary = "";
|
|
1967
2063
|
let changesApplied: boolean | null = null;
|
|
1968
2064
|
let hadAnyChanges = false;
|
|
2065
|
+
let rootPatchTexts: string[] = [];
|
|
1969
2066
|
let mergedBranchesForNestedPatches: Set<string> | null = null;
|
|
2067
|
+
let mergePhaseFailed = false;
|
|
2068
|
+
const setRecoveryAvailable = (result: SingleResult): void => {
|
|
2069
|
+
if (!result.recoveryRef) return;
|
|
2070
|
+
result.persistence = {
|
|
2071
|
+
outcome: "recovery_available",
|
|
2072
|
+
ownerWorktreeApplied: false,
|
|
2073
|
+
recoveryRef: result.recoveryRef,
|
|
2074
|
+
};
|
|
2075
|
+
};
|
|
1970
2076
|
if (isIsolated && repoRoot) {
|
|
1971
2077
|
try {
|
|
1972
2078
|
if (mergeMode === "branch") {
|
|
1973
2079
|
// Branch mode: merge task branches sequentially
|
|
1974
2080
|
const branchEntries = results
|
|
1975
|
-
.filter(r => r.branchName && r.exitCode === 0 && !r.aborted)
|
|
2081
|
+
.filter(r => r.branchName && r.exitCode === 0 && !r.error && !r.aborted && !r.paused)
|
|
1976
2082
|
.map(r => ({ branchName: r.branchName!, taskId: r.id, description: r.description }));
|
|
1977
2083
|
|
|
1978
2084
|
if (branchEntries.length === 0) {
|
|
@@ -1981,7 +2087,9 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1981
2087
|
mergeSummary = "\n\nNo changes to apply.";
|
|
1982
2088
|
} else {
|
|
1983
2089
|
const mergeResult = await mergeTaskBranches(repoRoot, branchEntries);
|
|
1984
|
-
mergedBranchesForNestedPatches = new Set(
|
|
2090
|
+
mergedBranchesForNestedPatches = new Set(
|
|
2091
|
+
mergeResult.merged.filter(branchName => !mergeResult.failed.includes(branchName)),
|
|
2092
|
+
);
|
|
1985
2093
|
changesApplied = mergeResult.failed.length === 0;
|
|
1986
2094
|
hadAnyChanges = changesApplied && mergeResult.merged.length > 0;
|
|
1987
2095
|
|
|
@@ -1998,15 +2106,48 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1998
2106
|
}
|
|
1999
2107
|
}
|
|
2000
2108
|
|
|
2109
|
+
for (const result of results) {
|
|
2110
|
+
const hasChanges = Boolean(
|
|
2111
|
+
result.branchName || result.nestedPatches?.length || result.recoveryRef,
|
|
2112
|
+
);
|
|
2113
|
+
if (!hasChanges) {
|
|
2114
|
+
if (result.exitCode === 0 && !result.error && !result.aborted && !result.paused) {
|
|
2115
|
+
result.persistence = { outcome: "no_changes", ownerWorktreeApplied: true };
|
|
2116
|
+
}
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
if (
|
|
2120
|
+
result.branchName &&
|
|
2121
|
+
mergedBranchesForNestedPatches?.has(result.branchName) &&
|
|
2122
|
+
result.exitCode === 0 &&
|
|
2123
|
+
!result.error &&
|
|
2124
|
+
!result.aborted &&
|
|
2125
|
+
!result.paused
|
|
2126
|
+
) {
|
|
2127
|
+
result.persistence = {
|
|
2128
|
+
outcome: "applied",
|
|
2129
|
+
ownerWorktreeApplied: true,
|
|
2130
|
+
recoveryRef: result.recoveryRef,
|
|
2131
|
+
};
|
|
2132
|
+
} else {
|
|
2133
|
+
setRecoveryAvailable(result);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2001
2137
|
// Clean up merged branches (keep failed ones for manual resolution)
|
|
2002
2138
|
const allBranches = branchEntries.map(b => b.branchName);
|
|
2003
2139
|
if (changesApplied) {
|
|
2004
2140
|
await cleanupTaskBranches(repoRoot, allBranches);
|
|
2005
2141
|
}
|
|
2006
2142
|
} else {
|
|
2007
|
-
// Patch mode: combine and apply patches
|
|
2008
|
-
const
|
|
2009
|
-
|
|
2143
|
+
// Patch mode: combine and apply only successful task patches. Failed/interrupted patches remain recovery-only.
|
|
2144
|
+
const successfulPatchResults = results.filter(
|
|
2145
|
+
result => result.exitCode === 0 && !result.error && !result.aborted && !result.paused,
|
|
2146
|
+
);
|
|
2147
|
+
const patchesInOrder = successfulPatchResults
|
|
2148
|
+
.map(result => result.patchPath)
|
|
2149
|
+
.filter(Boolean) as string[];
|
|
2150
|
+
const missingPatch = successfulPatchResults.some(result => !result.patchPath);
|
|
2010
2151
|
if (missingPatch) {
|
|
2011
2152
|
changesApplied = false;
|
|
2012
2153
|
hadAnyChanges = false;
|
|
@@ -2023,10 +2164,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2023
2164
|
changesApplied = true;
|
|
2024
2165
|
hadAnyChanges = false;
|
|
2025
2166
|
} else {
|
|
2026
|
-
|
|
2167
|
+
rootPatchTexts = nonEmptyPatches.map(patchPath =>
|
|
2027
2168
|
isolatedPatchBytes.get(patchPath)!.toString("utf8"),
|
|
2028
2169
|
);
|
|
2029
|
-
const combinedPatch =
|
|
2170
|
+
const combinedPatch = rootPatchTexts
|
|
2030
2171
|
.map(text => (text.endsWith("\n") ? text : `${text}\n`))
|
|
2031
2172
|
.join("");
|
|
2032
2173
|
if (!combinedPatch.trim()) {
|
|
@@ -2047,6 +2188,48 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2047
2188
|
}
|
|
2048
2189
|
}
|
|
2049
2190
|
|
|
2191
|
+
if (changesApplied && rootPatchTexts.length > 0 && baseline) {
|
|
2192
|
+
changesApplied = await verifyRootPatchesApplied(repoRoot, baseline, rootPatchTexts);
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
for (const result of results) {
|
|
2196
|
+
const patchBytes = result.patchPath ? isolatedPatchBytes.get(result.patchPath) : undefined;
|
|
2197
|
+
const hasChanges = Boolean(
|
|
2198
|
+
patchBytes?.toString("utf8").trim() ||
|
|
2199
|
+
(result.nestedPatches && result.nestedPatches.length > 0) ||
|
|
2200
|
+
result.recoveryRef,
|
|
2201
|
+
);
|
|
2202
|
+
if (!hasChanges) {
|
|
2203
|
+
if (result.exitCode === 0 && !result.error && !result.aborted && !result.paused) {
|
|
2204
|
+
result.persistence = {
|
|
2205
|
+
outcome: "no_changes",
|
|
2206
|
+
ownerWorktreeApplied: true,
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
continue;
|
|
2210
|
+
}
|
|
2211
|
+
if (
|
|
2212
|
+
changesApplied &&
|
|
2213
|
+
patchBytes &&
|
|
2214
|
+
result.exitCode === 0 &&
|
|
2215
|
+
!result.error &&
|
|
2216
|
+
!result.aborted &&
|
|
2217
|
+
!result.paused
|
|
2218
|
+
) {
|
|
2219
|
+
result.persistence = {
|
|
2220
|
+
outcome: "applied",
|
|
2221
|
+
ownerWorktreeApplied: true,
|
|
2222
|
+
recoveryRef: result.recoveryRef,
|
|
2223
|
+
};
|
|
2224
|
+
} else {
|
|
2225
|
+
result.persistence = {
|
|
2226
|
+
outcome: "recovery_available",
|
|
2227
|
+
ownerWorktreeApplied: false,
|
|
2228
|
+
recoveryRef: result.recoveryRef,
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2050
2233
|
if (changesApplied) {
|
|
2051
2234
|
mergeSummary = hadAnyChanges ? "\n\nApplied patches: yes" : "\n\nNo changes to apply.";
|
|
2052
2235
|
} else {
|
|
@@ -2063,21 +2246,37 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2063
2246
|
const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
|
|
2064
2247
|
changesApplied = false;
|
|
2065
2248
|
hadAnyChanges = false;
|
|
2249
|
+
mergePhaseFailed = true;
|
|
2250
|
+
for (const result of results) {
|
|
2251
|
+
if (result.producedChanges || result.recoveryRef || result.nestedPatches?.length) {
|
|
2252
|
+
setRecoveryAvailable(result);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2066
2255
|
mergeSummary = `\n\n<system-notification>Merge phase failed: ${msg}\nTask outputs are preserved but changes were not applied.</system-notification>`;
|
|
2067
2256
|
}
|
|
2068
2257
|
}
|
|
2069
2258
|
|
|
2070
2259
|
// Apply nested repo patches (separate from parent git)
|
|
2071
|
-
if (isIsolated && repoRoot && (mergeMode === "branch" || changesApplied !== false)) {
|
|
2260
|
+
if (isIsolated && repoRoot && !mergePhaseFailed && (mergeMode === "branch" || changesApplied !== false)) {
|
|
2072
2261
|
const allNestedPatches = results
|
|
2073
2262
|
.filter(r => {
|
|
2074
|
-
if (
|
|
2263
|
+
if (
|
|
2264
|
+
!r.nestedPatches ||
|
|
2265
|
+
r.nestedPatches.length === 0 ||
|
|
2266
|
+
r.exitCode !== 0 ||
|
|
2267
|
+
r.error ||
|
|
2268
|
+
r.aborted ||
|
|
2269
|
+
r.paused
|
|
2270
|
+
) {
|
|
2075
2271
|
return false;
|
|
2076
2272
|
}
|
|
2077
2273
|
if (mergeMode !== "branch") {
|
|
2078
2274
|
return true;
|
|
2079
2275
|
}
|
|
2080
|
-
if (!r.branchName
|
|
2276
|
+
if (!r.branchName) {
|
|
2277
|
+
return true;
|
|
2278
|
+
}
|
|
2279
|
+
if (!mergedBranchesForNestedPatches) {
|
|
2081
2280
|
return false;
|
|
2082
2281
|
}
|
|
2083
2282
|
return mergedBranchesForNestedPatches.has(r.branchName);
|
|
@@ -2085,32 +2284,46 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
2085
2284
|
.flatMap(r => r.nestedPatches!);
|
|
2086
2285
|
if (allNestedPatches.length > 0) {
|
|
2087
2286
|
try {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2287
|
+
await applyNestedPatches(repoRoot, allNestedPatches);
|
|
2288
|
+
if (!baseline || !(await verifyNestedPatchesApplied(repoRoot, baseline, allNestedPatches))) {
|
|
2289
|
+
throw new Error("Nested repository persistence proof failed");
|
|
2290
|
+
}
|
|
2291
|
+
for (const result of results) {
|
|
2292
|
+
const rootApplied =
|
|
2293
|
+
mergeMode !== "branch"
|
|
2294
|
+
? changesApplied !== false
|
|
2295
|
+
: !result.branchName || Boolean(mergedBranchesForNestedPatches?.has(result.branchName));
|
|
2296
|
+
if (
|
|
2297
|
+
result.nestedPatches?.length &&
|
|
2298
|
+
result.exitCode === 0 &&
|
|
2299
|
+
!result.error &&
|
|
2300
|
+
!result.aborted &&
|
|
2301
|
+
!result.paused &&
|
|
2302
|
+
rootApplied
|
|
2303
|
+
) {
|
|
2304
|
+
result.persistence = {
|
|
2305
|
+
outcome: "applied",
|
|
2306
|
+
ownerWorktreeApplied: true,
|
|
2307
|
+
recoveryRef: result.recoveryRef,
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2100
2311
|
} catch {
|
|
2101
|
-
|
|
2312
|
+
changesApplied = false;
|
|
2313
|
+
for (const result of results) {
|
|
2314
|
+
if (result.nestedPatches?.length) setRecoveryAvailable(result);
|
|
2315
|
+
}
|
|
2102
2316
|
mergeSummary +=
|
|
2103
|
-
"\n\n<system-notification>Some nested repository patches failed to apply.</system-notification>";
|
|
2317
|
+
"\n\n<system-notification>Some nested repository patches failed to apply; affected tasks remain recovery-only.</system-notification>";
|
|
2104
2318
|
}
|
|
2105
2319
|
}
|
|
2106
2320
|
}
|
|
2107
2321
|
|
|
2108
2322
|
// Build final output - match plugin format
|
|
2109
|
-
const cancelledCount = results.filter(r => r.aborted).length;
|
|
2110
|
-
const successCount = results.filter(r => r.exitCode === 0 && !r.error && !r.aborted).length;
|
|
2111
2323
|
const totalDuration = Date.now() - startTime;
|
|
2112
|
-
|
|
2113
2324
|
const receipts = results.map(buildTaskReceipt);
|
|
2325
|
+
const cancelledCount = receipts.filter(r => r.status === "aborted").length;
|
|
2326
|
+
const successCount = receipts.filter(r => r.status === "completed").length;
|
|
2114
2327
|
const roiSummary = buildTaskRoiSummary(receipts);
|
|
2115
2328
|
const roiReconciliation = executionOverrides?.suppressRoiReconciliation
|
|
2116
2329
|
? undefined
|
|
@@ -3,6 +3,7 @@ import type { AgentProgress, TaskToolDetails } from "./types";
|
|
|
3
3
|
export type ProviderRetryKind = NonNullable<AgentProgress["retryState"]>["kind"];
|
|
4
4
|
|
|
5
5
|
const FIRST_EVENT_TIMEOUT_PATTERN = /stream timed out while waiting for the first event/i;
|
|
6
|
+
const FIRST_EVENT_TIMEOUT_WITHOUT_ARTICLE_MESSAGE = "Provider stream timed out while waiting for first event";
|
|
6
7
|
const IDLE_STREAM_STALL_PATTERN = /stream stalled while waiting for the next event/i;
|
|
7
8
|
const STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE = "stream_first_event_timeout";
|
|
8
9
|
|
|
@@ -22,7 +23,9 @@ export function classifyProviderRetryFromTransport(facts: {
|
|
|
22
23
|
return "first_event_timeout";
|
|
23
24
|
}
|
|
24
25
|
const errorMessage = facts.errorMessage ?? "";
|
|
25
|
-
if (FIRST_EVENT_TIMEOUT_PATTERN.test(errorMessage))
|
|
26
|
+
if (FIRST_EVENT_TIMEOUT_PATTERN.test(errorMessage) || errorMessage === FIRST_EVENT_TIMEOUT_WITHOUT_ARTICLE_MESSAGE) {
|
|
27
|
+
return "first_event_timeout";
|
|
28
|
+
}
|
|
26
29
|
if (IDLE_STREAM_STALL_PATTERN.test(errorMessage)) return "idle_stream_stall";
|
|
27
30
|
return "provider_error";
|
|
28
31
|
}
|
package/src/task/receipt.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface TaskResultReceipt {
|
|
|
39
39
|
cost?: number;
|
|
40
40
|
usageCostBreakdownComplete?: true;
|
|
41
41
|
branchName?: string;
|
|
42
|
+
persistence?: SingleResult["persistence"];
|
|
42
43
|
retryFailure?: { attempt: number; errorSummary: string };
|
|
43
44
|
setupFailure?: { summary: string };
|
|
44
45
|
errorSummary?: string;
|
|
@@ -66,6 +67,9 @@ export interface TaskResultReceipt {
|
|
|
66
67
|
forkContextAdvisory?: SingleResult["forkContextAdvisory"];
|
|
67
68
|
/** Resolved repository identity for this delegated lane (#2901). */
|
|
68
69
|
repositoryBinding?: SingleResult["repositoryBinding"];
|
|
70
|
+
/** Runtime-derived review source disposition and rerun guidance (#3469). */
|
|
71
|
+
reviewSource?: SingleResult["reviewSource"];
|
|
72
|
+
|
|
69
73
|
roi?: TaskRoi;
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -128,6 +132,18 @@ function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outp
|
|
|
128
132
|
if (raw.retryFailure) {
|
|
129
133
|
return `Task ${status}; retry stopped after attempt ${raw.retryFailure.attempt}.`;
|
|
130
134
|
}
|
|
135
|
+
if (raw.persistence?.outcome === "recovery_available") {
|
|
136
|
+
const recovery = raw.persistence.recoveryRef
|
|
137
|
+
? ` Recovery patch: ${raw.persistence.recoveryRef.uri} (${raw.persistence.recoveryRef.sizeBytes} bytes).`
|
|
138
|
+
: "";
|
|
139
|
+
return `Task ${status}; changes were not persisted to the owner worktree.${recovery}`;
|
|
140
|
+
}
|
|
141
|
+
if (raw.persistence?.outcome === "applied") {
|
|
142
|
+
return `Task ${status}; changes persisted to the owner worktree.`;
|
|
143
|
+
}
|
|
144
|
+
if (raw.persistence?.outcome === "no_changes") {
|
|
145
|
+
return `Task ${status}; no changes to persist.`;
|
|
146
|
+
}
|
|
131
147
|
if (raw.abortReason) {
|
|
132
148
|
return `Task ${status}; abort reason recorded.`;
|
|
133
149
|
}
|
|
@@ -143,7 +159,7 @@ function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outp
|
|
|
143
159
|
function getStatus(raw: SingleResult): TaskResultReceipt["status"] {
|
|
144
160
|
if (raw.paused) return "paused";
|
|
145
161
|
if (raw.aborted) return "aborted";
|
|
146
|
-
if (raw.exitCode === 0 && raw.error) return "merge_failed";
|
|
162
|
+
if (raw.exitCode === 0 && (raw.error || raw.persistence?.outcome === "recovery_available")) return "merge_failed";
|
|
147
163
|
if (raw.exitCode !== 0 || raw.error) return "failed";
|
|
148
164
|
return "completed";
|
|
149
165
|
}
|
|
@@ -275,11 +291,18 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
275
291
|
usageCostBreakdownComplete:
|
|
276
292
|
raw.usageCostBreakdownComplete === true && hasCompleteUsageCostBreakdown(raw.usage) ? true : undefined,
|
|
277
293
|
branchName: raw.branchName,
|
|
294
|
+
persistence: raw.persistence,
|
|
278
295
|
fastMode: raw.fastMode,
|
|
279
296
|
retryFailure: raw.retryFailure
|
|
280
297
|
? { attempt: raw.retryFailure.attempt, errorSummary: "Retry failure recorded." }
|
|
281
298
|
: undefined,
|
|
282
|
-
errorSummary:
|
|
299
|
+
errorSummary:
|
|
300
|
+
raw.setupFailure?.summary ??
|
|
301
|
+
(raw.error
|
|
302
|
+
? "Error recorded."
|
|
303
|
+
: raw.persistence?.outcome === "recovery_available"
|
|
304
|
+
? "Changes were not persisted to the owner worktree."
|
|
305
|
+
: undefined),
|
|
283
306
|
setupFailure: raw.setupFailure ? { summary: raw.setupFailure.summary } : undefined,
|
|
284
307
|
abortSummary: raw.abortReason ? "Abort reason recorded." : undefined,
|
|
285
308
|
preview,
|
|
@@ -291,6 +314,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
|
291
314
|
forkContext: raw.forkContext,
|
|
292
315
|
forkContextAdvisory: raw.forkContextAdvisory,
|
|
293
316
|
repositoryBinding: raw.repositoryBinding,
|
|
317
|
+
reviewSource: raw.reviewSource,
|
|
294
318
|
roi: buildTaskRoi(raw),
|
|
295
319
|
};
|
|
296
320
|
}
|