@agentprojectcontext/apx 1.8.1 → 1.9.0

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/src/daemon/api.js CHANGED
@@ -43,6 +43,12 @@ import { readAgents } from "../core/parser.js";
43
43
  import { parseSessionFrontmatter } from "../core/parser.js";
44
44
  import { writeAgentFile, ensureAgentDir, regenerateAgentsMd } from "../core/scaffold.js";
45
45
  import { buildAgentSystem } from "../core/agent-system.js";
46
+ import {
47
+ createArtifact,
48
+ listArtifacts,
49
+ readArtifact,
50
+ removeArtifact,
51
+ } from "../core/artifacts-store.js";
46
52
 
47
53
  const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
48
54
 
@@ -518,6 +524,35 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
518
524
  }
519
525
  });
520
526
 
527
+ // POST /projects/:pid/super-agent/chat
528
+ app.post("/projects/:pid/super-agent/chat", async (req, res) => {
529
+ const p = project(req, res);
530
+ if (!p) return;
531
+ const { prompt, contextNote, previousMessages, model } = req.body || {};
532
+ if (!prompt) return res.status(400).json({ error: "prompt required" });
533
+ try {
534
+ const saResult = await runSuperAgent({
535
+ globalConfig: config,
536
+ projects,
537
+ plugins,
538
+ registries,
539
+ prompt,
540
+ contextNote: contextNote || `Context: Project ${p.id} (${p.name}) at ${p.path}`,
541
+ previousMessages: previousMessages || [],
542
+ overrideModel: model,
543
+ });
544
+ projects.rebuild(p.id);
545
+ res.json({
546
+ text: saResult.text,
547
+ usage: saResult.usage,
548
+ name: saResult.name,
549
+ trace: saResult.trace,
550
+ });
551
+ } catch (e) {
552
+ res.status(500).json({ error: e.message });
553
+ }
554
+ });
555
+
521
556
  // GET /projects/:pid/agents/:slug/conversations
522
557
  app.get("/projects/:pid/agents/:slug/conversations", (req, res) => {
523
558
  const p = project(req, res);
@@ -739,10 +774,14 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
739
774
  if (!p) return;
740
775
  const { id } = req.params;
741
776
 
742
- const agentsDir = path.join(p.path, ".apc", "agents");
777
+ const sessionRoots = [
778
+ path.join(p.storagePath || p.path, "agents"),
779
+ path.join(p.path, ".apc", "agents"),
780
+ ];
743
781
  let sessionFile = null;
744
782
  let agentSlug = null;
745
- if (fs.existsSync(agentsDir)) {
783
+ for (const agentsDir of sessionRoots) {
784
+ if (!fs.existsSync(agentsDir)) continue;
746
785
  for (const slug of fs.readdirSync(agentsDir)) {
747
786
  const f = path.join(agentsDir, slug, "sessions", `${id}.md`);
748
787
  if (fs.existsSync(f)) {
@@ -751,6 +790,7 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
751
790
  break;
752
791
  }
753
792
  }
793
+ if (sessionFile) break;
754
794
  }
755
795
  if (!sessionFile) return res.status(404).json({ error: `session ${id} not found` });
756
796
 
@@ -797,13 +837,13 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
797
837
  app.get("/projects/:pid/routines", (req, res) => {
798
838
  const p = project(req, res);
799
839
  if (!p) return;
800
- res.json(listRoutines(p.path));
840
+ res.json(listRoutines(p.storagePath));
801
841
  });
802
842
 
803
843
  app.get("/projects/:pid/routines/:name", (req, res) => {
804
844
  const p = project(req, res);
805
845
  if (!p) return;
806
- const r = getRoutine(p.path, req.params.name);
846
+ const r = getRoutine(p.storagePath, req.params.name);
807
847
  if (!r) return res.status(404).json({ error: "routine not found" });
808
848
  res.json(r);
809
849
  });
@@ -812,38 +852,76 @@ export function buildApi({ projects, registries, plugins, scheduler, version, st
812
852
  const p = project(req, res);
813
853
  if (!p) return;
814
854
  try {
815
- const r = upsertRoutine(p.path, req.body || {});
855
+ // Pass all fields including pipeline extensions.
856
+ const r = upsertRoutine(p.storagePath, req.body || {});
816
857
  res.status(201).json(r);
817
858
  } catch (e) {
818
859
  res.status(400).json({ error: e.message });
819
860
  }
820
861
  });
821
862
 
863
+ // ---- Artifacts (managed files in storagePath/artifacts/) ---------
864
+ app.get("/projects/:pid/artifacts", (req, res) => {
865
+ const p = project(req, res);
866
+ if (!p) return;
867
+ res.json(listArtifacts(p.storagePath));
868
+ });
869
+
870
+ app.post("/projects/:pid/artifacts", (req, res) => {
871
+ const p = project(req, res);
872
+ if (!p) return;
873
+ const { name, content = "" } = req.body || {};
874
+ if (!name) return res.status(400).json({ error: "name required" });
875
+ try {
876
+ const filePath = createArtifact(p.storagePath, name, content);
877
+ res.status(201).json({ name, path: filePath });
878
+ } catch (e) {
879
+ res.status(400).json({ error: e.message });
880
+ }
881
+ });
882
+
883
+ app.get("/projects/:pid/artifacts/:name", (req, res) => {
884
+ const p = project(req, res);
885
+ if (!p) return;
886
+ try {
887
+ res.json(readArtifact(p.storagePath, decodeURIComponent(req.params.name)));
888
+ } catch (e) {
889
+ res.status(404).json({ error: e.message });
890
+ }
891
+ });
892
+
893
+ app.delete("/projects/:pid/artifacts/:name", (req, res) => {
894
+ const p = project(req, res);
895
+ if (!p) return;
896
+ const ok = removeArtifact(p.storagePath, decodeURIComponent(req.params.name));
897
+ res.status(ok ? 204 : 404).end();
898
+ });
899
+
822
900
  app.delete("/projects/:pid/routines/:name", (req, res) => {
823
901
  const p = project(req, res);
824
902
  if (!p) return;
825
- const ok = deleteRoutine(p.path, req.params.name);
903
+ const ok = deleteRoutine(p.storagePath, req.params.name);
826
904
  res.status(ok ? 204 : 404).end();
827
905
  });
828
906
 
829
907
  app.post("/projects/:pid/routines/:name/enable", (req, res) => {
830
908
  const p = project(req, res);
831
909
  if (!p) return;
832
- setRoutineEnabled(p.path, req.params.name, true);
910
+ setRoutineEnabled(p.storagePath, req.params.name, true);
833
911
  res.json({ ok: true });
834
912
  });
835
913
 
836
914
  app.post("/projects/:pid/routines/:name/disable", (req, res) => {
837
915
  const p = project(req, res);
838
916
  if (!p) return;
839
- setRoutineEnabled(p.path, req.params.name, false);
917
+ setRoutineEnabled(p.storagePath, req.params.name, false);
840
918
  res.json({ ok: true });
841
919
  });
842
920
 
843
921
  app.post("/projects/:pid/routines/:name/run", async (req, res) => {
844
922
  const p = project(req, res);
845
923
  if (!p) return;
846
- const r = getRoutine(p.path, req.params.name);
924
+ const r = getRoutine(p.storagePath, req.params.name);
847
925
  if (!r) return res.status(404).json({ error: "routine not found" });
848
926
  try {
849
927
  const result = await runRoutineNow({ project: p, projects, plugins, registries, globalConfig: config }, r);
@@ -11,10 +11,15 @@
11
11
  // shell — run a shell command. spec: { command, timeout_ms? }
12
12
 
13
13
  import { spawn } from "node:child_process";
14
+ import { execFile } from "node:child_process";
15
+ import os from "node:os";
16
+ import fs from "node:fs";
17
+ import path from "node:path";
14
18
  import { callEngine } from "./engines/index.js";
15
19
  import { runSuperAgent } from "./super-agent.js";
16
20
  import { readAgents } from "../core/parser.js";
17
21
  import { buildAgentSystem } from "../core/agent-system.js";
22
+ import { resolveArtifactRef, ARTIFACTS_SKIP_SIGNAL } from "../core/artifacts-store.js";
18
23
  import {
19
24
  listRoutines,
20
25
  getRoutine,
@@ -169,29 +174,152 @@ const HANDLERS = {
169
174
  shell: handleShell,
170
175
  };
171
176
 
177
+ // --------------------- pipeline: pre/post shell commands --------------------
178
+
179
+ // Run a single shell command. Returns { exitCode, stdout, stderr }.
180
+ function runShellCmd(cmd, env = {}, cwd = os.homedir()) {
181
+ return new Promise((resolve) => {
182
+ const child = spawn("sh", ["-c", cmd], {
183
+ cwd,
184
+ env: { ...process.env, ...env },
185
+ stdio: ["ignore", "pipe", "pipe"],
186
+ });
187
+ let stdout = "";
188
+ let stderr = "";
189
+ child.stdout.on("data", (c) => (stdout += c.toString()));
190
+ child.stderr.on("data", (c) => (stderr += c.toString()));
191
+ child.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr }));
192
+ child.on("error", (e) => resolve({ exitCode: 1, stdout: "", stderr: e.message }));
193
+ });
194
+ }
195
+
196
+ // Inject {{pre_output}} into a prompt string.
197
+ function injectPreOutput(prompt, preOutput) {
198
+ if (!prompt || typeof prompt !== "string") return prompt;
199
+ return prompt.replace(/\{\{pre_output\}\}/g, preOutput || "");
200
+ }
201
+
202
+ // Determine whether to skip the LLM call based on skip_prompt_on + pre results.
203
+ function shouldSkipPrompt(routine, preExitCode, preStdout) {
204
+ const mode = routine.skip_prompt_on || "signal";
205
+ if (mode === "always") return true;
206
+ if (mode === "never") return false;
207
+ if (mode === "signal") return preStdout.includes(ARTIFACTS_SKIP_SIGNAL);
208
+ if (mode === "pre_failure") return preExitCode !== 0;
209
+ if (mode === "pre_success") return preExitCode === 0;
210
+ return false;
211
+ }
212
+
172
213
  // --------------------- runtime: run one + loop ------------------------------
173
214
 
174
215
  export async function runRoutineNow(ctx, routine) {
175
- const handler = HANDLERS[routine.kind];
176
- if (!handler) throw new Error(`unknown routine kind: ${routine.kind}`);
177
- let result;
216
+ // Determine the working directory for shell commands.
217
+ const cwd = ctx.project?.path || os.homedir();
218
+ const storagePath = ctx.project?.storagePath || os.homedir();
219
+
220
+ const hasPreCmds = Array.isArray(routine.pre_commands) && routine.pre_commands.length > 0;
221
+ const hasPostCmds = Array.isArray(routine.post_commands) && routine.post_commands.length > 0;
222
+
223
+ let preStdout = "";
224
+ let preExitCode = 0;
225
+ let preOutputFile = null;
226
+
227
+ // ── Phase 1: pre_commands ──────────────────────────────────────────────────
228
+ if (hasPreCmds) {
229
+ const combinedOut = [];
230
+ for (const rawCmd of routine.pre_commands) {
231
+ // Resolve "artifact:<name>" shorthand to its absolute path.
232
+ const cmd = resolveArtifactRef(rawCmd, storagePath);
233
+ const { exitCode, stdout, stderr } = await runShellCmd(cmd, {}, cwd);
234
+ combinedOut.push(stdout);
235
+ if (stderr) combinedOut.push(stderr);
236
+ preExitCode = exitCode;
237
+ if (exitCode !== 0 && (routine.skip_prompt_on === "pre_failure" || routine.skip_prompt_on === "signal")) {
238
+ // Stop running further pre_commands on failure when mode cares about exit code.
239
+ break;
240
+ }
241
+ }
242
+ preStdout = combinedOut.join("");
243
+
244
+ // Write pre output to a temp file so post_commands can reference it via
245
+ // $APX_PRE_OUTPUT_FILE even if the output is large.
246
+ try {
247
+ preOutputFile = path.join(os.tmpdir(), `apx-pre-${routine.name}-${Date.now()}.txt`);
248
+ fs.writeFileSync(preOutputFile, preStdout);
249
+ } catch { preOutputFile = null; }
250
+ }
251
+
252
+ // Env vars injected into post_commands and available in shell pre_commands output.
253
+ const pipelineEnv = {
254
+ APX_PRE_EXIT: String(preExitCode),
255
+ APX_PRE_OUTPUT: preStdout.slice(0, 32_000), // guard against huge outputs
256
+ APX_PRE_OUTPUT_FILE: preOutputFile || "",
257
+ APX_ROUTINE: routine.name,
258
+ };
259
+
260
+ // ── Phase 2: LLM / handler ────────────────────────────────────────────────
261
+ const skip = hasPreCmds && shouldSkipPrompt(routine, preExitCode, preStdout);
262
+
263
+ let result = { status: "ok" };
178
264
  let status = "ok";
179
265
  let errMsg = null;
180
- try {
181
- result = await handler(ctx, routine);
182
- if (result?.status === "error") {
266
+
267
+ if (!skip) {
268
+ // Inject {{pre_output}} into exec_agent and super_agent prompts.
269
+ const enrichedRoutine = (hasPreCmds && preStdout)
270
+ ? {
271
+ ...routine,
272
+ spec: {
273
+ ...routine.spec,
274
+ prompt: injectPreOutput(routine.spec?.prompt, preStdout),
275
+ },
276
+ }
277
+ : routine;
278
+
279
+ const handler = HANDLERS[enrichedRoutine.kind];
280
+ if (!handler) {
183
281
  status = "error";
184
- errMsg = result.error || result.stderr || `routine ${routine.name} returned error status`;
282
+ errMsg = `unknown routine kind: ${enrichedRoutine.kind}`;
283
+ } else {
284
+ try {
285
+ result = await handler(ctx, enrichedRoutine);
286
+ if (result?.status === "error") {
287
+ status = "error";
288
+ errMsg = result.error || result.stderr || `routine ${routine.name} returned error status`;
289
+ }
290
+ } catch (e) {
291
+ status = "error";
292
+ errMsg = e.message;
293
+ result = { status: "error", error: e.message };
294
+ }
185
295
  }
186
- } catch (e) {
187
- status = "error";
188
- errMsg = e.message;
189
- result = { status: "error", error: e.message };
296
+ } else {
297
+ result = { status: "ok", skipped: true, note: "pre_commands signalled skip" };
190
298
  }
299
+
300
+ // ── Phase 3: post_commands ────────────────────────────────────────────────
301
+ if (hasPostCmds) {
302
+ const llmOutput = result?.reply || result?.text || "";
303
+ const postEnv = {
304
+ ...pipelineEnv,
305
+ APX_LLM_OUTPUT: llmOutput.slice(0, 32_000),
306
+ APX_STATUS: status,
307
+ APX_SKIPPED: skip ? "1" : "0",
308
+ };
309
+ for (const rawCmd of routine.post_commands) {
310
+ const cmd = resolveArtifactRef(rawCmd, storagePath);
311
+ await runShellCmd(cmd, postEnv, cwd);
312
+ // Post-command failures are logged but don't change routine status.
313
+ }
314
+ }
315
+
316
+ // Cleanup temp file.
317
+ if (preOutputFile) try { fs.unlinkSync(preOutputFile); } catch {}
318
+
191
319
  const lastRun = nowIso();
192
320
  const next = computeNextRun({ schedule: routine.schedule, last_run_at: lastRun });
193
321
  const isOnce = parseSchedule(routine.schedule).kind === "once";
194
- updateRunState(ctx.project.path, routine.name, {
322
+ updateRunState(ctx.project.storagePath, routine.name, {
195
323
  last_run_at: lastRun,
196
324
  last_status: status,
197
325
  last_error: errMsg,
@@ -205,9 +333,9 @@ export async function runRoutineNow(ctx, routine) {
205
333
  actor_id: "apx:routine",
206
334
  author: "apx",
207
335
  body: status === "ok"
208
- ? `routine ${routine.name} ok`
336
+ ? `routine ${routine.name} ok${skip ? " (skipped LLM)" : ""}`
209
337
  : `routine ${routine.name} error: ${errMsg}`,
210
- meta: { routine: routine.name, status, result },
338
+ meta: { routine: routine.name, status, skipped: skip, result },
211
339
  });
212
340
  return { ...result, last_run_at: lastRun, next_run_at: next };
213
341
  }
@@ -246,7 +374,7 @@ export class RoutineScheduler {
246
374
  const nowStr = nowIso();
247
375
  for (const proj of this.projects.list().map((p) => this.projects.get(p.id))) {
248
376
  if (!proj) continue;
249
- const due = getDueRoutines(proj.path, nowStr);
377
+ const due = getDueRoutines(proj.storagePath, nowStr);
250
378
  for (const r of due) {
251
379
  this.log(`routine ${r.name} (${r.kind}) firing in project #${proj.id}`);
252
380
  await runRoutineNow(
@@ -3,8 +3,31 @@
3
3
  // Returns one JSON line with the result and session_id.
4
4
  // Reference: https://docs.claude.com/en/docs/claude-code/headless
5
5
 
6
+ import fs from "node:fs";
7
+ import path from "node:path";
6
8
  import { runProcess } from "./_spawn.js";
7
9
 
10
+ export function encodeClaudeProjectPath(cwd) {
11
+ return String(cwd || process.cwd()).replace(/[^A-Za-z0-9]/g, "-");
12
+ }
13
+
14
+ export function resolveClaudeSessionPath({ cwd, sessionId, home = process.env.HOME || process.env.USERPROFILE || "" }) {
15
+ if (!sessionId || !home) return null;
16
+ const projectsDir = path.join(home, ".claude", "projects");
17
+ const encodedCwd = encodeClaudeProjectPath(cwd);
18
+ const expected = path.join(projectsDir, encodedCwd, `${sessionId}.jsonl`);
19
+ if (fs.existsSync(expected)) return expected;
20
+
21
+ try {
22
+ for (const dir of fs.readdirSync(projectsDir)) {
23
+ const candidate = path.join(projectsDir, dir, `${sessionId}.jsonl`);
24
+ if (fs.existsSync(candidate)) return candidate;
25
+ }
26
+ } catch {}
27
+
28
+ return expected;
29
+ }
30
+
8
31
  export default {
9
32
  id: "claude-code",
10
33
  binary: "claude",
@@ -40,12 +63,7 @@ export default {
40
63
  }
41
64
 
42
65
  if (sessionId) {
43
- // Claude Code's session directory naming: replace BOTH "/" and "_" with
44
- // "-" (verified empirically against ~/.claude/projects/). The trailing
45
- // file is `<sessionId>.jsonl`.
46
- const home = process.env.HOME || process.env.USERPROFILE || "";
47
- const encodedCwd = (cwd || process.cwd()).replace(/[/_]/g, "-");
48
- externalSessionPath = `${home}/.claude/projects/${encodedCwd}/${sessionId}.jsonl`;
66
+ externalSessionPath = resolveClaudeSessionPath({ cwd, sessionId });
49
67
  }
50
68
 
51
69
  return {
@@ -18,6 +18,7 @@ import callRuntime from "./tools/call-runtime.js";
18
18
  import sendTelegram from "./tools/send-telegram.js";
19
19
  import setIdentity from "./tools/set-identity.js";
20
20
  import setPermissionMode from "./tools/set-permission-mode.js";
21
+ import searchFiles from "./tools/search-files.js";
21
22
  import { createPermissionGuard } from "./helpers.js";
22
23
 
23
24
  const TOOLS = [
@@ -41,6 +42,7 @@ const TOOLS = [
41
42
  sendTelegram,
42
43
  setIdentity,
43
44
  setPermissionMode,
45
+ searchFiles,
44
46
  ];
45
47
 
46
48
  export const TOOL_SCHEMAS = TOOLS.map((tool) => tool.schema);
@@ -1,4 +1,12 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  import { readAgents } from "../../../core/parser.js";
4
+ import {
5
+ buildApfHint,
6
+ closeRuntimeSession,
7
+ createRuntimeSession,
8
+ extractApfResult,
9
+ } from "../../apc-runtime-context.js";
2
10
  import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
3
11
  import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
4
12
 
@@ -20,18 +28,51 @@ function resolveProjectForAgent(projects, project, slug) {
20
28
  return resolveProject(projects, project);
21
29
  }
22
30
 
31
+ function projectName(project) {
32
+ try {
33
+ const meta = JSON.parse(fs.readFileSync(path.join(project.path, ".apc", "project.json"), "utf8"));
34
+ if (meta.name) return meta.name;
35
+ } catch {}
36
+ return path.basename(project.path);
37
+ }
38
+
39
+ function buildRuntimeSystem(project, agent, runtime, sessionId, caller) {
40
+ const agentSlug = agent?.slug || "apx";
41
+ const hint = buildApfHint({
42
+ projectName: projectName(project),
43
+ projectPath: project.path,
44
+ agentSlug,
45
+ sessionId,
46
+ });
47
+ if (agent) {
48
+ return buildAgentSystem(project, agent, {
49
+ invocation: "runtime",
50
+ runtime,
51
+ caller,
52
+ extraParts: [hint],
53
+ });
54
+ }
55
+
56
+ return [
57
+ "You are APX running inside an external coding runtime.",
58
+ "No APC agent was explicitly selected for this run.",
59
+ "Use the project context and runtime tools directly. Do not impersonate a project agent.",
60
+ hint,
61
+ ].join("\n\n");
62
+ }
63
+
23
64
  export default {
24
65
  name: "call_runtime",
25
66
  schema: {
26
67
  type: "function",
27
68
  function: {
28
69
  name: "call_runtime",
29
- description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider) impersonating an APC agent.",
70
+ description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider), optionally impersonating an APC agent.",
30
71
  parameters: {
31
72
  type: "object",
32
73
  properties: {
33
74
  project: { type: "string" },
34
- agent: { type: "string", description: "APC agent slug from AGENTS.md, not runtime name" },
75
+ agent: { type: "string", description: "Optional APC agent slug from AGENTS.md, not runtime name. Omit when the user did not name an agent." },
35
76
  runtime: {
36
77
  type: "string",
37
78
  enum: ["claude-code", "codex", "opencode", "aider"],
@@ -41,16 +82,16 @@ export default {
41
82
  timeout_s: { type: "integer", description: "seconds before SIGTERM; default 300" },
42
83
  confirmed: confirmedProperty("true only after explicit user confirmation for this exact runtime command"),
43
84
  },
44
- required: ["agent", "runtime", "prompt"],
85
+ required: ["runtime", "prompt"],
45
86
  },
46
87
  },
47
88
  },
48
89
  makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, runtime, prompt, timeout_s = 300, confirmed = false }) => {
49
90
  requirePermission("call_runtime", { dangerous: true, confirmed });
50
91
 
51
- const p = resolveProjectForAgent(projects, project, slug);
52
- const agent = readAgents(p.path).find((a) => a.slug === slug);
53
- if (!agent) {
92
+ const p = slug ? resolveProjectForAgent(projects, project, slug) : resolveProject(projects, project);
93
+ const agent = slug ? readAgents(p.path).find((a) => a.slug === slug) : null;
94
+ if (slug && !agent) {
54
95
  const directory = projects.list().map((entry) => ({
55
96
  project: entry.name,
56
97
  kind: entry.id === 0 ? "default" : "project",
@@ -67,45 +108,74 @@ export default {
67
108
  return { error: `${e.message}. Available runtimes: ${RUNTIME_IDS.join(", ")}` };
68
109
  }
69
110
 
70
- const r = await rt.run({
71
- system: buildAgentSystem(p, agent, {
72
- invocation: "runtime",
73
- runtime,
74
- caller: "super_agent_tool",
75
- }),
76
- prompt,
77
- cwd: p.path,
78
- timeoutMs: timeout_s * 1000,
111
+ const actor = agent?.slug || "apx";
112
+ const session = createRuntimeSession({
113
+ projectRoot: p.path,
114
+ storageRoot: p.storagePath,
115
+ agentSlug: actor,
116
+ runtime,
117
+ title: `Runtime: ${runtime}${agent ? ` (${agent.slug})` : ""}`,
79
118
  });
80
119
 
81
- p.logMessage({
82
- agent_slug: slug,
83
- channel: "runtime",
84
- direction: "in",
85
- author: "user",
86
- body: prompt,
87
- meta: { runtime, invoked_by: "super_agent_tool" },
88
- });
89
- p.logMessage({
90
- agent_slug: slug,
91
- channel: "runtime",
92
- direction: "out",
93
- author: slug,
94
- body: r.output || "",
95
- meta: {
120
+ try {
121
+ const r = await rt.run({
122
+ system: buildRuntimeSystem(p, agent, runtime, session.id, "super_agent_tool"),
123
+ prompt,
124
+ cwd: p.path,
125
+ timeoutMs: timeout_s * 1000,
126
+ });
127
+
128
+ const result = extractApfResult(r.output) || (r.output || "").slice(0, 200);
129
+ closeRuntimeSession({
130
+ filePath: session.path,
131
+ externalSessionPath: r.externalSessionPath || null,
132
+ exitCode: r.exitCode,
133
+ result,
134
+ });
135
+
136
+ p.logMessage({
137
+ agent_slug: actor,
138
+ channel: "runtime",
139
+ direction: "in",
140
+ author: "user",
141
+ body: prompt,
142
+ meta: { runtime, invoked_by: "super_agent_tool", apc_session: session.id },
143
+ });
144
+ p.logMessage({
145
+ agent_slug: actor,
146
+ channel: "runtime",
147
+ direction: "out",
148
+ author: actor,
149
+ body: r.output || "",
150
+ meta: {
151
+ runtime,
152
+ exit_code: r.exitCode,
153
+ external_session_path: r.externalSessionPath || null,
154
+ session_id: r.sessionId || null,
155
+ apc_session: session.id,
156
+ invoked_by: "super_agent_tool",
157
+ },
158
+ });
159
+
160
+ return {
96
161
  runtime,
162
+ agent: agent?.slug || null,
163
+ apc_session: session.id,
97
164
  exit_code: r.exitCode,
165
+ output: (r.output || "").slice(0, 4000),
166
+ truncated: (r.output || "").length > 4000,
98
167
  external_session_path: r.externalSessionPath || null,
99
- invoked_by: "super_agent_tool",
100
- },
101
- });
102
-
103
- return {
104
- runtime,
105
- exit_code: r.exitCode,
106
- output: (r.output || "").slice(0, 4000),
107
- truncated: (r.output || "").length > 4000,
108
- external_session_path: r.externalSessionPath || null,
109
- };
168
+ session_id: r.sessionId || null,
169
+ };
170
+ } catch (e) {
171
+ try {
172
+ closeRuntimeSession({
173
+ filePath: session.path,
174
+ exitCode: -1,
175
+ result: `error: ${e.message.slice(0, 200)}`,
176
+ });
177
+ } catch {}
178
+ throw e;
179
+ }
110
180
  },
111
181
  };