@11agents/cli 0.1.13 → 0.1.14

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
@@ -84,6 +84,13 @@ Run the daemon in the background:
84
84
 
85
85
  Background mode writes its pid to `~/.11agents/daemon.pid` and logs to `~/.11agents/daemon.log`.
86
86
 
87
+ Inspect daemon and task execution logs:
88
+
89
+ ```bash
90
+ 11agents logs daemon --tail 200
91
+ 11agents logs task <taskId> --project <slug>
92
+ ```
93
+
87
94
  Useful daemon options:
88
95
 
89
96
  ```bash
@@ -99,10 +106,11 @@ On startup, and every 30 minutes after that, the daemon syncs project metadata a
99
106
  - Agent-local skills: `~/.11agents/<project>/agents/<agent>/skills/`
100
107
  - Cloud database snapshot: `~/.11agents/<project>/database/snapshot.json`
101
108
  - Task scratch directory: `~/.11agents/<project>/tmp/<taskId>/`
109
+ - Task execution logs: `~/.11agents/<project>/runs/<taskId>/`
102
110
 
103
111
  Codex runs from `~/.11agents/<project>/` by default. Treat that directory as read-only project context. Task code may write temporary files only under `./tmp/<taskId>/`; the daemon removes that task scratch directory after the task finishes. Agent environment variables from the control plane are injected into the Codex child process and are not written to disk.
104
112
 
105
- The built-in Codex worker starts task executions with `codex --ask-for-approval never --yolo exec` and prefixes the task prompt with `/goal ` by default so remote runtime tasks can run without approval prompts or sandbox restrictions. To opt a daemon back into a Codex sandbox, start it with `--codex-sandbox read-only`, `--codex-sandbox workspace-write`, or `--codex-sandbox danger-full-access`.
113
+ The built-in Codex worker starts task executions with `codex --yolo exec` and prefixes the task prompt with `/goal ` by default so remote runtime tasks can run without approval prompts or sandbox restrictions. To opt a daemon back into a Codex sandbox, start it with `--codex-sandbox read-only`, `--codex-sandbox workspace-write`, or `--codex-sandbox danger-full-access`.
106
114
 
107
115
  The built-in task runner currently supports Codex tasks. A custom handler may export:
108
116
 
package/bin/11agents.js CHANGED
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { parseArgs } from '../src/args.js'
5
5
  import { knowledgeStatus, syncKnowledge } from '../src/commands/knowledge.js'
6
+ import { showDaemonLog, showTaskLog } from '../src/commands/logs.js'
6
7
  import { runNode } from '../src/commands/node.js'
7
8
  import { pushArtifact, pushBatch, pushObservation } from '../src/commands/push.js'
8
9
  import { registerRuntime, scanRuntime, startRuntimeDaemon } from '../src/commands/runtime.js'
@@ -24,6 +25,8 @@ Usage:
24
25
  11agents daemon status
25
26
  11agents daemon stop
26
27
  11agents daemon start --handler ./worker.js # optional custom worker override
28
+ 11agents logs daemon [--tail 200]
29
+ 11agents logs task <task-id> [--project <slug>] [--tail 120]
27
30
  11agents mcp start
28
31
  11agents validate <file>
29
32
  11agents push batch <file>
@@ -116,6 +119,17 @@ async function main() {
116
119
  return
117
120
  }
118
121
 
122
+ if (command === 'logs' && subcommand === 'daemon') {
123
+ await showDaemonLog(flags)
124
+ return
125
+ }
126
+
127
+ if (command === 'logs' && subcommand === 'task') {
128
+ if (!target) throw new Error('logs task requires a task id')
129
+ await showTaskLog(target, flags)
130
+ return
131
+ }
132
+
119
133
  if (command === 'mcp' && (!subcommand || subcommand === 'start')) {
120
134
  await startMcpServer()
121
135
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@11agents/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "11agents local runtime and telemetry CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,96 @@
1
+ import { readdir, readFile } from 'node:fs/promises'
2
+ import { homedir } from 'node:os'
3
+ import path from 'node:path'
4
+ import { backgroundPaths } from '../daemon-process.js'
5
+ import { flag } from '../args.js'
6
+
7
+ function tailText(text, lines) {
8
+ const count = Math.max(1, Number(lines) || 200)
9
+ const parts = String(text || '').replace(/\n$/, '').split('\n')
10
+ return parts.slice(-count).join('\n')
11
+ }
12
+
13
+ async function readText(filePath, fallback = '') {
14
+ try {
15
+ return await readFile(filePath, 'utf8')
16
+ } catch (error) {
17
+ if (error?.code === 'ENOENT') return fallback
18
+ throw error
19
+ }
20
+ }
21
+
22
+ async function readJson(filePath, fallback = {}) {
23
+ try {
24
+ return JSON.parse(await readFile(filePath, 'utf8'))
25
+ } catch (error) {
26
+ if (error?.code === 'ENOENT') return fallback
27
+ return fallback
28
+ }
29
+ }
30
+
31
+ export async function readDaemonLog({ homeDir = homedir(), tail = 200 } = {}) {
32
+ const text = await readText(backgroundPaths(homeDir).logPath, '')
33
+ return tailText(text, tail)
34
+ }
35
+
36
+ async function findTaskRunDir({ taskId, project = '', homeDir = homedir() }) {
37
+ if (!taskId) throw new Error('task id is required')
38
+ const baseDir = path.join(homeDir, '.11agents')
39
+ if (project) return path.join(baseDir, project, 'runs', taskId)
40
+
41
+ const entries = await readdir(baseDir, { withFileTypes: true }).catch(error => {
42
+ if (error?.code === 'ENOENT') return []
43
+ throw error
44
+ })
45
+ for (const entry of entries) {
46
+ if (!entry.isDirectory()) continue
47
+ const runDir = path.join(baseDir, entry.name, 'runs', taskId)
48
+ const meta = await readJson(path.join(runDir, 'meta.json'), null)
49
+ if (meta) return runDir
50
+ }
51
+ return path.join(baseDir, 'project', 'runs', taskId)
52
+ }
53
+
54
+ export async function formatTaskLog({ taskId, project = '', homeDir = homedir(), tail = 120 } = {}) {
55
+ const runDir = await findTaskRunDir({ taskId, project, homeDir })
56
+ const meta = await readJson(path.join(runDir, 'meta.json'), null)
57
+ if (!meta) throw new Error(`task log not found: ${taskId}`)
58
+
59
+ const stdout = tailText(await readText(path.join(runDir, 'stdout.log'), ''), tail)
60
+ const stderr = tailText(await readText(path.join(runDir, 'stderr.log'), ''), tail)
61
+ const completion = await readText(path.join(runDir, 'completion.json'), '')
62
+ return [
63
+ `task: ${meta.task_id || taskId}`,
64
+ `provider: ${meta.provider || ''}`,
65
+ `workdir: ${meta.workdir || ''}`,
66
+ `command: ${meta.command_line || ''}`,
67
+ `exit_code: ${meta.exit_code ?? ''}`,
68
+ `run_dir: ${runDir}`,
69
+ '',
70
+ 'stdout:',
71
+ stdout,
72
+ '',
73
+ 'stderr:',
74
+ stderr,
75
+ '',
76
+ 'completion:',
77
+ completion.trim(),
78
+ ].join('\n').trimEnd()
79
+ }
80
+
81
+ export async function showDaemonLog(flags = {}, deps = {}) {
82
+ const log = await readDaemonLog({ homeDir: deps.homeDir, tail: flag(flags, 'tail', '200') })
83
+ ;(deps.log || console.log)(log)
84
+ return log
85
+ }
86
+
87
+ export async function showTaskLog(taskId, flags = {}, deps = {}) {
88
+ const text = await formatTaskLog({
89
+ taskId,
90
+ project: flag(flags, 'project'),
91
+ homeDir: deps.homeDir,
92
+ tail: flag(flags, 'tail', '120'),
93
+ })
94
+ ;(deps.log || console.log)(text)
95
+ return text
96
+ }
@@ -693,6 +693,19 @@ async function appendTaskMemoryDelta({ task, completion, workdir }) {
693
693
  return { written: true, path: indexPath }
694
694
  }
695
695
 
696
+ async function writeRunFile(runDir, fileName, content) {
697
+ if (!runDir) return
698
+ await mkdir(runDir, { recursive: true })
699
+ await writeFile(path.join(runDir, fileName), content)
700
+ }
701
+
702
+ async function updateRunMeta(runDir, patch) {
703
+ if (!runDir) return
704
+ const metaPath = path.join(runDir, 'meta.json')
705
+ const current = await readJsonFile(metaPath, {})
706
+ await writeRunFile(runDir, 'meta.json', JSON.stringify({ ...current, ...patch }, null, 2))
707
+ }
708
+
696
709
  function databaseSyncSpec(task) {
697
710
  const spec = task.database || task.cloud_database || task.workspace?.database || null
698
711
  if (!spec || typeof spec !== 'object') return null
@@ -755,7 +768,9 @@ function agentEnvironment(task) {
755
768
  async function prepareRuntimeTask(task, flags, deps, config) {
756
769
  const workdir = flag(flags, 'codex-workdir') || projectDirForTask(task, flags, deps)
757
770
  const tmpDir = path.join(workdir, 'tmp', sanitizeTaskId(task.id))
771
+ const runDir = path.join(workdir, 'runs', sanitizeTaskId(task.id))
758
772
  await mkdir(tmpDir, { recursive: true })
773
+ await mkdir(runDir, { recursive: true })
759
774
 
760
775
  const database = await syncDatabaseIfNeeded({ task, workdir, config, flags, deps })
761
776
  const skills = await materializeSkillsIfChanged({ task, workdir, flags, deps })
@@ -770,6 +785,7 @@ async function prepareRuntimeTask(task, flags, deps, config) {
770
785
  return {
771
786
  workdir,
772
787
  tmp_dir: tmpDir,
788
+ run_dir: runDir,
773
789
  project_slug: projectSlugForTask(task, flags),
774
790
  readonly: true,
775
791
  env,
@@ -874,10 +890,22 @@ async function runCodex({ task, prompt, flags = {}, deps }) {
874
890
  if (profile) args.splice(execIndex + 1, 0, '--profile', profile)
875
891
 
876
892
  const commandLine = [codexBin, ...args].map(value => JSON.stringify(String(value))).join(' ')
893
+ const runDir = task.execution_context?.run_dir
894
+ await writeRunFile(runDir, 'prompt.md', `/goal ${prompt}`)
895
+ await updateRunMeta(runDir, {
896
+ provider: 'codex',
897
+ command: codexBin,
898
+ args,
899
+ command_line: commandLine,
900
+ workdir,
901
+ })
877
902
  deps.log(JSON.stringify({ running: 'codex exec', command: commandLine, workdir }, null, 2))
878
903
  const result = await deps.runProcess(codexBin, args, { input: `/goal ${prompt}`, cwd: workdir, env: task.execution_context?.env || process.env })
879
904
  const output = String(result.stdout || '').trim()
880
905
  const error = String(result.stderr || '').trim()
906
+ await writeRunFile(runDir, 'stdout.log', String(result.stdout || ''))
907
+ await writeRunFile(runDir, 'stderr.log', String(result.stderr || ''))
908
+ await updateRunMeta(runDir, { exit_code: result.code })
881
909
  if (result.code !== 0) {
882
910
  const body = error || output || `codex exited with status ${result.code}`
883
911
  const trustHint = body.includes('--skip-git-repo-check')
@@ -1002,6 +1030,15 @@ async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule,
1002
1030
  if (!completion) {
1003
1031
  executionContext = await prepareRuntimeTask(runtimeTask, flags, deps, config)
1004
1032
  runtimeTask.execution_context = executionContext
1033
+ await updateRunMeta(executionContext.run_dir, {
1034
+ task_id: String(runtimeTask.id || ''),
1035
+ runtime_id: String(runtimeTask.runtime_id || ''),
1036
+ provider: runtimeTask.runtime?.provider || runtime.provider || '',
1037
+ project_slug: executionContext.project_slug,
1038
+ agent: agentNameForTask(runtimeTask),
1039
+ issue_title: runtimeTask.issue?.title || '',
1040
+ started_at: new Date().toISOString(),
1041
+ })
1005
1042
  try {
1006
1043
  completion = await runWithRuntimeHeartbeat(
1007
1044
  () => handlerModule.handleRuntimeTask(runtimeTask),
@@ -1019,6 +1056,10 @@ async function claimAndRunRuntimeTasks(registration, flags, deps, handlerModule,
1019
1056
  await rm(executionContext.tmp_dir, { recursive: true, force: true })
1020
1057
  }
1021
1058
  }
1059
+ if (executionContext) {
1060
+ await writeRunFile(executionContext.run_dir, 'completion.json', JSON.stringify(normalizeTaskCompletion(runtimeTask, completion), null, 2))
1061
+ await updateRunMeta(executionContext.run_dir, { ended_at: new Date().toISOString() })
1062
+ }
1022
1063
  if (executionContext) {
1023
1064
  await appendTaskMemoryDelta({ task: runtimeTask, completion, workdir: executionContext.workdir })
1024
1065
  }