@action-llama/action-llama 0.27.5 → 0.29.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.
Files changed (288) hide show
  1. package/dist/agents/credential-setup.d.ts +0 -1
  2. package/dist/agents/credential-setup.d.ts.map +1 -1
  3. package/dist/agents/credential-setup.js +2 -23
  4. package/dist/agents/credential-setup.js.map +1 -1
  5. package/dist/agents/prompt.d.ts.map +1 -1
  6. package/dist/agents/prompt.js +12 -50
  7. package/dist/agents/prompt.js.map +1 -1
  8. package/dist/agents/scheduler-tools.d.ts +47 -0
  9. package/dist/agents/scheduler-tools.d.ts.map +1 -0
  10. package/dist/agents/scheduler-tools.js +276 -0
  11. package/dist/agents/scheduler-tools.js.map +1 -0
  12. package/dist/agents/status-reporter.d.ts +1 -1
  13. package/dist/agents/status-reporter.js +1 -1
  14. package/dist/agents/transport-runner.d.ts +123 -0
  15. package/dist/agents/transport-runner.d.ts.map +1 -0
  16. package/dist/agents/transport-runner.js +791 -0
  17. package/dist/agents/transport-runner.js.map +1 -0
  18. package/dist/build-info.json +1 -1
  19. package/dist/cli/commands/add.d.ts +1 -0
  20. package/dist/cli/commands/add.d.ts.map +1 -1
  21. package/dist/cli/commands/add.js +24 -9
  22. package/dist/cli/commands/add.js.map +1 -1
  23. package/dist/cli/commands/agent.d.ts +0 -3
  24. package/dist/cli/commands/agent.d.ts.map +1 -1
  25. package/dist/cli/commands/agent.js +3 -67
  26. package/dist/cli/commands/agent.js.map +1 -1
  27. package/dist/cli/commands/logs.d.ts.map +1 -1
  28. package/dist/cli/commands/logs.js +51 -339
  29. package/dist/cli/commands/logs.js.map +1 -1
  30. package/dist/cli/main.js +1 -30
  31. package/dist/cli/main.js.map +1 -1
  32. package/dist/control/routes/dashboard-api.d.ts.map +1 -1
  33. package/dist/control/routes/dashboard-api.js +3 -2
  34. package/dist/control/routes/dashboard-api.js.map +1 -1
  35. package/dist/control/routes/log-helpers.d.ts +4 -4
  36. package/dist/control/routes/log-helpers.d.ts.map +1 -1
  37. package/dist/control/routes/log-helpers.js +12 -7
  38. package/dist/control/routes/log-helpers.js.map +1 -1
  39. package/dist/control/routes/logs.d.ts.map +1 -1
  40. package/dist/control/routes/logs.js +10 -10
  41. package/dist/control/routes/logs.js.map +1 -1
  42. package/dist/control/session-store.js +1 -1
  43. package/dist/control/session-store.js.map +1 -1
  44. package/dist/docker/image.d.ts +26 -12
  45. package/dist/docker/image.d.ts.map +1 -1
  46. package/dist/docker/image.js +80 -88
  47. package/dist/docker/image.js.map +1 -1
  48. package/dist/docker/local-runtime.js +1 -1
  49. package/dist/docker/local-runtime.js.map +1 -1
  50. package/dist/docker/providers/index.d.ts +0 -4
  51. package/dist/docker/providers/index.d.ts.map +1 -1
  52. package/dist/docker/providers/index.js +0 -38
  53. package/dist/docker/providers/index.js.map +1 -1
  54. package/dist/docker/ssh-docker-runtime.js +1 -1
  55. package/dist/docker/ssh-docker-runtime.js.map +1 -1
  56. package/dist/execution/execution.d.ts +4 -2
  57. package/dist/execution/execution.d.ts.map +1 -1
  58. package/dist/execution/execution.js +14 -9
  59. package/dist/execution/execution.js.map +1 -1
  60. package/dist/execution/index.d.ts +1 -11
  61. package/dist/execution/index.d.ts.map +1 -1
  62. package/dist/execution/index.js +1 -8
  63. package/dist/execution/index.js.map +1 -1
  64. package/dist/execution/lifecycle/agent-lifecycle.d.ts +7 -0
  65. package/dist/execution/lifecycle/agent-lifecycle.d.ts.map +1 -1
  66. package/dist/execution/lifecycle/agent-lifecycle.js +65 -5
  67. package/dist/execution/lifecycle/agent-lifecycle.js.map +1 -1
  68. package/dist/execution/lifecycle/index.d.ts +2 -2
  69. package/dist/execution/lifecycle/index.d.ts.map +1 -1
  70. package/dist/execution/lifecycle/index.js +4 -2
  71. package/dist/execution/lifecycle/index.js.map +1 -1
  72. package/dist/execution/lifecycle/instance-lifecycle.d.ts +24 -0
  73. package/dist/execution/lifecycle/instance-lifecycle.d.ts.map +1 -1
  74. package/dist/execution/lifecycle/instance-lifecycle.js +41 -3
  75. package/dist/execution/lifecycle/instance-lifecycle.js.map +1 -1
  76. package/dist/execution/runner-setup.d.ts +9 -11
  77. package/dist/execution/runner-setup.d.ts.map +1 -1
  78. package/dist/execution/runner-setup.js +19 -14
  79. package/dist/execution/runner-setup.js.map +1 -1
  80. package/dist/execution/runtime-factory.d.ts +1 -15
  81. package/dist/execution/runtime-factory.d.ts.map +1 -1
  82. package/dist/execution/runtime-factory.js +1 -18
  83. package/dist/execution/runtime-factory.js.map +1 -1
  84. package/dist/execution/waiting-registry.d.ts +84 -0
  85. package/dist/execution/waiting-registry.d.ts.map +1 -0
  86. package/dist/execution/waiting-registry.js +185 -0
  87. package/dist/execution/waiting-registry.js.map +1 -0
  88. package/dist/frontend/assets/index-Bij4b7-g.js +16 -0
  89. package/dist/frontend/assets/index-Bij4b7-g.js.map +1 -0
  90. package/dist/frontend/assets/index-D11bfFWQ.css +2 -0
  91. package/dist/frontend/index.html +2 -2
  92. package/dist/gateway/index.d.ts +1 -1
  93. package/dist/gateway/index.d.ts.map +1 -1
  94. package/dist/gateway/index.js +6 -47
  95. package/dist/gateway/index.js.map +1 -1
  96. package/dist/gateway/routes/system.d.ts +1 -4
  97. package/dist/gateway/routes/system.d.ts.map +1 -1
  98. package/dist/gateway/routes/system.js +3 -8
  99. package/dist/gateway/routes/system.js.map +1 -1
  100. package/dist/gateway/stores.d.ts +0 -4
  101. package/dist/gateway/stores.d.ts.map +1 -1
  102. package/dist/gateway/stores.js +2 -10
  103. package/dist/gateway/stores.js.map +1 -1
  104. package/dist/gateway/types.d.ts +0 -13
  105. package/dist/gateway/types.d.ts.map +1 -1
  106. package/dist/mcp/server.d.ts.map +1 -1
  107. package/dist/mcp/server.js +11 -0
  108. package/dist/mcp/server.js.map +1 -1
  109. package/dist/scheduler/gateway-setup.d.ts +0 -2
  110. package/dist/scheduler/gateway-setup.d.ts.map +1 -1
  111. package/dist/scheduler/gateway-setup.js +2 -11
  112. package/dist/scheduler/gateway-setup.js.map +1 -1
  113. package/dist/scheduler/index.d.ts.map +1 -1
  114. package/dist/scheduler/index.js +113 -55
  115. package/dist/scheduler/index.js.map +1 -1
  116. package/dist/scheduler/types.d.ts +1 -1
  117. package/dist/scheduler/types.d.ts.map +1 -1
  118. package/dist/scheduler/validation.js +1 -1
  119. package/dist/scheduler/validation.js.map +1 -1
  120. package/dist/scheduler/watcher.d.ts +2 -8
  121. package/dist/scheduler/watcher.d.ts.map +1 -1
  122. package/dist/scheduler/watcher.js +7 -104
  123. package/dist/scheduler/watcher.js.map +1 -1
  124. package/dist/shared/config/load-agent.js +2 -2
  125. package/dist/shared/config/load-agent.js.map +1 -1
  126. package/dist/shared/config/load-project.js +2 -2
  127. package/dist/shared/config/load-project.js.map +1 -1
  128. package/dist/shared/config/types.d.ts +12 -2
  129. package/dist/shared/config/types.d.ts.map +1 -1
  130. package/dist/shared/constants.d.ts +3 -1
  131. package/dist/shared/constants.d.ts.map +1 -1
  132. package/dist/shared/constants.js +4 -2
  133. package/dist/shared/constants.js.map +1 -1
  134. package/dist/shared/credential-refs.js +1 -1
  135. package/dist/shared/credential-refs.js.map +1 -1
  136. package/dist/shared/paths.d.ts.map +1 -1
  137. package/dist/shared/paths.js +2 -2
  138. package/dist/shared/paths.js.map +1 -1
  139. package/dist/shared/validation.js +1 -1
  140. package/dist/shared/validation.js.map +1 -1
  141. package/dist/transport/docker-exec.d.ts +41 -0
  142. package/dist/transport/docker-exec.d.ts.map +1 -0
  143. package/dist/transport/docker-exec.js +243 -0
  144. package/dist/transport/docker-exec.js.map +1 -0
  145. package/dist/transport/host-user.d.ts +37 -0
  146. package/dist/transport/host-user.d.ts.map +1 -0
  147. package/dist/transport/host-user.js +232 -0
  148. package/dist/transport/host-user.js.map +1 -0
  149. package/dist/transport/index.d.ts +8 -0
  150. package/dist/transport/index.d.ts.map +1 -0
  151. package/dist/transport/index.js +7 -0
  152. package/dist/transport/index.js.map +1 -0
  153. package/dist/transport/memory.d.ts +36 -0
  154. package/dist/transport/memory.d.ts.map +1 -0
  155. package/dist/transport/memory.js +113 -0
  156. package/dist/transport/memory.js.map +1 -0
  157. package/dist/transport/operations.d.ts +68 -0
  158. package/dist/transport/operations.d.ts.map +1 -0
  159. package/dist/transport/operations.js +164 -0
  160. package/dist/transport/operations.js.map +1 -0
  161. package/dist/transport/ssh.d.ts +43 -0
  162. package/dist/transport/ssh.d.ts.map +1 -0
  163. package/dist/transport/ssh.js +225 -0
  164. package/dist/transport/ssh.js.map +1 -0
  165. package/dist/transport/transport.d.ts +61 -0
  166. package/dist/transport/transport.d.ts.map +1 -0
  167. package/dist/transport/transport.js +29 -0
  168. package/dist/transport/transport.js.map +1 -0
  169. package/dist/tui/App.d.ts.map +1 -1
  170. package/dist/tui/App.js +5 -4
  171. package/dist/tui/App.js.map +1 -1
  172. package/dist/tui/status-tracker.d.ts +11 -2
  173. package/dist/tui/status-tracker.d.ts.map +1 -1
  174. package/dist/tui/status-tracker.js +44 -0
  175. package/dist/tui/status-tracker.js.map +1 -1
  176. package/package.json +7 -2
  177. package/dist/agents/bash-prefix.d.ts +0 -9
  178. package/dist/agents/bash-prefix.d.ts.map +0 -1
  179. package/dist/agents/bash-prefix.js +0 -25
  180. package/dist/agents/bash-prefix.js.map +0 -1
  181. package/dist/agents/container-entry.d.ts +0 -31
  182. package/dist/agents/container-entry.d.ts.map +0 -1
  183. package/dist/agents/container-entry.js +0 -302
  184. package/dist/agents/container-entry.js.map +0 -1
  185. package/dist/agents/container-runner.d.ts +0 -59
  186. package/dist/agents/container-runner.d.ts.map +0 -1
  187. package/dist/agents/container-runner.js +0 -472
  188. package/dist/agents/container-runner.js.map +0 -1
  189. package/dist/agents/harness/claude-cli-harness.d.ts +0 -15
  190. package/dist/agents/harness/claude-cli-harness.d.ts.map +0 -1
  191. package/dist/agents/harness/claude-cli-harness.js +0 -260
  192. package/dist/agents/harness/claude-cli-harness.js.map +0 -1
  193. package/dist/agents/harness/consumer.d.ts +0 -31
  194. package/dist/agents/harness/consumer.d.ts.map +0 -1
  195. package/dist/agents/harness/consumer.js +0 -165
  196. package/dist/agents/harness/consumer.js.map +0 -1
  197. package/dist/agents/harness/factory.d.ts +0 -9
  198. package/dist/agents/harness/factory.d.ts.map +0 -1
  199. package/dist/agents/harness/factory.js +0 -25
  200. package/dist/agents/harness/factory.js.map +0 -1
  201. package/dist/agents/harness/index.d.ts +0 -9
  202. package/dist/agents/harness/index.d.ts.map +0 -1
  203. package/dist/agents/harness/index.js +0 -5
  204. package/dist/agents/harness/index.js.map +0 -1
  205. package/dist/agents/harness/pi-harness.d.ts +0 -18
  206. package/dist/agents/harness/pi-harness.d.ts.map +0 -1
  207. package/dist/agents/harness/pi-harness.js +0 -278
  208. package/dist/agents/harness/pi-harness.js.map +0 -1
  209. package/dist/agents/harness/types.d.ts +0 -57
  210. package/dist/agents/harness/types.d.ts.map +0 -1
  211. package/dist/agents/harness/types.js +0 -2
  212. package/dist/agents/harness/types.js.map +0 -1
  213. package/dist/agents/session-loop.d.ts +0 -36
  214. package/dist/agents/session-loop.d.ts.map +0 -1
  215. package/dist/agents/session-loop.js +0 -216
  216. package/dist/agents/session-loop.js.map +0 -1
  217. package/dist/agents/signals.d.ts +0 -34
  218. package/dist/agents/signals.d.ts.map +0 -1
  219. package/dist/agents/signals.js +0 -122
  220. package/dist/agents/signals.js.map +0 -1
  221. package/dist/cli/commands/claude.d.ts +0 -4
  222. package/dist/cli/commands/claude.d.ts.map +0 -1
  223. package/dist/cli/commands/claude.js +0 -6
  224. package/dist/cli/commands/claude.js.map +0 -1
  225. package/dist/cli/commands/run-agent.d.ts +0 -14
  226. package/dist/cli/commands/run-agent.d.ts.map +0 -1
  227. package/dist/cli/commands/run-agent.js +0 -270
  228. package/dist/cli/commands/run-agent.js.map +0 -1
  229. package/dist/docker/cloud-run-runtime.d.ts +0 -48
  230. package/dist/docker/cloud-run-runtime.d.ts.map +0 -1
  231. package/dist/docker/cloud-run-runtime.js +0 -490
  232. package/dist/docker/cloud-run-runtime.js.map +0 -1
  233. package/dist/execution/call-dispatcher.d.ts +0 -11
  234. package/dist/execution/call-dispatcher.d.ts.map +0 -1
  235. package/dist/execution/call-dispatcher.js +0 -75
  236. package/dist/execution/call-dispatcher.js.map +0 -1
  237. package/dist/execution/container-registry.d.ts +0 -42
  238. package/dist/execution/container-registry.d.ts.map +0 -1
  239. package/dist/execution/container-registry.js +0 -76
  240. package/dist/execution/container-registry.js.map +0 -1
  241. package/dist/execution/image-builder.d.ts +0 -48
  242. package/dist/execution/image-builder.d.ts.map +0 -1
  243. package/dist/execution/image-builder.js +0 -155
  244. package/dist/execution/image-builder.js.map +0 -1
  245. package/dist/execution/routes/calls.d.ts +0 -18
  246. package/dist/execution/routes/calls.d.ts.map +0 -1
  247. package/dist/execution/routes/calls.js +0 -74
  248. package/dist/execution/routes/calls.js.map +0 -1
  249. package/dist/execution/routes/locks.d.ts +0 -10
  250. package/dist/execution/routes/locks.d.ts.map +0 -1
  251. package/dist/execution/routes/locks.js +0 -166
  252. package/dist/execution/routes/locks.js.map +0 -1
  253. package/dist/execution/routes/shutdown.d.ts +0 -5
  254. package/dist/execution/routes/shutdown.d.ts.map +0 -1
  255. package/dist/execution/routes/shutdown.js +0 -24
  256. package/dist/execution/routes/shutdown.js.map +0 -1
  257. package/dist/execution/routes/signals.d.ts +0 -12
  258. package/dist/execution/routes/signals.d.ts.map +0 -1
  259. package/dist/execution/routes/signals.js +0 -123
  260. package/dist/execution/routes/signals.js.map +0 -1
  261. package/dist/execution/types.d.ts +0 -23
  262. package/dist/execution/types.d.ts.map +0 -1
  263. package/dist/execution/types.js +0 -2
  264. package/dist/execution/types.js.map +0 -1
  265. package/dist/frontend/assets/index-DnSu-8Kw.js +0 -16
  266. package/dist/frontend/assets/index-DnSu-8Kw.js.map +0 -1
  267. package/dist/frontend/assets/index-Z0IOXEax.css +0 -2
  268. package/dist/gateway/routes/execution.d.ts +0 -24
  269. package/dist/gateway/routes/execution.d.ts.map +0 -1
  270. package/dist/gateway/routes/execution.js +0 -13
  271. package/dist/gateway/routes/execution.js.map +0 -1
  272. package/dist/scheduler/orphan-recovery.d.ts +0 -25
  273. package/dist/scheduler/orphan-recovery.d.ts.map +0 -1
  274. package/dist/scheduler/orphan-recovery.js +0 -144
  275. package/dist/scheduler/orphan-recovery.js.map +0 -1
  276. package/docker/bin/_http-exit +0 -35
  277. package/docker/bin/al-exit +0 -4
  278. package/docker/bin/al-export +0 -50
  279. package/docker/bin/al-rerun +0 -8
  280. package/docker/bin/al-return +0 -14
  281. package/docker/bin/al-shutdown +0 -5
  282. package/docker/bin/al-status +0 -9
  283. package/docker/bin/al-subagent +0 -13
  284. package/docker/bin/al-subagent-check +0 -10
  285. package/docker/bin/al-subagent-wait +0 -37
  286. package/docker/bin/rlock +0 -12
  287. package/docker/bin/rlock-heartbeat +0 -12
  288. package/docker/bin/runlock +0 -12
@@ -0,0 +1,791 @@
1
+ /**
2
+ * TransportAgentRunner — runs Pi sessions in the scheduler process,
3
+ * driving a remote runtime via a Transport.
4
+ *
5
+ * Replaces ContainerAgentRunner for the centralized architecture where:
6
+ * - The scheduler is the "brain" (LLM session, tool orchestration)
7
+ * - The runtime is the "body" (filesystem, shell, credentials)
8
+ * - The transport connects them (DockerExecTransport, SshTransport, etc.)
9
+ *
10
+ * Key differences from ContainerAgentRunner:
11
+ * - Pi session runs in-process (not inside the container)
12
+ * - Events are captured directly (no JSON log parsing)
13
+ * - Credentials staged via transport (no volume mounts needed)
14
+ * - No gateway registration (direct access to scheduler services)
15
+ * - No signal files (scheduler tools handle reruns, returns, etc.)
16
+ */
17
+ import { randomBytes } from "crypto";
18
+ import { execFileSync } from "child_process";
19
+ import { getModel } from "@mariozechner/pi-ai";
20
+ import { AuthStorage, createAgentSession, SessionManager, SettingsManager, DefaultResourceLoader, ModelRegistry, } from "@mariozechner/pi-coding-agent";
21
+ import { sessionStatsToUsage } from "../shared/usage.js";
22
+ import { DEFAULT_AGENT_TIMEOUT } from "../shared/constants.js";
23
+ import { selectAvailableModels, isRateLimitError } from "./model-fallback.js";
24
+ import { DockerExecTransport } from "../transport/docker-exec.js";
25
+ import { SshTransport } from "../transport/ssh.js";
26
+ import { HostUserTransport } from "../transport/host-user.js";
27
+ import { createTransportTools } from "../transport/operations.js";
28
+ import { parseCredentialRef, getDefaultBackend } from "../shared/credentials.js";
29
+ import { parseFrontmatter } from "../shared/frontmatter.js";
30
+ import { withSpan } from "../telemetry/index.js";
31
+ import { SpanKind } from "@opentelemetry/api";
32
+ import { createSchedulerTools } from "./scheduler-tools.js";
33
+ const MAX_MODEL_PASSES = 3;
34
+ const DEFAULT_BACKOFF_MS = 30_000;
35
+ const MAX_BACKOFF_MS = 300_000;
36
+ const CONTAINER_CWD = "/tmp";
37
+ export class TransportAgentRunner {
38
+ _running = false;
39
+ _suspended = false;
40
+ _aborting = false;
41
+ _transport = null;
42
+ _containerName = null;
43
+ _session = null;
44
+ _turnTextBuffer = "";
45
+ _turnThinkingBuffer = "";
46
+ instanceId;
47
+ /** Trigger depth for subagent call tracking. Set by the scheduler before run(). */
48
+ depth = 0;
49
+ globalConfig;
50
+ agentConfig;
51
+ baseLogger;
52
+ logger;
53
+ circuitBreaker;
54
+ statusTracker;
55
+ baseImage;
56
+ projectPath;
57
+ providerKeys;
58
+ schedulerToolsDeps;
59
+ waitingRegistry;
60
+ onWaitStateChange;
61
+ constructor(opts) {
62
+ this.globalConfig = opts.globalConfig;
63
+ this.agentConfig = opts.agentConfig;
64
+ this.baseLogger = opts.logger;
65
+ this.logger = opts.logger;
66
+ this.circuitBreaker = opts.circuitBreaker;
67
+ this.statusTracker = opts.statusTracker;
68
+ this.baseImage = opts.baseImage;
69
+ this.projectPath = opts.projectPath;
70
+ this.providerKeys = opts.providerKeys ?? new Map();
71
+ this.schedulerToolsDeps = opts.schedulerToolsDeps;
72
+ this.waitingRegistry = opts.waitingRegistry;
73
+ this.onWaitStateChange = opts.onWaitStateChange;
74
+ this.instanceId = opts.agentConfig.name;
75
+ }
76
+ get isRunning() {
77
+ return this._running;
78
+ }
79
+ setAgentConfig(config) {
80
+ this.agentConfig = config;
81
+ }
82
+ get isSuspended() {
83
+ return this._suspended;
84
+ }
85
+ abort() {
86
+ this._aborting = true;
87
+ this.logger.info("Transport agent runner abort requested");
88
+ // Dispose the Pi session to stop the LLM loop
89
+ if (this._session) {
90
+ try {
91
+ this._session.dispose();
92
+ }
93
+ catch { /* best effort */ }
94
+ }
95
+ // Unpause container first if suspended, then kill
96
+ if (this._containerName) {
97
+ if (this._suspended) {
98
+ try {
99
+ execFileSync("docker", ["unpause", this._containerName], {
100
+ timeout: 10_000,
101
+ stdio: ["pipe", "pipe", "pipe"],
102
+ });
103
+ }
104
+ catch { /* container may not be paused */ }
105
+ }
106
+ try {
107
+ execFileSync("docker", ["kill", this._containerName], {
108
+ timeout: 10_000,
109
+ stdio: ["pipe", "pipe", "pipe"],
110
+ });
111
+ }
112
+ catch { /* container may already be dead */ }
113
+ }
114
+ // Close the transport
115
+ if (this._transport) {
116
+ this._transport.close().catch(() => { });
117
+ }
118
+ }
119
+ async run(prompt, triggerInfo, instanceId) {
120
+ if (this._running) {
121
+ this.logger.warn(`${this.agentConfig.name} is already running, skipping`);
122
+ return { result: "error", triggers: [] };
123
+ }
124
+ this._running = true;
125
+ this._aborting = false;
126
+ this.instanceId = instanceId ?? `${this.agentConfig.name}-${randomBytes(4).toString("hex")}`;
127
+ this.logger = this.baseLogger.child({ instance: this.instanceId });
128
+ try {
129
+ return await withSpan("transport_agent.run", async (span) => {
130
+ span.setAttributes({
131
+ "agent.name": this.agentConfig.name,
132
+ "agent.run_id": this.instanceId,
133
+ "agent.trigger_type": triggerInfo?.type || "manual",
134
+ "agent.trigger_source": triggerInfo?.source || "",
135
+ "agent.model_provider": this.agentConfig.models[0]?.provider,
136
+ "agent.model_name": this.agentConfig.models[0]?.model,
137
+ "execution.environment": "transport",
138
+ });
139
+ return this.runInternal(prompt, triggerInfo, span);
140
+ }, {}, SpanKind.INTERNAL);
141
+ }
142
+ catch (err) {
143
+ this._running = false;
144
+ this.logger.error({ err }, "transport run setup failed");
145
+ return { result: "error", triggers: [] };
146
+ }
147
+ }
148
+ async runInternal(prompt, triggerInfo, parentSpan) {
149
+ const runStartTime = Date.now();
150
+ let runResult = "error";
151
+ let runError;
152
+ let returnValue;
153
+ let tokenUsage;
154
+ // Surface run start in TUI
155
+ const runReason = triggerInfo
156
+ ? (triggerInfo.source
157
+ ? (triggerInfo.type === 'agent' ? `triggered by ${triggerInfo.source}` : `${triggerInfo.type} (${triggerInfo.source})`)
158
+ : triggerInfo.type)
159
+ : undefined;
160
+ this.statusTracker?.startRun(this.agentConfig.name, runReason);
161
+ this.statusTracker?.registerInstance({
162
+ id: this.instanceId,
163
+ agentName: this.agentConfig.name,
164
+ status: "running",
165
+ startedAt: new Date(),
166
+ trigger: triggerInfo?.source ? `${triggerInfo.type}:${triggerInfo.source}` : (triggerInfo?.type ?? "manual"),
167
+ });
168
+ this.logger.info(`Starting ${this.agentConfig.name} transport run`);
169
+ this.statusTracker?.addLogLine(this.agentConfig.name, `${this.instanceId} started (${runReason ?? "manual"})`);
170
+ // ── Timeout — kill the entire run if it exceeds the configured limit ──
171
+ const timeoutSeconds = this.agentConfig.timeout ?? this.globalConfig.local?.timeout ?? DEFAULT_AGENT_TIMEOUT;
172
+ const timeoutTimer = setTimeout(() => {
173
+ this.logger.error({ timeoutSeconds }, "agent timeout reached, aborting");
174
+ this.abort();
175
+ }, timeoutSeconds * 1000);
176
+ timeoutTimer.unref();
177
+ try {
178
+ // ── 1–2. Provision runtime & connect transport ──────────
179
+ const transport = await this.createTransport();
180
+ this._transport = transport;
181
+ // ── 3. Stage credentials ─────────────────────────────────
182
+ const providerKeys = await this.stageCredentials(transport);
183
+ // Merge with pre-configured provider keys
184
+ for (const [k, v] of this.providerKeys) {
185
+ if (!providerKeys.has(k))
186
+ providerKeys.set(k, v);
187
+ }
188
+ // ── 4. Run hooks.pre ─────────────────────────────────────
189
+ if (this.agentConfig.hooks?.pre && this.agentConfig.hooks.pre.length > 0) {
190
+ for (const hook of this.agentConfig.hooks.pre) {
191
+ this.logger.info({ hook }, "running pre hook via transport");
192
+ await transport.exec(hook);
193
+ }
194
+ }
195
+ // ── 5. Build SKILL.md and prompt ─────────────────────────
196
+ // Read SKILL.md from the project directory on the host
197
+ const { readFileSync, existsSync } = await import("fs");
198
+ const { join } = await import("path");
199
+ const skillPath = join(this.projectPath, "agents", this.agentConfig.name, "SKILL.md");
200
+ let skillBody = "";
201
+ if (existsSync(skillPath)) {
202
+ const { body } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
203
+ skillBody = body;
204
+ }
205
+ // Process context injection (runs commands on the transport)
206
+ // TODO: In the future, context injection should execute on the transport
207
+ // For now, we skip it since the commands won't have access to the runtime
208
+ // ── 6. Create Pi session with transport tools ────────────
209
+ const result = await this.runPiSession(prompt, skillBody, transport, providerKeys);
210
+ runResult = result.result;
211
+ returnValue = result.returnValue;
212
+ tokenUsage = result.usage;
213
+ runError = result.error;
214
+ // ── 7. Run hooks.post ────────────────────────────────────
215
+ if (this.agentConfig.hooks?.post && this.agentConfig.hooks.post.length > 0) {
216
+ for (const hook of this.agentConfig.hooks.post) {
217
+ this.logger.info({ hook }, "running post hook via transport");
218
+ try {
219
+ await transport.exec(hook);
220
+ }
221
+ catch (err) {
222
+ this.logger.error({ err }, "post hook failed");
223
+ }
224
+ }
225
+ }
226
+ }
227
+ catch (err) {
228
+ this.logger.error({ err }, `${this.agentConfig.name} transport run failed`);
229
+ runError = String(err?.message || err).slice(0, 200);
230
+ }
231
+ finally {
232
+ clearTimeout(timeoutTimer);
233
+ // ── 8. Cleanup ───────────────────────────────────────────
234
+ if (this._transport) {
235
+ try {
236
+ await this._transport.close();
237
+ }
238
+ catch { /* best effort */ }
239
+ this._transport = null;
240
+ }
241
+ if (this._containerName) {
242
+ try {
243
+ execFileSync("docker", ["rm", "-f", this._containerName], {
244
+ timeout: 10_000,
245
+ stdio: ["pipe", "pipe", "pipe"],
246
+ });
247
+ }
248
+ catch { /* best effort */ }
249
+ this._containerName = null;
250
+ }
251
+ const elapsed = Date.now() - runStartTime;
252
+ const instanceStatus = this._aborting ? "killed" : runError ? "error" : "completed";
253
+ this.statusTracker?.completeInstance(this.instanceId, instanceStatus);
254
+ this.statusTracker?.endRun(this.agentConfig.name, elapsed, runError, tokenUsage);
255
+ const elapsedStr = (elapsed / 1000).toFixed(1);
256
+ const turnInfo = tokenUsage?.turnCount != null ? `, ${tokenUsage.turnCount} turns` : "";
257
+ const costInfo = tokenUsage?.cost != null ? `, $${tokenUsage.cost.toFixed(4)}` : "";
258
+ const errInfo = runError ? ` — ${runError.slice(0, 100)}` : "";
259
+ this.statusTracker?.addLogLine(this.agentConfig.name, `${this.instanceId} ${runResult} (${elapsedStr}s${turnInfo}${costInfo})${errInfo}`);
260
+ this.logger.info({
261
+ result: runResult,
262
+ elapsed: `${elapsedStr}s`,
263
+ hasReturnValue: !!returnValue,
264
+ turnCount: tokenUsage?.turnCount,
265
+ inputTokens: tokenUsage?.inputTokens,
266
+ outputTokens: tokenUsage?.outputTokens,
267
+ totalTokens: tokenUsage?.totalTokens,
268
+ cost: tokenUsage?.cost,
269
+ error: runError,
270
+ }, "run outcome");
271
+ if (parentSpan) {
272
+ parentSpan.setAttributes({
273
+ "execution.result": runResult,
274
+ "execution.has_return_value": !!returnValue,
275
+ });
276
+ if (tokenUsage) {
277
+ parentSpan.setAttributes({
278
+ "llm.token.input": tokenUsage.inputTokens,
279
+ "llm.token.output": tokenUsage.outputTokens,
280
+ "llm.token.total": tokenUsage.totalTokens,
281
+ "llm.cost.total": tokenUsage.cost,
282
+ "llm.turns": tokenUsage.turnCount,
283
+ });
284
+ }
285
+ if (runResult === "error") {
286
+ parentSpan.recordException(new Error(`Transport execution failed: ${runError || "Unknown error"}`));
287
+ }
288
+ }
289
+ this._running = false;
290
+ }
291
+ return {
292
+ result: runResult,
293
+ triggers: [],
294
+ returnValue,
295
+ usage: tokenUsage,
296
+ exitReason: runError,
297
+ };
298
+ }
299
+ /**
300
+ * Handle a wait_for_trigger call from the agent.
301
+ * Suspends the container, registers in the waiting registry, and returns
302
+ * a promise that resolves when a matching trigger arrives.
303
+ */
304
+ async _handleWait(filter, timeoutMs, transport) {
305
+ if (!this.waitingRegistry) {
306
+ throw new Error("Waiting registry not available");
307
+ }
308
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
309
+ const deadline = Date.now() + timeoutMs;
310
+ // Disconnect transport and pause container
311
+ await transport.close();
312
+ this._suspended = true;
313
+ if (runtimeType === "container" && this._containerName) {
314
+ try {
315
+ execFileSync("docker", ["pause", this._containerName], {
316
+ timeout: 10_000,
317
+ stdio: ["pipe", "pipe", "pipe"],
318
+ });
319
+ this.logger.info({ container: this._containerName }, "container paused for wait");
320
+ }
321
+ catch (err) {
322
+ this.logger.warn({ err: err.message }, "failed to pause container");
323
+ }
324
+ }
325
+ // Notify status tracker
326
+ this.statusTracker?.setInstanceWaiting(this.instanceId);
327
+ this.onWaitStateChange?.(this.instanceId, "waiting");
328
+ return new Promise((resolve, reject) => {
329
+ const entry = {
330
+ instanceId: this.instanceId,
331
+ agentName: this.agentConfig.name,
332
+ filter,
333
+ deadline,
334
+ registeredAt: Date.now(),
335
+ runId: this._containerName ?? this.instanceId,
336
+ runtimeType,
337
+ cwd: CONTAINER_CWD,
338
+ resolve: async (payload) => {
339
+ // Resume: unpause container, reconnect transport
340
+ try {
341
+ await this._resumeFromWait(transport);
342
+ resolve(payload);
343
+ }
344
+ catch (err) {
345
+ reject(err);
346
+ }
347
+ },
348
+ reject: (err) => {
349
+ // Timeout or kill — still need to clean up
350
+ this._suspended = false;
351
+ this.onWaitStateChange?.(this.instanceId, "resumed");
352
+ reject(err);
353
+ },
354
+ };
355
+ this.waitingRegistry.register(entry);
356
+ this.logger.info({ filter, deadline: new Date(deadline).toISOString() }, "instance registered for wait");
357
+ });
358
+ }
359
+ /**
360
+ * Resume from a suspended wait state: unpause container and reconnect transport.
361
+ */
362
+ async _resumeFromWait(transport) {
363
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
364
+ if (runtimeType === "container" && this._containerName) {
365
+ try {
366
+ execFileSync("docker", ["unpause", this._containerName], {
367
+ timeout: 10_000,
368
+ stdio: ["pipe", "pipe", "pipe"],
369
+ });
370
+ this.logger.info({ container: this._containerName }, "container unpaused after wait");
371
+ }
372
+ catch (err) {
373
+ this.logger.warn({ err: err.message }, "failed to unpause container");
374
+ }
375
+ }
376
+ // Reconnect the transport
377
+ await transport.connect();
378
+ this._suspended = false;
379
+ this.statusTracker?.resumeInstance(this.instanceId);
380
+ this.onWaitStateChange?.(this.instanceId, "resumed");
381
+ this.logger.info("transport reconnected after wait resume");
382
+ }
383
+ /**
384
+ * Create and connect the appropriate transport based on the agent's runtime config.
385
+ */
386
+ async createTransport() {
387
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
388
+ switch (runtimeType) {
389
+ case "ssh": {
390
+ const rt = this.agentConfig.runtime;
391
+ if (!rt.host)
392
+ throw new Error("SSH runtime requires 'host' in [runtime] config");
393
+ const transport = new SshTransport({
394
+ host: rt.host,
395
+ port: rt.port,
396
+ user: rt.user,
397
+ keyPath: rt.key_path,
398
+ cwd: rt.cwd,
399
+ sshOptions: rt.ssh_options,
400
+ });
401
+ await transport.connect();
402
+ this.logger.debug({ host: rt.host, user: rt.user }, "SSH transport connected");
403
+ return transport;
404
+ }
405
+ case "host-user": {
406
+ const rt = this.agentConfig.runtime ?? {};
407
+ const user = rt.run_as ?? "al-agent";
408
+ const transport = new HostUserTransport({
409
+ user,
410
+ groups: rt.groups,
411
+ cwd: rt.cwd,
412
+ });
413
+ await transport.connect();
414
+ this.logger.debug({ user }, "Host-user transport connected");
415
+ return transport;
416
+ }
417
+ case "container":
418
+ default: {
419
+ const containerName = await this.provisionContainer();
420
+ this._containerName = containerName;
421
+ const transport = new DockerExecTransport({
422
+ container: containerName,
423
+ cwd: CONTAINER_CWD,
424
+ });
425
+ await transport.connect();
426
+ this.logger.debug({ container: containerName }, "Docker transport connected");
427
+ return transport;
428
+ }
429
+ }
430
+ }
431
+ /**
432
+ * Provision a Docker container that stays alive for the transport to connect to.
433
+ * Returns the container name.
434
+ */
435
+ async provisionContainer() {
436
+ const runId = randomBytes(4).toString("hex");
437
+ const containerName = `al-${this.agentConfig.name}-${runId}`;
438
+ const memory = this.globalConfig.local?.memory || "4g";
439
+ // Build agent-specific image if a Dockerfile exists, otherwise use base
440
+ const { ensureAgentImage } = await import("../docker/image.js");
441
+ const image = await ensureAgentImage(this.agentConfig.name, this.projectPath, this.baseImage, (msg) => {
442
+ this.logger.debug(msg);
443
+ this.statusTracker?.addLogLine(this.agentConfig.name, msg);
444
+ });
445
+ // Ensure the image is available locally
446
+ this.ensureImageAvailable(image);
447
+ const args = [
448
+ "run", "-d",
449
+ "--name", containerName,
450
+ "--tmpfs", "/tmp:rw,exec,nosuid",
451
+ "--tmpfs", "/credentials:rw,nosuid,nodev,noexec",
452
+ "--cap-drop", "ALL",
453
+ "--security-opt", "no-new-privileges:true",
454
+ "--memory", memory,
455
+ ];
456
+ if (this.globalConfig.local?.cpus) {
457
+ args.push("--cpus", String(this.globalConfig.local.cpus));
458
+ }
459
+ // Keep the container alive with tail -f /dev/null
460
+ args.push(image, "tail", "-f", "/dev/null");
461
+ execFileSync("docker", args, {
462
+ timeout: 30_000,
463
+ encoding: "utf-8",
464
+ });
465
+ this.logger.debug({ container: containerName }, "container provisioned");
466
+ return containerName;
467
+ }
468
+ /**
469
+ * Ensure a Docker image is available locally. If not, attempt to pull it.
470
+ */
471
+ ensureImageAvailable(image) {
472
+ try {
473
+ execFileSync("docker", ["image", "inspect", image], {
474
+ timeout: 10_000,
475
+ stdio: ["pipe", "pipe", "pipe"],
476
+ });
477
+ }
478
+ catch {
479
+ // Image not found locally — try to pull
480
+ this.logger.debug({ image }, "image not found locally, pulling...");
481
+ try {
482
+ execFileSync("docker", ["pull", image], {
483
+ timeout: 120_000,
484
+ stdio: ["pipe", "pipe", "pipe"],
485
+ });
486
+ }
487
+ catch (pullErr) {
488
+ throw new Error(`Base image "${image}" not found and pull failed: ${pullErr.message}`);
489
+ }
490
+ }
491
+ }
492
+ /**
493
+ * Stage credentials onto the runtime via the transport.
494
+ * Returns a map of provider → API key for Pi session auth.
495
+ */
496
+ async stageCredentials(transport) {
497
+ const providerKeys = new Map();
498
+ const credRefs = [...new Set(this.agentConfig.credentials ?? [])];
499
+ // Add provider keys for all configured models
500
+ for (const mc of this.agentConfig.models) {
501
+ if (mc.authType === "pi_auth")
502
+ continue;
503
+ const providerKey = `${mc.provider}_key`;
504
+ if (!credRefs.some((r) => r === providerKey || r.startsWith(`${providerKey}:`))) {
505
+ credRefs.push(providerKey);
506
+ }
507
+ }
508
+ if (credRefs.length === 0)
509
+ return providerKeys;
510
+ // For host-user runtime, stage credentials to a temp dir (not /credentials which is root-only).
511
+ // For container runtimes, /credentials is inside the container filesystem.
512
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
513
+ const isHostUser = runtimeType === "host-user";
514
+ const credBase = isHostUser
515
+ ? `/tmp/al-creds-${randomBytes(4).toString("hex")}`
516
+ : "/credentials";
517
+ const backend = getDefaultBackend();
518
+ const credFiles = new Map();
519
+ for (const credRef of credRefs) {
520
+ const { type, instance } = parseCredentialRef(credRef);
521
+ const fields = await backend.readAll(type, instance);
522
+ if (!fields)
523
+ continue;
524
+ for (const [field, value] of Object.entries(fields)) {
525
+ // Stage credential file on the runtime
526
+ const remotePath = `${credBase}/${type}/${instance}/${field}`;
527
+ credFiles.set(remotePath, Buffer.from(value + "\n", "utf-8"));
528
+ // Extract provider API keys for Pi session auth
529
+ if (field === "api_key" || field === "token") {
530
+ if (type.endsWith("_key")) {
531
+ const provider = type.replace(/_key$/, "");
532
+ providerKeys.set(provider, value);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ // Batch write all credential files
538
+ if (credFiles.size > 0) {
539
+ // Clean and recreate credential directory for a fresh state
540
+ await transport.exec(`rm -rf ${credBase} 2>/dev/null; mkdir -p ${credBase}`);
541
+ await transport.writeFiles(credFiles);
542
+ await transport.exec(`find ${credBase} -type f -exec chmod 400 {} +`);
543
+ this.logger.debug({ count: credFiles.size, credBase }, "credentials staged via transport");
544
+ }
545
+ return providerKeys;
546
+ }
547
+ /**
548
+ * Run the Pi session in-process with transport-backed tools.
549
+ */
550
+ async runPiSession(prompt, skillBody, transport, providerKeys) {
551
+ const models = this.agentConfig.models;
552
+ // Create resource loader with the skill body
553
+ const agentsContent = skillBody || `# ${this.agentConfig.name} Agent\n\nCustom agent.\n`;
554
+ const resourceLoader = new DefaultResourceLoader({
555
+ noExtensions: true,
556
+ agentsFilesOverride: () => ({
557
+ agentsFiles: [
558
+ { path: "/tmp/SKILL.md", content: agentsContent },
559
+ ],
560
+ }),
561
+ });
562
+ await resourceLoader.reload();
563
+ const settingsManager = SettingsManager.inMemory({
564
+ compaction: { enabled: true },
565
+ retry: { enabled: true, maxRetries: 2 },
566
+ });
567
+ // Create transport-backed tools
568
+ const tools = createTransportTools(transport, CONTAINER_CWD);
569
+ // Create scheduler tools (locks, calls, status, return, wait) if deps provided
570
+ let capturedReturnValue;
571
+ const customTools = this.schedulerToolsDeps
572
+ ? createSchedulerTools({
573
+ ...this.schedulerToolsDeps,
574
+ agentName: this.agentConfig.name,
575
+ instanceId: this.instanceId,
576
+ depth: this.depth,
577
+ onReturnValue: (value) => { capturedReturnValue = value; },
578
+ onWait: this.waitingRegistry ? (filter, timeoutMs) => this._handleWait(filter, timeoutMs, transport) : undefined,
579
+ defaultWaitTimeout: this.agentConfig.waitTimeout ?? this.globalConfig.defaultWaitTimeout,
580
+ })
581
+ : undefined;
582
+ let anyModelSucceeded = false;
583
+ for (let pass = 0; pass <= MAX_MODEL_PASSES; pass++) {
584
+ const availableModels = selectAvailableModels(models, this.circuitBreaker);
585
+ let modelSucceeded = false;
586
+ for (const modelConfig of availableModels) {
587
+ if (this._aborting) {
588
+ return { result: "error", error: "Aborted" };
589
+ }
590
+ const authStorage = AuthStorage.create();
591
+ const providerKey = providerKeys.get(modelConfig.provider);
592
+ if (providerKey) {
593
+ authStorage.setRuntimeApiKey(modelConfig.provider, providerKey);
594
+ }
595
+ // Resolve model — either from built-in registry or custom provider with baseUrl
596
+ let llmModel;
597
+ let customModelRegistry;
598
+ if (modelConfig.baseUrl) {
599
+ // Create a model registry with the custom provider registered
600
+ customModelRegistry = ModelRegistry.inMemory(authStorage);
601
+ const providerName = `custom_${modelConfig.provider}`;
602
+ // Register API key under custom provider name too
603
+ if (providerKey) {
604
+ authStorage.setRuntimeApiKey(providerName, providerKey);
605
+ }
606
+ customModelRegistry.registerProvider(providerName, {
607
+ baseUrl: modelConfig.baseUrl,
608
+ apiKey: providerKey || "dummy-key",
609
+ models: [{
610
+ id: modelConfig.model,
611
+ name: modelConfig.model,
612
+ api: "openai-completions",
613
+ reasoning: false,
614
+ input: ["text"],
615
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
616
+ contextWindow: 128000,
617
+ maxTokens: 16384,
618
+ }],
619
+ });
620
+ llmModel = customModelRegistry.find(providerName, modelConfig.model);
621
+ if (!llmModel) {
622
+ throw new Error(`Failed to register custom model ${modelConfig.provider}/${modelConfig.model} at ${modelConfig.baseUrl}`);
623
+ }
624
+ }
625
+ else {
626
+ llmModel = getModel(modelConfig.provider, modelConfig.model);
627
+ }
628
+ this.logger.debug({
629
+ model: modelConfig.model,
630
+ thinking: modelConfig.thinkingLevel,
631
+ baseUrl: modelConfig.baseUrl,
632
+ }, "creating Pi session with transport tools");
633
+ const { session } = await createAgentSession({
634
+ cwd: CONTAINER_CWD,
635
+ model: llmModel,
636
+ modelRegistry: customModelRegistry,
637
+ thinkingLevel: modelConfig.thinkingLevel,
638
+ authStorage,
639
+ resourceLoader,
640
+ tools,
641
+ customTools,
642
+ sessionManager: SessionManager.inMemory(),
643
+ settingsManager,
644
+ });
645
+ this._session = session;
646
+ // Subscribe to events for logging and status tracking
647
+ this.subscribeToEvents(session);
648
+ try {
649
+ const state = session.state;
650
+ this.logger.debug({
651
+ promptLength: prompt.length,
652
+ model: state?.model ? { id: state.model.id, provider: state.model.provider, api: state.model.api, baseUrl: state.model.baseUrl } : "NO MODEL",
653
+ toolCount: state?.tools?.length ?? 0,
654
+ }, "about to call session.prompt()");
655
+ await session.prompt(prompt);
656
+ this.flushTurnBuffers();
657
+ const allMessages = session.state?.messages ?? [];
658
+ const lastMsg = allMessages[allMessages.length - 1];
659
+ if (lastMsg?.stopReason === "error") {
660
+ this.logger.error({
661
+ errorMessage: lastMsg?.errorMessage ?? session.state?.errorMessage,
662
+ }, "session.prompt() completed with error");
663
+ }
664
+ this.circuitBreaker.recordSuccess(modelConfig.provider, modelConfig.model);
665
+ // Get usage stats (logged later in "run outcome")
666
+ const sessionStats = session.getSessionStats();
667
+ const usage = sessionStatsToUsage(sessionStats);
668
+ session.dispose();
669
+ this._session = null;
670
+ modelSucceeded = true;
671
+ anyModelSucceeded = true;
672
+ return { result: "completed", usage, returnValue: capturedReturnValue };
673
+ }
674
+ catch (promptErr) {
675
+ const msg = String(promptErr?.message || promptErr || "");
676
+ if (isRateLimitError(msg)) {
677
+ this.circuitBreaker.recordFailure(modelConfig.provider, modelConfig.model);
678
+ this.logger.warn({
679
+ provider: modelConfig.provider,
680
+ model: modelConfig.model,
681
+ }, "rate limited, trying next model");
682
+ session.dispose();
683
+ this._session = null;
684
+ continue;
685
+ }
686
+ session.dispose();
687
+ this._session = null;
688
+ return { result: "error", error: msg };
689
+ }
690
+ }
691
+ if (modelSucceeded)
692
+ break;
693
+ if (pass < MAX_MODEL_PASSES) {
694
+ const delayMs = Math.min(DEFAULT_BACKOFF_MS * Math.pow(2, pass), MAX_BACKOFF_MS);
695
+ this.logger.warn({ pass: pass + 1, delayMs }, "all models exhausted, backing off");
696
+ await new Promise((r) => setTimeout(r, delayMs));
697
+ }
698
+ }
699
+ if (!anyModelSucceeded) {
700
+ this.logger.error("all models exhausted across all retry passes");
701
+ return { result: "error", error: "All models exhausted — rate limited across all retries" };
702
+ }
703
+ return { result: "completed" };
704
+ }
705
+ /**
706
+ * Subscribe to Pi session events and forward them to the logger and status tracker.
707
+ * Since the session runs in-process, we get direct access to events.
708
+ */
709
+ subscribeToEvents(session) {
710
+ session.subscribe((event) => {
711
+ this.logger.debug({ eventType: event.type }, "session event");
712
+ if (event.type === "tool_execution_start") {
713
+ const toolName = String(event.toolName || "unknown");
714
+ const summary = toolName === "bash"
715
+ ? String(event.args?.command || "").slice(0, 200)
716
+ : JSON.stringify(event.args ?? {}).slice(0, 200);
717
+ this.logger.info({ toolName, summary }, "[tool]");
718
+ }
719
+ if (event.type === "tool_execution_end") {
720
+ const toolName = String(event.toolName || "unknown");
721
+ const resultText = this.extractToolResultText(event.result);
722
+ if (event.isError) {
723
+ const errorSummary = resultText.slice(0, 200);
724
+ this.statusTracker?.setAgentError(this.agentConfig.name, errorSummary);
725
+ this.logger.warn({ toolName, error: errorSummary }, "[error]");
726
+ }
727
+ else if (resultText) {
728
+ this.logger.info({ toolName, summary: resultText.slice(0, 200) }, "[result]");
729
+ }
730
+ }
731
+ // Pi SDK emits thinking/text as message_update events with assistantMessageEvent
732
+ if (event.type === "message_update" && event.assistantMessageEvent) {
733
+ const ame = event.assistantMessageEvent;
734
+ if (ame.type === "thinking_delta") {
735
+ const delta = String(ame.delta ?? "");
736
+ if (delta)
737
+ this._turnThinkingBuffer += delta;
738
+ }
739
+ if (ame.type === "text_delta") {
740
+ const delta = String(ame.delta ?? ame.content ?? "");
741
+ if (delta)
742
+ this._turnTextBuffer += delta;
743
+ }
744
+ }
745
+ if (event.type === "turn_end") {
746
+ this.flushTurnBuffers();
747
+ }
748
+ if (event.type === "error") {
749
+ const err = event.error;
750
+ const errorMsg = err?.errorMessage
751
+ || (typeof err === "string" ? err : null)
752
+ || JSON.stringify(event);
753
+ this.logger.error({ error: String(errorMsg).slice(0, 300) }, "[error]");
754
+ this.statusTracker?.setAgentError(this.agentConfig.name, String(errorMsg).slice(0, 200));
755
+ }
756
+ });
757
+ }
758
+ /**
759
+ * Extract human-readable text from a tool result, unwrapping the Pi SDK content array format.
760
+ * Returns empty string for empty/trivial results.
761
+ */
762
+ extractToolResultText(result) {
763
+ if (result == null)
764
+ return "";
765
+ if (typeof result === "string")
766
+ return result.trim();
767
+ // Pi SDK wraps results as { content: [{ type: "text", text: "..." }] }
768
+ const obj = result;
769
+ if (obj.content && Array.isArray(obj.content)) {
770
+ const texts = obj.content
771
+ .filter((c) => c.type === "text" && c.text)
772
+ .map((c) => String(c.text).trim())
773
+ .filter(Boolean);
774
+ return texts.join("\n");
775
+ }
776
+ const str = JSON.stringify(result);
777
+ return str === "{}" || str === "[]" ? "" : str;
778
+ }
779
+ /** Flush accumulated text/thinking buffers to the logger. */
780
+ flushTurnBuffers() {
781
+ if (this._turnThinkingBuffer) {
782
+ this.logger.info({ text: this._turnThinkingBuffer.slice(0, 2000) }, "[thinking]");
783
+ this._turnThinkingBuffer = "";
784
+ }
785
+ if (this._turnTextBuffer) {
786
+ this.logger.info({ text: this._turnTextBuffer.slice(0, 2000) }, "[text]");
787
+ this._turnTextBuffer = "";
788
+ }
789
+ }
790
+ }
791
+ //# sourceMappingURL=transport-runner.js.map