@action-llama/action-llama 0.27.4 → 0.28.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 (242) 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.js +1 -1
  6. package/dist/agents/prompt.js.map +1 -1
  7. package/dist/agents/scheduler-tools.d.ts +40 -0
  8. package/dist/agents/scheduler-tools.d.ts.map +1 -0
  9. package/dist/agents/scheduler-tools.js +178 -0
  10. package/dist/agents/scheduler-tools.js.map +1 -0
  11. package/dist/agents/transport-runner.d.ts +95 -0
  12. package/dist/agents/transport-runner.d.ts.map +1 -0
  13. package/dist/agents/transport-runner.js +653 -0
  14. package/dist/agents/transport-runner.js.map +1 -0
  15. package/dist/build-info.json +1 -1
  16. package/dist/cli/commands/add.d.ts +1 -0
  17. package/dist/cli/commands/add.d.ts.map +1 -1
  18. package/dist/cli/commands/add.js +24 -9
  19. package/dist/cli/commands/add.js.map +1 -1
  20. package/dist/cli/commands/agent.d.ts +0 -3
  21. package/dist/cli/commands/agent.d.ts.map +1 -1
  22. package/dist/cli/commands/agent.js +3 -67
  23. package/dist/cli/commands/agent.js.map +1 -1
  24. package/dist/cli/commands/logs.d.ts.map +1 -1
  25. package/dist/cli/commands/logs.js +2 -3
  26. package/dist/cli/commands/logs.js.map +1 -1
  27. package/dist/cli/main.js +1 -30
  28. package/dist/cli/main.js.map +1 -1
  29. package/dist/control/routes/dashboard-api.js +1 -1
  30. package/dist/control/routes/dashboard-api.js.map +1 -1
  31. package/dist/control/routes/log-helpers.d.ts +4 -4
  32. package/dist/control/routes/log-helpers.d.ts.map +1 -1
  33. package/dist/control/routes/log-helpers.js +12 -7
  34. package/dist/control/routes/log-helpers.js.map +1 -1
  35. package/dist/control/routes/log-summary.d.ts.map +1 -1
  36. package/dist/control/routes/log-summary.js +22 -9
  37. package/dist/control/routes/log-summary.js.map +1 -1
  38. package/dist/control/routes/logs.d.ts.map +1 -1
  39. package/dist/control/routes/logs.js +10 -10
  40. package/dist/control/routes/logs.js.map +1 -1
  41. package/dist/docker/providers/index.d.ts +0 -4
  42. package/dist/docker/providers/index.d.ts.map +1 -1
  43. package/dist/docker/providers/index.js +0 -38
  44. package/dist/docker/providers/index.js.map +1 -1
  45. package/dist/execution/execution.d.ts +0 -1
  46. package/dist/execution/execution.d.ts.map +1 -1
  47. package/dist/execution/execution.js +5 -9
  48. package/dist/execution/execution.js.map +1 -1
  49. package/dist/execution/index.d.ts +1 -11
  50. package/dist/execution/index.d.ts.map +1 -1
  51. package/dist/execution/index.js +1 -8
  52. package/dist/execution/index.js.map +1 -1
  53. package/dist/execution/runner-setup.d.ts +6 -11
  54. package/dist/execution/runner-setup.d.ts.map +1 -1
  55. package/dist/execution/runner-setup.js +18 -14
  56. package/dist/execution/runner-setup.js.map +1 -1
  57. package/dist/execution/runtime-factory.d.ts +1 -15
  58. package/dist/execution/runtime-factory.d.ts.map +1 -1
  59. package/dist/execution/runtime-factory.js +1 -18
  60. package/dist/execution/runtime-factory.js.map +1 -1
  61. package/dist/frontend/assets/{index-a78B5S5H.js → index-DnSu-8Kw.js} +1 -1
  62. package/dist/frontend/assets/index-DnSu-8Kw.js.map +1 -0
  63. package/dist/frontend/index.html +1 -1
  64. package/dist/gateway/index.d.ts +1 -1
  65. package/dist/gateway/index.d.ts.map +1 -1
  66. package/dist/gateway/index.js +6 -47
  67. package/dist/gateway/index.js.map +1 -1
  68. package/dist/gateway/routes/system.d.ts +1 -4
  69. package/dist/gateway/routes/system.d.ts.map +1 -1
  70. package/dist/gateway/routes/system.js +3 -8
  71. package/dist/gateway/routes/system.js.map +1 -1
  72. package/dist/gateway/stores.d.ts +0 -4
  73. package/dist/gateway/stores.d.ts.map +1 -1
  74. package/dist/gateway/stores.js +2 -10
  75. package/dist/gateway/stores.js.map +1 -1
  76. package/dist/gateway/types.d.ts +0 -13
  77. package/dist/gateway/types.d.ts.map +1 -1
  78. package/dist/mcp/server.d.ts.map +1 -1
  79. package/dist/mcp/server.js +11 -0
  80. package/dist/mcp/server.js.map +1 -1
  81. package/dist/models/providers/custom.d.ts.map +1 -1
  82. package/dist/models/providers/custom.js +3 -2
  83. package/dist/models/providers/custom.js.map +1 -1
  84. package/dist/models/providers/openai.d.ts.map +1 -1
  85. package/dist/models/providers/openai.js +3 -2
  86. package/dist/models/providers/openai.js.map +1 -1
  87. package/dist/scheduler/gateway-setup.d.ts +0 -2
  88. package/dist/scheduler/gateway-setup.d.ts.map +1 -1
  89. package/dist/scheduler/gateway-setup.js +2 -11
  90. package/dist/scheduler/gateway-setup.js.map +1 -1
  91. package/dist/scheduler/index.d.ts.map +1 -1
  92. package/dist/scheduler/index.js +95 -55
  93. package/dist/scheduler/index.js.map +1 -1
  94. package/dist/scheduler/validation.js +1 -1
  95. package/dist/scheduler/validation.js.map +1 -1
  96. package/dist/scheduler/watcher.d.ts +2 -8
  97. package/dist/scheduler/watcher.d.ts.map +1 -1
  98. package/dist/scheduler/watcher.js +7 -104
  99. package/dist/scheduler/watcher.js.map +1 -1
  100. package/dist/shared/config/load-agent.js +2 -2
  101. package/dist/shared/config/load-agent.js.map +1 -1
  102. package/dist/shared/config/load-project.js +2 -2
  103. package/dist/shared/config/load-project.js.map +1 -1
  104. package/dist/shared/config/types.d.ts +10 -2
  105. package/dist/shared/config/types.d.ts.map +1 -1
  106. package/dist/shared/constants.d.ts +1 -1
  107. package/dist/shared/constants.d.ts.map +1 -1
  108. package/dist/shared/constants.js +2 -2
  109. package/dist/shared/constants.js.map +1 -1
  110. package/dist/shared/credential-refs.js +1 -1
  111. package/dist/shared/credential-refs.js.map +1 -1
  112. package/dist/shared/paths.d.ts.map +1 -1
  113. package/dist/shared/paths.js +2 -2
  114. package/dist/shared/paths.js.map +1 -1
  115. package/dist/shared/validation.js +1 -1
  116. package/dist/shared/validation.js.map +1 -1
  117. package/dist/transport/docker-exec.d.ts +41 -0
  118. package/dist/transport/docker-exec.d.ts.map +1 -0
  119. package/dist/transport/docker-exec.js +331 -0
  120. package/dist/transport/docker-exec.js.map +1 -0
  121. package/dist/transport/host-user.d.ts +37 -0
  122. package/dist/transport/host-user.d.ts.map +1 -0
  123. package/dist/transport/host-user.js +232 -0
  124. package/dist/transport/host-user.js.map +1 -0
  125. package/dist/transport/index.d.ts +8 -0
  126. package/dist/transport/index.d.ts.map +1 -0
  127. package/dist/transport/index.js +7 -0
  128. package/dist/transport/index.js.map +1 -0
  129. package/dist/transport/memory.d.ts +35 -0
  130. package/dist/transport/memory.d.ts.map +1 -0
  131. package/dist/transport/memory.js +110 -0
  132. package/dist/transport/memory.js.map +1 -0
  133. package/dist/transport/operations.d.ts +68 -0
  134. package/dist/transport/operations.d.ts.map +1 -0
  135. package/dist/transport/operations.js +164 -0
  136. package/dist/transport/operations.js.map +1 -0
  137. package/dist/transport/ssh.d.ts +43 -0
  138. package/dist/transport/ssh.d.ts.map +1 -0
  139. package/dist/transport/ssh.js +225 -0
  140. package/dist/transport/ssh.js.map +1 -0
  141. package/dist/transport/transport.d.ts +59 -0
  142. package/dist/transport/transport.d.ts.map +1 -0
  143. package/dist/transport/transport.js +29 -0
  144. package/dist/transport/transport.js.map +1 -0
  145. package/package.json +1 -1
  146. package/dist/agents/container-entry.d.ts +0 -31
  147. package/dist/agents/container-entry.d.ts.map +0 -1
  148. package/dist/agents/container-entry.js +0 -302
  149. package/dist/agents/container-entry.js.map +0 -1
  150. package/dist/agents/container-runner.d.ts +0 -59
  151. package/dist/agents/container-runner.d.ts.map +0 -1
  152. package/dist/agents/container-runner.js +0 -472
  153. package/dist/agents/container-runner.js.map +0 -1
  154. package/dist/agents/harness/claude-cli-harness.d.ts +0 -15
  155. package/dist/agents/harness/claude-cli-harness.d.ts.map +0 -1
  156. package/dist/agents/harness/claude-cli-harness.js +0 -240
  157. package/dist/agents/harness/claude-cli-harness.js.map +0 -1
  158. package/dist/agents/harness/consumer.d.ts +0 -31
  159. package/dist/agents/harness/consumer.d.ts.map +0 -1
  160. package/dist/agents/harness/consumer.js +0 -161
  161. package/dist/agents/harness/consumer.js.map +0 -1
  162. package/dist/agents/harness/factory.d.ts +0 -9
  163. package/dist/agents/harness/factory.d.ts.map +0 -1
  164. package/dist/agents/harness/factory.js +0 -25
  165. package/dist/agents/harness/factory.js.map +0 -1
  166. package/dist/agents/harness/index.d.ts +0 -9
  167. package/dist/agents/harness/index.d.ts.map +0 -1
  168. package/dist/agents/harness/index.js +0 -5
  169. package/dist/agents/harness/index.js.map +0 -1
  170. package/dist/agents/harness/pi-harness.d.ts +0 -18
  171. package/dist/agents/harness/pi-harness.d.ts.map +0 -1
  172. package/dist/agents/harness/pi-harness.js +0 -259
  173. package/dist/agents/harness/pi-harness.js.map +0 -1
  174. package/dist/agents/harness/types.d.ts +0 -57
  175. package/dist/agents/harness/types.d.ts.map +0 -1
  176. package/dist/agents/harness/types.js +0 -2
  177. package/dist/agents/harness/types.js.map +0 -1
  178. package/dist/agents/session-loop.d.ts +0 -36
  179. package/dist/agents/session-loop.d.ts.map +0 -1
  180. package/dist/agents/session-loop.js +0 -216
  181. package/dist/agents/session-loop.js.map +0 -1
  182. package/dist/agents/signals.d.ts +0 -34
  183. package/dist/agents/signals.d.ts.map +0 -1
  184. package/dist/agents/signals.js +0 -122
  185. package/dist/agents/signals.js.map +0 -1
  186. package/dist/cli/commands/claude.d.ts +0 -4
  187. package/dist/cli/commands/claude.d.ts.map +0 -1
  188. package/dist/cli/commands/claude.js +0 -6
  189. package/dist/cli/commands/claude.js.map +0 -1
  190. package/dist/cli/commands/run-agent.d.ts +0 -14
  191. package/dist/cli/commands/run-agent.d.ts.map +0 -1
  192. package/dist/cli/commands/run-agent.js +0 -270
  193. package/dist/cli/commands/run-agent.js.map +0 -1
  194. package/dist/docker/cloud-run-runtime.d.ts +0 -48
  195. package/dist/docker/cloud-run-runtime.d.ts.map +0 -1
  196. package/dist/docker/cloud-run-runtime.js +0 -490
  197. package/dist/docker/cloud-run-runtime.js.map +0 -1
  198. package/dist/docker/image.d.ts +0 -19
  199. package/dist/docker/image.d.ts.map +0 -1
  200. package/dist/docker/image.js +0 -111
  201. package/dist/docker/image.js.map +0 -1
  202. package/dist/execution/call-dispatcher.d.ts +0 -11
  203. package/dist/execution/call-dispatcher.d.ts.map +0 -1
  204. package/dist/execution/call-dispatcher.js +0 -75
  205. package/dist/execution/call-dispatcher.js.map +0 -1
  206. package/dist/execution/container-registry.d.ts +0 -42
  207. package/dist/execution/container-registry.d.ts.map +0 -1
  208. package/dist/execution/container-registry.js +0 -76
  209. package/dist/execution/container-registry.js.map +0 -1
  210. package/dist/execution/image-builder.d.ts +0 -48
  211. package/dist/execution/image-builder.d.ts.map +0 -1
  212. package/dist/execution/image-builder.js +0 -155
  213. package/dist/execution/image-builder.js.map +0 -1
  214. package/dist/execution/routes/calls.d.ts +0 -18
  215. package/dist/execution/routes/calls.d.ts.map +0 -1
  216. package/dist/execution/routes/calls.js +0 -74
  217. package/dist/execution/routes/calls.js.map +0 -1
  218. package/dist/execution/routes/locks.d.ts +0 -10
  219. package/dist/execution/routes/locks.d.ts.map +0 -1
  220. package/dist/execution/routes/locks.js +0 -166
  221. package/dist/execution/routes/locks.js.map +0 -1
  222. package/dist/execution/routes/shutdown.d.ts +0 -5
  223. package/dist/execution/routes/shutdown.d.ts.map +0 -1
  224. package/dist/execution/routes/shutdown.js +0 -24
  225. package/dist/execution/routes/shutdown.js.map +0 -1
  226. package/dist/execution/routes/signals.d.ts +0 -12
  227. package/dist/execution/routes/signals.d.ts.map +0 -1
  228. package/dist/execution/routes/signals.js +0 -123
  229. package/dist/execution/routes/signals.js.map +0 -1
  230. package/dist/execution/types.d.ts +0 -23
  231. package/dist/execution/types.d.ts.map +0 -1
  232. package/dist/execution/types.js +0 -2
  233. package/dist/execution/types.js.map +0 -1
  234. package/dist/frontend/assets/index-a78B5S5H.js.map +0 -1
  235. package/dist/gateway/routes/execution.d.ts +0 -24
  236. package/dist/gateway/routes/execution.d.ts.map +0 -1
  237. package/dist/gateway/routes/execution.js +0 -13
  238. package/dist/gateway/routes/execution.js.map +0 -1
  239. package/dist/scheduler/orphan-recovery.d.ts +0 -25
  240. package/dist/scheduler/orphan-recovery.d.ts.map +0 -1
  241. package/dist/scheduler/orphan-recovery.js +0 -144
  242. package/dist/scheduler/orphan-recovery.js.map +0 -1
@@ -0,0 +1,653 @@
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 = "/workspace";
37
+ export class TransportAgentRunner {
38
+ _running = false;
39
+ _aborting = false;
40
+ _transport = null;
41
+ _containerName = null;
42
+ _session = null;
43
+ instanceId;
44
+ /** Trigger depth for subagent call tracking. Set by the scheduler before run(). */
45
+ depth = 0;
46
+ globalConfig;
47
+ agentConfig;
48
+ baseLogger;
49
+ logger;
50
+ circuitBreaker;
51
+ statusTracker;
52
+ baseImage;
53
+ projectPath;
54
+ providerKeys;
55
+ schedulerToolsDeps;
56
+ constructor(opts) {
57
+ this.globalConfig = opts.globalConfig;
58
+ this.agentConfig = opts.agentConfig;
59
+ this.baseLogger = opts.logger;
60
+ this.logger = opts.logger;
61
+ this.circuitBreaker = opts.circuitBreaker;
62
+ this.statusTracker = opts.statusTracker;
63
+ this.baseImage = opts.baseImage;
64
+ this.projectPath = opts.projectPath;
65
+ this.providerKeys = opts.providerKeys ?? new Map();
66
+ this.schedulerToolsDeps = opts.schedulerToolsDeps;
67
+ this.instanceId = opts.agentConfig.name;
68
+ }
69
+ get isRunning() {
70
+ return this._running;
71
+ }
72
+ setAgentConfig(config) {
73
+ this.agentConfig = config;
74
+ }
75
+ abort() {
76
+ this._aborting = true;
77
+ this.logger.info("Transport agent runner abort requested");
78
+ // Dispose the Pi session to stop the LLM loop
79
+ if (this._session) {
80
+ try {
81
+ this._session.dispose();
82
+ }
83
+ catch { /* best effort */ }
84
+ }
85
+ // Kill the container (only applies to container runtime)
86
+ if (this._containerName) {
87
+ try {
88
+ execFileSync("docker", ["kill", this._containerName], {
89
+ timeout: 10_000,
90
+ stdio: ["pipe", "pipe", "pipe"],
91
+ });
92
+ }
93
+ catch { /* container may already be dead */ }
94
+ }
95
+ // Close the transport
96
+ if (this._transport) {
97
+ this._transport.close().catch(() => { });
98
+ }
99
+ }
100
+ async run(prompt, triggerInfo, instanceId) {
101
+ if (this._running) {
102
+ this.logger.warn(`${this.agentConfig.name} is already running, skipping`);
103
+ return { result: "error", triggers: [] };
104
+ }
105
+ this._running = true;
106
+ this._aborting = false;
107
+ this.instanceId = instanceId ?? `${this.agentConfig.name}-${randomBytes(4).toString("hex")}`;
108
+ this.logger = this.baseLogger.child({ instance: this.instanceId });
109
+ try {
110
+ return await withSpan("transport_agent.run", async (span) => {
111
+ span.setAttributes({
112
+ "agent.name": this.agentConfig.name,
113
+ "agent.run_id": this.instanceId,
114
+ "agent.trigger_type": triggerInfo?.type || "manual",
115
+ "agent.trigger_source": triggerInfo?.source || "",
116
+ "agent.model_provider": this.agentConfig.models[0]?.provider,
117
+ "agent.model_name": this.agentConfig.models[0]?.model,
118
+ "execution.environment": "transport",
119
+ });
120
+ return this.runInternal(prompt, triggerInfo, span);
121
+ }, {}, SpanKind.INTERNAL);
122
+ }
123
+ catch (err) {
124
+ this._running = false;
125
+ this.logger.error({ err }, "transport run setup failed");
126
+ return { result: "error", triggers: [] };
127
+ }
128
+ }
129
+ async runInternal(prompt, triggerInfo, parentSpan) {
130
+ const runStartTime = Date.now();
131
+ let runResult = "error";
132
+ let runError;
133
+ let returnValue;
134
+ let tokenUsage;
135
+ // Surface run start in TUI
136
+ const runReason = triggerInfo
137
+ ? (triggerInfo.source
138
+ ? (triggerInfo.type === 'agent' ? `triggered by ${triggerInfo.source}` : `${triggerInfo.type} (${triggerInfo.source})`)
139
+ : triggerInfo.type)
140
+ : undefined;
141
+ this.statusTracker?.startRun(this.agentConfig.name, runReason);
142
+ this.statusTracker?.registerInstance({
143
+ id: this.instanceId,
144
+ agentName: this.agentConfig.name,
145
+ status: "running",
146
+ startedAt: new Date(),
147
+ trigger: triggerInfo?.source ? `${triggerInfo.type}:${triggerInfo.source}` : (triggerInfo?.type ?? "manual"),
148
+ });
149
+ this.logger.info(`Starting ${this.agentConfig.name} transport run`);
150
+ this.statusTracker?.addLogLine(this.agentConfig.name, `${this.instanceId} started (${runReason ?? "manual"})`);
151
+ // ── Timeout — kill the entire run if it exceeds the configured limit ──
152
+ const timeoutSeconds = this.agentConfig.timeout ?? this.globalConfig.local?.timeout ?? DEFAULT_AGENT_TIMEOUT;
153
+ const timeoutTimer = setTimeout(() => {
154
+ this.logger.error({ timeoutSeconds }, "agent timeout reached, aborting");
155
+ this.abort();
156
+ }, timeoutSeconds * 1000);
157
+ timeoutTimer.unref();
158
+ try {
159
+ // ── 1–2. Provision runtime & connect transport ──────────
160
+ const transport = await this.createTransport();
161
+ this._transport = transport;
162
+ // ── 3. Stage credentials ─────────────────────────────────
163
+ const providerKeys = await this.stageCredentials(transport);
164
+ // Merge with pre-configured provider keys
165
+ for (const [k, v] of this.providerKeys) {
166
+ if (!providerKeys.has(k))
167
+ providerKeys.set(k, v);
168
+ }
169
+ // ── 4. Run hooks.pre ─────────────────────────────────────
170
+ if (this.agentConfig.hooks?.pre && this.agentConfig.hooks.pre.length > 0) {
171
+ for (const hook of this.agentConfig.hooks.pre) {
172
+ this.logger.info({ hook }, "running pre hook via transport");
173
+ await transport.exec(hook);
174
+ }
175
+ }
176
+ // ── 5. Build SKILL.md and prompt ─────────────────────────
177
+ // Read SKILL.md from the project directory on the host
178
+ const { readFileSync, existsSync } = await import("fs");
179
+ const { join } = await import("path");
180
+ const skillPath = join(this.projectPath, "agents", this.agentConfig.name, "SKILL.md");
181
+ let skillBody = "";
182
+ if (existsSync(skillPath)) {
183
+ const { body } = parseFrontmatter(readFileSync(skillPath, "utf-8"));
184
+ skillBody = body;
185
+ }
186
+ // Process context injection (runs commands on the transport)
187
+ // TODO: In the future, context injection should execute on the transport
188
+ // For now, we skip it since the commands won't have access to the runtime
189
+ // ── 6. Create Pi session with transport tools ────────────
190
+ const result = await this.runPiSession(prompt, skillBody, transport, providerKeys);
191
+ runResult = result.result;
192
+ returnValue = result.returnValue;
193
+ tokenUsage = result.usage;
194
+ runError = result.error;
195
+ // ── 7. Run hooks.post ────────────────────────────────────
196
+ if (this.agentConfig.hooks?.post && this.agentConfig.hooks.post.length > 0) {
197
+ for (const hook of this.agentConfig.hooks.post) {
198
+ this.logger.info({ hook }, "running post hook via transport");
199
+ try {
200
+ await transport.exec(hook);
201
+ }
202
+ catch (err) {
203
+ this.logger.error({ err }, "post hook failed");
204
+ }
205
+ }
206
+ }
207
+ }
208
+ catch (err) {
209
+ this.logger.error({ err }, `${this.agentConfig.name} transport run failed`);
210
+ runError = String(err?.message || err).slice(0, 200);
211
+ }
212
+ finally {
213
+ clearTimeout(timeoutTimer);
214
+ // ── 8. Cleanup ───────────────────────────────────────────
215
+ if (this._transport) {
216
+ try {
217
+ await this._transport.close();
218
+ }
219
+ catch { /* best effort */ }
220
+ this._transport = null;
221
+ }
222
+ if (this._containerName) {
223
+ try {
224
+ execFileSync("docker", ["rm", "-f", this._containerName], {
225
+ timeout: 10_000,
226
+ stdio: ["pipe", "pipe", "pipe"],
227
+ });
228
+ }
229
+ catch { /* best effort */ }
230
+ this._containerName = null;
231
+ }
232
+ const elapsed = Date.now() - runStartTime;
233
+ const instanceStatus = this._aborting ? "killed" : runError ? "error" : "completed";
234
+ this.statusTracker?.completeInstance(this.instanceId, instanceStatus);
235
+ this.statusTracker?.endRun(this.agentConfig.name, elapsed, runError, tokenUsage);
236
+ const elapsedStr = (elapsed / 1000).toFixed(1);
237
+ const turnInfo = tokenUsage?.turnCount != null ? `, ${tokenUsage.turnCount} turns` : "";
238
+ const costInfo = tokenUsage?.cost != null ? `, $${tokenUsage.cost.toFixed(4)}` : "";
239
+ const errInfo = runError ? ` — ${runError.slice(0, 100)}` : "";
240
+ this.statusTracker?.addLogLine(this.agentConfig.name, `${this.instanceId} ${runResult} (${elapsedStr}s${turnInfo}${costInfo})${errInfo}`);
241
+ this.logger.info({
242
+ result: runResult,
243
+ elapsed: `${elapsedStr}s`,
244
+ hasReturnValue: !!returnValue,
245
+ turnCount: tokenUsage?.turnCount,
246
+ totalTokens: tokenUsage?.totalTokens,
247
+ cost: tokenUsage?.cost,
248
+ error: runError,
249
+ }, "run outcome");
250
+ if (parentSpan) {
251
+ parentSpan.setAttributes({
252
+ "execution.result": runResult,
253
+ "execution.has_return_value": !!returnValue,
254
+ });
255
+ if (tokenUsage) {
256
+ parentSpan.setAttributes({
257
+ "llm.token.input": tokenUsage.inputTokens,
258
+ "llm.token.output": tokenUsage.outputTokens,
259
+ "llm.token.total": tokenUsage.totalTokens,
260
+ "llm.cost.total": tokenUsage.cost,
261
+ "llm.turns": tokenUsage.turnCount,
262
+ });
263
+ }
264
+ if (runResult === "error") {
265
+ parentSpan.recordException(new Error(`Transport execution failed: ${runError || "Unknown error"}`));
266
+ }
267
+ }
268
+ this._running = false;
269
+ }
270
+ return {
271
+ result: runResult,
272
+ triggers: [],
273
+ returnValue,
274
+ usage: tokenUsage,
275
+ exitReason: runError,
276
+ };
277
+ }
278
+ /**
279
+ * Create and connect the appropriate transport based on the agent's runtime config.
280
+ */
281
+ async createTransport() {
282
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
283
+ switch (runtimeType) {
284
+ case "ssh": {
285
+ const rt = this.agentConfig.runtime;
286
+ if (!rt.host)
287
+ throw new Error("SSH runtime requires 'host' in [runtime] config");
288
+ const transport = new SshTransport({
289
+ host: rt.host,
290
+ port: rt.port,
291
+ user: rt.user,
292
+ keyPath: rt.key_path,
293
+ cwd: rt.cwd,
294
+ sshOptions: rt.ssh_options,
295
+ });
296
+ await transport.connect();
297
+ this.logger.info({ host: rt.host, user: rt.user }, "SSH transport connected");
298
+ return transport;
299
+ }
300
+ case "host-user": {
301
+ const rt = this.agentConfig.runtime ?? {};
302
+ const user = rt.run_as ?? "al-agent";
303
+ const transport = new HostUserTransport({
304
+ user,
305
+ groups: rt.groups,
306
+ cwd: rt.cwd,
307
+ });
308
+ await transport.connect();
309
+ this.logger.info({ user }, "Host-user transport connected");
310
+ return transport;
311
+ }
312
+ case "container":
313
+ default: {
314
+ const containerName = await this.provisionContainer();
315
+ this._containerName = containerName;
316
+ const transport = new DockerExecTransport({
317
+ container: containerName,
318
+ cwd: CONTAINER_CWD,
319
+ });
320
+ await transport.connect();
321
+ this.logger.info({ container: containerName }, "Docker transport connected");
322
+ return transport;
323
+ }
324
+ }
325
+ }
326
+ /**
327
+ * Provision a Docker container that stays alive for the transport to connect to.
328
+ * Returns the container name.
329
+ */
330
+ async provisionContainer() {
331
+ const runId = randomBytes(4).toString("hex");
332
+ const containerName = `al-${this.agentConfig.name}-${runId}`;
333
+ const memory = this.globalConfig.local?.memory || "4g";
334
+ // Ensure the base image is available locally
335
+ this.ensureImageAvailable(this.baseImage);
336
+ const args = [
337
+ "run", "-d",
338
+ "--name", containerName,
339
+ "--tmpfs", "/workspace:rw,exec,nosuid",
340
+ "--tmpfs", "/tmp:rw,exec,nosuid",
341
+ "--cap-drop", "ALL",
342
+ "--security-opt", "no-new-privileges:true",
343
+ "--memory", memory,
344
+ ];
345
+ if (this.globalConfig.local?.cpus) {
346
+ args.push("--cpus", String(this.globalConfig.local.cpus));
347
+ }
348
+ // Keep the container alive with tail -f /dev/null
349
+ args.push(this.baseImage, "tail", "-f", "/dev/null");
350
+ execFileSync("docker", args, {
351
+ timeout: 30_000,
352
+ encoding: "utf-8",
353
+ });
354
+ this.logger.info({ container: containerName }, "container provisioned");
355
+ return containerName;
356
+ }
357
+ /**
358
+ * Ensure a Docker image is available locally. If not, attempt to pull it.
359
+ */
360
+ ensureImageAvailable(image) {
361
+ try {
362
+ execFileSync("docker", ["image", "inspect", image], {
363
+ timeout: 10_000,
364
+ stdio: ["pipe", "pipe", "pipe"],
365
+ });
366
+ }
367
+ catch {
368
+ // Image not found locally — try to pull
369
+ this.logger.info({ image }, "image not found locally, pulling...");
370
+ try {
371
+ execFileSync("docker", ["pull", image], {
372
+ timeout: 120_000,
373
+ stdio: ["pipe", "pipe", "pipe"],
374
+ });
375
+ }
376
+ catch (pullErr) {
377
+ throw new Error(`Base image "${image}" not found and pull failed: ${pullErr.message}`);
378
+ }
379
+ }
380
+ }
381
+ /**
382
+ * Stage credentials onto the runtime via the transport.
383
+ * Returns a map of provider → API key for Pi session auth.
384
+ */
385
+ async stageCredentials(transport) {
386
+ const providerKeys = new Map();
387
+ const credRefs = [...new Set(this.agentConfig.credentials ?? [])];
388
+ // Add provider keys for all configured models
389
+ for (const mc of this.agentConfig.models) {
390
+ if (mc.authType === "pi_auth")
391
+ continue;
392
+ const providerKey = `${mc.provider}_key`;
393
+ if (!credRefs.some((r) => r === providerKey || r.startsWith(`${providerKey}:`))) {
394
+ credRefs.push(providerKey);
395
+ }
396
+ }
397
+ if (credRefs.length === 0)
398
+ return providerKeys;
399
+ // For host-user runtime, stage credentials to a temp dir (not /credentials which is root-only).
400
+ // For container runtimes, /credentials is inside the container filesystem.
401
+ const runtimeType = this.agentConfig.runtime?.type ?? "container";
402
+ const isHostUser = runtimeType === "host-user";
403
+ const credBase = isHostUser
404
+ ? `/tmp/al-creds-${randomBytes(4).toString("hex")}`
405
+ : "/credentials";
406
+ const backend = getDefaultBackend();
407
+ const credFiles = new Map();
408
+ for (const credRef of credRefs) {
409
+ const { type, instance } = parseCredentialRef(credRef);
410
+ const fields = await backend.readAll(type, instance);
411
+ if (!fields)
412
+ continue;
413
+ for (const [field, value] of Object.entries(fields)) {
414
+ // Stage credential file on the runtime
415
+ const remotePath = `${credBase}/${type}/${instance}/${field}`;
416
+ credFiles.set(remotePath, Buffer.from(value + "\n", "utf-8"));
417
+ // Extract provider API keys for Pi session auth
418
+ if (field === "api_key" || field === "token") {
419
+ if (type.endsWith("_key")) {
420
+ const provider = type.replace(/_key$/, "");
421
+ providerKeys.set(provider, value);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ // Batch write all credential files
427
+ if (credFiles.size > 0) {
428
+ // Clean and recreate credential directory for a fresh state
429
+ await transport.exec(`rm -rf ${credBase} 2>/dev/null; mkdir -p ${credBase}`);
430
+ await transport.writeFiles(credFiles);
431
+ await transport.exec(`find ${credBase} -type f -exec chmod 400 {} +`);
432
+ this.logger.info({ count: credFiles.size, credBase }, "credentials staged via transport");
433
+ }
434
+ return providerKeys;
435
+ }
436
+ /**
437
+ * Run the Pi session in-process with transport-backed tools.
438
+ */
439
+ async runPiSession(prompt, skillBody, transport, providerKeys) {
440
+ const models = this.agentConfig.models;
441
+ // Create resource loader with the skill body
442
+ const agentsContent = skillBody || `# ${this.agentConfig.name} Agent\n\nCustom agent.\n`;
443
+ const resourceLoader = new DefaultResourceLoader({
444
+ noExtensions: true,
445
+ agentsFilesOverride: () => ({
446
+ agentsFiles: [
447
+ { path: "/tmp/SKILL.md", content: agentsContent },
448
+ ],
449
+ }),
450
+ });
451
+ await resourceLoader.reload();
452
+ const settingsManager = SettingsManager.inMemory({
453
+ compaction: { enabled: true },
454
+ retry: { enabled: true, maxRetries: 2 },
455
+ });
456
+ // Create transport-backed tools
457
+ const tools = createTransportTools(transport, CONTAINER_CWD);
458
+ // Create scheduler tools (locks, calls, status, return) if deps provided
459
+ let capturedReturnValue;
460
+ const customTools = this.schedulerToolsDeps
461
+ ? createSchedulerTools({
462
+ ...this.schedulerToolsDeps,
463
+ agentName: this.agentConfig.name,
464
+ instanceId: this.instanceId,
465
+ depth: this.depth,
466
+ onReturnValue: (value) => { capturedReturnValue = value; },
467
+ })
468
+ : undefined;
469
+ let anyModelSucceeded = false;
470
+ for (let pass = 0; pass <= MAX_MODEL_PASSES; pass++) {
471
+ const availableModels = selectAvailableModels(models, this.circuitBreaker);
472
+ let modelSucceeded = false;
473
+ for (const modelConfig of availableModels) {
474
+ if (this._aborting) {
475
+ return { result: "error", error: "Aborted" };
476
+ }
477
+ const authStorage = AuthStorage.create();
478
+ const providerKey = providerKeys.get(modelConfig.provider);
479
+ if (providerKey) {
480
+ authStorage.setRuntimeApiKey(modelConfig.provider, providerKey);
481
+ }
482
+ // Resolve model — either from built-in registry or custom provider with baseUrl
483
+ let llmModel;
484
+ let customModelRegistry;
485
+ if (modelConfig.baseUrl) {
486
+ // Create a model registry with the custom provider registered
487
+ customModelRegistry = ModelRegistry.inMemory(authStorage);
488
+ const providerName = `custom_${modelConfig.provider}`;
489
+ // Register API key under custom provider name too
490
+ if (providerKey) {
491
+ authStorage.setRuntimeApiKey(providerName, providerKey);
492
+ }
493
+ customModelRegistry.registerProvider(providerName, {
494
+ baseUrl: modelConfig.baseUrl,
495
+ apiKey: providerKey || "dummy-key",
496
+ models: [{
497
+ id: modelConfig.model,
498
+ name: modelConfig.model,
499
+ api: "openai-completions",
500
+ reasoning: false,
501
+ input: ["text"],
502
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
503
+ contextWindow: 128000,
504
+ maxTokens: 16384,
505
+ }],
506
+ });
507
+ llmModel = customModelRegistry.find(providerName, modelConfig.model);
508
+ if (!llmModel) {
509
+ throw new Error(`Failed to register custom model ${modelConfig.provider}/${modelConfig.model} at ${modelConfig.baseUrl}`);
510
+ }
511
+ }
512
+ else {
513
+ llmModel = getModel(modelConfig.provider, modelConfig.model);
514
+ }
515
+ this.logger.info({
516
+ model: modelConfig.model,
517
+ thinking: modelConfig.thinkingLevel,
518
+ baseUrl: modelConfig.baseUrl,
519
+ }, "creating Pi session with transport tools");
520
+ const { session } = await createAgentSession({
521
+ cwd: CONTAINER_CWD,
522
+ model: llmModel,
523
+ modelRegistry: customModelRegistry,
524
+ thinkingLevel: modelConfig.thinkingLevel,
525
+ authStorage,
526
+ resourceLoader,
527
+ tools,
528
+ customTools,
529
+ sessionManager: SessionManager.inMemory(),
530
+ settingsManager,
531
+ });
532
+ this._session = session;
533
+ // Subscribe to events for logging and status tracking
534
+ this.subscribeToEvents(session);
535
+ try {
536
+ const state = session.state;
537
+ this.logger.debug({
538
+ promptLength: prompt.length,
539
+ model: state?.model ? { id: state.model.id, provider: state.model.provider, api: state.model.api, baseUrl: state.model.baseUrl } : "NO MODEL",
540
+ toolCount: state?.tools?.length ?? 0,
541
+ }, "about to call session.prompt()");
542
+ await session.prompt(prompt);
543
+ const allMessages = session.state?.messages ?? [];
544
+ const lastMsg = allMessages[allMessages.length - 1];
545
+ if (lastMsg?.stopReason === "error") {
546
+ this.logger.error({
547
+ errorMessage: lastMsg?.errorMessage ?? session.state?.errorMessage,
548
+ }, "session.prompt() completed with error");
549
+ }
550
+ this.circuitBreaker.recordSuccess(modelConfig.provider, modelConfig.model);
551
+ // Get usage stats
552
+ const sessionStats = session.getSessionStats();
553
+ const usage = sessionStatsToUsage(sessionStats);
554
+ this.logger.info({
555
+ inputTokens: usage.inputTokens,
556
+ outputTokens: usage.outputTokens,
557
+ turnCount: usage.turnCount,
558
+ cost: usage.cost,
559
+ }, "session completed");
560
+ session.dispose();
561
+ this._session = null;
562
+ modelSucceeded = true;
563
+ anyModelSucceeded = true;
564
+ return { result: "completed", usage, returnValue: capturedReturnValue };
565
+ }
566
+ catch (promptErr) {
567
+ const msg = String(promptErr?.message || promptErr || "");
568
+ if (isRateLimitError(msg)) {
569
+ this.circuitBreaker.recordFailure(modelConfig.provider, modelConfig.model);
570
+ this.logger.warn({
571
+ provider: modelConfig.provider,
572
+ model: modelConfig.model,
573
+ }, "rate limited, trying next model");
574
+ session.dispose();
575
+ this._session = null;
576
+ continue;
577
+ }
578
+ session.dispose();
579
+ this._session = null;
580
+ return { result: "error", error: msg };
581
+ }
582
+ }
583
+ if (modelSucceeded)
584
+ break;
585
+ if (pass < MAX_MODEL_PASSES) {
586
+ const delayMs = Math.min(DEFAULT_BACKOFF_MS * Math.pow(2, pass), MAX_BACKOFF_MS);
587
+ this.logger.warn({ pass: pass + 1, delayMs }, "all models exhausted, backing off");
588
+ await new Promise((r) => setTimeout(r, delayMs));
589
+ }
590
+ }
591
+ if (!anyModelSucceeded) {
592
+ this.logger.error("all models exhausted across all retry passes");
593
+ return { result: "error", error: "All models exhausted — rate limited across all retries" };
594
+ }
595
+ return { result: "completed" };
596
+ }
597
+ /**
598
+ * Subscribe to Pi session events and forward them to the logger and status tracker.
599
+ * Since the session runs in-process, we get direct access to events.
600
+ */
601
+ subscribeToEvents(session) {
602
+ session.subscribe((event) => {
603
+ // Log all event types at debug level
604
+ this.logger.debug({ eventType: event.type }, "session event");
605
+ if (event.type === "tool_execution_start") {
606
+ const cmd = String(event.args?.command || "");
607
+ this.logger.debug({
608
+ toolName: event.toolName,
609
+ toolCallId: event.toolCallId,
610
+ command: cmd || undefined,
611
+ }, "tool started");
612
+ }
613
+ if (event.type === "tool_execution_end") {
614
+ const resultStr = typeof event.result === "string"
615
+ ? event.result
616
+ : JSON.stringify(event.result);
617
+ if (event.isError) {
618
+ const cmdPrefix = event.args?.command ? `$ ${String(event.args.command).slice(0, 80)} — ` : "";
619
+ this.statusTracker?.setAgentError(this.agentConfig.name, `${cmdPrefix}${resultStr.slice(0, 200)}`);
620
+ }
621
+ this.logger.debug({
622
+ toolName: event.toolName,
623
+ toolCallId: event.toolCallId,
624
+ isError: !!event.isError,
625
+ resultLength: resultStr.length,
626
+ }, "tool ended");
627
+ }
628
+ if (event.type === "error") {
629
+ const err = event.error;
630
+ const errorMsg = err?.errorMessage
631
+ || (typeof err === "string" ? err : null)
632
+ || JSON.stringify(event);
633
+ this.logger.error({ error: errorMsg }, "session error");
634
+ this.statusTracker?.setAgentError(this.agentConfig.name, String(errorMsg).slice(0, 200));
635
+ }
636
+ // Context usage tracking
637
+ if (event.type === "turn_end") {
638
+ try {
639
+ const ctx = session.getContextUsage?.();
640
+ if (ctx && ctx.percent != null) {
641
+ this.logger.info({
642
+ contextPercent: Math.round(ctx.percent * 10) / 10,
643
+ contextWindow: ctx.contextWindow,
644
+ contextTokens: ctx.tokens,
645
+ }, "context-usage");
646
+ }
647
+ }
648
+ catch { /* context usage not available */ }
649
+ }
650
+ });
651
+ }
652
+ }
653
+ //# sourceMappingURL=transport-runner.js.map