@11agents/cli 0.1.15 → 0.1.17

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@11agents/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "11agents local runtime and telemetry CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import { createHash } from 'node:crypto'
3
- import { readFileSync } from 'node:fs'
3
+ import { appendFileSync, readFileSync } from 'node:fs'
4
4
  import { appendFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
5
5
  import os from 'node:os'
6
6
  import { dirname, resolve } from 'node:path'
@@ -20,7 +20,7 @@ function sleep(ms) {
20
20
  return new Promise(resolve => setTimeout(resolve, ms))
21
21
  }
22
22
 
23
- export function runProcess(command, args, { input = '', cwd = process.cwd(), env = process.env } = {}) {
23
+ export function runProcess(command, args, { input = '', cwd = process.cwd(), env = process.env, onStdoutChunk } = {}) {
24
24
  return new Promise(resolve => {
25
25
  let settled = false
26
26
  const child = spawn(command, args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
@@ -31,7 +31,10 @@ export function runProcess(command, args, { input = '', cwd = process.cwd(), env
31
31
  settled = true
32
32
  resolve(result)
33
33
  }
34
- child.stdout.on('data', chunk => { stdout += chunk })
34
+ child.stdout.on('data', chunk => {
35
+ stdout += chunk
36
+ if (onStdoutChunk) onStdoutChunk(chunk)
37
+ })
35
38
  child.stderr.on('data', chunk => { stderr += chunk })
36
39
  child.on('error', error => {
37
40
  settle({ code: 127, stdout, stderr: error.message })
@@ -74,6 +77,7 @@ function runtimeDeps(overrides = {}) {
74
77
  log: overrides.log || (value => console.log(value)),
75
78
  homeDir: overrides.homeDir || os.homedir(),
76
79
  runCodex: overrides.runCodex || runCodex,
80
+ runClaude: overrides.runClaude || runClaude,
77
81
  runProcess: overrides.runProcess || runProcess,
78
82
  requestJson: overrides.requestJson || requestJson,
79
83
  sleep: overrides.sleep || sleep,
@@ -889,12 +893,17 @@ async function prepareRuntimeTask(task, flags, deps, config) {
889
893
 
890
894
  const database = await syncDatabaseIfNeeded({ task, workdir, config, flags, deps })
891
895
  const skills = await materializeSkillsIfChanged({ task, workdir, flags, deps })
896
+
897
+ // Resolve and inject the project token so MCP subprocesses (e.g. `11agents mcp start`)
898
+ // spawned by the runtime agent can authenticate without needing credentials on disk.
899
+ const projectToken = await projectSyncToken(projectTokenCandidatesForTask(task, flags), flags, deps)
892
900
  const env = {
893
901
  ...process.env,
894
902
  ...agentEnvironment(task),
895
903
  ELEVENAGENTS_PROJECT_DIR: workdir,
896
904
  ELEVENAGENTS_TASK_TMP: tmpDir,
897
905
  ELEVENAGENTS_TASK_ID: String(task.id || ''),
906
+ ...(projectToken ? { GTM_SWARM_TOKEN: projectToken } : {}),
898
907
  }
899
908
 
900
909
  return {
@@ -981,34 +990,20 @@ async function runCodex({ task, prompt, flags = {}, deps }) {
981
990
  const runDir = task.execution_context?.run_dir
982
991
  const lastMessagePath = runDir ? path.join(runDir, 'last_message.md') : ''
983
992
  const jsonLogArgs = lastMessagePath ? ['--json', '--output-last-message', lastMessagePath] : ['--json']
984
- const args = sandbox
985
- ? [
986
- '--ask-for-approval',
987
- 'never',
988
- 'exec',
989
- ...jsonLogArgs,
990
- '--skip-git-repo-check',
991
- '--sandbox',
992
- sandbox,
993
- '-C',
994
- workdir,
995
- '-',
996
- ]
997
- : [
998
- '--yolo',
999
- 'exec',
1000
- ...jsonLogArgs,
1001
- '--skip-git-repo-check',
1002
- '-C',
1003
- workdir,
1004
- '-',
1005
- ]
1006
993
  const model = flag(flags, 'codex-model')
1007
994
  const profile = flag(flags, 'codex-profile')
1008
- const execIndex = args.indexOf('exec')
1009
- if (model) args.splice(execIndex + 1, 0, '--model', model)
1010
- if (profile) args.splice(execIndex + 1, 0, '--profile', profile)
1011
995
 
996
+ function buildCodexArgs(withSandbox) {
997
+ const built = withSandbox
998
+ ? ['--ask-for-approval', 'never', 'exec', ...jsonLogArgs, '--skip-git-repo-check', '--sandbox', withSandbox, '-C', workdir, '-']
999
+ : ['--yolo', 'exec', ...jsonLogArgs, '--skip-git-repo-check', '-C', workdir, '-']
1000
+ const execIdx = built.indexOf('exec')
1001
+ if (model) built.splice(execIdx + 1, 0, '--model', model)
1002
+ if (profile) built.splice(execIdx + 1, 0, '--profile', profile)
1003
+ return built
1004
+ }
1005
+
1006
+ const args = buildCodexArgs(sandbox)
1012
1007
  const commandLine = [codexBin, ...args].map(value => JSON.stringify(String(value))).join(' ')
1013
1008
  await writeRunFile(runDir, 'prompt.md', `/goal ${prompt}`)
1014
1009
  await updateRunMeta(runDir, {
@@ -1019,7 +1014,25 @@ async function runCodex({ task, prompt, flags = {}, deps }) {
1019
1014
  workdir,
1020
1015
  })
1021
1016
  deps.log(JSON.stringify({ running: 'codex exec', command: commandLine, workdir }, null, 2))
1022
- const result = await deps.runProcess(codexBin, args, { input: `/goal ${prompt}`, cwd: workdir, env: task.execution_context?.env || process.env })
1017
+ const taskEnv = task.execution_context?.env || process.env
1018
+ const stdoutLogPath = runDir ? path.join(runDir, 'stdout.log') : ''
1019
+ if (stdoutLogPath) await writeRunFile(runDir, 'stdout.log', '')
1020
+ const onStdoutChunk = stdoutLogPath ? (chunk) => { try { appendFileSync(stdoutLogPath, chunk) } catch {} } : undefined
1021
+ let result = await deps.runProcess(codexBin, args, { input: `/goal ${prompt}`, cwd: workdir, env: taskEnv, onStdoutChunk })
1022
+
1023
+ // When --codex-sandbox is set but bwrap is unavailable in the container (missing CAP_NET_ADMIN),
1024
+ // fall back to --yolo exec which runs commands directly without bwrap.
1025
+ if (result.code !== 0 && sandbox) {
1026
+ const errOutput = String(result.stderr || '') + String(result.stdout || '')
1027
+ if (errOutput.includes('bwrap:') && (errOutput.includes('Operation not permitted') || errOutput.includes('RTM_NEWADDR'))) {
1028
+ const fallbackArgs = buildCodexArgs(null)
1029
+ const fallbackLine = [codexBin, ...fallbackArgs].map(v => JSON.stringify(String(v))).join(' ')
1030
+ deps.log(JSON.stringify({ warning: 'bwrap unavailable in this environment, retrying without sandbox', fallback_command: fallbackLine }))
1031
+ await updateRunMeta(runDir, { bwrap_fallback: true, fallback_command_line: fallbackLine })
1032
+ if (stdoutLogPath) await writeRunFile(runDir, 'stdout.log', '')
1033
+ result = await deps.runProcess(codexBin, fallbackArgs, { input: `/goal ${prompt}`, cwd: workdir, env: taskEnv, onStdoutChunk })
1034
+ }
1035
+ }
1023
1036
  const rawStdout = String(result.stdout || '')
1024
1037
  const transcript = codexTranscriptFromJsonl(rawStdout)
1025
1038
  const assistantText = codexAssistantTextFromJsonl(rawStdout)
@@ -1077,10 +1090,100 @@ async function runCodex({ task, prompt, flags = {}, deps }) {
1077
1090
  }
1078
1091
  }
1079
1092
 
1093
+ async function runClaude({ task, prompt, flags = {}, deps }) {
1094
+ const claudeBin = flag(flags, 'claude-bin', 'claude')
1095
+ const workdir = flag(flags, 'claude-workdir') || flag(flags, 'codex-workdir') || task.execution_context?.workdir || process.cwd()
1096
+ const model = flag(flags, 'claude-model')
1097
+ const runDir = task.execution_context?.run_dir
1098
+
1099
+ // --dangerously-skip-permissions bypasses bwrap sandboxing and auto-approves MCP tool calls,
1100
+ // which are both required for headless remote runtimes running inside containers.
1101
+ const args = ['--dangerously-skip-permissions', '--print']
1102
+ if (model) args.push('--model', model)
1103
+
1104
+ const commandLine = [claudeBin, ...args].map(a => JSON.stringify(String(a))).join(' ')
1105
+ await writeRunFile(runDir, 'prompt.md', prompt)
1106
+ await updateRunMeta(runDir, {
1107
+ provider: 'claude',
1108
+ command: claudeBin,
1109
+ args,
1110
+ command_line: commandLine,
1111
+ workdir,
1112
+ })
1113
+
1114
+ deps.log(JSON.stringify({ running: 'claude --print', command: commandLine, workdir }, null, 2))
1115
+
1116
+ const stdoutLogPath = runDir ? path.join(runDir, 'stdout.log') : ''
1117
+ if (stdoutLogPath) await writeRunFile(runDir, 'stdout.log', '')
1118
+ const onStdoutChunk = stdoutLogPath ? (chunk) => { try { appendFileSync(stdoutLogPath, chunk) } catch {} } : undefined
1119
+ const result = await deps.runProcess(claudeBin, args, {
1120
+ input: prompt,
1121
+ cwd: workdir,
1122
+ env: task.execution_context?.env || process.env,
1123
+ onStdoutChunk,
1124
+ })
1125
+
1126
+ const rawStdout = String(result.stdout || '')
1127
+ const output = rawStdout.trim()
1128
+ const error = String(result.stderr || '').trim()
1129
+
1130
+ await writeRunFile(runDir, 'stdout.log', rawStdout)
1131
+ await writeRunFile(runDir, 'stderr.log', String(result.stderr || ''))
1132
+ await updateRunMeta(runDir, { exit_code: result.code })
1133
+
1134
+ if (result.code !== 0) {
1135
+ return {
1136
+ comment: `${error || output || `claude exited with status ${result.code}`}\n\nClaude command: ${commandLine}`,
1137
+ status: 'failed',
1138
+ }
1139
+ }
1140
+
1141
+ if (knowledgeDeepOrganizeSpec(task)) {
1142
+ const structured = extractJsonObject(output)
1143
+ if (structured?.knowledge_snapshot || structured?.knowledgeSnapshot) {
1144
+ return {
1145
+ ...structured,
1146
+ comment: String(structured.comment || structured.summary || output || `Claude completed task ${task.id}.`),
1147
+ memory_delta: String(structured.memory_delta || structured.memoryDelta || `Claude completed task ${task.id}.`),
1148
+ status: structured.status ? String(structured.status) : 'in_review',
1149
+ }
1150
+ }
1151
+ const recoveredSnapshot = buildRecoveredDeepKnowledgeSnapshot(task, [
1152
+ structured?.comment || structured?.summary || '',
1153
+ structured?.memory_delta || structured?.memoryDelta || '',
1154
+ output,
1155
+ ].filter(Boolean).join('\n\n'))
1156
+ if (recoveredSnapshot) {
1157
+ return {
1158
+ comment: [
1159
+ String(structured?.comment || structured?.summary || output || `Claude completed task ${task.id}.`).trim(),
1160
+ 'Recovered a knowledge_snapshot from the worker output because local tooling or MCP push was unavailable.',
1161
+ ].filter(Boolean).join('\n\n'),
1162
+ memory_delta: [
1163
+ String(structured?.memory_delta || structured?.memoryDelta || '').trim(),
1164
+ 'Recovered deep knowledge organization snapshot from worker output.',
1165
+ ].filter(Boolean).join('\n'),
1166
+ status: 'in_review',
1167
+ knowledge_snapshot: recoveredSnapshot,
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ return {
1173
+ comment: output || `Claude completed task ${task.id}.`,
1174
+ memory_delta: `Claude completed task ${task.id}.`,
1175
+ status: 'in_review',
1176
+ }
1177
+ }
1178
+
1080
1179
  function defaultTaskHandler(flags, deps) {
1081
1180
  return {
1082
1181
  async handleRuntimeTask(task) {
1083
1182
  const provider = task.runtime?.provider || ''
1183
+ if (provider === 'claude') {
1184
+ const prompt = buildCodexPrompt(task)
1185
+ return deps.runClaude({ task, prompt, flags, deps })
1186
+ }
1084
1187
  if (provider !== 'codex') {
1085
1188
  return {
1086
1189
  comment: `unsupported runtime provider: ${provider || 'unknown'}`,
package/src/mcp.js CHANGED
@@ -17,6 +17,22 @@ const TOOLS = [
17
17
  },
18
18
  },
19
19
  },
20
+ {
21
+ name: 'database_sync',
22
+ description: 'Pull or push the cloud project database snapshot with a project token.',
23
+ inputSchema: {
24
+ type: 'object',
25
+ required: ['project', 'mode'],
26
+ properties: {
27
+ project: { type: 'string' },
28
+ mode: { type: 'string', enum: ['pull', 'push'] },
29
+ token: { type: 'string' },
30
+ server: { type: 'string' },
31
+ snapshot: { type: 'object' },
32
+ metadata: { type: 'object' },
33
+ },
34
+ },
35
+ },
20
36
  ]
21
37
 
22
38
  function textResult(value) {
@@ -64,6 +80,22 @@ export async function handleMcpRequest(request, deps = {}) {
64
80
  })
65
81
  return textResult(result)
66
82
  }
83
+ if (name === 'database_sync') {
84
+ const token = await resolveProjectToken(args.project, {
85
+ homeDir: deps.homeDir,
86
+ token: args.token,
87
+ })
88
+ const server = args.server || 'https://app.11agents.ai'
89
+ const result = await (deps.requestJson || requestJson)(
90
+ `/api/projects/${encodeURIComponent(args.project)}/database/sync`,
91
+ {
92
+ method: 'POST',
93
+ body: { mode: args.mode || 'pull', snapshot: args.snapshot, metadata: args.metadata },
94
+ config: { token, server },
95
+ }
96
+ )
97
+ return textResult(result)
98
+ }
67
99
  throw new Error(`unknown MCP tool: ${name}`)
68
100
  }
69
101