@11agents/cli 0.1.21 → 0.1.23
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/package.json +1 -1
- package/src/commands/runtime.js +81 -5
package/package.json
CHANGED
package/src/commands/runtime.js
CHANGED
|
@@ -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, readdir, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { appendFile, mkdir, readFile, readdir, rm, stat, 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'
|
|
@@ -312,6 +312,63 @@ function normalizeTaskCompletion(task, completion) {
|
|
|
312
312
|
return body
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
function runtimeAssetContentType(filePath) {
|
|
316
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
317
|
+
if (ext === '.html') return 'text/html; charset=utf-8'
|
|
318
|
+
if (ext === '.png') return 'image/png'
|
|
319
|
+
if (ext === '.webp') return 'image/webp'
|
|
320
|
+
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg'
|
|
321
|
+
if (ext === '.mp4') return 'video/mp4'
|
|
322
|
+
if (ext === '.md') return 'text/markdown; charset=utf-8'
|
|
323
|
+
if (ext === '.json') return 'application/json; charset=utf-8'
|
|
324
|
+
return 'application/octet-stream'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isInsideDir(filePath, dir) {
|
|
328
|
+
if (!filePath || !dir) return false
|
|
329
|
+
const relative = path.relative(resolve(dir), resolve(filePath))
|
|
330
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function localMarkdownLinkTargets(text) {
|
|
334
|
+
const targets = new Set()
|
|
335
|
+
const linkPattern = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g
|
|
336
|
+
for (const match of String(text || '').matchAll(linkPattern)) {
|
|
337
|
+
const raw = String(match[1] || '').trim()
|
|
338
|
+
if (!raw || /^https?:\/\//i.test(raw) || raw.startsWith('/api/')) continue
|
|
339
|
+
let decoded = raw
|
|
340
|
+
try { decoded = decodeURI(raw) } catch {}
|
|
341
|
+
if (path.isAbsolute(decoded)) targets.add(decoded)
|
|
342
|
+
}
|
|
343
|
+
return [...targets]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function attachRuntimeAssetsToCompletion(body, task, executionContext) {
|
|
347
|
+
if (!executionContext || !body?.comment) return body
|
|
348
|
+
const allowedDirs = [executionContext.agent_results_dir, executionContext.run_dir].filter(Boolean)
|
|
349
|
+
const projectSlug = projectSlugForTask(task)
|
|
350
|
+
const assets = []
|
|
351
|
+
let nextComment = String(body.comment || '')
|
|
352
|
+
for (const target of localMarkdownLinkTargets(nextComment)) {
|
|
353
|
+
if (!allowedDirs.some(dir => isInsideDir(target, dir))) continue
|
|
354
|
+
const fileStat = await stat(target).catch(() => null)
|
|
355
|
+
if (!fileStat?.isFile()) continue
|
|
356
|
+
const bytes = await readFile(target)
|
|
357
|
+
const assetHash = createHash('sha256').update(target).digest('hex')
|
|
358
|
+
const url = `/api/projects/${encodeURIComponent(projectSlug)}/assets/${assetHash}`
|
|
359
|
+
nextComment = nextComment.split(target).join(url)
|
|
360
|
+
assets.push({
|
|
361
|
+
original_path: target,
|
|
362
|
+
asset_hash: assetHash,
|
|
363
|
+
filename: path.basename(target),
|
|
364
|
+
content_type: runtimeAssetContentType(target),
|
|
365
|
+
size_bytes: fileStat.size,
|
|
366
|
+
body_base64: bytes.toString('base64'),
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
return assets.length ? { ...body, comment: nextComment, assets } : body
|
|
370
|
+
}
|
|
371
|
+
|
|
315
372
|
function failedClaimCompletionBody(claim, comment) {
|
|
316
373
|
return {
|
|
317
374
|
task_id: String(claim?.task_id || ''),
|
|
@@ -992,6 +1049,9 @@ function buildCodexPrompt(task) {
|
|
|
992
1049
|
'4. tmp_dir: Scratch/working files only. This directory is deleted by the CLI after the task finishes — do NOT put deliverables here.',
|
|
993
1050
|
'5. workdir: Treat as read-only project context except for the four agent directories and knowledge_base above.',
|
|
994
1051
|
'',
|
|
1052
|
+
'MCP tool guidance:',
|
|
1053
|
+
'- If the task needs image generation, use the hosted 11agents MCP `image_generate` tool when available.',
|
|
1054
|
+
'',
|
|
995
1055
|
'Task context:',
|
|
996
1056
|
compactJson({
|
|
997
1057
|
queue_event: task.queue_event,
|
|
@@ -1382,7 +1442,11 @@ async function runOneRuntimeTaskSlot(runtime, config, machineKey, registration,
|
|
|
1382
1442
|
}
|
|
1383
1443
|
}
|
|
1384
1444
|
|
|
1385
|
-
const body =
|
|
1445
|
+
const body = await attachRuntimeAssetsToCompletion(
|
|
1446
|
+
normalizeTaskCompletion(runtimeTask, completion),
|
|
1447
|
+
runtimeTask,
|
|
1448
|
+
executionContext
|
|
1449
|
+
)
|
|
1386
1450
|
const result = await runWithDaemonRetry('complete runtime task', () => (
|
|
1387
1451
|
deps.requestJson('/api/runtime/tasks/complete', {
|
|
1388
1452
|
method: 'POST',
|
|
@@ -1409,6 +1473,15 @@ async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule,
|
|
|
1409
1473
|
return results.filter(r => r.status === 'fulfilled' && r.value === true).length
|
|
1410
1474
|
}
|
|
1411
1475
|
|
|
1476
|
+
async function drainRuntimeTasks(registration, flags, deps, handlerModule, retryState = createRetryState(), heartbeatIntervalMs = 15000, maxConcurrent = 1) {
|
|
1477
|
+
let completed = 0
|
|
1478
|
+
while (true) {
|
|
1479
|
+
const claimed = await claimAndRunRuntimeTasks(registration, flags, deps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1480
|
+
completed += claimed
|
|
1481
|
+
if (claimed === 0) return completed
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1412
1485
|
export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
1413
1486
|
const resolvedDeps = runtimeDeps(deps)
|
|
1414
1487
|
const heartbeatIntervalMs = Number(flag(flags, 'heartbeat-interval', '15')) * 1000
|
|
@@ -1443,8 +1516,11 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1443
1516
|
'Runtime task failed locally: daemon restarted with a persisted claimed task that had not completed.'
|
|
1444
1517
|
), resolvedDeps, retryState)
|
|
1445
1518
|
await syncRuntimeProjectMetadataBestEffort(flags, resolvedDeps)
|
|
1446
|
-
|
|
1447
|
-
|
|
1519
|
+
if (once) {
|
|
1520
|
+
await claimAndRunRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1521
|
+
return
|
|
1522
|
+
}
|
|
1523
|
+
await drainRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1448
1524
|
|
|
1449
1525
|
let lastScan = Date.now()
|
|
1450
1526
|
let lastHeartbeat = Date.now()
|
|
@@ -1462,7 +1538,7 @@ export async function startRuntimeDaemon(flags = {}, deps = {}) {
|
|
|
1462
1538
|
lastHeartbeat = now
|
|
1463
1539
|
}
|
|
1464
1540
|
if (now - lastTaskPoll >= taskIntervalMs) {
|
|
1465
|
-
await
|
|
1541
|
+
await drainRuntimeTasks(registration, flags, resolvedDeps, handlerModule, retryState, heartbeatIntervalMs, maxConcurrent)
|
|
1466
1542
|
lastTaskPoll = now
|
|
1467
1543
|
}
|
|
1468
1544
|
if (now - lastProjectRefresh >= projectRefreshIntervalMs) {
|