agents
Version:
A home for your AI agents
2,151 lines • 100 kB
JavaScript
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../classPrivateFieldGet2-DZBYAB34.js";
import { t as _classPrivateMethodInitSpec } from "../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { t as LifecycleCapability } from "../../capability-DBAKc6UZ.js";
import { n as openHarnessStore } from "../../store-CslVzfN7.js";
import { d as parseDaemonMessage, i as CONTAINER_HEALTH_PATH, l as chunkEntries, o as CONTAINER_SESSION_PATH, r as CONTAINER_ENV, s as CONTAINER_TOKEN_HEADER } from "../../protocol-cOklVBBD.js";
import { WorkerEntrypoint } from "cloudflare:workers";
//#region src/harness/container/link.ts
/**
* The object's side of one session socket: dial the daemon through the
* container's TCP port, read its `hello`, then hand every later frame to
* the harness. Failures to reach the daemon are values, so the harness can
* decide between retrying and giving up.
*/
const HELLO_TIMEOUT_MS = 1e4;
const CONNECT_TIMEOUT_MS = 1e4;
const HEALTH_TIMEOUT_MS = 2e3;
function unreachable(message) {
return {
_tag: "err",
error: {
_tag: "unreachable",
message
}
};
}
function errorText$1(error) {
return error instanceof Error ? error.message : String(error);
}
/**
* Wait until the daemon answers its health check.
*
* @param port - The container's TCP port.
* @param timeoutMs - How long to keep trying.
* @param running - Whether the container still runs; waiting stops early
* once it does not.
* @returns `ok`, or `unreachable` after the timeout or once it stops.
*/
async function waitHealthy(port, timeoutMs, running) {
const deadline = Date.now() + timeoutMs;
let delay = 250;
let last = "no answer";
for (;;) {
try {
const response = await port.fetch(`http://container${CONTAINER_HEALTH_PATH}`, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
await response.body?.cancel();
if (response.ok) return {
_tag: "ok",
value: void 0
};
last = `health check answered ${response.status}`;
} catch (error) {
last = errorText$1(error);
}
if (!running()) return unreachable("the container stopped");
if (Date.now() + delay > deadline) return unreachable(last);
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 2e3);
}
}
var _socket = /* @__PURE__ */ new WeakMap();
var _open = /* @__PURE__ */ new WeakMap();
/** One open session socket. */
var DaemonLink = class DaemonLink {
constructor(socket) {
_classPrivateFieldInitSpec(this, _socket, void 0);
_classPrivateFieldInitSpec(this, _open, true);
_classPrivateFieldSet2(_socket, this, socket);
}
/**
* Dial the daemon for one session and wait for its `hello`.
*
* @param port - The container's TCP port.
* @param session - The session id.
* @param token - The shared secret the container was started with.
* @param handlers - Called for frames after `hello`, and on close.
* @returns The link and the greeting, or why it failed.
*/
static async open(port, session, token, handlers) {
let response;
try {
response = await port.fetch(`http://container${CONTAINER_SESSION_PATH}${encodeURIComponent(session)}`, {
headers: {
Upgrade: "websocket",
[CONTAINER_TOKEN_HEADER]: token
},
signal: AbortSignal.timeout(CONNECT_TIMEOUT_MS)
});
} catch (error) {
return unreachable(errorText$1(error));
}
const socket = response.webSocket;
if (!socket) {
await response.body?.cancel();
return unreachable(`upgrade refused with ${response.status}`);
}
socket.accept();
const link = new DaemonLink(socket);
return new Promise((resolve) => {
let greeted = false;
const timer = setTimeout(() => {
if (greeted) return;
link.close();
resolve(unreachable("no hello from the daemon"));
}, HELLO_TIMEOUT_MS);
socket.addEventListener("message", (event) => {
const message = parseDaemonMessage(typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data));
if (!message) return;
if (!greeted) {
if (message.type !== "hello") return;
greeted = true;
clearTimeout(timer);
if (message.protocol !== 1) {
link.close();
resolve({
_tag: "err",
error: {
_tag: "protocol",
message: `daemon speaks protocol ${message.protocol}, the harness 1`
}
});
return;
}
resolve({
_tag: "ok",
value: {
link,
hello: message
}
});
return;
}
if (message.type === "frame") handlers.frame(message.seq, message.frame);
else if (message.type === "caught-up") handlers.caughtUp();
});
const onClose = () => {
if (!_classPrivateFieldGet2(_open, link)) return;
_classPrivateFieldSet2(_open, link, false);
clearTimeout(timer);
if (!greeted) resolve(unreachable("socket closed before hello"));
else handlers.closed();
};
socket.addEventListener("close", onClose);
socket.addEventListener("error", onClose);
});
}
/** Whether the socket is still open. */
get open() {
return _classPrivateFieldGet2(_open, this);
}
/**
* Send one message. Returns false when the socket has closed.
*
* @param message - The message.
* @returns Whether it was handed to the socket.
*/
send(message) {
if (!_classPrivateFieldGet2(_open, this)) return false;
try {
_classPrivateFieldGet2(_socket, this).send(JSON.stringify(message));
return true;
} catch {
return false;
}
}
/** Close the socket. Does not call `closed`. */
close() {
if (!_classPrivateFieldGet2(_open, this)) return;
_classPrivateFieldSet2(_open, this, false);
try {
_classPrivateFieldGet2(_socket, this).close(1e3, "closed by the harness");
} catch {}
}
};
//#endregion
//#region src/harness/container/daemon-source.generated.ts
/** The harness daemon, bundled: one Node ES module. */
const DAEMON_SOURCE = "var $=\"/healthz\",M=\"/sessions/\",z=\"x-cf-harness-token\",x={token:\"CF_HARNESS_TOKEN\",runtimeId:\"CF_HARNESS_RUNTIME_ID\",port:\"CF_HARNESS_PORT\"},K=4001,W=4003,xe=256*1024;function B(n){return typeof n==\"object\"&&n!==null&&!Array.isArray(n)}function k(n){return typeof n==\"string\"}function E(n){return typeof n==\"number\"&&Number.isSafeInteger(n)&&n>=0}function A(n){if(n===null)return!0;switch(typeof n){case\"string\":case\"boolean\":return!0;case\"number\":return Number.isFinite(n);case\"object\":return Array.isArray(n)?n.every(A):Object.values(n).every(A);default:return!1}}function be(n){return k(n)?!0:Array.isArray(n)&&n.every(e=>B(e)&&(e.type===\"text\"&&k(e.text)||e.type===\"image\"&&k(e.mediaType)&&k(e.data)))}function U(n){return B(n)&&(n.model===void 0||k(n.model))&&(n.options===void 0||A(n.options))}function Ie(n){try{return JSON.parse(n)}catch{return}}function G(n){let e=Ie(n);if(B(e))switch(e.type){case\"restore\":return E(e.generation)&&Array.isArray(e.entries)&&e.entries.every(A)?{type:\"restore\",generation:e.generation,entries:e.entries}:void 0;case\"open\":return E(e.generation)&&U(e.settings)&&Array.isArray(e.restore)&&e.restore.every(A)?{type:\"open\",generation:e.generation,settings:e.settings,restore:e.restore}:void 0;case\"replay\":return E(e.after)?{type:\"replay\",after:e.after}:void 0;case\"prompt\":return k(e.operationId)&&be(e.input)&&(e.whenBusy===\"followUp\"||e.whenBusy===\"steer\")?{type:\"prompt\",operationId:e.operationId,input:e.input,whenBusy:e.whenBusy}:void 0;case\"abort\":return e.operationId===void 0?{type:\"abort\"}:k(e.operationId)?{type:\"abort\",operationId:e.operationId}:void 0;case\"configure\":return U(e.settings)?{type:\"configure\",settings:e.settings}:void 0;case\"ack\":return E(e.seq)?{type:\"ack\",seq:e.seq}:void 0;default:return}}function _(n){return typeof n==\"string\"?n:n.map(e=>e.type===\"text\"?e.text:\"\").join(`\n`)}function Y(n){let e=[],t=[],r=0;for(let o of n){let s=JSON.stringify(o).length;t.length>0&&r+s>xe&&(e.push(t),t=[],r=0),t.push(o),r+=s}return t.length>0&&e.push(t),e}var X=1e3,T=class{#e;#n;#s;#i=new Map;constructor(e){this.#e=e.adapter,this.#n=e.runtimeId,this.#s=e.maxKeptFrames??1e4}connect(e,t){let r=this.#i.get(e);return r||(r=new J(e,this.#e,this.#n,this.#s),this.#i.set(e,r)),r.attach(t)}async close(){await Promise.all([...this.#i.values()].map(e=>e.shutdown()))}},J=class{#e;#n;#s;#i;#a=0;#r=[];#t=new Map;#l=[];#o;#d;#y;#u;#f=!1;#h=Promise.resolve();constructor(e,t,r,o){this.#e=e,this.#n=t,this.#s=r,this.#i=o}attach(e){let t=this.#u;return this.#u=e,this.#f=!1,t?.close(K,\"replaced by a newer connection\"),this.#c(e,this.#w()),{receive:r=>{if(this.#u!==e)return;let o=G(r);if(!o){this.#c(e,{type:\"error\",message:\"unparseable message\"});return}this.#h=this.#h.then(()=>this.#C(o)).catch(s=>{this.#c(e,{type:\"error\",message:Z(s)})})},closed:()=>{this.#u===e&&(this.#u=void 0,this.#f=!1)}}}async shutdown(){this.#o?.controller.abort();let e=this.#d;this.#d=void 0,await e?.session.close().catch(()=>{})}#w(){let e=[];for(let t of this.#t.values()){let r=t.state;e.push(r.tag===\"settled\"?{operationId:t.id,status:\"settled\",outcome:r.outcome}:{operationId:t.id,status:r.tag})}return{type:\"hello\",protocol:1,runtimeId:this.#s,session:this.#e,adapter:{id:this.#n.id,version:this.#n.version,capabilities:[...this.#n.capabilities??[]]},open:this.#d?.generation??null,lastSeq:this.#a,operations:e}}async#C(e){switch(e.type){case\"restore\":this.#y?.generation!==e.generation&&(this.#y={generation:e.generation,entries:[]}),this.#y.entries.push(...e.entries);return;case\"open\":await this.#x(e);return;case\"replay\":this.#b(e.after);return;case\"prompt\":await this.#I(e.operationId,e.input,e.whenBusy);return;case\"abort\":this.#S(e.operationId);return;case\"configure\":await this.#d?.session.configure?.(e.settings);return;case\"ack\":this.#r=this.#r.filter(t=>t.seq>e.seq);return}}async#x(e){let t=this.#y;if(this.#y=void 0,this.#d?.generation===e.generation)return;let r=t?.generation===e.generation?[...t.entries,...e.restore]:e.restore;this.#d&&(this.#o?.controller.abort(),await this.#d.session.close().catch(()=>{}),this.#d=void 0);let o=await this.#n.open({session:this.#e,settings:e.settings,restore:r,emit:s=>this.#p({kind:\"event\",operationId:this.#o?.id??null,event:s}),persist:s=>{for(let i of Y(s))this.#p({kind:\"persist\",entries:i})},log:(s,i)=>this.#p({kind:\"event\",operationId:this.#o?.id??null,event:{type:\"log\",level:s,message:i}})});this.#d={generation:e.generation,session:o},this.#g()}#b(e){let t=this.#u;if(t){for(let r of this.#r)r.seq>e&&this.#c(t,{type:\"frame\",seq:r.seq,frame:r.frame});this.#c(t,{type:\"caught-up\",lastSeq:this.#a}),this.#f=!0}}async#I(e,t,r){if(this.#t.has(e))return;let o={id:e,input:t,whenBusy:r,state:{tag:\"queued\"}};this.#t.set(e,o),this.#A();let s=this.#o,i=this.#d?.session.steer;if(r===\"steer\"&&s&&i&&await i.call(this.#d?.session,t)&&this.#o===s){o.state={tag:\"running\"},s.folded.push(e),this.#p({kind:\"start\",operationId:e});return}this.#l.push(e),this.#g()}#S(e){let t=e===void 0?[...this.#t.keys()]:this.#t.has(e)?[e]:[];for(let r of t){let o=this.#t.get(r);if(o)if(o.state.tag===\"queued\"){let s=this.#l.indexOf(r);s>=0&&this.#l.splice(s,1),this.#m(o,{status:\"unanswered\",reason:\"aborted\"})}else o.state.tag===\"running\"&&this.#o?.controller.abort()}}#g(){let e=this.#d;if(this.#o||!e)return;let t=this.#l.shift();if(t===void 0)return;let r=this.#t.get(t);if(!r||r.state.tag!==\"queued\"){this.#g();return}let o={id:t,folded:[],controller:new AbortController};this.#o=o,r.state={tag:\"running\"},this.#p({kind:\"start\",operationId:t}),this.#k(e,r,o)}async#k(e,t,r){let o;try{o=await e.session.run({operationId:t.id,input:t.input,signal:r.controller.signal}),r.controller.signal.aborted&&o.status===\"done\"&&(o={status:\"unanswered\",reason:\"aborted\"})}catch(s){o={status:\"unanswered\",reason:r.controller.signal.aborted?\"aborted\":Z(s)}}this.#o===r&&(this.#o=void 0),this.#m(t,o);for(let s of r.folded){let i=this.#t.get(s);i&&this.#m(i,o)}this.#g()}#m(e,t){e.state.tag!==\"settled\"&&(e.state={tag:\"settled\",outcome:t},this.#p({kind:\"settle\",operationId:e.id,outcome:t}))}#p(e){this.#a+=1;let t=this.#a;this.#r.push({seq:t,frame:e}),this.#r.length>this.#i&&this.#r.splice(0,this.#r.length-this.#i);let r=this.#u;r&&this.#f&&this.#c(r,{type:\"frame\",seq:t,frame:e})}#c(e,t){try{e.send(JSON.stringify(t))}catch{}}#A(){if(!(this.#t.size<=X))for(let[e,t]of this.#t){if(this.#t.size<=X)return;t.state.tag===\"settled\"&&this.#t.delete(e)}}};function Z(n){return n instanceof Error?n.message:String(n)}var Q={id:\"echo\",version:\"1\",capabilities:[\"steer\"],open(n){let e=n.restore.length,t=[];return{async run(r){let o=_(r.input),s=/^slow (\\d+) ?(.*)$/s.exec(o);s&&await ke(Number(s[1]),r.signal);let i=/^fail ?(.*)$/s.exec(o);if(i)return{status:\"unanswered\",reason:i[1]||\"failed\"};e+=1;let d=s?s[2]??\"\":o,a=t.splice(0).map(u=>` + ${u}`).join(\"\"),l=`echo: ${d}${a} (turn ${e})`,c=`${r.operationId}:assistant`;return n.emit({type:\"text-delta\",messageId:c,delta:l}),n.emit({type:\"message\",message:{id:c,role:\"assistant\",parts:[{type:\"text\",text:l}],operationId:r.operationId,createdAt:Date.now()}}),n.persist([{turn:e,text:d}]),{status:\"done\",text:l}},steer(r){return t.push(_(r)),!0},async close(){}}}};function ke(n,e){return new Promise((t,r)=>{if(e.aborted){r(new Error(\"aborted\"));return}let o=setTimeout(()=>{e.removeEventListener(\"abort\",s),t()},n),s=()=>{clearTimeout(o),r(new Error(\"aborted\"))};e.addEventListener(\"abort\",s,{once:!0})})}import{spawn as Ne}from\"node:child_process\";import{createHash as De}from\"node:crypto\";import{constants as Me}from\"node:fs\";import{chown as re,copyFile as Be,mkdir as oe,readdir as se,readlink as Je,symlink as Re}from\"node:fs/promises\";import{homedir as Le}from\"node:os\";import{join as O,resolve as L,sep as Ve}from\"node:path\";import{createInterface as Fe}from\"node:readline\";import{mkdir as Ae,open as P,readdir as _e,rm as ve,stat as ee}from\"node:fs/promises\";import{dirname as Oe,join as Ee,relative as Te,resolve as te,sep as ne}from\"node:path\";function w(n){return typeof n==\"object\"&&n!==null&&!Array.isArray(n)}function v(n){if(Array.isArray(n))return n.filter(e=>typeof e==\"string\")}var R=128*1024;function Pe(n){return w(n)&&n.kind===\"file\"&&typeof n.path==\"string\"&&typeof n.offset==\"number\"&&typeof n.data==\"string\"}var N=class{#e;#n;#s=new Map;constructor(e,t){this.#e=te(e),this.#n=t}async restore(e){for(let t of this.#n){let r=this.#t(t);r!==void 0&&r!==this.#e&&await ve(r,{recursive:!0,force:!0})}for(let t of e){if(!Pe(t))continue;let r=this.#t(t.path);if(r===void 0)continue;await Ae(Oe(r),{recursive:!0});let o=t.offset===0?await P(r,\"w\"):await P(r,\"r+\").catch(()=>P(r,\"w\"));try{t.offset>0&&await o.truncate(t.offset),await o.write(Buffer.from(t.data,\"base64\"),0,void 0,t.offset)}finally{await o.close()}}for(let t of await this.#r()){let r=await ee(t);this.#s.set(t,{size:r.size,mtimeMs:r.mtimeMs})}}async collect(){let e=[];for(let t of await this.#r()){let r=await ee(t).catch(()=>{});if(!r)continue;let o=this.#s.get(t);if(o&&o.size===r.size&&o.mtimeMs===r.mtimeMs)continue;let s=o&&r.size>o.size?o.size:0,i=await this.#a(t,s,r.size),d=[];for(let a=0;a<i.length;a+=R)d.push(i.subarray(a,a+R));d.length===0&&d.push(i),d.forEach((a,l)=>{e.push(this.#i(t,s+l*R,a))}),this.#s.set(t,{size:s+i.length,mtimeMs:r.mtimeMs})}return e}#i(e,t,r){return{kind:\"file\",path:Te(this.#e,e).split(ne).join(\"/\"),offset:t,data:r.toString(\"base64\")}}async#a(e,t,r){let o=await P(e,\"r\");try{let s=Buffer.alloc(Math.max(0,r-t)),{bytesRead:i}=await o.read(s,0,s.length,t);return s.subarray(0,i)}finally{await o.close()}}async#r(){let e=[],t=async r=>{let o=await _e(r,{withFileTypes:!0}).catch(()=>[]);for(let s of o){let i=Ee(r,s.name);s.isDirectory()?await t(i):s.isFile()&&e.push(i)}};for(let r of this.#n){let o=this.#t(r);o!==void 0&&await t(o)}return e.sort()}#t(e){let t=te(this.#e,e);return t===this.#e||t.startsWith(this.#e+ne)?t:void 0}};var He=5e3,je=4096;function qe(n){let e={};for(let t of n){if(!w(t)||t.kind!==\"state\")continue;let r=t.values;if(w(r))for(let[o,s]of Object.entries(r))typeof s==\"string\"&&(e[o]=s)}return e}async function ie(n,e){await re(n,e.uid,e.gid);let t=await se(n,{withFileTypes:!0}).catch(()=>[]);for(let r of t){let o=O(n,r.name);r.isDirectory()?await ie(o,e):await re(o,e.uid,e.gid).catch(()=>{})}}async function Ue(n,e,t){let r=L(t),o=async(s,i)=>{let d=await se(s,{withFileTypes:!0}).catch(()=>[]);for(let a of d){let l=O(s,a.name),c=L(l);if(c===r||c.startsWith(r+Ve))continue;let u=O(i,a.name);a.isDirectory()?(await oe(u,{recursive:!0}),await o(l,u)):a.isSymbolicLink()?await Re(await Je(l),u).catch(()=>{}):a.isFile()&&await Be(l,u,Me.COPYFILE_EXCL).catch(()=>{})}};await o(L(n),e)}function $e(n,e){return O(n,De(\"sha256\").update(e).digest(\"hex\").slice(0,32))}var V=class{#e;#n;#s;#i;#a;#r;#t=Promise.resolve();constructor(e,t,r,o){this.#e=e,this.#n=t,this.#s=r,this.#i=o,this.#a=qe(t.restore),this.#r=t.settings}async run(e){let t={operationId:e.operationId,prompt:_(e.input),settings:this.#r,state:this.#a},r=this.#e.command(t),o=this.#e.parser(t),[s,...i]=r.argv;if(s===void 0)throw new Error(\"The CLI command is empty\");let d=this.#e.user,a=Ne(s,i,{cwd:this.#e.cwd??\"/workspace\",env:{...process.env,...r.env,HOME:this.#s},stdio:[\"pipe\",\"pipe\",\"pipe\"],...d?{uid:d.uid,gid:d.gid}:{}});a.stdin.on(\"error\",()=>{}),a.stdin.end(r.stdin??\"\");let l=\"\";a.stderr.setEncoding(\"utf8\"),a.stderr.on(\"data\",p=>{l=(l+p).slice(-je)});let c;Fe({input:a.stdout}).on(\"line\",p=>{if(p.trim()===\"\")return;let y;try{y=o.line(p)}catch(h){this.#n.log(\"warn\",`unreadable CLI output: ${String(h)}`);return}for(let h of y.events??[])this.#n.emit(h);y.state&&this.#l(y.state),y.outcome&&(c=y.outcome)});let b=()=>{a.kill(\"SIGINT\"),setTimeout(()=>a.kill(\"SIGKILL\"),He).unref()};e.signal.addEventListener(\"abort\",b,{once:!0});let m=setInterval(()=>{this.#o()},this.#e.syncIntervalMs??5e3),f=await new Promise(p=>{a.on(\"error\",y=>{l+=`\n${y.message}`,p(null)}),a.on(\"close\",y=>p(y))});return clearInterval(m),e.signal.removeEventListener(\"abort\",b),await this.#o(),e.signal.aborted?{status:\"unanswered\",reason:\"aborted\"}:c??o.end({code:f,stderr:l})}configure(e){this.#r=e}async close(){await this.#t}#l(e){Object.entries(e).some(([r,o])=>this.#a[r]!==o)&&(this.#a={...this.#a,...e},this.#n.persist([{kind:\"state\",values:{...e}}]))}#o(){return this.#t=this.#t.then(async()=>{try{let e=await this.#i.collect();e.length>0&&this.#n.persist(e)}catch(e){this.#n.log(\"warn\",`state sync failed: ${String(e)}`)}}),this.#t}};function ae(n){let e=n.homes??O(Le(),\".harness\",\"sessions\");return{id:n.id,version:n.version??\"1\",capabilities:[\"resume\",\"fork\",\"model\"],async open(t){let r=$e(e,t.session);await oe(r,{recursive:!0}),n.skeleton&&await Ue(n.skeleton,r,e);let o=new N(r,n.stateDirs??[]);return await o.restore(t.restore),n.user&&await ie(r,n.user),new V(n,t,r,o)}}}function de(n){if(n===void 0)return null;try{return JSON.parse(JSON.stringify(n))}catch{return String(n)}}function ze(n){if(typeof n==\"string\")return n;if(Array.isArray(n)){let e=n.map(t=>typeof t==\"object\"&&t!==null&&\"type\"in t&&t.type===\"text\"&&\"text\"in t&&typeof t.text==\"string\"?t.text:void 0).filter(t=>t!==void 0);if(e.length===n.length)return e.join(`\n`)}return de(n)}var D=class{#e=new Map;#n=new Map;#s=new Set;#i;#a;constructor(e=Date.now){this.#a=e}project(e,t){if(e.parent_tool_use_id)return[];switch(e.type){case\"stream_event\":return this.#r(e);case\"assistant\":return this.#t(e,t);case\"user\":return this.#l(e);case\"result\":return this.#o(e);default:return[]}}outcome(e,t){if(t)return{status:\"unanswered\",reason:\"aborted\"};if(e.subtype===\"success\"&&e.is_error!==!0)return{status:\"done\",text:e.result??\"\"};let r=e.errors?.join(\"; \")||e.result;return{status:\"unanswered\",reason:[e.subtype&&e.subtype!==\"success\"?e.subtype:\"\",r].filter(s=>s).join(\": \")||\"error\"}}#r(e){let t=e.event;if(!t)return[];if(t.type===\"message_start\")return this.#i=t.message?.id,[];let r=this.#i;return t.type!==\"content_block_delta\"||!r||!t.delta?[]:t.delta.type===\"text_delta\"&&t.delta.text?[{type:\"text-delta\",messageId:r,delta:t.delta.text}]:t.delta.type===\"thinking_delta\"&&t.delta.thinking?[{type:\"reasoning-delta\",messageId:r,delta:t.delta.thinking}]:[]}#t(e,t){let r=e.message?.id,o=e.message?.content;if(!r||!Array.isArray(o))return[];if(e.uuid!==void 0){if(this.#s.has(e.uuid))return[];this.#s.add(e.uuid)}let s=this.#e.get(r);s||(s={id:r,operationId:t,createdAt:this.#a(),parts:[]},this.#e.set(r,s));for(let i of o)i.type===\"text\"&&i.text?s.parts.push({type:\"text\",text:i.text}):i.type===\"thinking\"&&i.thinking?s.parts.push({type:\"reasoning\",text:i.thinking}):i.type===\"tool_use\"&&i.id&&(s.parts.push({type:\"tool\",toolCallId:i.id,toolName:i.name??\"tool\",input:de(i.input),state:\"running\"}),this.#n.set(i.id,r));return[{type:\"message\",message:this.#d(s)}]}#l(e){let t=e.message?.content;if(!Array.isArray(t))return[];let r=new Set;for(let o of t){if(o.type!==\"tool_result\"||!o.tool_use_id)continue;let s=this.#n.get(o.tool_use_id),i=s===void 0?void 0:this.#e.get(s);i&&(i.parts=i.parts.map(d=>d.type===\"tool\"&&d.toolCallId===o.tool_use_id?{...d,state:o.is_error?\"error\":\"done\",output:ze(o.content)}:d),r.add(i))}return[...r].map(o=>({type:\"message\",message:this.#d(o)}))}#o(e){return[{type:\"usage\",...e.usage?.input_tokens===void 0?{}:{inputTokens:e.usage.input_tokens},...e.usage?.output_tokens===void 0?{}:{outputTokens:e.usage.output_tokens},...e.total_cost_usd===void 0?{}:{costUsd:e.total_cost_usd}}]}#d(e){return{id:e.id,role:\"assistant\",parts:[...e.parts],...e.operationId===void 0?{}:{operationId:e.operationId},createdAt:e.createdAt}}};function Ke(n){if(!w(n))return{};let e=v(n.allowedTools),t=v(n.disallowedTools);return{...typeof n.appendSystemPrompt==\"string\"?{appendSystemPrompt:n.appendSystemPrompt}:{},...e?{allowedTools:e}:{},...t?{disallowedTools:t}:{},...typeof n.maxTurns==\"number\"?{maxTurns:n.maxTurns}:{}}}function We(n){let e=JSON.parse(n);if(!(typeof e!=\"object\"||e===null||!(\"type\"in e)))return e}var le={id:\"claude-code\",stateDirs:[\".claude/projects\"],command(n){let e=Ke(n.settings.options),t=[\"claude\",\"-p\",\"--output-format\",\"stream-json\",\"--verbose\",\"--include-partial-messages\",\"--dangerously-skip-permissions\"];return n.settings.model&&t.push(\"--model\",n.settings.model),n.state.sessionId&&t.push(\"--resume\",n.state.sessionId),e.appendSystemPrompt&&t.push(\"--append-system-prompt\",e.appendSystemPrompt),e.allowedTools?.length&&t.push(\"--allowedTools\",e.allowedTools.join(\",\")),e.disallowedTools?.length&&t.push(\"--disallowedTools\",e.disallowedTools.join(\",\")),e.maxTurns!==void 0&&t.push(\"--max-turns\",String(e.maxTurns)),{argv:t,stdin:n.prompt,env:{DISABLE_AUTOUPDATER:\"1\"}}},parser(n){let e=new D;return{line(t){let r=We(t);return r?{events:e.project(r,n.operationId),...r.session_id?{state:{sessionId:r.session_id}}:{},...r.type===\"result\"?{outcome:e.outcome(r,!1)}:{}}:{}},end({code:t,stderr:r}){return{status:\"unanswered\",reason:`claude exited with ${t??\"a signal\"}: ${r.trim()||\"no output\"}`}}}}};function Ge(n){let e=JSON.parse(n);if(!(typeof e!=\"object\"||e===null))return e}function Ye(n){return n===\"completed\"?\"done\":n===\"failed\"||n===\"declined\"?\"error\":\"running\"}function Xe(n){let e=n.id??\"item\",t=Ye(n.status);switch(n.type){case\"agent_message\":return{type:\"text\",text:n.text??\"\"};case\"reasoning\":return{type:\"reasoning\",text:n.text??\"\"};case\"command_execution\":return{type:\"tool\",toolCallId:e,toolName:\"shell\",input:{command:n.command??\"\"},state:t===\"done\"&&n.exit_code!==0&&n.exit_code!=null?\"error\":t,...n.aggregated_output?{output:n.aggregated_output}:{}};case\"file_change\":return{type:\"tool\",toolCallId:e,toolName:\"file_change\",input:n.changes??null,state:t};case\"mcp_tool_call\":return{type:\"tool\",toolCallId:e,toolName:`${n.server??\"mcp\"}.${n.tool??\"tool\"}`,input:n.arguments??null,state:t,...n.result!==void 0||n.error!==void 0?{output:n.result??n.error??null}:{}};case\"web_search\":return{type:\"tool\",toolCallId:e,toolName:\"web_search\",input:{query:n.query??\"\"},state:t};case\"todo_list\":return{type:\"tool\",toolCallId:e,toolName:\"todo_list\",input:n.items??null,state:t};default:return}}function Ze(n){if(!w(n))return{};let e=v(n.config);return e?{config:e}:{}}var ue={id:\"codex\",stateDirs:[\".codex/sessions\"],command(n){let e=[\"codex\",\"exec\"];n.state.threadId&&e.push(\"resume\",n.state.threadId),e.push(\"--json\",\"--skip-git-repo-check\",\"--dangerously-bypass-approvals-and-sandbox\");let t=process.env.OPENAI_BASE_URL;t&&e.push(\"-c\",\"model_provider=gateway\",\"-c\",`model_providers.gateway={name=\"AI Gateway\",base_url=${JSON.stringify(t)},env_key=\"OPENAI_API_KEY\",wire_api=\"responses\"}`);for(let r of Ze(n.settings.options).config??[])e.push(\"-c\",r);return n.settings.model&&e.push(\"-m\",n.settings.model),e.push(\"-\"),{argv:e,stdin:n.prompt}},parser(n){let e=`${n.operationId}:assistant`,t=Date.now(),r=new Map,o=\"\",s,i=()=>({type:\"message\",message:{id:e,role:\"assistant\",parts:[...r.values()],operationId:n.operationId,createdAt:t}});return{line(d){let a=Ge(d);if(!a)return{};switch(a.type){case\"thread.started\":return a.thread_id?{state:{threadId:a.thread_id}}:{};case\"item.started\":case\"item.updated\":case\"item.completed\":{let l=a.item;if(!l)return{};if(l.type===\"error\")return{events:[{type:\"log\",level:\"warn\",message:l.message??\"error\"}]};let c=Xe(l);return c?(r.set(l.id??String(r.size),c),c.type===\"text\"&&a.type===\"item.completed\"&&(o=c.text),{events:[i()]}):{}}case\"turn.completed\":return{events:[{type:\"usage\",...a.usage?.input_tokens===void 0?{}:{inputTokens:a.usage.input_tokens},...a.usage?.output_tokens===void 0?{}:{outputTokens:a.usage.output_tokens}}],outcome:{status:\"done\",text:o}};case\"turn.failed\":return{outcome:{status:\"unanswered\",reason:a.error?.message??\"turn failed\"}};case\"error\":return s=a.message,{events:[{type:\"log\",level:\"warn\",message:a.message??\"error\"}]};default:return{}}},end({code:d,stderr:a}){return{status:\"unanswered\",reason:`codex exited with ${d??\"a signal\"}: ${s??(a.trim()||\"no output\")}`}}}}};import{timingSafeEqual as st}from\"node:crypto\";import{createServer as it}from\"node:http\";import{createHash as Qe}from\"node:crypto\";var et=\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\",pe=64*1024*1024,tt=0,ce=1,nt=2,F=8,rt=9,ye=10;function ot(n,e){let t=e.length,r=t<126?Buffer.from([128|n,t]):t<65536?Buffer.from([128|n,126,t>>8,t&255]):Buffer.concat([Buffer.from([128|n,127]),(()=>{let o=Buffer.alloc(8);return o.writeBigUInt64BE(BigInt(t)),o})()]);return Buffer.concat([r,e])}function H(n,e,t,r){let o=n.headers[\"sec-websocket-key\"];if(typeof o!=\"string\"||n.headers.upgrade?.toLowerCase()!==\"websocket\"){e.destroy();return}let s=Qe(\"sha1\").update(o+et).digest(\"base64\");e.write(`HTTP/1.1 101 Switching Protocols\\r\nUpgrade: websocket\\r\nConnection: Upgrade\\r\nSec-WebSocket-Accept: ${s}\\r\n\\r\n`);let i=!0,d=!1,a=()=>{d||(d=!0,i=!1,r.closed())},l=(p,y)=>{i&&e.write(ot(p,y))},c={send:p=>l(ce,Buffer.from(p,\"utf8\")),close:(p,y)=>{if(!i)return;let h=Buffer.alloc(2+Buffer.byteLength(y));h.writeUInt16BE(p),h.write(y,2),l(F,h),i=!1,e.end()}},u=t.length>0?Buffer.from(t):Buffer.alloc(0),b=[],m=0,f=()=>{for(;;){if(u.length<2)return;let p=u[0]??0,y=u[1]??0,h=(p&128)!==0,we=p&15,q=(y&128)!==0,I=y&127,C=2;if(I===126){if(u.length<4)return;I=u.readUInt16BE(2),C=4}else if(I===127){if(u.length<10)return;let g=u.readBigUInt64BE(2);if(g>BigInt(pe)){c.close(1009,\"message too big\");return}I=Number(g),C=10}let Ce=C;if(q&&(C+=4),u.length<C+I)return;let S=Buffer.from(u.subarray(C,C+I));if(q)for(let g=0;g<S.length;g++)S[g]=(S[g]??0)^(u[Ce+g%4]??0);switch(u=u.subarray(C+I),we){case ce:case nt:case tt:{if(b.push(S),m+=S.length,m>pe){c.close(1009,\"message too big\");return}if(h){let g=Buffer.concat(b).toString(\"utf8\");b=[],m=0,r.message(g)}break}case rt:l(ye,S);break;case ye:break;case F:i&&(l(F,S.subarray(0,2)),i=!1),e.end(),a();return;default:c.close(1002,\"unsupported opcode\");return}}};return e.on(\"data\",p=>{u=u.length===0?p:Buffer.concat([u,p]),f()}),e.on(\"close\",a),e.on(\"error\",a),u.length>0&&queueMicrotask(f),c}function at(n,e){if(e===void 0)return!1;let t=Buffer.from(n),r=Buffer.from(e);return t.length===r.length&&st(t,r)}async function dt(n){let e=new T({adapter:n.adapter,runtimeId:n.runtimeId}),t=new Set,r=it((i,d)=>{if(i.url===$){d.writeHead(200,{\"content-type\":\"text/plain\"}),d.end(\"ok\");return}d.writeHead(404),d.end()});r.on(\"upgrade\",(i,d,a)=>{let l=new URL(i.url??\"/\",\"http://container\");if(!l.pathname.startsWith(M)){d.destroy();return}let c=decodeURIComponent(l.pathname.slice(M.length)),u=i.headers[z];if(!at(n.token,Array.isArray(u)?u[0]:u)||c.length===0){H(i,d,a,{message:()=>{},closed:()=>{}})?.close(W,\"unauthorized\");return}let m,f=H(i,d,a,{message:p=>m?.receive(p),closed:()=>{f&&t.delete(f),m?.closed()}});f&&(t.add(f),m=e.connect(c,f))}),await new Promise(i=>{r.listen(n.port,n.host??\"0.0.0.0\",i)});let o=r.address();return{port:typeof o==\"object\"&&o?o.port:0,async close(){for(let i of t)i.close(1001,\"shutting down\");await new Promise(i=>r.close(()=>i())),await e.close()}}}async function fe(n,e=process.env){let t=e[x.token],r=e[x.runtimeId],o=Number(e[x.port]??\"8080\");if(!t||!r||!Number.isInteger(o)||o<=0)throw new Error(`${x.token}, ${x.runtimeId} and ${x.port} must be set; ContainerHarness sets them`);delete e[x.token];let s=await dt({adapter:n,runtimeId:r,token:t,port:o});console.log(`harness-daemon: ${n.id} on ${s.port} (${r})`);let i=()=>{s.close().finally(()=>process.exit(0))};return process.once(\"SIGTERM\",i),process.once(\"SIGINT\",i),s}var lt={\"claude-code\":le,codex:ue};function ut(n){let e=n?/^(\\d+):(\\d+)$/.exec(n):null;return e?{uid:Number(e[1]),gid:Number(e[2])}:void 0}var j=process.env.CF_HARNESS_ADAPTER??\"claude-code\",ge=ut(process.env.CF_HARNESS_USER),me=lt[j],he=j===\"echo\"?Q:me?ae({...me,...ge?{user:ge,homes:\"/home/agent/.harness/sessions\",skeleton:\"/home/agent\"}:{}}):void 0;he||(console.error(`harness-daemon: unknown CF_HARNESS_ADAPTER ${j}`),process.exit(1));await fe(he).catch(n=>{console.error(`harness-daemon: ${String(n)}`),process.exit(1)});\n";
/** A hash of `DAEMON_SOURCE`, to tell daemon builds apart. */
const DAEMON_VERSION = "d45c6a8182ef9070";
//#endregion
//#region src/harness/container/managed-image.ts
/**
* Containers built at runtime from `cloudflare/debian-trixie`, with no
* image of your own.
*
* The first start of a setup runs its steps with `exec()` (create the
* unprivileged user, install the CLI), writes the bundled daemon, and
* snapshots the filesystem. Later starts restore the snapshot and are
* ready as soon as the daemon boots. A snapshot is keyed by the image, the
* steps, and the daemon build, so changing any of them sets up afresh.
*
* The container's entrypoint waits for the daemon to be written and then
* runs it, so the same entrypoint serves a fresh container (where setup
* writes the daemon) and a restored one (where it is already there).
*/
/** The managed system image: Debian Trixie with Node.js. */
const MANAGED_IMAGE = "cloudflare/debian-trixie";
/** The unprivileged user CLIs run as, `uid:gid`. */
const AGENT_USER = "10001:10001";
const DAEMON_DIR = "/opt/harness";
/** Waits for setup to write the daemon, then becomes it. */
const MANAGED_ENTRYPOINT = [
"sh",
"-c",
`while [ ! -f ${DAEMON_DIR}/ready ]; do sleep 0.1; done; exec node ${DAEMON_DIR}/daemon.mjs`
];
/** The agent user's home, which every session home starts as a copy of. */
const AGENT_HOME = "/home/agent";
/** Steps every managed agent needs before its own. */
const BASE_STEPS = [{
name: "create the agent user and workspace",
command: [
"sh",
"-c",
`id -u agent >/dev/null 2>&1 || useradd --create-home --uid 10001 --shell /bin/sh agent; mkdir -p /workspace ${DAEMON_DIR} && chown 10001:10001 /workspace`
]
}, {
name: "install git",
command: [
"sh",
"-c",
"command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq --no-install-recommends git ca-certificates)"
]
}];
/** How long one setup step may run. */
const STEP_TIMEOUT_MS = 10 * 6e4;
/**
* The key a snapshot of this setup is stored under.
*
* @param steps - The setup steps.
* @returns A stable key for the image, the steps, and the daemon build.
*/
async function setupKey(steps) {
const text = JSON.stringify({
image: MANAGED_IMAGE,
steps: steps.map((step) => [step.user ?? "root", ...step.command]),
daemon: DAEMON_VERSION
});
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
return [...new Uint8Array(digest)].slice(0, 16).map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
async function run(container, step, stdin) {
const asAgent = step.user === "agent";
const process = await container.exec([...step.command], {
env: {
HOME: asAgent ? AGENT_HOME : "/root",
DEBIAN_FRONTEND: "noninteractive"
},
...asAgent ? {
user: AGENT_USER,
cwd: AGENT_HOME
} : {},
stderr: "combined",
...stdin === void 0 ? {} : { stdin: new Blob([stdin]).stream() }
});
let exited = false;
process.exitCode.then(() => exited = true, () => exited = true);
const timer = setTimeout(() => {
if (!exited) process.kill(9);
}, STEP_TIMEOUT_MS);
try {
const output = await process.output();
if (output.exitCode === 0) return void 0;
const text = new TextDecoder().decode(output.stdout).slice(-2e3);
return {
_tag: "setup",
message: `${step.name} exited with ${output.exitCode}: ${text.trim()}`
};
} finally {
clearTimeout(timer);
}
}
/**
* Set a fresh managed container up: run the steps, install the daemon, and
* snapshot the result. A failed snapshot is not an error: the container
* still works, and the next start sets up again.
*
* @param container - The running container, started from `MANAGED_IMAGE`
* with `MANAGED_ENTRYPOINT`.
* @param steps - The agent's setup, after `BASE_STEPS`.
* @param key - The setup's key, from `setupKey`.
* @returns The snapshot to store, `undefined` when none was taken, or why
* setup failed.
*/
async function setUp(container, steps, key) {
for (const step of [...BASE_STEPS, ...steps]) {
const failed = await run(container, step).catch((error) => ({
_tag: "setup",
message: `${step.name} failed: ${error instanceof Error ? error.message : String(error)}`
}));
if (failed) return {
_tag: "err",
error: failed
};
}
const installed = await run(container, {
name: "install the daemon",
command: [
"sh",
"-c",
`cat > ${DAEMON_DIR}/daemon.mjs && touch ${DAEMON_DIR}/ready`
]
}, DAEMON_SOURCE);
if (installed) return {
_tag: "err",
error: installed
};
try {
return {
_tag: "ok",
snapshot: {
key,
snapshot: { id: (await container.snapshotContainer({ name: `harness-${key}` })).id }
}
};
} catch {
return {
_tag: "ok",
snapshot: void 0
};
}
}
//#endregion
//#region src/harness/container/harness.ts
/** The root session's id. */
const ROOT_SESSION = "root";
/** Default `idleTimeoutMs`: the container stops five minutes after its last work. */
const IDLE_TIMEOUT_MS = 5 * 6e4;
/** Default `startTimeoutMs`: how long a new container may take to answer. */
const START_TIMEOUT_MS = 6e4;
/** How long a container that already runs may take to answer before it is replaced. */
const ADOPT_TIMEOUT_MS = 1e4;
/** Default `port`: where the daemon listens in the container. */
const DAEMON_PORT = 8080;
/**
* Longest one wake waits on the container. It waits inside an alarm
* invocation, which has a 15 minute wall-time limit, so a longer run is
* waited on across several alarms.
*/
const WAIT_BUDGET_MS = 10 * 6e4;
/**
* The wake job's heartbeat while it waits. If the object is evicted, the
* job is still due and its alarm restarts the object, which reattaches.
*/
const HEARTBEAT_MS = 3e4;
/** Consecutive failed starts before a session's open work is given up. */
const MAX_START_FAILURES = 5;
/** How often a wait checks that the container still runs. */
const CONTAINER_CHECK_MS = 2e3;
/** How often `wait()` re-reads the store while it waits. */
const SETTLE_POLL_MS = 1e3;
/** Frames between acknowledgements, so the daemon can forget them. */
const ACK_EVERY = 32;
const WAKE_FN = "wake";
const IDLE_FN = "idle";
const IDLE_JOB = "container-idle";
const RUNTIME_KEY = "container-harness:runtime";
const SNAPSHOT_KEY = "container-harness:snapshot";
const WORKSPACE_KEY = "container-harness:workspace";
/**
* How much longer than `idleTimeoutMs` the platform keeps an inactive
* object's container. The harness's own idle stop runs first, so it can
* snapshot the workspace before the container goes.
*/
const IDLE_GRACE_MS = 2 * 6e4;
/** The longest inactivity timeout the platform accepts. */
const MAX_INACTIVITY_MS = 360 * 6e4;
/**
* Snapshots live 30 days from their last restore. One unused for nearly
* that long is treated as gone rather than tried.
*/
const SNAPSHOT_STALE_MS = 696 * 60 * 6e4;
/**
* The platform does not say why a start failed. A snapshot that fails this
* many starts in a row is skipped (not deleted) in favour of the next
* source: whether that one works decides whether the snapshot was at fault.
*/
const SKIP_AFTER_FAILURES = 2;
/** Snapshot sources, most preferred first. */
const SNAPSHOT_SOURCES = [{
from: "workspace",
storageKey: WORKSPACE_KEY
}, {
from: "snapshot",
storageKey: SNAPSHOT_KEY
}];
const MESSAGES = "messages";
const ENGINE = "engine";
const OPEN = ["queued", "running"];
function wakeJobId(session) {
return `container-wake:${session}`;
}
function parseWakePayload(payload) {
if (typeof payload !== "object" || payload === null || !("session" in payload) || typeof payload.session !== "string") return;
const failures = "failures" in payload && typeof payload.failures === "number" ? payload.failures : 0;
return {
session: payload.session,
failures
};
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function parseSessionState(value) {
const fields = isRecord(value) ? value : {};
const cursor = fields.cursor;
return {
model: typeof fields.model === "string" ? fields.model : void 0,
cursor: isRecord(cursor) && typeof cursor.runtimeId === "string" && typeof cursor.seq === "number" ? {
runtimeId: cursor.runtimeId,
seq: cursor.seq
} : null,
generation: typeof fields.generation === "number" ? fields.generation : 1,
messagesFrom: typeof fields.messagesFrom === "number" ? fields.messagesFrom : 0,
handoff: typeof fields.handoff === "string" ? fields.handoff : void 0
};
}
function sessionStateJson(state) {
return {
...state.model === void 0 ? {} : { model: state.model },
cursor: state.cursor,
generation: state.generation,
messagesFrom: state.messagesFrom,
...state.handoff === void 0 ? {} : { handoff: state.handoff }
};
}
function parseMeta(value) {
const fields = isRecord(value) ? value : {};
return {
whenBusy: fields.whenBusy === "steer" ? "steer" : "followUp",
delivered: typeof fields.delivered === "string" ? fields.delivered : null
};
}
function metaJson(meta) {
return {
whenBusy: meta.whenBusy,
delivered: meta.delivered
};
}
function parseInput(value) {
if (typeof value === "string") return value;
return value;
}
function inputJson(input) {
return input;
}
function parseMessage(value) {
return value;
}
function messageJson(message) {
return message;
}
function userParts(input) {
if (typeof input === "string") return [{
type: "text",
text: input
}];
return input.map((part) => part.type === "text" ? {
type: "text",
text: part.text
} : {
type: "image",
mediaType: part.mediaType,
data: part.data
});
}
function withHandoff(input, handoff) {
const note = {
type: "text",
text: handoff
};
if (typeof input === "string") return [note, {
type: "text",
text: input
}];
return [note, ...input];
}
function errorText(error) {
return error instanceof Error ? error.message : String(error);
}
var _options = /* @__PURE__ */ new WeakMap();
var _idleTimeoutMs = /* @__PURE__ */ new WeakMap();
var _startTimeoutMs = /* @__PURE__ */ new WeakMap();
var _waitBudgetMs = /* @__PURE__ */ new WeakMap();
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
var _retryBaseMs = /* @__PURE__ */ new WeakMap();
var _idleStopMs = /* @__PURE__ */ new WeakMap();
var _store = /* @__PURE__ */ new WeakMap();
var _runtime = /* @__PURE__ */ new WeakMap();
var _starting = /* @__PURE__ */ new WeakMap();
var _timeoutSet = /* @__PURE__ */ new WeakMap();
var _attached = /* @__PURE__ */ new WeakMap();
var _attaching = /* @__PURE__ */ new WeakMap();
var _waits = /* @__PURE__ */ new WeakMap();
var _settleWaiters = /* @__PURE__ */ new WeakMap();
var _changeWaiters = /* @__PURE__ */ new WeakMap();
var _listeners = /* @__PURE__ */ new WeakMap();
var _ContainerHarness_brand = /* @__PURE__ */ new WeakSet();
/**
* An agent CLI running in a Cloudflare Container, driven from a Durable
* Object, behind the same interface as `PiHarness`: `harness.prompt()`,
* `harness.sessions`, `harness.session(id)`.
*
* The harness does not know which agent runs in the container. A preset
* (`claudeCode()`, `codex()`) says what to install and which adapter the
* daemon runs; the harness builds the container from
* `cloudflare/debian-trixie` on first use, installs the bundled daemon, and
* snapshots the result, so there is no image to build. Credentials stay
* outside: the CLI calls a placeholder host, and `ContainerEgress` adds the
* real key. Any adapter that produces `ContainerEvent`s works, including
* one in an image of your own (`containerAgent()`).
*
* The object owns the durable record, in a `HarnessStore`: sessions,
* operations, the transcript, and the adapter's resume state. The container
* is disposable. It is started on demand with `ctx.container.start()`, kept
* for `idleTimeoutMs` after its last work, and then stopped. The next
* prompt starts a new container and hands the adapter everything it
* persisted, so the agent resumes its own session there.
*
* Each session has one Lifecycle wake job. It runs while the session has
* open operations: it starts or reattaches to the container, reconciles
* what the container knows with what the object stored, delivers queued
* operations, and waits for them, rescheduling itself as a heartbeat. An
* eviction mid-run leaves the job due, so its alarm restarts the object,
* which reattaches and replays the frames it missed from its cursor.
*
* Recovery, by what was lost:
*
* - **The object** (eviction, deploy): the container keeps running for
* `idleTimeoutMs`. The wake reattaches and replays from the cursor.
* - **The socket**: the same, on the next heartbeat.
* - **The container** (idle stop, crash, platform restart): a new one
* answers with a different runtime id. The session is reopened in it
* from the persisted resume state; queued operations run there; an
* operation that was running is settled `container_lost` or run again,
* per `onContainerLost`.
*
* @experimental The API may change before it stabilizes.
*/
var ContainerHarness = class extends LifecycleCapability {
/**
* @param options - The container, the agent, and session defaults.
*/
constructor(options) {
super("container-harness");
_classPrivateMethodInitSpec(this, _ContainerHarness_brand);
_classPrivateFieldInitSpec(this, _options, void 0);
_classPrivateFieldInitSpec(this, _idleTimeoutMs, void 0);
_classPrivateFieldInitSpec(this, _startTimeoutMs, void 0);
_classPrivateFieldInitSpec(this, _waitBudgetMs, WAIT_BUDGET_MS);
_classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
_classPrivateFieldInitSpec(this, _retryBaseMs, 1e3);
_classPrivateFieldInitSpec(this, _idleStopMs, void 0);
_classPrivateFieldInitSpec(this, _store, void 0);
_classPrivateFieldInitSpec(this, _runtime, void 0);
_classPrivateFieldInitSpec(this, _starting, void 0);
_classPrivateFieldInitSpec(this, _timeoutSet, false);
_classPrivateFieldInitSpec(this, _attached, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _attaching, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _waits, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _settleWaiters, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _changeWaiters, /* @__PURE__ */ new Map());
_classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Map());
const idle = options.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
if (!Number.isFinite(idle) || idle <= 0 || idle > MAX_INACTIVITY_MS - IDLE_GRACE_MS) throw new Error("ContainerHarness idleTimeoutMs must be positive and at most six hours less two minutes");
for (const route of options.agent.egress) if (!URL.canParse(route.upstream) || new URL(route.upstream).protocol !== "https:") throw new Error(`ContainerHarness: egress upstream for ${route.host} must be an https URL, got ${JSON.stringify(route.upstream)}`);
if (options.agent.egress.length > 0 && options.egress === void 0) throw new Error("ContainerHarness: the agent's credentials need `egress: ctx.exports.ContainerEgress`, and `export { ContainerEgress } from \"agents/harness/container\"` in your main module");
_classPrivateFieldSet2(_options, this, options);
_classPrivateFieldSet2(_idleTimeoutMs, this, idle);
_classPrivateFieldSet2(_startTimeoutMs, this, options.startTimeoutMs ?? START_TIMEOUT_MS);
_classPrivateFieldSet2(_idleStopMs, this, idle);
this.sessions = new ContainerSessions(this);
}
/** Reopen the store, re-arm the container's idle timeout, and wake open sessions. */
async onStart(_context) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
const container = _classPrivateFieldGet2(_options, this).container;
if (container.running) {
await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
_classPrivateFieldSet2(_timeoutSet, this, true);
}
const open = store.operations({ status: OPEN });
const sessions = new Set(open.map((operation) => operation.session));
for (const session of sessions) if (!this.lifecycle.jobs.get(wakeJobId(session))) await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
if (sessions.size === 0 && container.running) await _assertClassBrand(_ContainerHarness_brand, this, _scheduleIdle).call(this);
}
/** Dispatch the harness's jobs: one wake per session, and the idle stop. */
async onJob(context) {
if (context.job.fn === IDLE_FN) return _assertClassBrand(_ContainerHarness_brand, this, _idleStep).call(this);
if (context.job.fn !== WAKE_FN) return;
const wake = parseWakePayload(context.job.payload);
if (wake !== void 0) return _assertClassBrand(_ContainerHarness_brand, this, _wakeStep).call(this, wake);
}
/** Close the session sockets. Durable state and the container are untouched. */
async dispose() {
for (const session of [..._classPrivateFieldGet2(_attached, this).keys()]) _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
}
/**
* A handle on one session. No I/O until you call it.
*
* @param id - The session. Default: the root session.
* @returns The handle.
*/
session(id = ROOT_SESSION) {
return new ContainerSession(this, id);
}
/**
* Submit a prompt and wait for its answer.
*
* @param input - The prompt.
* @param options - Session, idempotency key, and busy behaviour.
* @returns The result, with the session's transcript after it.
*/
prompt(input, options = {}) {
return this.session(options.session).prompt(input, options);
}
/**
* Durably submit a prompt. Resolves before the agent runs.
*
* @param input - The prompt.
* @param options - Session, idempotency key, and busy behaviour.
* @returns The receipt.
*/
submit(input, options = {}) {
return this.session(options.session).submit(input, options);
}
/**
* Stop one operation, or everything running in a session.
*
* @param options - The session, and optionally one operation.
* @returns Whether anything was stopped.
*/
abort(options = {}) {
return this.session(options.session).abort(options.operationId);
}
/**
* Wait for an operation to settle.
*
* @param operationId - The operation.
* @param options - The session, and a signal that stops only the wait.
* @returns How it ended.
*/
wait(operationId, options = {}) {
return this.session(options.session).wait(operationId, options.signal);
}
/**
* The session's active transcript.
*
* @param options - The session.
* @returns Its messages since the newest reset.
*/
messages(options = {}) {
return this.session(options.session).messages();
}
/**
* Submissions that have not settled yet, oldest first.
*
* @param options - Narrow to one session. Default: every session.
* @returns The open operations.
*/
async pending(options = {}) {
return (await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({
...options.session === void 0 ? {} : { session: options.session },
status: OPEN
}).map((operation) => ({
operationId: operation.id,
session: operation.session,
status: operation.status === "running" ? "running" : "queued"
}));
}
/**
* The container, as the harness sees it.
*
* @returns Whether it runs, and the id the harness started it with.
*/
async container() {
const runtime = await _assertClassBrand(_ContainerHarness_brand, this, _runtimeRecord).call(this);
return {
running: _classPrivateFieldGet2(_options, this).container.running,
runtimeId: runtime?.runtimeId
};
}
/**
* Stop the container now. Sessions survive: the next prompt starts a new
* container and resumes them in it. Running operations are reconciled as
* if the container had died.
*/
async stop() {
await this.dispose();
const container = _classPrivateFieldGet2(_options, this).container;
if (container.running) {
await _assertClassBrand(_ContainerHarness_brand, this, _saveWorkspace).call(this, container);
await container.destroy();
}
_assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
type: "container",
status: "stopped"
});
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
const sessions = new Set(store.operations({ status: OPEN }).map((operation) => operation.session));
for (const session of sessions) await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
}
/** @internal */
async store() {
return _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
}
/** @internal Create a session with the defaults, or fork one. */
async createSession(id, from) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
store.transaction(() => {
if (from === void 0) {
store.createSession({
id,
state: sessionStateJson(_assertClassBrand(_ContainerHarness_brand, this, _initial).call(this))
});
return;
}
const parent = store.session(from);
if (!parent) throw new Error(`Unknown container session ${from}`);
const parentState = parseSessionState(parent.state);
store.createSession({
id,
parent: from,
state: sessionStateJson({
..._assertClassBrand(_ContainerHarness_brand, this, _initial).call(this),
model: parentState.model
})
});
store.append(id, MESSAGES, store.read(from, MESSAGES, { after: parentState.messagesFrom }).map((entry) => entry.id === void 0 ? { data: entry.data } : {
id: entry.id,
data: entry.data
}));
store.copy(from, id, ENGINE);
});
}
/** @internal */
async enqueue(session, input, options) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session);
const operationId = options.operationId ?? crypto.randomUUID();
const meta = {
whenBusy: options.whenBusy ?? "followUp",
delivered: null
};
const { accepted } = store.enqueue({
session,
id: operationId,
input: inputJson(input),
meta: metaJson(meta)
});
if (accepted) _assertClassBrand(_ContainerHarness_brand, this, _deliver).call(this, store, session);
await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session);
return {
operationId,
session,
accepted
};
}
/** @internal Withdraw or abort one operation, or all of a session's. */
async withdraw(session, operationId) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
const targets = operationId === void 0 ? store.operations({
session,
status: OPEN
}) : [store.operation(session, operationId)].filter((operation) => operation !== void 0 && (operation.status === "queued" || operation.status === "running"));
if (targets.length === 0) return operationId === void 0;
const attached = _classPrivateFieldGet2(_attached, this).get(session);
if (operationId === void 0) attached?.link.send({ type: "abort" });
else if (targets.some((each) => parseMeta(each.meta).delivered !== null)) attached?.link.send({
type: "abort",
operationId
});
for (const operation of targets) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
status: "unanswered",
reason: "aborted"
});
return true;
}
/** @internal Wait for an operation to settle. */
async settled(session, operationId, signal) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
for (;;) {
const operation = store.operation(session, operationId);
if (!operation) return {
operationId,
session,
status: "unanswered",
reason: "not_found"
};
const result = resultOf(operation);
if (result) return result;
if (signal?.aborted) throw signal.reason;
await _assertClassBrand(_ContainerHarness_brand, this, _until).call(this, _classPrivateFieldGet2(_settleWaiters, this), `${session}\0${operationId}`, signal, SETTLE_POLL_MS);
}
}
/** @internal The active transcript. */
async transcript(session) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
return store.read(session, MESSAGES, { after: state.messagesFrom }).map((entry) => parseMessage(entry.data));
}
/** @internal */
async busy(session) {
return (await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({
session,
status: OPEN
}).length > 0;
}
/** @internal Start a new context: a new agent session, without history. */
async resetSession(session, handoff) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
if (store.operations({
session,
status: OPEN
}).length > 0) throw new Error(`Container session ${session} is busy; abort it first`);
store.transaction(() => {
const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
store.clear(session, ENGINE);
store.setSessionState(session, sessionStateJson({
...state,
generation: state.generation + 1,
messagesFrom: store.end(session, MESSAGES),
handoff
}));
});
_assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
}
/** @internal */
async setModel(session, model) {
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
store.setSessionState(session, sessionStateJson({
...state,
model
}));
_classPrivateFieldGet2(_attached, this).get(session)?.link.send({
type: "configure",
settings: _assertClassBrand(_ContainerHarness_brand, this, _settings).call(this, {
...state,
model
})
});
}
/** @internal */
async events(session) {
let buffered = [];
let listener;
let stopped = false;
const relay = (events) => {
if (buffered) buffered.push(...events);
else listener?.(events);
};
let set = _classPrivateFieldGet2(_listeners, this).get(session);
if (!set) {
set = /* @__PURE__ */ new Set();
_classPrivateFieldGet2(_listeners, this).set(session, set);
}
set.add(relay);
let snapshot;
try {
snapshot = {
session,
messages: await this.transcript(session),
pending: await this.pending({ session }),
busy: await this.busy(session)
};
} catch (error) {
set.delete(relay);
throw error;
}
return {
snapshot,
start: (next) => {
if (listener || stopped) throw new Error("The event stream has already started");
listener = next;
const held = buffered ?? [];
buffered = void 0;
if (held.length > 0) next(held);
},
stop: async () => {
stopped = true;
buffered = void 0;
listener = void 0;
_classPrivateFieldGet2(_listeners, this).get(session)?.delete(relay);
}
};
}
};
/** Schedule the session's wake job now, or pull it forward. */
function _wake(session, time = Date.now(), failures = 0) {
const payload = {
session,
failures
};
return this.lifecycle.jobs.push({
id: wakeJobId(session),
fn: WAKE_FN,
time,
payload,
singleflight: true,
recoveryLoop: true
});
}
/**
* One run of a session's wake job. It never blocks on the container: it
* hands the drive (start or attach, reconcile, deliver, wait) to the
* alarm's tracked work and returns a heartbeat. It completes when the
* session has no open operations.
*/
async function _wakeStep(wake) {
const { session } = wake;
const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
if (_classPrivateFieldGet2(_waits, this).has(session)) return heartbeat;
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
if (!store.session(session)) return void 0;
if (store.operations({
session,
status: OPEN
}).length === 0) {
_assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
return;
}
const drive = _assertClassBrand(_ContainerHarness_brand, this, _drive).call(this, store, wake).catch((error) => {
const detail = {
session,
error: errorText(error)
};
console.error("[container-harness] drive failed", detail);
this.lifecycle.events.emit("container:drive_error", detail);
return {
at: Date.now() + _classPrivateFieldGet2(_retryBaseMs, this),
failures: wake.failures
};
}).then((next) => {
_classPrivateFieldGet2(_waits, this).delete(session);
_assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, next.at, next.failures);
});
_classPrivateFieldGet2(_waits, this).set(session, drive);
this.lifecycle.trackAlarmWork(drive);
return heartbeat;
}
/**
* Attach, deliver, and wait while the session has open work and a live
* socket. Resolves with when the wake should look again.
*/
async function _drive(store, wake) {
const { session } = wake;
const failures = wake.failures + 1;
await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), failures);
const attached = await _assertClassBrand(_ContainerHarness_brand, this, _attach).call(this, session);
if (attached._tag === "err" && attached.error._tag === "retry") {
await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), wake.failures);
return {
at: Date.now(),
failures: wake.failures
};
}
if (attached._tag === "err") {
const detail = {
session,
failures,
error: attached.error.message
};
console.warn("[container-harness] attach failed", detail);
this.lifecycle.events.emit("container:attach_error", detail);
if (failures < MAX_START_FAILURES) {
const backoff = Math.min(_classPrivateFieldGet2(_retryBaseMs, this) * 2 ** (failures - 1), 3e4);
return {
at: Date.now() + backoff,
failures
};
}
await _assertClassBrand(_ContainerHarness_brand, this, _clearAllFailures).call(this);
for (const operation of store.operations({
session,
status: OPEN
})) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
status: "unanswered",
reason: "container_unavailable"
});
return {
at: Date.now(),
failures: 0
};
}
await _assertClassBrand(_ContainerHarness_brand, this, _wake).call(this, session, Date.now() + _classPrivateFieldGet2(_heartbeatMs, this), 0);
_assertClassBrand(_ContainerHarness_brand, this, _deliver).call(this, store, session);
await _assertClassBrand(_ContainerHarness_brand, this, _waitForWork).call(this, store, session);
return {
at: Date.now(),
failures: 0
};
}
/**
* Resolves when the session has no open work, its socket closed, the
* container stopped, or the budget ran out.
*/
async function _waitForWork(store, session) {
const budget = new AbortController();
const timer = setTimeout(() => budget.abort(), _classPrivateFieldGet2(_waitBudgetMs, this));
const watch = setInterval(() => {
if (!_classPrivateFieldGet2(_options, this).container.running) _assertClassBrand(_ContainerHarness_brand, this, _detach).call(this, session);
}, CONTAINER_CHECK_MS);
try {
while (!budget.signal.aborted && _classPrivateFieldGet2(_attached, this).get(session)?.link.open === true && store.operations({
session,
status: OPEN
}).length > 0) await _assertClassBrand(_ContainerHarness_brand, this, _until).call(this, _classPrivateFieldGet2(_changeWaiters, this), session, budget.signal);
} catch {} finally {
clearTimeout(timer);
clearInterval(watch);
}
}
/** The platform's inactivity timeout: past the harness's own idle stop. */
function _inactivityTimeoutMs() {
return _classPrivateFieldGet2(_idleTimeoutMs, this) + IDLE_GRACE_MS;
}
/** The idle stop: destroy the container once nothing has used it for `idleTimeoutMs`. */
async function _idleStep() {
if ((await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this)).operations({ status: OPEN }).length > 0) return void 0;
const container = _classPrivateFieldGet2(_options, this).container;
if (!container.running) return void 0;
await this.dispose();
await _assertClassBrand(_ContainerHarness_brand, this, _saveWorkspace).call(this, container);
await container.destroy();
_assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
type: "container",
status: "stopped"
});
}
async function _scheduleIdle() {
await this.lifecycle.jobs.push({
id: IDLE_JOB,
fn: IDLE_FN,
time: Date.now() + _classPrivateFieldGet2(_idleStopMs, this)
});
}
async function _runtimeRecord() {
if (_classPrivateFieldGet2(_runtime, this)) return _classPrivateFieldGet2(_runtime, this);
await this.lifecycle.ready();
const stored = await this.lifecycle.storage.get(RUNTIME_KEY);
_classPrivateFieldSet2(_runtime, this, stored);
return stored;
}
/** The running container, started if it is not. Single-flight. */
function _ensureRuntime() {
_classPrivateFieldGet2(_starting, this) ?? _classPrivateFieldSet2(_starting, this, _assertClassBrand(_ContainerHarness_brand, this, _doEnsureRuntime).call(this).finally(() => {
_classPrivateFieldSet2(_starting, this, void 0);
}));
return _classPrivateFieldGet2(_starting, this);
}
async function _doEnsureRuntime() {
const container = _classPrivateFieldGet2(_options, this).container;
const known = await _assertClassBrand(_ContainerHarness_brand, this, _runtimeRecord).call(this);
if (container.running && known) {
if (!_classPrivateFieldGet2(_timeoutSet, this)) {
await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
_classPrivateFieldSet2(_timeoutSet, this, true);
}
if ((await waitHealthy(_assertClassBrand(_ContainerHarness_brand, this, _port).call(this), Math.min(_classPrivateFieldGet2(_startTimeoutMs, this), ADOPT_TIMEOUT_MS), () => container.running))._tag === "ok") return {
_tag: "ok",
value: known
};
await container.destroy().catch(() => void 0);
} else if (container.running) await container.destroy().catch(() => void 0);
return _assertClassBrand(_ContainerHarness_brand, this, _launch).call(this, container);
}
/**
* The daemon's port on the container that runs now. Taken fresh each
* time: a port taken before `start()` stays bound to no instance.
*/
function _port() {
return _classPrivateFieldGet2(_options, this).container.getTcpPort(_classPrivateFieldGet2(_options, this).port ?? DAEMON_PORT);
}
/**
* Start the agent's container and route its egress. A managed image is
* restored from this setup's snapshot when there is one, and set up (and
* snapshotted) when there is not.
*/
async function _start(container, runtime) {
const agent = _classPrivateFieldGet2(_options, this).agent;
const image = agent.image;
const env = {
...agent.env,
CF_HARNESS_ADAPTER: agent.adapter,
[CONTAINER_ENV.token]: runtime.token,
[CONTAINER_ENV.runtimeId]: runtime.runtimeId,
[CONTAINER_ENV.port]: String(_classPrivateFieldGet2(_options, this).port ?? DAEMON_PORT)
};
const base = {
enableInternet: _classPrivateFieldGet2(_options, this).enableInternet ?? true,
instance: _classPrivateFieldGet2(_options, this).instance ?? "standard-1"
};
let from = "image";
let key;
let options;
const baseKey = await _assertClassBrand(_ContainerHarness_brand, this, _baseKey).call(this);
const workspace = await _assertClassBrand(_ContainerHarness_brand, this, _usableSnapshot).call(this, WORKSPACE_KEY, baseKey);
if (image.kind === "custom") if (workspace) {
from = "workspace";
options = {
...base,
env,
containerSnapshot: workspace.snapshot
};
} else options = {
...base,
env,
image: image.image
};
else {
key = baseKey;
const stored = await _assertClassBrand(_ContainerHarness_brand, this, _usableSnapshot).call(this, SNAPSHOT_KEY, key);
const managed = {
...base,
env: {
...env,
CF_HARNESS_USER: AGENT_USER
},
entrypoint: [...MANAGED_ENTRYPOINT]
};
if (workspace) {
from = "workspace";
options = {
...managed,
containerSnapshot: workspace.snapshot
};
} else if (stored) {
from = "snapshot";
options = {
...managed,
containerSnapshot: stored.snapshot
};
} else {
from = "setup";
options = {
...managed,
image: MANAGED_IMAGE
};
}
}
try {
container.start(options);
} catch (error) {
await _assertClassBrand(_ContainerHarness_brand, this, _snapshotFailed).call(this, from);
return {
_tag: "err",
error: {
_tag: "unreachable",
message: errorText(error)
}
};
}
try {
await container.setInactivityTimeout(_assertClassBrand(_ContainerHarness_brand, this, _inactivityTimeoutMs).call(this));
_classPrivateFieldSet2(_timeoutSet, this, true);
const egress = _classPrivateFieldGet2(_options, this).egress;
if (egress && agent.egress.length > 0) {
const binding = egress({ props: { routes: agent.egress } });
for (const route of agent.egress) await container.interceptOutboundHttp(route.host, binding);
}
} catch (error) {
return {
_tag: "err",
error: {
_tag: "unreachable",
message: errorText(error)
}
};
}
if (from === "setup" && image.kind === "managed" && key !== void 0) {
const setup = await setUp(container, image.setup, key);
if (setup._tag === "err") return {
_tag: "err",
error: {
_tag: "unreachable",
message: setup.error.message
}
};
if (setup.snapshot) {
const record = {
...setup.snapshot,
usedAt: Date.now()
};
await this.lifecycle.storage.put(SNAPSHOT_KEY, record);
}
}
return {
_tag: "ok",
value: from
};
}
/**
* The stored snapshot for `key`, unless it is for another setup, too old
* to restore (deleted), or failing (skipped for now, kept).
*/
async function _usableSnapshot(storageKey, key) {
const record = await this.lifecycle.storage.get(storageKey);
if (!record || record.key !== key) return void 0;
if (Date.now() - (record.usedAt ?? Date.now()) > SNAPSHOT_STALE_MS) {
await this.lifecycle.storage.delete(storageKey);
return;
}
if (record.probe) return record;
if ((record.failures?.count ?? 0) >= SKIP_AFTER_FAILURES) return void 0;
return record;
}
/** Index of a start source in `SNAPSHOT_SOURCES`; the image or setup is last. */
function _rank(from) {
const index = SNAPSHOT_SOURCES.findIndex((source) => source.from === from);
return index === -1 ? SNAPSHOT_SOURCES.length : index;
}
/**
* A start failed. Count it against the snapshot it came from. A
* preferred snapshot being skipped is not to blame when its fallback
* fails too (no capacity, say): its count is cleared, so it is tried
* again.
*/
async function _snapshotFailed(from) {
const rank = _assertClassBrand(_ContainerHarness_brand, this, _rank).call(this, from);
for (const [index, source] of SNAPSHOT_SOURCES.entries()) {
const record = await this.lifecycle.storage.get(source.storageKey);
if (!record) continue;
if (index === rank && record.probe) {
console.warn(`[container-harness] dropping the ${source.from} snapshot: it no longer starts`);
await this.lifecycle.storage.delete(source.storageKey);
} else if (index === rank) {
const now = Date.now();
const failed = {
...record,
failures: {
count: (record.failures?.count ?? 0) + 1,
since: record.failures?.since ?? now
}
};
await this.lifecycle.storage.put(source.storageKey, failed);
} else if (index < rank && record.failures) await _assertClassBrand(_ContainerHarness_brand, this, _clearFailures).call(this, source.storageKey, record);
}
}
/**
* A start came up. Its snapshot's lifetime restarts and its failures
* clear. A preferred snapshot that was being skipped is not proven
* broken by this (the failures may have been an outage that has just
* ended): it is marked to be tried once more, and the caller stops this
* container to do so. Returns whether it should.
*/
async function _snapshotWorked(from) {
const rank = _assertClassBrand(_ContainerHarness_brand, this, _rank).call(this, from);
let retry = false;
for (const [index, source] of SNAPSHOT_SOURCES.entries()) {
const record = await this.lifecycle.storage.get(source.storageKey);
if (!record) continue;
if (index === rank) {
const worked = {
key: record.key,
snapshot: record.snapshot,
usedAt: Date.now()
};
await this.lifecycle.storage.put(source.storageKey, worked);
} else if (index < rank && !record.probe && (record.failures?.count ?? 0) >= SKIP_AFTER_FAILURES) {
const probe = {
key: record.key,
snapshot: record.snapshot,
...record.usedAt === void 0 ? {} : { usedAt: record.usedAt },
probe: true
};
await this.lifecycle.storage.put(source.storageKey, probe);
retry = true;
}
}
return retry;
}
/** Forget every snapshot's failures: nothing could be concluded from them. */
async function _clearAllFailures() {
for (const source of SNAPSHOT_SOURCES) {
const record = await this.lifecycle.storage.get(source.storageKey);
if (record?.failures || record?.probe) await _assertClassBrand(_ContainerHarness_brand, this, _clearFailures).call(this, source.storageKey, record);
}
}
async function _clearFailures(storageKey, record) {
const cleared = {
key: record.key,
snapshot: record.snapshot,
...record.usedAt === void 0 ? {} : { usedAt: record.usedAt }
};
await this.lifecycle.storage.put(storageKey, cleared);
}
/** What a container's filesystem is built from: the setup, or the image. */
async function _baseKey() {
const image = _classPrivateFieldGet2(_options, this).agent.image;
return image.kind === "managed" ? setupKey(image.setup) : `image:${image.image}`;
}
/**
* Snapshot the container's filesystem before the harness stops it, so the
* next container starts with the workspace as this one left it. A failed
* snapshot costs the workspace edits, never the stop.
*/
async function _saveWorkspace(container) {
try {
const snapshot = await container.snapshotContainer({ name: `workspace-${Date.now()}` });
const saved = {
key: await _assertClassBrand(_ContainerHarness_brand, this, _baseKey).call(this),
snapshot: { id: snapshot.id },
usedAt: Date.now()
};
await this.lifecycle.storage.put(WORKSPACE_KEY, saved);
} catch (error) {
await this.lifecycle.storage.delete(WORKSPACE_KEY);
console.warn("[container-harness] workspace snapshot failed; the next container starts without this workspace", { error: errorText(error) });
}
}
async function _launch(container) {
const runtime = {
runtimeId: crypto.randomUUID(),
token: `${crypto.randomUUID()}${crypto.randomUUID()}`.replaceAll("-", "")
};
await this.lifecycle.storage.put(RUNTIME_KEY, runtime);
_classPrivateFieldSet2(_runtime, this, runtime);
_assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
type: "container",
status: "starting"
});
const started = await _assertClassBrand(_ContainerHarness_brand, this, _start).call(this, container, runtime);
if (started._tag === "err") {
await container.destroy().catch(() => void 0);
return started;
}
const healthy = await waitHealthy(_assertClassBrand(_ContainerHarness_brand, this, _port).call(this), _classPrivateFieldGet2(_startTimeoutMs, this), () => container.running);
if (healthy._tag === "err") {
await _assertClassBrand(_ContainerHarness_brand, this, _snapshotFailed).call(this, started.value);
await container.destroy().catch(() => void 0);
return healthy;
}
if (await _assertClassBrand(_ContainerHarness_brand, this, _snapshotWorked).call(this, started.value)) {
await container.destroy().catch(() => void 0);
return {
_tag: "err",
error: {
_tag: "retry",
message: "retrying a snapshot skipped after failed starts"
}
};
}
_assertClassBrand(_ContainerHarness_brand, this, _broadcast).call(this, {
type: "container",
status: "ready"
});
return {
_tag: "ok",
value: runtime
};
}
/** The session's socket, opened and reconciled if it is not. Single-flight. */
function _attach(session) {
const attached = _classPrivateFieldGet2(_attached, this).get(session);
if (attached?.link.open) return Promise.resolve({
_tag: "ok",
value: attached
});
let attaching = _classPrivateFieldGet2(_attaching, this).get(session);
if (!attaching) {
attaching = _assertClassBrand(_ContainerHarness_brand, this, _doAttach).call(this, session).finally(() => {
_classPrivateFieldGet2(_attaching, this).delete(session);
});
_classPrivateFieldGet2(_attaching, this).set(session, attaching);
}
return attaching;
}
async function _doAttach(session) {
const runtime = await _assertClassBrand(_ContainerHarness_brand, this, _ensureRuntime).call(this);
if (runtime._tag === "err") return runtime;
const port = _assertClassBrand(_ContainerHarness_brand, this, _port).call(this);
let ready;
const early = [];
let caughtUpEarly = false;
const opened = await DaemonLink.open(port, session, runtime.value.token, {
frame: (seq, frame) => {
if (ready) _assertClassBrand(_ContainerHarness_brand, this, _onFrame).call(this, session, ready, seq, frame);
else early.push({
seq,
frame
});
},
caughtUp: () => {
if (ready) _assertClassBrand(_ContainerHarness_brand, this, _onCaughtUp).call(this, session, ready);
else caughtUpEarly = true;
},
closed: () => _assertClassBrand(_ContainerHarness_brand, this, _onClosed).call(this, session, ready)
});
if (opened._tag === "err") return opened;
const { link, hello } = opened.value;
const attached = {
link,
runtimeId: hello.runtimeId,
sinceAck: 0,
settledThere: []
};
if (hello.runtimeId !== runtime.value.runtimeId) {
const adopted = {
...runtime.value,
runtimeId: hello.runtimeId
};
await this.lifecycle.storage.put(RUNTIME_KEY, adopted);
_classPrivateFieldSet2(_runtime, this, adopted);
}
const store = await _assertClassBrand(_ContainerHarness_brand, this, _openStore).call(this);
_assertClassBrand(_ContainerHarness_brand, this, _reconcile).call(this, store, session, attached, hello);
_classPrivateFieldGet2(_attached, this).get(session)?.link.close();
_classPrivateFieldGet2(_attached, this).set(session, attached);
ready = attached;
for (const each of early) _assertClassBrand(_ContainerHarness_brand, this, _onFrame).call(this, session, attached, each.seq, each.frame);
if (caughtUpEarly) _assertClassBrand(_ContainerHarness_brand, this, _onCaughtUp).call(this, session, attached);
return {
_tag: "ok",
value: attached
};
}
/**
* The replay is complete. Operations the container reported settled in
* `hello` whose frames it no longer kept are settled from the report now,
* after every frame it did keep, so their messages land first.
*/
function _onCaughtUp(session, attached) {
const store = _classPrivateFieldGet2(_store, this);
if (!store) return;
for (const { operationId, outcome } of attached.settledThere.splice(0)) _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operationId, outcome);
}
/**
* Make the container's view of a session and the object's agree, before
* anything is delivered: open the adapter session if the container does
* not hold it, settle what the container already settled, decide what to
* do with work a lost container took with it, abort what the container
* still runs but the object has given up on, and replay missed frames.
*/
function _reconcile(store, session, attached, hello) {
const { link } = attached;
let state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
const sameRuntime = state.cursor?.runtimeId === hello.runtimeId;
let lostAny = false;
if (hello.open !== state.generation) {
const groups = chunkEntries(store.read(session, ENGINE).map((entry) => entry.data));
const last = groups.pop() ?? [];
for (const entries of groups) link.send({
type: "restore",
generation: state.generation,
entries
});
link.send({
type: "open",
generation: state.generation,
settings: _assertClassBrand(_ContainerHarness_brand, this, _settings).call(this, state),
restore: last
});
}
const known = new Map(hello.operations.map((operation) => [operation.operationId, operation]));
for (const operation of store.operations({
session,
status: OPEN
})) {
const there = known.get(operation.id);
if (there?.status === "settled") {
attached.settledThere.push({
operationId: operation.id,
outcome: there.outcome
});
continue;
}
if (there) continue;
const meta = parseMeta(operation.meta);
if (!(operation.status === "running" && meta.delivered !== hello.runtimeId)) {
if (meta.delivered !== null) store.setOperationMeta(session, operation.id, metaJson({
...meta,
delivered: null
}));
continue;
}
lostAny = true;
if (_classPrivateFieldGet2(_options, this).onContainerLost === "retry") store.transaction(() => {
store.requeue(session, operation.id);
store.setOperationMeta(session, operation.id, metaJson({
...meta,
delivered: null
}));
});
else _assertClassBrand(_ContainerHarness_brand, this, _settle).call(this, store, session, operation.id, {
status: "unanswered",
reason: "container_lost"
});
}
for (const there of hello.operations) {
if (there.status === "settled") continue;
const operation = store.operation(session, there.operationId);
if (!operation || operation.status === "done" || operation.status === "unanswered") link.send({
type: "abort",
operationId: there.operationId
});
}
if (lostAny) _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [{
type: "container",
status: "lost"
}]);
if (!sameRuntime) {
state = {
...state,
cursor: {
runtimeId: hello.runtimeId,
seq: 0
}
};
store.setSessionState(session, sessionStateJson(state));
}
link.send({
type: "replay",
after: state.cursor?.seq ?? 0
});
}
/** Hand every undelivered open operation of the session to the container. */
function _deliver(store, session) {
const attached = _classPrivateFieldGet2(_attached, this).get(session);
if (!attached?.link.open) return;
for (const operation of store.operations({
session,
status: OPEN
})) {
const meta = parseMeta(operation.meta);
if (meta.delivered === attached.runtimeId) continue;
let input = parseInput(operation.input);
const state = parseSessionState(_assertClassBrand(_ContainerHarness_brand, this, _requireSession).call(this, store, session).state);
if (state.handoff !== void 0) input = withHandoff(input, state.handoff);
if (!attached.link.send({
type: "prompt",
operationId: operation.id,
input,
whenBusy: meta.whenBusy
})) return;
store.transaction(() => {
store.setOperationMeta(session, operation.id, metaJson({
...meta,
delivered: attached.runtimeId
}));
if (state.handoff !== void 0) store.setSessionState(session, sessionStateJson({
...state,
handoff: void 0
}));
});
}
}
/** Apply one frame from the container. */
function _onFrame(session, attached, seq, frame) {
const store = _classPrivateFieldGet2(_store, this);
if (!store) return;
const events = [];
let settled;
store.transaction(() => {
const record = store.session(session);
if (!record) return;
let state = parseSessionState(record.state);
if (state.cursor?.runtimeId === attached.runtimeId && seq <= state.cursor.seq) return;
switch (frame.kind) {
case "start":
if (store.start(session, frame.operationId)) {
const operation = store.operation(session, frame.operationId);
if (operation) {
const message = {
id: `${operation.id}:user`,
role: "user",
parts: userParts(parseInput(operation.input)),
operationId: operation.id,
createdAt: operation.createdAt
};
store.append(session, MESSAGES, [{
id: message.id,
data: messageJson(message)
}]);
events.push({
type: "message",
message,
operationId: operation.id
});
}
events.push({
type: "operation-start",
operationId: frame.operationId
});
}
break;
case "event":
events.push({
...frame.event,
operationId: frame.operationId
});
if (frame.event.type === "message") store.append(session, MESSAGES, [{
id: frame.event.message.id,
data: messageJson(frame.event.message)
}]);
break;
case "persist":
store.append(session, ENGINE, frame.entries.map((data) => ({ data })));
break;
case "settle":
settled = _assertClassBrand(_ContainerHarness_brand, this, _settleInStore).call(this, store, session, frame.operationId, frame.outcome);
break;
}
state = {
...state,
cursor: {
runtimeId: attached.runtimeId,
seq
}
};
store.setSessionState(session, sessionStateJson(state));
});
if (settled) events.push({
type: "operation-end",
result: settled
});
_assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, events);
attached.sinceAck += 1;
if (settled || attached.sinceAck >= ACK_EVERY) {
attached.sinceAck = 0;
attached.link.send({
type: "ack",
seq
});
}
if (settled) _assertClassBrand(_ContainerHarness_brand, this, _afterSettle).call(this, store, session, settled);
}
function _onClosed(session, attached) {
if (attached && _classPrivateFieldGet2(_attached, this).get(session) === attached) _classPrivateFieldGet2(_attached, this).delete(session);
_assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
}
function _detach(session) {
const attached = _classPrivateFieldGet2(_attached, this).get(session);
_classPrivateFieldGet2(_attached, this).delete(session);
attached?.link.close();
_assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
}
function _settleInStore(store, session, operationId, outcome) {
if (!store.settle(session, operationId, outcome.status === "done" ? {
status: "done",
result: { text: outcome.text }
} : outcome)) return void 0;
const operation = store.operation(session, operationId);
return operation ? resultOf(operation) : void 0;
}
/** Settle outside a frame, and tell everyone. */
function _settle(store, session, operationId, outcome) {
const result = _assertClassBrand(_ContainerHarness_brand, this, _settleInStore).call(this, store, session, operationId, outcome);
if (!result) return;
_assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [{
type: "operation-end",
result
}]);
_assertClassBrand(_ContainerHarness_brand, this, _afterSettle).call(this, store, session, result);
}
function _afterSettle(store, session, result) {
_assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_settleWaiters, this), `${session}\0${result.operationId}`);
_assertClassBrand(_ContainerHarness_brand, this, _notify).call(this, _classPrivateFieldGet2(_changeWaiters, this), session);
if (store.operations({ status: OPEN }).length === 0) _assertClassBrand(_ContainerHarness_brand, this, _scheduleIdle).call(this).catch(() => void 0);
}
async function _openStore() {
if (_classPrivateFieldGet2(_store, this)) return _classPrivateFieldGet2(_store, this);
await this.lifecycle.ready();
if (_classPrivateFieldGet2(_store, this)) return _classPrivateFieldGet2(_store, this);
const store = openHarnessStore(this.lifecycle.storage, { prefix: _classPrivateFieldGet2(_options, this).storePrefix ?? "container_" });
store.createSession({
id: ROOT_SESSION,
state: sessionStateJson(_assertClassBrand(_ContainerHarness_brand, this, _initial).call(this))
});
_classPrivateFieldSet2(_store, this, store);
return store;
}
function _initial() {
return {
model: _classPrivateFieldGet2(_options, this).defaults?.model ?? _classPrivateFieldGet2(_options, this).agent.model,
cursor: null,
generation: 1,
messagesFrom: 0,
handoff: void 0
};
}
function _settings(state) {
const options = _classPrivateFieldGet2(_options, this).defaults?.options ?? _classPrivateFieldGet2(_options, this).agent.options;
return {
...state.model === void 0 ? {} : { model: state.model },
...options === void 0 ? {} : { options }
};
}
function _requireSession(store, session) {
const record = store.session(session);
if (!record) throw new Error(`Unknown container session ${session}`);
return record;
}
function _emit(session, events) {
if (events.length === 0) return;
const listeners = _classPrivateFieldGet2(_listeners, this).get(session);
if (!listeners) return;
for (const listener of listeners) try {
listener(events);
} catch {}
}
function _broadcast(event) {
for (const session of _classPrivateFieldGet2(_listeners, this).keys()) _assertClassBrand(_ContainerHarness_brand, this, _emit).call(this, session, [event]);
}
function _notify(waiters, key) {
const set = waiters.get(key);
waiters.delete(key);
if (set) for (const wake of set) wake();
}
/**
* Resolve on the next notify for `key`, after `pollMs` if given, or
* reject when `signal` aborts.
*
* The poll matters for waits in a request: the notify comes from the
* alarm that drives the container, a different request context, and a
* request whose only pending work is a promise another context resolves
* is cancelled by the runtime as hung. A timer of its own keeps it alive,
* and the caller re-reads the store when it fires.
*/
function _until(waiters, key, signal, pollMs) {
return new Promise((resolve, reject) => {
let set = waiters.get(key);
if (!set) {
set = /* @__PURE__ */ new Set();
waiters.set(key, set);
}
let timer;
const done = () => {
clearTimeout(timer);
set?.delete(wake);
signal?.removeEventListener("abort", onAbort);
};
const onAbort = () => {
done();
reject(signal?.reason);
};
const wake = () => {
done();
resolve();
};
if (signal?.aborted) {
onAbort();
return;
}
signal?.addEventListener("abort", onAbort, { once: true });
set.add(wake);
if (pollMs !== void 0) timer = setTimeout(wake, pollMs);
});
}
function resultOf(operation) {
const base = {
operationId: operation.id,
session: operation.session
};
if (operation.status === "done") {
const result = operation.result;
const text = isRecord(result) && typeof result.text === "string" ? result.text : "";
return {
...base,
status: "done",
text
};
}
if (operation.status === "unanswered") return {
...base,
status: "unanswered",
reason: operation.reason ?? "unanswered"
};
}
var _harness = /* @__PURE__ */ new WeakMap();
/**
* One session, addressed through the harness.
*
* @experimental The API may change before it stabilizes.
*/
var ContainerSession = class {
/**
* @param harness - The harness.
* @param id - The session id.
*/
constructor(harness, id) {
_classPrivateFieldInitSpec(this, _harness, void 0);
_classPrivateFieldSet2(_harness, this, harness);
this.id = id;
}
/**
* Durably submit a prompt. Resolves before the agent runs.
*
* @param input - The prompt.
* @param options - Idempotency key and busy behaviour.
* @returns The receipt.
*/
submit(input, options = {}) {
return _classPrivateFieldGet2(_harness, this).enqueue(this.id, input, options);
}
/**
* Submit and wait for the answer and the updated transcript.
*
* @param input - The prompt.
* @param options - Idempotency key and busy behaviour.
* @returns The result, with the transcript after it.
*/
async prompt(input, options = {}) {
const receipt = await this.submit(input, options);
return {
...await this.wait(receipt.operationId),
messages: await this.messages()
};
}
/**
* Join the running turn, when the adapter can steer; otherwise queue.
*
* @param input - The prompt.
* @param options - Idempotency key.
* @returns The receipt.
*/
steer(input, options = {}) {
return this.submit(input, {
...options,
whenBusy: "steer"
});
}
/**
* Wait for an operation to settle. Aborting `signal` stops only the wait.
*
* @param operationId - The operation.
* @param signal - Stops the wait.
* @returns How it ended.
*/
wait(operationId, signal) {
return _classPrivateFieldGet2(_harness, this).settled(this.id, operationId, signal);
}
/**
* Withdraw or abort one operation, or, with no id, every open operation
* of the session. Aborted operations settle `unanswered` with `aborted`.
*
* @param operationId - The operation. Default: all of them.
* @returns Whether anything was open to abort (always true with no id).
*/
abort(operationId) {
return _classPrivateFieldGet2(_harness, this).withdraw(this.id, operationId);
}
/**
* Start a new context: a new agent session without the old history,
* optionally seeded with a handoff note prepended to the next prompt.
* Throws while the session is busy.
*
* @param handoff - The note.
*/
reset(handoff) {
return _classPrivateFieldGet2(_harness, this).resetSession(this.id, handoff);
}
/**
* Change this session's model, by the adapter's model name.
*
* @param model - The model.
*/
setModel(model) {
return _classPrivateFieldGet2(_harness, this).setModel(this.id, model);
}
/**
* The active transcript, since the newest reset.
*
* @returns The messages, oldest first.
*/
messages() {
return _classPrivateFieldGet2(_harness, this).transcript(this.id);
}
/**
* This session's events: a snapshot, then live batches.
*
* @returns The stream.
*/
events() {
return _classPrivateFieldGet2(_harness, this).events(this.id);
}
/**
* Whether the session has open operations.
*
* @returns True while anything is queued or running.
*/
busy() {
return _classPrivateFieldGet2(_harness, this).busy(this.id);
}
};
var _harness2 = /* @__PURE__ */ new WeakMap();
/**
* Every session in this object.
*
* @experimental The API may change before it stabilizes.
*/
var ContainerSessions = class {
/**
* @param harness - The harness.
*/
constructor(harness) {
_classPrivateFieldInitSpec(this, _harness2, void 0);
_classPrivateFieldSet2(_harness2, this, harness);
}
/**
* A handle on one session.
*
* @param id - The session id.
* @returns The handle.
*/
get(id) {
return _classPrivateFieldGet2(_harness2, this).session(id);
}
/**
* A new top-level session, with the harness's defaults.
*
* @returns The new session.
*/
async create() {
const id = crypto.randomUUID();
await _classPrivateFieldGet2(_harness2, this).createSession(id);
return _classPrivateFieldGet2(_harness2, this).session(id);
}
/**
* A new session that continues `from`'s history as a branch.
*
* @param from - The session to fork.
* @returns The new session.
*/
async fork(from) {
const id = crypto.randomUUID();
await _classPrivateFieldGet2(_harness2, this).createSession(id, from);
return _classPrivateFieldGet2(_harness2, this).session(id);
}
/**
* Every session, oldest first.
*
* @returns Each session's id, parent, and whether it is busy.
*/
async list() {
const store = await _classPrivateFieldGet2(_harness2, this).store();
const busy = new Set(store.operations({ status: OPEN }).map((operation) => operation.session));
return store.sessions().map((record) => ({
id: record.id,
...record.parent === void 0 ? {} : { parent: record.parent },
busy: busy.has(record.id)
}));
}
};
//#endregion
//#region src/harness/container/agents.ts
/** What the CLI sends instead of a key; egress replaces it. */
const PLACEHOLDER_KEY = "harness-egress";
const CREDENTIAL_HEADERS = [
"authorization",
"x-api-key",
"cf-aig-authorization"
];
function npmInstall(name, pkg) {
return {
name: `install ${name}`,
command: [
"npm",
"install",
"--global",
"--no-fund",
"--no-audit",
pkg
]
};
}
/** The egress route for one provider. */
function route(host, connection, provider) {
return {
host,
upstream: connection.baseUrl ?? provider.baseUrl,
headers: {
...connection.apiKey === void 0 ? {} : provider.keyHeader(connection.apiKey),
...connection.headers
},
strip: CREDENTIAL_HEADERS
};
}
/**
* Claude Code, installed at runtime on `cloudflare/debian-trixie`.
*
* @param options - Its provider connection (`apiKey`, `baseUrl`,
* `headers`) and settings.
* @returns The agent, for `ContainerHarness`'s `agent`.
*
* @experimental The API may change before it stabilizes.
*/
function claudeCode(options) {
const host = "anthropic.harness.internal";
return {
adapter: "claude-code",
image: {
kind: "managed",
setup: [npmInstall("Claude Code", `@anthropic-ai/claude-code@${options.version ?? "2.1.289"}`), ...options.setup ?? []]
},
env: {
ANTHROPIC_BASE_URL: `http://${host}`,
ANTHROPIC_API_KEY: PLACEHOLDER_KEY,
DISABLE_AUTOUPDATER: "1"
},
egress: [route(host, options, {
baseUrl: "https://api.anthropic.com",
keyHeader: (key) => ({ "x-api-key": key })
})],
model: options.model ?? "claude-sonnet-4-5",
options: {
...options.appendSystemPrompt === void 0 ? {} : { appendSystemPrompt: options.appendSystemPrompt },
...options.allowedTools === void 0 ? {} : { allowedTools: [...options.allowedTools] },
...options.disallowedTools === void 0 ? {} : { disallowedTools: [...options.disallowedTools] },
...options.maxTurns === void 0 ? {} : { maxTurns: options.maxTurns }
}
};
}
/**
* Codex, installed at runtime on `cloudflare/debian-trixie`.
*
* @param options - Its provider connection (`apiKey`, `baseUrl`,
* `headers`) and settings.
* @returns The agent, for `ContainerHarness`'s `agent`.
*
* @experimental The API may change before it stabilizes.
*/
function codex(options) {
const host = "openai.harness.internal";
return {
adapter: "codex",
image: {
kind: "managed",
setup: [npmInstall("Codex", `@openai/codex@${options.version ?? "0.157.0"}`), ...options.setup ?? []]
},
env: {
OPENAI_BASE_URL: `http://${host}`,
OPENAI_API_KEY: PLACEHOLDER_KEY
},
egress: [route(host, options, {
baseUrl: "https://api.openai.com/v1",
keyHeader: (key) => ({ authorization: `Bearer ${key}` })
})],
model: options.model ?? "gpt-5.1",
options: options.config === void 0 ? {} : { config: [...options.config] }
};
}
/**
* An agent in an image you built, whose entrypoint serves the daemon
* (`serveFromEnv` from `agents/harness/container/runtime`).
*
* @param options - The image, the adapter, and its environment.
* @returns The agent, for `ContainerHarness`'s `agent`.
*
* @experimental The API may change before it stabilizes.
*/
function containerAgent(options) {
return {
adapter: options.adapter,
image: {
kind: "custom",
image: options.image
},
env: options.env ?? {},
egress: options.egress ?? [],
...options.model === void 0 ? {} : { model: options.model },
...options.options === void 0 ? {} : { options: options.options }
};
}
/**
* The echo adapter on `cloudflare/debian-trixie`: no CLI, no model, no
* credentials. For smoke tests of the whole path.
*
* @returns The agent, for `ContainerHarness`'s `agent`.
*
* @experimental The API may change before it stabilizes.
*/
function echoAgent() {
return {
adapter: "echo",
image: {
kind: "managed",
setup: []
},
env: {},
egress: []
};
}
//#endregion
//#region src/harness/container/egress.ts
/**
* Credentials that never enter the container.
*
* The CLI in the container is pointed at a placeholder host over plain
* HTTP (`http://anthropic.harness.internal`) with a placeholder key. The
* harness intercepts that host with `ctx.container.interceptOutboundHttp()`
* and hands each request to `ContainerEgress`, a `WorkerEntrypoint` running
* in your Worker, which drops the placeholder credentials, adds the real
* ones, and forwards the request to the real upstream (an AI Gateway, or
* the provider). The container can run any command it likes and still has
* nothing to steal.
*
* Plain HTTP inside the container means no certificate to trust: the
* connection to the intercept never leaves Cloudflare, and the request to
* the upstream is HTTPS.
*/
/**
* Forward one intercepted request to its route's upstream.
*
* @param request - The request the container made.
* @param routes - The routes, by placeholder host.
* @param fetcher - How to make the upstream request. Default `fetch`.
* @returns The upstream's response, or 403 for a host without a route.
*/
async function forwardEgress(request, routes, fetcher = fetch) {
const url = new URL(request.url);
const route = routes.find((each) => each.host === url.hostname);
if (!route) return new Response(`No egress route for ${url.hostname}`, { status: 403 });
const target = `${route.upstream.replace(/\/+$/, "")}${url.pathname}${url.search}`;
const headers = new Headers(request.headers);
headers.delete("host");
for (const name of route.strip) headers.delete(name);
for (const [name, value] of Object.entries(route.headers)) headers.set(name, value);
const hasBody = request.method !== "GET" && request.method !== "HEAD";
return fetcher(target, {
method: request.method,
headers,
...hasBody ? { body: request.body } : {},
redirect: "manual"
});
}
/**
* The egress for a `ContainerHarness`'s credentials. Export it from your
* Worker's main module so `ctx.exports.ContainerEgress` exists:
*
* ```ts
* export { ContainerEgress } from "agents/harness/container";
* ```
*
* @experimental The API may change before it stabilizes.
*/
var ContainerEgress = class extends WorkerEntrypoint {
/**
* Forward one request the container made.
*
* @param request - The intercepted request.
* @returns The upstream's response.
*/
fetch(request) {
return forwardEgress(request, this.ctx.props.routes);
}
};
//#endregion
export { ContainerEgress, ContainerHarness, ContainerSession, ContainerSessions, ROOT_SESSION, claudeCode, codex, containerAgent, echoAgent };
//# sourceMappingURL=index.js.map