@11agents/cli 0.1.17 → 0.1.19
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 +19 -7
- package/bin/11agents.js +3 -3
- package/package.json +1 -1
- package/src/commands/runtime.js +178 -141
package/README.md
CHANGED
|
@@ -125,17 +125,29 @@ export async function handleRuntimeTask(task) {
|
|
|
125
125
|
}
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
## MCP
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
|
|
128
|
+
## Hosted MCP
|
|
129
|
+
|
|
130
|
+
Runtime agents and MCP clients should use the hosted project MCP endpoint:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"11agents-project-sync": {
|
|
136
|
+
"url": "https://app.11agents.ai/mcp",
|
|
137
|
+
"transport": "streamable-http",
|
|
138
|
+
"headers": {
|
|
139
|
+
"Authorization": "Bearer $PROJECT_SWARM_TOKEN"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
134
144
|
```
|
|
135
145
|
|
|
136
|
-
|
|
146
|
+
The hosted server exposes project-scoped tools such as:
|
|
137
147
|
|
|
138
148
|
- `knowledge_sync` — pull/push the project knowledge base between cloud and `~/.11agents/<project>/knowledge_base/`.
|
|
149
|
+
- `database_sync` — pull/push the cloud project database snapshot.
|
|
150
|
+
- `agent_planning` — list, import, generate, and dispatch agent calendar work.
|
|
139
151
|
|
|
140
152
|
## Telemetry Compatibility
|
|
141
153
|
|
package/bin/11agents.js
CHANGED
|
@@ -21,13 +21,12 @@ Usage:
|
|
|
21
21
|
11agents -v | --version
|
|
22
22
|
11agents runtime scan
|
|
23
23
|
11agents runtime register [--server <url>] [--token <token>] [--machine <key>]
|
|
24
|
-
11agents daemon start [--server <url>] [--token <token>] [--machine <key>] [--task-interval <seconds>] [--project-refresh-interval <seconds>] [--background]
|
|
24
|
+
11agents daemon start [--server <url>] [--token <token>] [--machine <key>] [--concurrency <n>] [--task-interval <seconds>] [--project-refresh-interval <seconds>] [--background]
|
|
25
25
|
11agents daemon status
|
|
26
26
|
11agents daemon stop
|
|
27
27
|
11agents daemon start --handler ./worker.js # optional custom worker override
|
|
28
28
|
11agents logs daemon [--tail 200]
|
|
29
29
|
11agents logs task <task-id> [--project <slug>] [--tail 120]
|
|
30
|
-
11agents mcp start
|
|
31
30
|
11agents validate <file>
|
|
32
31
|
11agents push batch <file>
|
|
33
32
|
11agents push artifact --workspace <slug> --agent <key> --platform x --type post --external-id <id>
|
|
@@ -42,7 +41,7 @@ Environment:
|
|
|
42
41
|
GTM_WRITES_TOKEN control-plane token for runtime registration
|
|
43
42
|
ELEVENAGENTS_MACHINE stable machine key, defaults to hostname
|
|
44
43
|
GTM_SWARM_TOKEN project swarm token for push/node commands
|
|
45
|
-
~/.11agents/credentials project token map for MCP
|
|
44
|
+
~/.11agents/credentials project token map for hosted MCP and sync commands
|
|
46
45
|
|
|
47
46
|
Runtime task workspace:
|
|
48
47
|
Daemon project headquarters live under ~/.11agents/<project>/.
|
|
@@ -99,6 +98,7 @@ async function main() {
|
|
|
99
98
|
console.log(`log: ${result.logPath}`)
|
|
100
99
|
return
|
|
101
100
|
}
|
|
101
|
+
if (flags.concurrency === undefined) flags.concurrency = '3'
|
|
102
102
|
await startRuntimeDaemon(flags)
|
|
103
103
|
return
|
|
104
104
|
}
|
package/package.json
CHANGED
package/src/commands/runtime.js
CHANGED
|
@@ -772,7 +772,15 @@ async function materializeSkillsIfChanged({ task, workdir, flags, deps }) {
|
|
|
772
772
|
const statePath = path.join(skillsDir, 'skills-state.json')
|
|
773
773
|
const nextHash = stableHash(skills)
|
|
774
774
|
const current = await readJsonFile(statePath, {})
|
|
775
|
-
if (current.hash === nextHash)
|
|
775
|
+
if (current.hash === nextHash) {
|
|
776
|
+
const allExist = (await Promise.all(
|
|
777
|
+
skills.map(skill =>
|
|
778
|
+
readFile(path.join(skillsDir, slugify(skill.name, 'skill'), 'SKILL.md'), 'utf8')
|
|
779
|
+
.then(() => true).catch(() => false)
|
|
780
|
+
)
|
|
781
|
+
)).every(Boolean)
|
|
782
|
+
if (allExist) return { changed: false, count: skills.length }
|
|
783
|
+
}
|
|
776
784
|
|
|
777
785
|
await mkdir(skillsDir, { recursive: true })
|
|
778
786
|
for (const skill of skills) {
|
|
@@ -886,21 +894,31 @@ function agentEnvironment(task) {
|
|
|
886
894
|
|
|
887
895
|
async function prepareRuntimeTask(task, flags, deps, config) {
|
|
888
896
|
const workdir = flag(flags, 'codex-workdir') || projectDirForTask(task, flags, deps)
|
|
889
|
-
const
|
|
897
|
+
const agentDir = path.join(workdir, 'agents', slugify(agentNameForTask(task), 'agent'))
|
|
898
|
+
const agentSkillsDir = path.join(agentDir, 'skills')
|
|
899
|
+
const agentMemoryDir = path.join(agentDir, 'memory')
|
|
900
|
+
const agentResultsDir = path.join(agentDir, 'results')
|
|
901
|
+
const tmpDir = path.join(agentDir, 'tmp', sanitizeTaskId(task.id))
|
|
890
902
|
const runDir = path.join(workdir, 'runs', sanitizeTaskId(task.id))
|
|
891
903
|
await mkdir(tmpDir, { recursive: true })
|
|
892
904
|
await mkdir(runDir, { recursive: true })
|
|
905
|
+
await mkdir(agentResultsDir, { recursive: true })
|
|
906
|
+
await mkdir(agentMemoryDir, { recursive: true })
|
|
893
907
|
|
|
894
908
|
const database = await syncDatabaseIfNeeded({ task, workdir, config, flags, deps })
|
|
895
909
|
const skills = await materializeSkillsIfChanged({ task, workdir, flags, deps })
|
|
896
910
|
|
|
897
|
-
// Resolve and inject the project token so MCP
|
|
898
|
-
//
|
|
911
|
+
// Resolve and inject the project token so hosted MCP calls spawned by the
|
|
912
|
+
// runtime agent can authenticate without needing credentials on disk.
|
|
899
913
|
const projectToken = await projectSyncToken(projectTokenCandidatesForTask(task, flags), flags, deps)
|
|
900
914
|
const env = {
|
|
901
915
|
...process.env,
|
|
902
916
|
...agentEnvironment(task),
|
|
903
917
|
ELEVENAGENTS_PROJECT_DIR: workdir,
|
|
918
|
+
ELEVENAGENTS_AGENT_DIR: agentDir,
|
|
919
|
+
ELEVENAGENTS_AGENT_SKILLS_DIR: agentSkillsDir,
|
|
920
|
+
ELEVENAGENTS_AGENT_MEMORY_DIR: agentMemoryDir,
|
|
921
|
+
ELEVENAGENTS_AGENT_RESULTS_DIR: agentResultsDir,
|
|
904
922
|
ELEVENAGENTS_TASK_TMP: tmpDir,
|
|
905
923
|
ELEVENAGENTS_TASK_ID: String(task.id || ''),
|
|
906
924
|
...(projectToken ? { GTM_SWARM_TOKEN: projectToken } : {}),
|
|
@@ -908,6 +926,10 @@ async function prepareRuntimeTask(task, flags, deps, config) {
|
|
|
908
926
|
|
|
909
927
|
return {
|
|
910
928
|
workdir,
|
|
929
|
+
agent_dir: agentDir,
|
|
930
|
+
agent_skills_dir: agentSkillsDir,
|
|
931
|
+
agent_memory_dir: agentMemoryDir,
|
|
932
|
+
agent_results_dir: agentResultsDir,
|
|
911
933
|
tmp_dir: tmpDir,
|
|
912
934
|
run_dir: runDir,
|
|
913
935
|
project_slug: projectSlugForTask(task, flags),
|
|
@@ -926,13 +948,23 @@ function buildCodexPrompt(task) {
|
|
|
926
948
|
'Execution workspace:',
|
|
927
949
|
compactJson({
|
|
928
950
|
workdir: task.execution_context?.workdir,
|
|
951
|
+
agent_skills_dir: task.execution_context?.agent_skills_dir,
|
|
952
|
+
agent_memory_dir: task.execution_context?.agent_memory_dir,
|
|
953
|
+
agent_results_dir: task.execution_context?.agent_results_dir,
|
|
929
954
|
tmp_dir: task.execution_context?.tmp_dir,
|
|
930
955
|
project_slug: task.execution_context?.project_slug,
|
|
931
|
-
knowledge_base: './knowledge_base
|
|
932
|
-
rule: 'Treat the project directory as read-only project context except ./knowledge_base/ for durable project knowledge updates and ./tmp/<taskId>/ for temporary scratch files.',
|
|
933
|
-
cleanup: 'Temporary files under ./tmp/<taskId>/ are removed by the CLI after the task finishes.',
|
|
956
|
+
knowledge_base: task.execution_context?.workdir ? path.join(task.execution_context.workdir, 'knowledge_base') : './knowledge_base',
|
|
934
957
|
}),
|
|
935
958
|
'',
|
|
959
|
+
'Directory rules — follow strictly:',
|
|
960
|
+
'1. agent_skills_dir: BEFORE starting work, list files here to see which skills are installed.',
|
|
961
|
+
' Each skill is a subdirectory with a SKILL.md. If a skill you need is present, read its SKILL.md and follow it.',
|
|
962
|
+
'2. agent_results_dir: Write all final deliverables and task outputs here (generated content, reports, files).',
|
|
963
|
+
'3. agent_memory_dir: After completing work, record key decisions, facts, and learned context here.',
|
|
964
|
+
' Append to index.qmd or create new .qmd files. This memory persists across future task runs.',
|
|
965
|
+
'4. tmp_dir: Scratch/working files only. This directory is deleted by the CLI after the task finishes — do NOT put deliverables here.',
|
|
966
|
+
'5. workdir: Treat as read-only project context except for the four agent directories and knowledge_base above.',
|
|
967
|
+
'',
|
|
936
968
|
'Task context:',
|
|
937
969
|
compactJson({
|
|
938
970
|
queue_event: task.queue_event,
|
|
@@ -1196,156 +1228,160 @@ function defaultTaskHandler(flags, deps) {
|
|
|
1196
1228
|
}
|
|
1197
1229
|
}
|
|
1198
1230
|
|
|
1199
|
-
async function
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
if (!runtime?.id) continue
|
|
1207
|
-
const claim = await runWithDaemonRetry('claim runtime task', () => (
|
|
1208
|
-
deps.requestJson('/api/runtime/tasks/claim', {
|
|
1209
|
-
method: 'POST',
|
|
1210
|
-
body: {
|
|
1211
|
-
runtime_id: runtime.id,
|
|
1212
|
-
machine_key: machineKey,
|
|
1213
|
-
},
|
|
1214
|
-
config,
|
|
1215
|
-
})
|
|
1216
|
-
), deps, retryState)
|
|
1217
|
-
const task = claim?.task
|
|
1218
|
-
if (!task) continue
|
|
1219
|
-
|
|
1220
|
-
const runtimeTask = {
|
|
1221
|
-
...task,
|
|
1222
|
-
runtime_id: task.runtime_id || runtime.id,
|
|
1223
|
-
runtime: {
|
|
1224
|
-
provider: runtime.provider,
|
|
1225
|
-
model: runtime.model || '',
|
|
1226
|
-
...(task.runtime || {}),
|
|
1227
|
-
id: task.runtime?.id || runtime.id,
|
|
1228
|
-
machine_key: task.runtime?.machine_key || machineKey,
|
|
1231
|
+
async function runOneRuntimeTaskSlot(runtime, config, machineKey, registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs) {
|
|
1232
|
+
const claim = await runWithDaemonRetry('claim runtime task', () => (
|
|
1233
|
+
deps.requestJson('/api/runtime/tasks/claim', {
|
|
1234
|
+
method: 'POST',
|
|
1235
|
+
body: {
|
|
1236
|
+
runtime_id: runtime.id,
|
|
1237
|
+
machine_key: machineKey,
|
|
1229
1238
|
},
|
|
1239
|
+
config,
|
|
1240
|
+
})
|
|
1241
|
+
), deps, retryState)
|
|
1242
|
+
const task = claim?.task
|
|
1243
|
+
if (!task) return false
|
|
1244
|
+
|
|
1245
|
+
const runtimeTask = {
|
|
1246
|
+
...task,
|
|
1247
|
+
runtime_id: task.runtime_id || runtime.id,
|
|
1248
|
+
runtime: {
|
|
1249
|
+
provider: runtime.provider,
|
|
1250
|
+
model: runtime.model || '',
|
|
1251
|
+
...(task.runtime || {}),
|
|
1252
|
+
id: task.runtime?.id || runtime.id,
|
|
1253
|
+
machine_key: task.runtime?.machine_key || machineKey,
|
|
1254
|
+
},
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
deps.log(JSON.stringify({ claimed: runtimeTask.id, runtime_id: runtime.id }, null, 2))
|
|
1258
|
+
await writeCurrentClaim(deps, runtimeTask, machineKey)
|
|
1259
|
+
let completion = null
|
|
1260
|
+
let executionContext = null
|
|
1261
|
+
if (runtimeTask.workspace?.slug) {
|
|
1262
|
+
const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
|
|
1263
|
+
const syncConfig = { ...config, token }
|
|
1264
|
+
try {
|
|
1265
|
+
await runWithTaskRetry('sync knowledge base', () => (
|
|
1266
|
+
deps.syncKnowledge({
|
|
1267
|
+
project: runtimeTask.workspace.slug,
|
|
1268
|
+
mode: 'pull',
|
|
1269
|
+
server: flags.server,
|
|
1270
|
+
token,
|
|
1271
|
+
}, {
|
|
1272
|
+
requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
|
|
1273
|
+
log: () => {},
|
|
1274
|
+
})
|
|
1275
|
+
), deps)
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
completion = {
|
|
1278
|
+
comment: `Knowledge sync pull failed before task execution: ${errorMessage(error)}`,
|
|
1279
|
+
status: 'failed',
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (!completion) {
|
|
1284
|
+
executionContext = await prepareRuntimeTask(runtimeTask, flags, deps, config)
|
|
1285
|
+
runtimeTask.execution_context = executionContext
|
|
1286
|
+
await updateRunMeta(executionContext.run_dir, {
|
|
1287
|
+
task_id: String(runtimeTask.id || ''),
|
|
1288
|
+
runtime_id: String(runtimeTask.runtime_id || ''),
|
|
1289
|
+
provider: runtimeTask.runtime?.provider || runtime.provider || '',
|
|
1290
|
+
project_slug: executionContext.project_slug,
|
|
1291
|
+
agent: agentNameForTask(runtimeTask),
|
|
1292
|
+
issue_title: runtimeTask.issue?.title || '',
|
|
1293
|
+
started_at: new Date().toISOString(),
|
|
1294
|
+
})
|
|
1295
|
+
try {
|
|
1296
|
+
completion = await runWithRuntimeHeartbeat(
|
|
1297
|
+
() => handlerModule.handleRuntimeTask(runtimeTask),
|
|
1298
|
+
registration,
|
|
1299
|
+
flags,
|
|
1300
|
+
deps,
|
|
1301
|
+
heartbeatIntervalMs
|
|
1302
|
+
)
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
completion = {
|
|
1305
|
+
comment: error instanceof Error ? error.message : String(error),
|
|
1306
|
+
status: 'failed',
|
|
1307
|
+
}
|
|
1308
|
+
} finally {
|
|
1309
|
+
await rm(executionContext.tmp_dir, { recursive: true, force: true })
|
|
1230
1310
|
}
|
|
1311
|
+
}
|
|
1312
|
+
if (executionContext) {
|
|
1313
|
+
await writeRunFile(executionContext.run_dir, 'completion.json', JSON.stringify(normalizeTaskCompletion(runtimeTask, completion), null, 2))
|
|
1314
|
+
await updateRunMeta(executionContext.run_dir, { ended_at: new Date().toISOString() })
|
|
1315
|
+
}
|
|
1316
|
+
if (executionContext) {
|
|
1317
|
+
await appendTaskMemoryDelta({ task: runtimeTask, completion, workdir: executionContext.workdir })
|
|
1318
|
+
}
|
|
1231
1319
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1320
|
+
if (executionContext && runtimeTask.workspace?.slug) {
|
|
1321
|
+
const isDeepOrganize = Boolean(knowledgeDeepOrganizeSpec(runtimeTask))
|
|
1322
|
+
const syncBack = isDeepOrganize ? deps.mcpKnowledgeSync : deps.syncKnowledge
|
|
1323
|
+
const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
|
|
1324
|
+
const syncConfig = { ...config, token }
|
|
1325
|
+
const pushKnowledge = () => syncBack({
|
|
1326
|
+
project: runtimeTask.workspace.slug,
|
|
1327
|
+
mode: 'push',
|
|
1328
|
+
server: flags.server,
|
|
1329
|
+
token,
|
|
1330
|
+
}, {
|
|
1331
|
+
requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
|
|
1332
|
+
log: () => {},
|
|
1333
|
+
})
|
|
1334
|
+
if (isDeepOrganize) {
|
|
1239
1335
|
try {
|
|
1240
|
-
await
|
|
1241
|
-
deps.syncKnowledge({
|
|
1242
|
-
project: runtimeTask.workspace.slug,
|
|
1243
|
-
mode: 'pull',
|
|
1244
|
-
server: flags.server,
|
|
1245
|
-
token,
|
|
1246
|
-
}, {
|
|
1247
|
-
requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
|
|
1248
|
-
log: () => {},
|
|
1249
|
-
})
|
|
1250
|
-
), deps)
|
|
1336
|
+
await pushKnowledge()
|
|
1251
1337
|
} catch (error) {
|
|
1338
|
+
const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}. If a knowledge_snapshot is included in completion, the platform will write it directly.`
|
|
1252
1339
|
completion = {
|
|
1253
|
-
|
|
1254
|
-
|
|
1340
|
+
...(completion && typeof completion === 'object' ? completion : {}),
|
|
1341
|
+
comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
|
|
1342
|
+
memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
|
|
1255
1343
|
}
|
|
1256
1344
|
}
|
|
1257
|
-
}
|
|
1258
|
-
if (!completion) {
|
|
1259
|
-
executionContext = await prepareRuntimeTask(runtimeTask, flags, deps, config)
|
|
1260
|
-
runtimeTask.execution_context = executionContext
|
|
1261
|
-
await updateRunMeta(executionContext.run_dir, {
|
|
1262
|
-
task_id: String(runtimeTask.id || ''),
|
|
1263
|
-
runtime_id: String(runtimeTask.runtime_id || ''),
|
|
1264
|
-
provider: runtimeTask.runtime?.provider || runtime.provider || '',
|
|
1265
|
-
project_slug: executionContext.project_slug,
|
|
1266
|
-
agent: agentNameForTask(runtimeTask),
|
|
1267
|
-
issue_title: runtimeTask.issue?.title || '',
|
|
1268
|
-
started_at: new Date().toISOString(),
|
|
1269
|
-
})
|
|
1345
|
+
} else {
|
|
1270
1346
|
try {
|
|
1271
|
-
|
|
1272
|
-
() => handlerModule.handleRuntimeTask(runtimeTask),
|
|
1273
|
-
registration,
|
|
1274
|
-
flags,
|
|
1275
|
-
deps,
|
|
1276
|
-
heartbeatIntervalMs
|
|
1277
|
-
)
|
|
1347
|
+
await runWithTaskRetry('sync knowledge base back to cloud', pushKnowledge, deps)
|
|
1278
1348
|
} catch (error) {
|
|
1349
|
+
const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}`
|
|
1279
1350
|
completion = {
|
|
1280
|
-
|
|
1351
|
+
...(completion && typeof completion === 'object' ? completion : {}),
|
|
1352
|
+
comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
|
|
1353
|
+
memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
|
|
1281
1354
|
status: 'failed',
|
|
1282
1355
|
}
|
|
1283
|
-
} finally {
|
|
1284
|
-
await rm(executionContext.tmp_dir, { recursive: true, force: true })
|
|
1285
1356
|
}
|
|
1286
1357
|
}
|
|
1287
|
-
|
|
1288
|
-
await writeRunFile(executionContext.run_dir, 'completion.json', JSON.stringify(normalizeTaskCompletion(runtimeTask, completion), null, 2))
|
|
1289
|
-
await updateRunMeta(executionContext.run_dir, { ended_at: new Date().toISOString() })
|
|
1290
|
-
}
|
|
1291
|
-
if (executionContext) {
|
|
1292
|
-
await appendTaskMemoryDelta({ task: runtimeTask, completion, workdir: executionContext.workdir })
|
|
1293
|
-
}
|
|
1358
|
+
}
|
|
1294
1359
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
log: () => {},
|
|
1308
|
-
})
|
|
1309
|
-
if (isDeepOrganize) {
|
|
1310
|
-
try {
|
|
1311
|
-
await pushKnowledge()
|
|
1312
|
-
} catch (error) {
|
|
1313
|
-
const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}. If a knowledge_snapshot is included in completion, the platform will write it directly.`
|
|
1314
|
-
completion = {
|
|
1315
|
-
...(completion && typeof completion === 'object' ? completion : {}),
|
|
1316
|
-
comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
|
|
1317
|
-
memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
} else {
|
|
1321
|
-
try {
|
|
1322
|
-
await runWithTaskRetry('sync knowledge base back to cloud', pushKnowledge, deps)
|
|
1323
|
-
} catch (error) {
|
|
1324
|
-
const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}`
|
|
1325
|
-
completion = {
|
|
1326
|
-
...(completion && typeof completion === 'object' ? completion : {}),
|
|
1327
|
-
comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
|
|
1328
|
-
memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
|
|
1329
|
-
status: 'failed',
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1360
|
+
const body = normalizeTaskCompletion(runtimeTask, completion)
|
|
1361
|
+
const result = await runWithDaemonRetry('complete runtime task', () => (
|
|
1362
|
+
deps.requestJson('/api/runtime/tasks/complete', {
|
|
1363
|
+
method: 'POST',
|
|
1364
|
+
body,
|
|
1365
|
+
config,
|
|
1366
|
+
})
|
|
1367
|
+
), deps, retryState)
|
|
1368
|
+
await clearCurrentClaim(deps)
|
|
1369
|
+
deps.log(JSON.stringify(result, null, 2))
|
|
1370
|
+
return true
|
|
1371
|
+
}
|
|
1334
1372
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
})
|
|
1342
|
-
), deps, retryState)
|
|
1343
|
-
await clearCurrentClaim(deps)
|
|
1344
|
-
deps.log(JSON.stringify(result, null, 2))
|
|
1345
|
-
handled += 1
|
|
1346
|
-
}
|
|
1373
|
+
async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule, retryState = createRetryState(), heartbeatIntervalMs = 15000, maxConcurrent = 1) {
|
|
1374
|
+
if (!handlerModule) return 0
|
|
1375
|
+
const runtimes = (registration?.runtimes || []).filter(r => r?.id)
|
|
1376
|
+
if (!runtimes.length) return 0
|
|
1377
|
+
const config = configFromFlags(flags)
|
|
1378
|
+
const machineKey = registration?.machine?.machine_key || machineOverride(flags) || ''
|
|
1347
1379
|
|
|
1348
|
-
|
|
1380
|
+
const slots = Array.from({ length: maxConcurrent }, (_, i) => runtimes[i % runtimes.length])
|
|
1381
|
+
const results = await Promise.allSettled(
|
|
1382
|
+
slots.map(runtime => runOneRuntimeTaskSlot(runtime, config, machineKey, registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs))
|
|
1383
|
+
)
|
|
1384
|
+
return results.filter(r => r.status === 'fulfilled' && r.value === true).length
|
|
1349
1385
|
}
|
|
1350
1386
|
|
|
1351
1387
|
export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
@@ -1354,6 +1390,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1354
1390
|
const scanIntervalMs = Number(flag(flags, 'scan-interval', '60')) * 1000
|
|
1355
1391
|
const taskIntervalMs = Number(flag(flags, 'task-interval', flag(flags, 'heartbeat-interval', '15'))) * 1000
|
|
1356
1392
|
const projectRefreshIntervalMs = Number(flag(flags, 'project-refresh-interval', '1800')) * 1000
|
|
1393
|
+
const maxConcurrent = Math.max(1, Number(flag(flags, 'concurrency', '1')) || 1)
|
|
1357
1394
|
const once = Boolean(flags.once)
|
|
1358
1395
|
const handlerPath = flag(flags, 'handler')
|
|
1359
1396
|
|
|
@@ -1381,7 +1418,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1381
1418
|
'Runtime task failed locally: daemon restarted with a persisted claimed task that had not completed.'
|
|
1382
1419
|
), resolvedDeps, retryState)
|
|
1383
1420
|
await syncRuntimeProjectMetadataBestEffort(flags, resolvedDeps)
|
|
1384
|
-
await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs)
|
|
1421
|
+
await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1385
1422
|
if (once) return
|
|
1386
1423
|
|
|
1387
1424
|
let lastScan = Date.now()
|
|
@@ -1400,7 +1437,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1400
1437
|
lastHeartbeat = now
|
|
1401
1438
|
}
|
|
1402
1439
|
if (now - lastTaskPoll >= taskIntervalMs) {
|
|
1403
|
-
await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs)
|
|
1440
|
+
await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1404
1441
|
lastTaskPoll = now
|
|
1405
1442
|
}
|
|
1406
1443
|
if (now - lastProjectRefresh >= projectRefreshIntervalMs) {
|