@11agents/cli 0.1.18 → 0.1.20

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 CHANGED
@@ -125,17 +125,29 @@ export async function handleRuntimeTask(task) {
125
125
  }
126
126
  ```
127
127
 
128
- ## MCP Project Sync Server
129
-
130
- Run the local MCP server over stdio:
131
-
132
- ```bash
133
- 11agents mcp start
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
- Configure your MCP client to run that command. The server automatically reads matching project tokens from `~/.11agents/credentials`; a tool-call token can still be passed explicitly when needed. The server exposes:
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 knowledge sync
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@11agents/cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "11agents local runtime and telemetry CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { createHash } from 'node:crypto'
3
3
  import { appendFileSync, readFileSync } from 'node:fs'
4
- import { appendFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
4
+ import { appendFile, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
5
5
  import os from 'node:os'
6
6
  import { dirname, resolve } from 'node:path'
7
7
  import path from 'node:path'
@@ -86,27 +86,49 @@ function runtimeDeps(overrides = {}) {
86
86
  }
87
87
  }
88
88
 
89
- function currentClaimPath(homeDir) {
89
+ function legacyClaimPath(homeDir) {
90
90
  return path.join(homeDir, '.11agents', 'claim_id')
91
91
  }
92
92
 
93
+ function claimsDirPath(homeDir) {
94
+ return path.join(homeDir, '.11agents', 'claims')
95
+ }
96
+
97
+ function claimFilePath(homeDir, taskId) {
98
+ return path.join(claimsDirPath(homeDir), `${sanitizeTaskId(taskId)}.json`)
99
+ }
100
+
93
101
  async function writeCurrentClaim(deps, task, machineKey) {
94
102
  const payload = {
95
103
  task_id: String(task.id || ''),
96
104
  runtime_id: String(task.runtime_id || task.runtime?.id || ''),
97
105
  machine_key: String(task.runtime?.machine_key || machineKey || ''),
98
106
  }
99
- await mkdir(path.dirname(currentClaimPath(deps.homeDir)), { recursive: true })
100
- await writeFile(currentClaimPath(deps.homeDir), JSON.stringify(payload))
107
+ await mkdir(claimsDirPath(deps.homeDir), { recursive: true })
108
+ await writeFile(claimFilePath(deps.homeDir, task.id), JSON.stringify(payload))
101
109
  return payload
102
110
  }
103
111
 
104
- async function readCurrentClaim(deps) {
105
- return readJsonFile(currentClaimPath(deps.homeDir), null)
112
+ async function clearCurrentClaim(deps, taskId) {
113
+ await rm(claimFilePath(deps.homeDir, taskId), { force: true })
106
114
  }
107
115
 
108
- async function clearCurrentClaim(deps) {
109
- await rm(currentClaimPath(deps.homeDir), { force: true })
116
+ async function readAllCurrentClaims(deps) {
117
+ const claims = []
118
+ // Backward compat: consume legacy single-claim file
119
+ const legacy = await readJsonFile(legacyClaimPath(deps.homeDir), null)
120
+ if (legacy?.task_id && legacy?.runtime_id) {
121
+ claims.push({ ...legacy, _path: legacyClaimPath(deps.homeDir) })
122
+ }
123
+ // Per-task claim files
124
+ const entries = await readdir(claimsDirPath(deps.homeDir)).catch(err => (err?.code === 'ENOENT' ? [] : Promise.reject(err)))
125
+ for (const entry of entries) {
126
+ if (!entry.endsWith('.json')) continue
127
+ const filePath = path.join(claimsDirPath(deps.homeDir), entry)
128
+ const claim = await readJsonFile(filePath, null)
129
+ if (claim?.task_id && claim?.runtime_id) claims.push({ ...claim, _path: filePath })
130
+ }
131
+ return claims
110
132
  }
111
133
 
112
134
  function errorMessage(error) {
@@ -339,15 +361,20 @@ function codexAssistantTextFromJsonl(text) {
339
361
  return messages.join('\n\n')
340
362
  }
341
363
 
342
- async function failPersistedCurrentClaim(flags, deps, comment) {
343
- const claim = await readCurrentClaim(deps)
344
- if (!claim?.task_id || !claim?.runtime_id) return false
345
- await deps.requestJson('/api/runtime/tasks/complete', {
346
- method: 'POST',
347
- body: failedClaimCompletionBody(claim, comment),
348
- config: configFromFlags(flags),
349
- })
350
- await clearCurrentClaim(deps)
364
+ async function failAllPersistedClaims(flags, deps, comment) {
365
+ const claims = await readAllCurrentClaims(deps)
366
+ if (!claims.length) return false
367
+ const config = configFromFlags(flags)
368
+ for (const claim of claims) {
369
+ try {
370
+ await deps.requestJson('/api/runtime/tasks/complete', {
371
+ method: 'POST',
372
+ body: failedClaimCompletionBody(claim, comment),
373
+ config,
374
+ })
375
+ } catch {}
376
+ await rm(claim._path, { force: true })
377
+ }
351
378
  return true
352
379
  }
353
380
 
@@ -357,14 +384,14 @@ function installCurrentClaimExitHandlers(flags, deps) {
357
384
  if (shuttingDown) return
358
385
  shuttingDown = true
359
386
  try {
360
- await failPersistedCurrentClaim(
387
+ await failAllPersistedClaims(
361
388
  flags,
362
389
  deps,
363
390
  `Runtime task failed locally: daemon received ${signal} before the claimed task completed.`
364
391
  )
365
392
  } catch (error) {
366
393
  deps.log(JSON.stringify({
367
- warning: 'failed to mark current claimed task failed during daemon shutdown',
394
+ warning: 'failed to mark current claimed tasks failed during daemon shutdown',
368
395
  signal,
369
396
  error: errorMessage(error),
370
397
  }, null, 2))
@@ -908,8 +935,8 @@ async function prepareRuntimeTask(task, flags, deps, config) {
908
935
  const database = await syncDatabaseIfNeeded({ task, workdir, config, flags, deps })
909
936
  const skills = await materializeSkillsIfChanged({ task, workdir, flags, deps })
910
937
 
911
- // Resolve and inject the project token so MCP subprocesses (e.g. `11agents mcp start`)
912
- // spawned by the runtime agent can authenticate without needing credentials on disk.
938
+ // Resolve and inject the project token so hosted MCP calls spawned by the
939
+ // runtime agent can authenticate without needing credentials on disk.
913
940
  const projectToken = await projectSyncToken(projectTokenCandidatesForTask(task, flags), flags, deps)
914
941
  const env = {
915
942
  ...process.env,
@@ -1228,156 +1255,160 @@ function defaultTaskHandler(flags, deps) {
1228
1255
  }
1229
1256
  }
1230
1257
 
1231
- async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule, retryState = createRetryState(), heartbeatIntervalMs = 15000) {
1232
- if (!handlerModule) return 0
1233
- const config = configFromFlags(flags)
1234
- const machineKey = registration?.machine?.machine_key || machineOverride(flags) || ''
1235
- let handled = 0
1236
-
1237
- for (const runtime of registration?.runtimes || []) {
1238
- if (!runtime?.id) continue
1239
- const claim = await runWithDaemonRetry('claim runtime task', () => (
1240
- deps.requestJson('/api/runtime/tasks/claim', {
1241
- method: 'POST',
1242
- body: {
1243
- runtime_id: runtime.id,
1244
- machine_key: machineKey,
1245
- },
1246
- config,
1247
- })
1248
- ), deps, retryState)
1249
- const task = claim?.task
1250
- if (!task) continue
1251
-
1252
- const runtimeTask = {
1253
- ...task,
1254
- runtime_id: task.runtime_id || runtime.id,
1255
- runtime: {
1256
- provider: runtime.provider,
1257
- model: runtime.model || '',
1258
- ...(task.runtime || {}),
1259
- id: task.runtime?.id || runtime.id,
1260
- machine_key: task.runtime?.machine_key || machineKey,
1258
+ async function runOneRuntimeTaskSlot(runtime, config, machineKey, registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs) {
1259
+ const claim = await runWithDaemonRetry('claim runtime task', () => (
1260
+ deps.requestJson('/api/runtime/tasks/claim', {
1261
+ method: 'POST',
1262
+ body: {
1263
+ runtime_id: runtime.id,
1264
+ machine_key: machineKey,
1261
1265
  },
1266
+ config,
1267
+ })
1268
+ ), deps, retryState)
1269
+ const task = claim?.task
1270
+ if (!task) return false
1271
+
1272
+ const runtimeTask = {
1273
+ ...task,
1274
+ runtime_id: task.runtime_id || runtime.id,
1275
+ runtime: {
1276
+ provider: runtime.provider,
1277
+ model: runtime.model || '',
1278
+ ...(task.runtime || {}),
1279
+ id: task.runtime?.id || runtime.id,
1280
+ machine_key: task.runtime?.machine_key || machineKey,
1281
+ },
1282
+ }
1283
+
1284
+ deps.log(JSON.stringify({ claimed: runtimeTask.id, runtime_id: runtime.id }, null, 2))
1285
+ await writeCurrentClaim(deps, runtimeTask, machineKey)
1286
+ let completion = null
1287
+ let executionContext = null
1288
+ if (runtimeTask.workspace?.slug) {
1289
+ const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
1290
+ const syncConfig = { ...config, token }
1291
+ try {
1292
+ await runWithTaskRetry('sync knowledge base', () => (
1293
+ deps.syncKnowledge({
1294
+ project: runtimeTask.workspace.slug,
1295
+ mode: 'pull',
1296
+ server: flags.server,
1297
+ token,
1298
+ }, {
1299
+ requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
1300
+ log: () => {},
1301
+ })
1302
+ ), deps)
1303
+ } catch (error) {
1304
+ completion = {
1305
+ comment: `Knowledge sync pull failed before task execution: ${errorMessage(error)}`,
1306
+ status: 'failed',
1307
+ }
1308
+ }
1309
+ }
1310
+ if (!completion) {
1311
+ executionContext = await prepareRuntimeTask(runtimeTask, flags, deps, config)
1312
+ runtimeTask.execution_context = executionContext
1313
+ await updateRunMeta(executionContext.run_dir, {
1314
+ task_id: String(runtimeTask.id || ''),
1315
+ runtime_id: String(runtimeTask.runtime_id || ''),
1316
+ provider: runtimeTask.runtime?.provider || runtime.provider || '',
1317
+ project_slug: executionContext.project_slug,
1318
+ agent: agentNameForTask(runtimeTask),
1319
+ issue_title: runtimeTask.issue?.title || '',
1320
+ started_at: new Date().toISOString(),
1321
+ })
1322
+ try {
1323
+ completion = await runWithRuntimeHeartbeat(
1324
+ () => handlerModule.handleRuntimeTask(runtimeTask),
1325
+ registration,
1326
+ flags,
1327
+ deps,
1328
+ heartbeatIntervalMs
1329
+ )
1330
+ } catch (error) {
1331
+ completion = {
1332
+ comment: error instanceof Error ? error.message : String(error),
1333
+ status: 'failed',
1334
+ }
1335
+ } finally {
1336
+ await rm(executionContext.tmp_dir, { recursive: true, force: true })
1262
1337
  }
1338
+ }
1339
+ if (executionContext) {
1340
+ await writeRunFile(executionContext.run_dir, 'completion.json', JSON.stringify(normalizeTaskCompletion(runtimeTask, completion), null, 2))
1341
+ await updateRunMeta(executionContext.run_dir, { ended_at: new Date().toISOString() })
1342
+ }
1343
+ if (executionContext) {
1344
+ await appendTaskMemoryDelta({ task: runtimeTask, completion, workdir: executionContext.workdir })
1345
+ }
1263
1346
 
1264
- deps.log(JSON.stringify({ claimed: runtimeTask.id, runtime_id: runtime.id }, null, 2))
1265
- await writeCurrentClaim(deps, runtimeTask, machineKey)
1266
- let completion = null
1267
- let executionContext = null
1268
- if (runtimeTask.workspace?.slug) {
1269
- const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
1270
- const syncConfig = { ...config, token }
1347
+ if (executionContext && runtimeTask.workspace?.slug) {
1348
+ const isDeepOrganize = Boolean(knowledgeDeepOrganizeSpec(runtimeTask))
1349
+ const syncBack = isDeepOrganize ? deps.mcpKnowledgeSync : deps.syncKnowledge
1350
+ const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
1351
+ const syncConfig = { ...config, token }
1352
+ const pushKnowledge = () => syncBack({
1353
+ project: runtimeTask.workspace.slug,
1354
+ mode: 'push',
1355
+ server: flags.server,
1356
+ token,
1357
+ }, {
1358
+ requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
1359
+ log: () => {},
1360
+ })
1361
+ if (isDeepOrganize) {
1271
1362
  try {
1272
- await runWithTaskRetry('sync knowledge base', () => (
1273
- deps.syncKnowledge({
1274
- project: runtimeTask.workspace.slug,
1275
- mode: 'pull',
1276
- server: flags.server,
1277
- token,
1278
- }, {
1279
- requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
1280
- log: () => {},
1281
- })
1282
- ), deps)
1363
+ await pushKnowledge()
1283
1364
  } catch (error) {
1365
+ 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.`
1284
1366
  completion = {
1285
- comment: `Knowledge sync pull failed before task execution: ${errorMessage(error)}`,
1286
- status: 'failed',
1367
+ ...(completion && typeof completion === 'object' ? completion : {}),
1368
+ comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
1369
+ memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
1287
1370
  }
1288
1371
  }
1289
- }
1290
- if (!completion) {
1291
- executionContext = await prepareRuntimeTask(runtimeTask, flags, deps, config)
1292
- runtimeTask.execution_context = executionContext
1293
- await updateRunMeta(executionContext.run_dir, {
1294
- task_id: String(runtimeTask.id || ''),
1295
- runtime_id: String(runtimeTask.runtime_id || ''),
1296
- provider: runtimeTask.runtime?.provider || runtime.provider || '',
1297
- project_slug: executionContext.project_slug,
1298
- agent: agentNameForTask(runtimeTask),
1299
- issue_title: runtimeTask.issue?.title || '',
1300
- started_at: new Date().toISOString(),
1301
- })
1372
+ } else {
1302
1373
  try {
1303
- completion = await runWithRuntimeHeartbeat(
1304
- () => handlerModule.handleRuntimeTask(runtimeTask),
1305
- registration,
1306
- flags,
1307
- deps,
1308
- heartbeatIntervalMs
1309
- )
1374
+ await runWithTaskRetry('sync knowledge base back to cloud', pushKnowledge, deps)
1310
1375
  } catch (error) {
1376
+ const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}`
1311
1377
  completion = {
1312
- comment: error instanceof Error ? error.message : String(error),
1378
+ ...(completion && typeof completion === 'object' ? completion : {}),
1379
+ comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
1380
+ memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
1313
1381
  status: 'failed',
1314
1382
  }
1315
- } finally {
1316
- await rm(executionContext.tmp_dir, { recursive: true, force: true })
1317
1383
  }
1318
1384
  }
1319
- if (executionContext) {
1320
- await writeRunFile(executionContext.run_dir, 'completion.json', JSON.stringify(normalizeTaskCompletion(runtimeTask, completion), null, 2))
1321
- await updateRunMeta(executionContext.run_dir, { ended_at: new Date().toISOString() })
1322
- }
1323
- if (executionContext) {
1324
- await appendTaskMemoryDelta({ task: runtimeTask, completion, workdir: executionContext.workdir })
1325
- }
1385
+ }
1326
1386
 
1327
- if (executionContext && runtimeTask.workspace?.slug) {
1328
- const isDeepOrganize = Boolean(knowledgeDeepOrganizeSpec(runtimeTask))
1329
- const syncBack = isDeepOrganize ? deps.mcpKnowledgeSync : deps.syncKnowledge
1330
- const token = await projectSyncToken(projectTokenCandidatesForTask(runtimeTask, flags), flags, deps)
1331
- const syncConfig = { ...config, token }
1332
- const pushKnowledge = () => syncBack({
1333
- project: runtimeTask.workspace.slug,
1334
- mode: 'push',
1335
- server: flags.server,
1336
- token,
1337
- }, {
1338
- requestJson: (apiPath, options = {}) => deps.requestJson(apiPath, { ...options, config: syncConfig }),
1339
- log: () => {},
1340
- })
1341
- if (isDeepOrganize) {
1342
- try {
1343
- await pushKnowledge()
1344
- } catch (error) {
1345
- 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.`
1346
- completion = {
1347
- ...(completion && typeof completion === 'object' ? completion : {}),
1348
- comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
1349
- memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
1350
- }
1351
- }
1352
- } else {
1353
- try {
1354
- await runWithTaskRetry('sync knowledge base back to cloud', pushKnowledge, deps)
1355
- } catch (error) {
1356
- const message = `Knowledge sync push failed before task completion: ${errorMessage(error)}`
1357
- completion = {
1358
- ...(completion && typeof completion === 'object' ? completion : {}),
1359
- comment: [String(completion?.comment || completion?.summary || '').trim(), message].filter(Boolean).join('\n\n'),
1360
- memory_delta: [String(completion?.memory_delta || completion?.memoryDelta || '').trim(), message].filter(Boolean).join('\n'),
1361
- status: 'failed',
1362
- }
1363
- }
1364
- }
1365
- }
1387
+ const body = normalizeTaskCompletion(runtimeTask, completion)
1388
+ const result = await runWithDaemonRetry('complete runtime task', () => (
1389
+ deps.requestJson('/api/runtime/tasks/complete', {
1390
+ method: 'POST',
1391
+ body,
1392
+ config,
1393
+ })
1394
+ ), deps, retryState)
1395
+ await clearCurrentClaim(deps, runtimeTask.id)
1396
+ deps.log(JSON.stringify(result, null, 2))
1397
+ return true
1398
+ }
1366
1399
 
1367
- const body = normalizeTaskCompletion(runtimeTask, completion)
1368
- const result = await runWithDaemonRetry('complete runtime task', () => (
1369
- deps.requestJson('/api/runtime/tasks/complete', {
1370
- method: 'POST',
1371
- body,
1372
- config,
1373
- })
1374
- ), deps, retryState)
1375
- await clearCurrentClaim(deps)
1376
- deps.log(JSON.stringify(result, null, 2))
1377
- handled += 1
1378
- }
1400
+ async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule, retryState = createRetryState(), heartbeatIntervalMs = 15000, maxConcurrent = 1) {
1401
+ if (!handlerModule) return 0
1402
+ const runtimes = (registration?.runtimes || []).filter(r => r?.id)
1403
+ if (!runtimes.length) return 0
1404
+ const config = configFromFlags(flags)
1405
+ const machineKey = registration?.machine?.machine_key || machineOverride(flags) || ''
1379
1406
 
1380
- return handled
1407
+ const slots = Array.from({ length: maxConcurrent }, (_, i) => runtimes[i % runtimes.length])
1408
+ const results = await Promise.allSettled(
1409
+ slots.map(runtime => runOneRuntimeTaskSlot(runtime, config, machineKey, registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs))
1410
+ )
1411
+ return results.filter(r => r.status === 'fulfilled' && r.value === true).length
1381
1412
  }
1382
1413
 
1383
1414
  export async function startRuntimeDaemon(flags = {}, deps = {}) {
@@ -1386,6 +1417,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
1386
1417
  const scanIntervalMs = Number(flag(flags, 'scan-interval', '60')) * 1000
1387
1418
  const taskIntervalMs = Number(flag(flags, 'task-interval', flag(flags, 'heartbeat-interval', '15'))) * 1000
1388
1419
  const projectRefreshIntervalMs = Number(flag(flags, 'project-refresh-interval', '1800')) * 1000
1420
+ const maxConcurrent = Math.max(1, Number(flag(flags, 'concurrency', '1')) || 1)
1389
1421
  const once = Boolean(flags.once)
1390
1422
  const handlerPath = flag(flags, 'handler')
1391
1423
 
@@ -1407,13 +1439,13 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
1407
1439
  const uninstallExitHandlers = installCurrentClaimExitHandlers(flags, resolvedDeps)
1408
1440
  try {
1409
1441
  let registration = await runWithDaemonRetry('register runtime', () => registerRuntime(flags, resolvedDeps), resolvedDeps, retryState)
1410
- await runWithDaemonRetry('recover current claimed task', () => failPersistedCurrentClaim(
1442
+ await runWithDaemonRetry('recover current claimed task', () => failAllPersistedClaims(
1411
1443
  flags,
1412
1444
  resolvedDeps,
1413
1445
  'Runtime task failed locally: daemon restarted with a persisted claimed task that had not completed.'
1414
1446
  ), resolvedDeps, retryState)
1415
1447
  await syncRuntimeProjectMetadataBestEffort(flags, resolvedDeps)
1416
- await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs)
1448
+ await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
1417
1449
  if (once) return
1418
1450
 
1419
1451
  let lastScan = Date.now()
@@ -1432,7 +1464,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
1432
1464
  lastHeartbeat = now
1433
1465
  }
1434
1466
  if (now - lastTaskPoll >= taskIntervalMs) {
1435
- await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs)
1467
+ await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
1436
1468
  lastTaskPoll = now
1437
1469
  }
1438
1470
  if (now - lastProjectRefresh >= projectRefreshIntervalMs) {
@@ -1442,7 +1474,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
1442
1474
  }
1443
1475
  } catch (error) {
1444
1476
  try {
1445
- await failPersistedCurrentClaim(
1477
+ await failAllPersistedClaims(
1446
1478
  flags,
1447
1479
  resolvedDeps,
1448
1480
  `Runtime task failed locally: daemon exited with error before the claimed task completed: ${errorMessage(error)}`