@kkelly-offical/kkcode 0.1.3 → 0.1.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/README.md +110 -172
- package/package.json +46 -46
- package/src/agent/agent.mjs +41 -0
- package/src/agent/prompt/frontend-designer.txt +58 -0
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -0
- package/src/agent/prompt/longagent-coding-agent.txt +37 -0
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -0
- package/src/agent/prompt/longagent-preview-agent.txt +63 -0
- package/src/config/defaults.mjs +260 -195
- package/src/config/schema.mjs +71 -6
- package/src/core/constants.mjs +91 -46
- package/src/index.mjs +1 -1
- package/src/knowledge/frontend-aesthetics.txt +39 -0
- package/src/knowledge/loader.mjs +2 -1
- package/src/knowledge/tailwind.txt +12 -3
- package/src/mcp/client-http.mjs +141 -157
- package/src/mcp/client-sse.mjs +288 -286
- package/src/mcp/client-stdio.mjs +533 -451
- package/src/mcp/constants.mjs +2 -0
- package/src/mcp/registry.mjs +479 -394
- package/src/mcp/stdio-framing.mjs +133 -127
- package/src/mcp/tool-result.mjs +24 -0
- package/src/observability/index.mjs +42 -0
- package/src/observability/metrics.mjs +137 -0
- package/src/observability/tracer.mjs +137 -0
- package/src/orchestration/background-manager.mjs +372 -358
- package/src/orchestration/background-worker.mjs +305 -245
- package/src/orchestration/longagent-manager.mjs +171 -116
- package/src/orchestration/stage-scheduler.mjs +728 -489
- package/src/permission/exec-policy.mjs +9 -11
- package/src/provider/anthropic.mjs +1 -0
- package/src/provider/openai.mjs +340 -339
- package/src/provider/retry-policy.mjs +68 -68
- package/src/provider/router.mjs +241 -228
- package/src/provider/sse.mjs +104 -91
- package/src/repl.mjs +1 -1
- package/src/session/checkpoint.mjs +66 -3
- package/src/session/engine.mjs +227 -225
- package/src/session/longagent-4stage.mjs +460 -0
- package/src/session/longagent-hybrid.mjs +1081 -0
- package/src/session/longagent-plan.mjs +365 -329
- package/src/session/longagent-project-memory.mjs +53 -0
- package/src/session/longagent-scaffold.mjs +291 -100
- package/src/session/longagent-task-bus.mjs +54 -0
- package/src/session/longagent-utils.mjs +472 -0
- package/src/session/longagent.mjs +884 -1462
- package/src/session/project-context.mjs +30 -0
- package/src/session/store.mjs +510 -503
- package/src/session/task-validator.mjs +4 -3
- package/src/skill/builtin/design.mjs +76 -0
- package/src/skill/builtin/frontend.mjs +8 -0
- package/src/skill/registry.mjs +390 -336
- package/src/storage/ghost-commit-store.mjs +18 -8
- package/src/tool/executor.mjs +11 -0
- package/src/tool/git-auto.mjs +0 -19
- package/src/tool/registry.mjs +71 -37
- package/src/ui/activity-renderer.mjs +664 -410
- package/src/util/git.mjs +23 -0
|
@@ -1,245 +1,305 @@
|
|
|
1
|
-
import { appendFile } from "node:fs/promises"
|
|
2
|
-
import { readJson, writeJson } from "../storage/json-store.mjs"
|
|
3
|
-
import { ensureBackgroundTaskRuntimeDir, backgroundTaskCheckpointPath, backgroundTaskLogPath } from "../storage/paths.mjs"
|
|
4
|
-
import { buildContext } from "../context.mjs"
|
|
5
|
-
import { ToolRegistry } from "../tool/registry.mjs"
|
|
6
|
-
import { executeTurn } from "../session/engine.mjs"
|
|
7
|
-
|
|
8
|
-
function now() {
|
|
9
|
-
return Date.now()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function argValue(flag) {
|
|
13
|
-
const idx = process.argv.indexOf(flag)
|
|
14
|
-
if (idx < 0) return null
|
|
15
|
-
return process.argv[idx + 1] || null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeAbortError(reason = "aborted") {
|
|
19
|
-
const err = new Error(reason)
|
|
20
|
-
err.code = "ABORT_ERR"
|
|
21
|
-
return err
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isAbortError(error) {
|
|
25
|
-
return error?.code === "ABORT_ERR" || error?.name === "AbortError"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function readTask(taskId) {
|
|
29
|
-
return readJson(backgroundTaskCheckpointPath(taskId), null)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function patchTask(taskId, updater) {
|
|
33
|
-
const current = await readTask(taskId)
|
|
34
|
-
if (!current) return null
|
|
35
|
-
const next = {
|
|
36
|
-
...current,
|
|
37
|
-
...updater(current),
|
|
38
|
-
updatedAt: now()
|
|
39
|
-
}
|
|
40
|
-
await writeJson(backgroundTaskCheckpointPath(taskId), next)
|
|
41
|
-
return next
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
1
|
+
import { appendFile } from "node:fs/promises"
|
|
2
|
+
import { readJson, writeJson } from "../storage/json-store.mjs"
|
|
3
|
+
import { ensureBackgroundTaskRuntimeDir, backgroundTaskCheckpointPath, backgroundTaskLogPath } from "../storage/paths.mjs"
|
|
4
|
+
import { buildContext } from "../context.mjs"
|
|
5
|
+
import { ToolRegistry } from "../tool/registry.mjs"
|
|
6
|
+
import { executeTurn } from "../session/engine.mjs"
|
|
7
|
+
|
|
8
|
+
function now() {
|
|
9
|
+
return Date.now()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function argValue(flag) {
|
|
13
|
+
const idx = process.argv.indexOf(flag)
|
|
14
|
+
if (idx < 0) return null
|
|
15
|
+
return process.argv[idx + 1] || null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeAbortError(reason = "aborted") {
|
|
19
|
+
const err = new Error(reason)
|
|
20
|
+
err.code = "ABORT_ERR"
|
|
21
|
+
return err
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isAbortError(error) {
|
|
25
|
+
return error?.code === "ABORT_ERR" || error?.name === "AbortError"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function readTask(taskId) {
|
|
29
|
+
return readJson(backgroundTaskCheckpointPath(taskId), null)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function patchTask(taskId, updater) {
|
|
33
|
+
const current = await readTask(taskId)
|
|
34
|
+
if (!current) return null
|
|
35
|
+
const next = {
|
|
36
|
+
...current,
|
|
37
|
+
...updater(current),
|
|
38
|
+
updatedAt: now()
|
|
39
|
+
}
|
|
40
|
+
await writeJson(backgroundTaskCheckpointPath(taskId), next)
|
|
41
|
+
return next
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let _maxLogLines = 300
|
|
45
|
+
|
|
46
|
+
async function appendTaskLog(taskId, line) {
|
|
47
|
+
await appendFile(backgroundTaskLogPath(taskId), `${line}\n`, "utf8")
|
|
48
|
+
await patchTask(taskId, (current) => ({
|
|
49
|
+
logs: [...(current.logs || []), String(line)].slice(-_maxLogLines),
|
|
50
|
+
lastHeartbeatAt: now()
|
|
51
|
+
}))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runDelegateTask(task, signal) {
|
|
55
|
+
const payload = task.payload || {}
|
|
56
|
+
const cwd = payload.cwd || process.cwd()
|
|
57
|
+
process.chdir(cwd)
|
|
58
|
+
|
|
59
|
+
const ctx = await buildContext({ cwd })
|
|
60
|
+
_maxLogLines = Number(ctx.configState.config?.background?.max_log_lines || 300)
|
|
61
|
+
await ToolRegistry.initialize({
|
|
62
|
+
config: ctx.configState.config,
|
|
63
|
+
cwd
|
|
64
|
+
})
|
|
65
|
+
const { CustomAgentRegistry } = await import("../agent/custom-agent-loader.mjs")
|
|
66
|
+
await CustomAgentRegistry.initialize(cwd)
|
|
67
|
+
|
|
68
|
+
const providerType = payload.providerType || ctx.configState.config.provider.default
|
|
69
|
+
const providerDefault = ctx.configState.config.provider[providerType]
|
|
70
|
+
const model = payload.model || providerDefault?.default_model
|
|
71
|
+
|
|
72
|
+
const out = await executeTurn({
|
|
73
|
+
prompt: String(payload.prompt || ""),
|
|
74
|
+
mode: "agent",
|
|
75
|
+
model,
|
|
76
|
+
providerType,
|
|
77
|
+
sessionId: payload.subSessionId,
|
|
78
|
+
configState: ctx.configState,
|
|
79
|
+
signal,
|
|
80
|
+
allowQuestion: payload.allowQuestion !== true ? false : true,
|
|
81
|
+
toolContext: {
|
|
82
|
+
taskId: task.id,
|
|
83
|
+
stageId: payload.stageId || null,
|
|
84
|
+
logicalTaskId: payload.logicalTaskId || null
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const plannedFiles = Array.isArray(payload.plannedFiles)
|
|
89
|
+
? payload.plannedFiles.map((item) => String(item || "").trim()).filter(Boolean)
|
|
90
|
+
: []
|
|
91
|
+
const completedFilesFromTools = out.toolEvents
|
|
92
|
+
.filter((event) => ["write", "edit"].includes(event.name) && event.status === "completed")
|
|
93
|
+
.map((event) => {
|
|
94
|
+
const p = event.args?.path
|
|
95
|
+
return p ? String(p).trim() : ""
|
|
96
|
+
})
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
|
|
99
|
+
const fileChanges = out.toolEvents
|
|
100
|
+
.flatMap((event) => Array.isArray(event?.metadata?.fileChanges) ? event.metadata.fileChanges : [])
|
|
101
|
+
.map((item) => ({
|
|
102
|
+
path: String(item?.path || "").trim(),
|
|
103
|
+
addedLines: Math.max(0, Number(item?.addedLines || 0)),
|
|
104
|
+
removedLines: Math.max(0, Number(item?.removedLines || 0)),
|
|
105
|
+
stageId: item?.stageId ? String(item.stageId) : (payload.stageId || ""),
|
|
106
|
+
taskId: item?.taskId ? String(item.taskId) : (payload.logicalTaskId || "")
|
|
107
|
+
}))
|
|
108
|
+
.filter((item) => item.path)
|
|
109
|
+
|
|
110
|
+
const completedFileSet = new Set(
|
|
111
|
+
completedFilesFromTools.filter((file) => plannedFiles.length === 0 || plannedFiles.includes(file))
|
|
112
|
+
)
|
|
113
|
+
const completedFiles = [...completedFileSet]
|
|
114
|
+
const remainingFiles = plannedFiles.filter((file) => !completedFileSet.has(file))
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
session_id: payload.subSessionId,
|
|
118
|
+
parent_session_id: payload.parentSessionId || null,
|
|
119
|
+
subagent: payload.subagent || null,
|
|
120
|
+
reply: out.reply,
|
|
121
|
+
tool_events: out.toolEvents?.length || 0,
|
|
122
|
+
completed_files: completedFiles,
|
|
123
|
+
remaining_files: remainingFiles,
|
|
124
|
+
file_changes: fileChanges,
|
|
125
|
+
cost: out.cost,
|
|
126
|
+
budget_warnings: out.budgetWarnings || []
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const SILENT_ERROR_PATTERNS = [
|
|
131
|
+
/provider[\s._-]*error/i,
|
|
132
|
+
/api[\s._-]*timeout/i,
|
|
133
|
+
/rate[\s._-]?limit/i,
|
|
134
|
+
/\b(429|503|502|500)\b/,
|
|
135
|
+
/missing api key/i,
|
|
136
|
+
/stream idle timeout/i,
|
|
137
|
+
/\b(econnreset|econnrefused|etimedout)\b/i,
|
|
138
|
+
/budget exceeded/i
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
function detectSilentError(result, payload) {
|
|
142
|
+
const reply = String(result?.reply || "")
|
|
143
|
+
const toolEvents = Number(result?.tool_events || 0)
|
|
144
|
+
const plannedFiles = Array.isArray(payload?.plannedFiles) ? payload.plannedFiles : []
|
|
145
|
+
const completedFiles = Array.isArray(result?.completed_files) ? result.completed_files : []
|
|
146
|
+
const remainingFiles = Array.isArray(result?.remaining_files) ? result.remaining_files : []
|
|
147
|
+
|
|
148
|
+
// Guard: tasks without plannedFiles (review/analysis) skip all detection
|
|
149
|
+
if (plannedFiles.length === 0) return { hasError: false, errorMessage: "" }
|
|
150
|
+
|
|
151
|
+
// Guard: [TASK_COMPLETE] marker present — trust the agent's self-report
|
|
152
|
+
if (reply.toLowerCase().includes("[task_complete]")) return { hasError: false, errorMessage: "" }
|
|
153
|
+
|
|
154
|
+
// Guard: has tool activity and substantial reply — likely real work done
|
|
155
|
+
if (toolEvents > 0 && reply.length >= 200) return { hasError: false, errorMessage: "" }
|
|
156
|
+
|
|
157
|
+
// Pattern matching: known provider error signatures in reply
|
|
158
|
+
for (const pattern of SILENT_ERROR_PATTERNS) {
|
|
159
|
+
if (pattern.test(reply)) {
|
|
160
|
+
return { hasError: true, errorMessage: `silent provider error detected: ${reply.slice(0, 200)}` }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Heuristic: planned files exist but none completed, low activity
|
|
165
|
+
if (completedFiles.length === 0
|
|
166
|
+
&& remainingFiles.length === plannedFiles.length
|
|
167
|
+
&& (reply.length < 200 || toolEvents === 0)) {
|
|
168
|
+
return { hasError: true, errorMessage: `heuristic: no files completed, no tool activity (reply ${reply.length} chars, ${toolEvents} tool events)` }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { hasError: false, errorMessage: "" }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function main() {
|
|
175
|
+
const taskId = argValue("--task-id") || process.env.KKCODE_BACKGROUND_TASK_ID || null
|
|
176
|
+
if (!taskId) {
|
|
177
|
+
process.exit(1)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await ensureBackgroundTaskRuntimeDir()
|
|
182
|
+
const task = await readTask(taskId)
|
|
183
|
+
if (!task) {
|
|
184
|
+
process.exit(1)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (task.cancelled) {
|
|
189
|
+
await patchTask(taskId, () => ({
|
|
190
|
+
status: "cancelled",
|
|
191
|
+
endedAt: now()
|
|
192
|
+
}))
|
|
193
|
+
process.exit(0)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await patchTask(taskId, () => ({
|
|
198
|
+
status: "running",
|
|
199
|
+
workerPid: process.pid,
|
|
200
|
+
startedAt: now(),
|
|
201
|
+
lastHeartbeatAt: now()
|
|
202
|
+
}))
|
|
203
|
+
|
|
204
|
+
const abortController = new AbortController()
|
|
205
|
+
const parentPid = process.ppid
|
|
206
|
+
const heartbeatTimer = setInterval(() => {
|
|
207
|
+
patchTask(taskId, () => ({ lastHeartbeatAt: now() })).catch(() => {})
|
|
208
|
+
}, 2000)
|
|
209
|
+
|
|
210
|
+
const cancelPoll = setInterval(() => {
|
|
211
|
+
// Orphan detection: if parent process died, self-terminate
|
|
212
|
+
try { process.kill(parentPid, 0) } catch {
|
|
213
|
+
if (!abortController.signal.aborted) {
|
|
214
|
+
abortController.abort(makeAbortError("parent process exited, worker orphaned"))
|
|
215
|
+
}
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
readTask(taskId).then((latest) => {
|
|
219
|
+
if (latest?.cancelled && !abortController.signal.aborted) {
|
|
220
|
+
abortController.abort(makeAbortError("cancelled by user"))
|
|
221
|
+
}
|
|
222
|
+
}).catch(() => {})
|
|
223
|
+
}, 1500)
|
|
224
|
+
|
|
225
|
+
const timeoutMs = Math.max(1000, Number(task.payload?.workerTimeoutMs || 900000))
|
|
226
|
+
const timeoutTimer = setTimeout(() => {
|
|
227
|
+
if (!abortController.signal.aborted) {
|
|
228
|
+
abortController.abort(makeAbortError(`worker timeout after ${timeoutMs}ms`))
|
|
229
|
+
}
|
|
230
|
+
}, timeoutMs)
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await appendTaskLog(taskId, `task started (worker pid=${process.pid})`)
|
|
234
|
+
|
|
235
|
+
const latest = await readTask(taskId)
|
|
236
|
+
if (!latest?.payload?.workerType || latest.payload.workerType !== "delegate_task") {
|
|
237
|
+
throw new Error(`unsupported workerType: ${latest?.payload?.workerType || "unknown"}`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const result = await runDelegateTask(latest, abortController.signal)
|
|
241
|
+
const silentCheck = detectSilentError(result, latest.payload)
|
|
242
|
+
if (silentCheck.hasError) {
|
|
243
|
+
await appendTaskLog(taskId, `silent error detected: ${silentCheck.errorMessage}`)
|
|
244
|
+
await patchTask(taskId, () => ({
|
|
245
|
+
status: "error",
|
|
246
|
+
result,
|
|
247
|
+
error: silentCheck.errorMessage,
|
|
248
|
+
endedAt: now(),
|
|
249
|
+
lastHeartbeatAt: now()
|
|
250
|
+
}))
|
|
251
|
+
process.exit(1)
|
|
252
|
+
} else {
|
|
253
|
+
await appendTaskLog(taskId, "task completed")
|
|
254
|
+
await patchTask(taskId, () => ({
|
|
255
|
+
status: "completed",
|
|
256
|
+
result,
|
|
257
|
+
error: null,
|
|
258
|
+
endedAt: now(),
|
|
259
|
+
lastHeartbeatAt: now()
|
|
260
|
+
}))
|
|
261
|
+
process.exit(0)
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const latest = await readTask(taskId)
|
|
265
|
+
const cancelled = latest?.cancelled
|
|
266
|
+
const aborted = isAbortError(error)
|
|
267
|
+
if (cancelled) {
|
|
268
|
+
await appendTaskLog(taskId, "task cancelled")
|
|
269
|
+
await patchTask(taskId, () => ({
|
|
270
|
+
status: "cancelled",
|
|
271
|
+
endedAt: now(),
|
|
272
|
+
error: null
|
|
273
|
+
}))
|
|
274
|
+
process.exit(0)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (aborted) {
|
|
279
|
+
await appendTaskLog(taskId, `task interrupted: ${error.message}`)
|
|
280
|
+
await patchTask(taskId, () => ({
|
|
281
|
+
status: "interrupted",
|
|
282
|
+
error: error.message,
|
|
283
|
+
endedAt: now()
|
|
284
|
+
}))
|
|
285
|
+
process.exit(2)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await appendTaskLog(taskId, `task error: ${error.message}`)
|
|
290
|
+
await patchTask(taskId, () => ({
|
|
291
|
+
status: "error",
|
|
292
|
+
error: error.message,
|
|
293
|
+
endedAt: now()
|
|
294
|
+
}))
|
|
295
|
+
process.exit(1)
|
|
296
|
+
} finally {
|
|
297
|
+
clearInterval(heartbeatTimer)
|
|
298
|
+
clearInterval(cancelPoll)
|
|
299
|
+
clearTimeout(timeoutTimer)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
main().catch(() => {
|
|
304
|
+
process.exit(1)
|
|
305
|
+
})
|